appoiint 4.18.1 → 4.20.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.
package/dist/commands/admin.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
const{getFlag:getFlag,positionalArgs:positionalArgs}=require("../config"),fmt=require("../format");async function run(e,t,s,o){const a=positionalArgs(t),n=a[0];if("ice"===n||"turn"===n)return require("./ice").run(o,t,s);if("tenants"===n){const t=await e.adminTenants();if(s)return console.log(JSON.stringify(t,null,2));const o=t.tenants||t.data||t;return Array.isArray(o)&&o.length?(console.log(`\n[1mTenants (${o.length})[0m\n`),void o.forEach(e=>{const t=e.plan||"free",s=e.suspended?"[31msuspended[0m":"[32mactive[0m";console.log(` ${(e.slug||e.name||"?").padEnd(20)} ${t.padEnd(12)} ${s} ${e.email||""}`)})):fmt.info("No tenants.")}if("tenant-view"===n){const t=a[1];t||(fmt.err("Usage: apo admin tenant-view SLUG"),process.exit(1));const o=await e.adminTenantView(t);if(s)return console.log(JSON.stringify(o,null,2));const n=o.tenant||o.data||o;return console.log(`\n[1m${n.name||n.slug}[0m`),console.log(` Slug: ${n.slug}`),console.log(` Plan: ${n.plan||"free"}`),console.log(` Email: ${n.email||"—"}`),console.log(" Status: "+(n.suspended?"suspended":"active")),console.log(` Created: ${n.createdAt||"—"}`),void(n.stats&&(console.log(` Rooms: ${n.stats.rooms||0}`),console.log(` Bookings: ${n.stats.bookings||0}`),console.log(` Users: ${n.stats.users||0}`)))}if("tenant-create"===n){const o=a[1],n=getFlag(t,"plan")||"starter",r=getFlag(t,"email");o||(fmt.err("Usage: apo admin tenant-create SLUG [--plan X] [--email X]"),process.exit(1));const i=await e.adminTenantCreate({slug:o,plan:n,email:r});return s?console.log(JSON.stringify(i,null,2)):(fmt.ok(`Tenant created: ${o} (${n})`),void(i.apiKey&&console.log(` API Key: ${i.apiKey}`)))}if("tenant-plan"===n){const s=a[1],o=getFlag(t,"plan");return s&&o||(fmt.err("Usage: apo admin tenant-plan SLUG --plan X"),process.exit(1)),await e.adminTenantPlan(s,o),void fmt.ok(`Plan changed: ${s} → ${o}`)}if("tenant-verify"===n){const t=a[1];return t||(fmt.err("Usage: apo admin tenant-verify SLUG"),process.exit(1)),await e.adminTenantVerify(t),void fmt.ok(`Payment verified for: ${t}`)}if("tenant-suspend"===n){const t=a[1];return t||(fmt.err("Usage: apo admin tenant-suspend SLUG"),process.exit(1)),await e.adminTenantSuspend(t),void fmt.ok(`Tenant suspended: ${t}`)}if("tenant-activate"===n){const t=a[1];return t||(fmt.err("Usage: apo admin tenant-activate SLUG"),process.exit(1)),await e.adminTenantActivate(t),void fmt.ok(`Tenant activated: ${t}`)}if("tenant-delete"===n){const t=a[1];return t||(fmt.err("Usage: apo admin tenant-delete SLUG"),process.exit(1)),await e.adminTenantDelete(t),void fmt.ok(`Tenant deleted: ${t}`)}if("users"===n){const t=await e.adminUsers();if(s)return console.log(JSON.stringify(t,null,2));const o=t.users||t.data||t;return Array.isArray(o)&&o.length?(console.log(`\n[1mUsers (${o.length})[0m\n`),void o.forEach(e=>{const t=e.role||"user",s=e.locked?"[31mlocked[0m":"[32mactive[0m";console.log(` ${(e.email||"?").padEnd(30)} ${t.padEnd(10)} ${s}`)})):fmt.info("No users.")}if("user-create"===n){const o=getFlag(t,"email"),a=getFlag(t,"password")||getFlag(t,"p"),n=getFlag(t,"tenant"),r=getFlag(t,"role")||"user";o&&a||(fmt.err("Usage: apo admin user-create --email X --password X [--tenant SLUG] [--role X]"),process.exit(1));const i=await e.adminUserCreate({email:o,password:a,tenant:n,role:r});return s?console.log(JSON.stringify(i,null,2)):void fmt.ok(`User created: ${o}`)}if("user-reset"===n){const s=a[1],o=getFlag(t,"password")||getFlag(t,"p");return s&&o||(fmt.err("Usage: apo admin user-reset EMAIL --password NEWPASS"),process.exit(1)),await e.adminUserReset(s,{password:o}),void fmt.ok(`Password reset for: ${s}`)}if("user-delete"===n){const t=a[1];return t||(fmt.err("Usage: apo admin user-delete EMAIL"),process.exit(1)),await e.adminUserDelete(t),void fmt.ok(`User deleted: ${t}`)}if("profile-cover"===n||"profile-avatar"===n){const e="profile-avatar"===n?"avatar":getFlag(t,"type")||"cover",r=(a[2]||a[1],getFlag(t,"password")||getFlag(t,"p")),i=getFlag(t,"tenants"),l=getFlag(t,"tenants-file");let c=[];c=l?require("fs").readFileSync(l,"utf8").split("\n").map(e=>e.trim()).filter(e=>e&&!e.startsWith("#")).map(e=>{const[t,s]=e.split(/[,:\s]+/);return{email:t,password:s||r}}):i?i.split(",").map(e=>({email:e.trim(),password:r})):[{email:a[1],password:r}];const d=i||l?a[1]:a[2];d&&!c.some(e=>!e.email||!e.password)||(fmt.err("Usage (single): apo admin profile-cover EMAIL <URL|file> --password PASS [--type cover|avatar]"),fmt.err("Usage (bulk): apo admin profile-cover <URL|file> --tenants a@x,b@y --password PASS [--type cover|avatar]"),fmt.err(" or: apo admin profile-cover <URL|file> --tenants-file list.txt [--password SHARED]"),process.exit(1));const{ledger:u,allVerified:g}=await coverOrchestrate({baseUrl:o.baseUrl,type:e,source:d,targets:c});if(s)return console.log(JSON.stringify(u,null,2)),process.exit(g?0:1);console.log(`\n[1m${e} rollout — ${c.length} tenant(s)[0m\n`);for(const e of u.rows){const t="verified"===e.status?"[32m✓[0m":e.status.startsWith("rolled_back")?"[33m↩[0m":"[31m✗[0m";console.log(` ${t} ${e.email.padEnd(24)} ${e.status}${e.note?" — "+e.note:""}`)}const p=u.rows.filter(e=>"verified"===e.status).length,f=u.rows.filter(e=>e.status.startsWith("rolled_back")).length,m=u.rows.length-p-f;return console.log(`\n [1m${p} verified, ${f} rolled back, ${m} failed[0m`),console.log(` ledger: ${u.path}\n`),process.exit(g?0:1)}if("profile-website"===n){const e=getFlag(t,"password")||getFlag(t,"p"),n=getFlag(t,"tenants"),r=getFlag(t,"tenants-file");let i=[];i=r?require("fs").readFileSync(r,"utf8").split("\n").map(e=>e.trim()).filter(e=>e&&!e.startsWith("#")).map(t=>{const[s,o,a]=t.split(/[,\s]+/);return{email:s,website:o,password:a||e}}):n?n.split(",").map(t=>{const[s,o]=t.split("=");return{email:s.trim(),website:(o||"").trim(),password:e}}):[{email:a[1],website:a[2],password:e}],i.length&&!i.some(e=>!e.email||!e.website||!e.password)||(fmt.err("Usage (single): apo admin profile-website EMAIL https://site.com --password PASS"),fmt.err("Usage (bulk): apo admin profile-website --tenants a@x=https://x.com,b@y=https://y.com --password SHARED"),fmt.err(" or: apo admin profile-website --tenants-file map.txt (lines: email website [password])"),process.exit(1));const{ledger:l,allVerified:c}=await websiteOrchestrate({baseUrl:o.baseUrl,targets:i});if(s)return console.log(JSON.stringify(l,null,2)),process.exit(c?0:1);console.log(`\n[1mwebsite CTA rollout — ${i.length} tenant(s)[0m\n`);for(const e of l.rows){const t="verified"===e.status?"[32m✓[0m":e.status.startsWith("rolled_back")?"[33m↩[0m":"[31m✗[0m";console.log(` ${t} ${e.email.padEnd(24)} ${e.status}${e.after?" → "+e.after:""}${e.note?" — "+e.note:""}`)}const d=l.rows.filter(e=>"verified"===e.status).length,u=l.rows.filter(e=>e.status.startsWith("rolled_back")).length,g=l.rows.length-d-u;return console.log(`\n [1m${d} verified, ${u} rolled back, ${g} failed[0m`),console.log(` ledger: ${l.path}\n`),process.exit(c?0:1)}if("billing"===n){const t=await e.adminBilling();if(s)return console.log(JSON.stringify(t,null,2));const o=t.data||t;return console.log("\n[1mBilling[0m\n"),console.log(` MRR: $${o.mrr||0}`),console.log(` ARR: $${o.arr||0}`),console.log(` Revenue: $${o.totalRevenue||0}`),console.log(` Fees: $${o.fees||0}`),void console.log(` Payouts: $${o.payouts||0}`)}if("discount"===n){const o=a[1],n=getFlag(t,"plan"),r=getFlag(t,"months")||"1";o||(fmt.err("Usage: apo admin discount SLUG [--plan X] [--months N]"),process.exit(1));const i=await e.adminDiscount({slug:o,plan:n,months:parseInt(r)});return s?console.log(JSON.stringify(i,null,2)):void fmt.ok(`Discount created: ${i.code||"done"}`)}if("costs"===n){const t=a[1];if(t){const o=await e.costsByRoom(t);if(s)return console.log(JSON.stringify(o,null,2));const a=o.data||o;return console.log(`\n[1mCosts: ${t}[0m\n`),console.log(` STT: $${(a.stt?.cost||0).toFixed(6)} (${a.stt?.requests||0} requests, ${a.stt?.provider||"—"})`),console.log(` TTS: $${(a.tts?.cost||0).toFixed(6)} (${a.tts?.requests||0} requests, ${a.tts?.provider||"—"})`),console.log(` LLM: $${(a.llm?.cost||0).toFixed(6)} (${a.llm?.requests||0} requests, ${a.llm?.provider||"—"})`),void console.log(` [1mTotal: $${(a.total||0).toFixed(6)}[0m`)}const o=await e.costs();if(s)return console.log(JSON.stringify(o,null,2));const n=o.data||o;return console.log("\n[1mPlatform Costs[0m\n"),console.log(` Rooms tracked: ${n.rooms||0}`),console.log(` STT total: $${(n.stt||0).toFixed(6)}`),console.log(` TTS total: $${(n.tts||0).toFixed(6)}`),console.log(` LLM total: $${(n.llm||0).toFixed(6)}`),console.log(` [1mTotal: $${(n.total||0).toFixed(6)}[0m`),void(n.byRoom?.length&&(console.log("\n Top rooms:"),n.byRoom.slice(0,10).forEach(e=>{console.log(` ${e.roomId.slice(0,12)}... $${e.total.toFixed(6)} (STT:$${e.stt.toFixed(6)} TTS:$${e.tts.toFixed(6)} LLM:$${e.llm.toFixed(6)})`)})))}if("models"===n){const t=await e.costsModels();if(s)return console.log(JSON.stringify(t,null,2));const o=t.data||t,a=o.models||[];return console.log(`\n[1mSpend by Provider / Model[0m Total: $${(o.total||0).toFixed(6)}\n`),a.length?(console.log(` ${"TYPE".padEnd(5)} ${"PROVIDER".padEnd(14)} ${"MODEL".padEnd(20)} ${"REQ".padStart(6)} ${"IN TOK".padStart(10)} ${"OUT TOK".padStart(10)} ${"COST".padStart(12)}`),void a.forEach(e=>{console.log(` ${(e.type||"").toUpperCase().padEnd(5)} ${(e.provider||"—").padEnd(14)} ${(e.model||"—").padEnd(20)} ${String(e.requests||0).padStart(6)} ${String(e.inputTokens||0).padStart(10)} ${String(e.outputTokens||0).padStart(10)} ${("$"+(e.cost||0).toFixed(6)).padStart(12)}`)})):fmt.info("No usage recorded yet.")}if("users-cost"===n||"usage"===n){const t=await e.costsUsers();if(s)return console.log(JSON.stringify(t,null,2));const o=t.data||t,a=o.users||[];return console.log(`\n[1mSpend by User[0m Total: $${(o.total||0).toFixed(6)}\n`),a.length?(console.log(` ${"USER".padEnd(28)} ${"REQ".padStart(6)} ${"LLM".padStart(12)} ${"STT".padStart(12)} ${"TTS".padStart(12)} ${"TOTAL".padStart(12)}`),void a.forEach(e=>{console.log(` ${String(e.userId||"—").padEnd(28)} ${String(e.requests||0).padStart(6)} ${("$"+(e.llm||0).toFixed(6)).padStart(12)} ${("$"+(e.stt||0).toFixed(6)).padStart(12)} ${("$"+(e.tts||0).toFixed(6)).padStart(12)} ${("$"+(e.cost||0).toFixed(6)).padStart(12)}`)})):fmt.info("No usage recorded yet.")}if("timeseries"===n){const o=getFlag(t,"bucket")||"hour",a=await e.costsTimeseries(o);if(s)return console.log(JSON.stringify(a,null,2));const n=a.data||a,r=n.buckets||[];return console.log(`\n[1mSpend Over Time (${n.bucket||o})[0m\n`),r.length?(console.log(` ${"WHEN".padEnd(20)} ${"LLM".padStart(12)} ${"STT".padStart(12)} ${"TTS".padStart(12)} ${"TOTAL".padStart(12)}`),void r.forEach(e=>{const t=new Date(e.t).toISOString().replace("T"," ").slice(0,16);console.log(` ${t.padEnd(20)} ${("$"+(e.llm||0).toFixed(6)).padStart(12)} ${("$"+(e.stt||0).toFixed(6)).padStart(12)} ${("$"+(e.tts||0).toFixed(6)).padStart(12)} ${("$"+(e.total||0).toFixed(6)).padStart(12)}`)})):fmt.info("No usage recorded yet.")}if("pricing"===n){const t=await e.costsPricing();if(s)return console.log(JSON.stringify(t,null,2));const o=t.data||t;return console.log("\n[1mPricing Table[0m\n"),console.log(" STT:"),Object.entries(o.stt||{}).forEach(([e,t])=>console.log(` ${e.padEnd(16)} $${t.perMinute}/min`)),console.log(" TTS:"),Object.entries(o.tts||{}).forEach(([e,t])=>console.log(` ${e.padEnd(16)} $${t.perChar}/char`)),console.log(" LLM (per MTok, in/out):"),void Object.entries(o.llm||{}).forEach(([e,t])=>{Object.entries(t||{}).forEach(([t,s])=>{if(!s||null==s.perInputMTok)return;const o="_default"===t?`${e} (default)`:`${e}/${t}`;console.log(` ${o.padEnd(28)} $${s.perInputMTok}/$${s.perOutputMTok}`)})})}fmt.err("Usage: apo admin [tenants|tenant-view|tenant-create|tenant-plan|tenant-verify|tenant-suspend|tenant-activate|tenant-delete|users|user-create|user-reset|user-delete|profile-cover|profile-avatar|billing|discount|costs|costs ROOM_ID|models|usage|timeseries|pricing|ice]"),process.exit(1)}async function coverOrchestrate({baseUrl:e,type:t,source:s,targets:o}){const{api:a,resolveImageSource:n,uploadProfileImage:r,deleteProfileImage:i}=require("../api"),l=e=>(e&&(e.images||e.user&&e.user.images)||[]).find(e=>e.type===t)||null,c=e=>e&&(e.fileId||e.url)||"",d=await n(s),u=[];for(const s of o){u.length&&await new Promise(e=>setTimeout(e,600));const o={email:s.email,userId:null,before:null,after:null,status:"pending",note:""};u.push(o);try{const u=a({baseUrl:e,apiKey:""}).signin;let g,p;for(let e=0;e<4;e++){e&&await new Promise(t=>setTimeout(t,1500*e));try{g=await u({email:s.email,password:s.password});break}catch(e){p=e}}if(!g)throw p;const f=g.token||g.accessToken||g.apiKey;let m=g.userId||g.user?.id||g.user?._id;if(!m&&f&&f.includes("."))try{const e=JSON.parse(Buffer.from(f.split(".")[1],"base64").toString());m=e.userId||e.id||e.sub}catch{}if(!f||!m){o.status="auth_failed",o.note="no token/userId";continue}o.userId=m;const $=a({baseUrl:e,apiKey:f}),w=l(await $.profileGet().catch(()=>null));o.before=w?w.url:null,await r({baseUrl:e,token:f,userId:m,type:t,buffer:d.buffer,filename:d.filename,mime:d.mime});const S=l(await $.profileGet().catch(()=>null));if(o.after=S?S.url:null,S&&c(S)!==c(w)&&/\/(cover|avatar)\//.test(S.url||"")){o.status="verified";continue}o.note="gate failed (cover did not move); rolling back";try{if(w&&w.url){const s=await n(w.url);await r({baseUrl:e,token:f,userId:m,type:t,buffer:s.buffer,filename:s.filename,mime:s.mime}),o.status="rolled_back"}else await i({baseUrl:e,token:f,userId:m,type:t}),o.status="rolled_back_to_default"}catch(e){o.status="rollback_failed",o.note=`gate failed AND rollback failed: ${e.message}`}}catch(e){o.status="pending"===o.status?"apply_failed":o.status,o.note=o.note||e.message}}const g=require("fs"),p=require("path"),f=require("os"),m=p.join(f.homedir(),".appoiint","ledgers");g.mkdirSync(m,{recursive:!0});const $=p.join(m,`cover-${t}-${Date.now()}.json`),w={at:(new Date).toISOString(),type:t,source:s,rows:u,path:$};return g.writeFileSync($,JSON.stringify(w,null,2)),{ledger:w,allVerified:u.every(e=>"verified"===e.status)}}async function websiteOrchestrate({baseUrl:e,targets:t}){const{api:s}=require("../api"),o=e=>{const t=e&&(e.data||e)||{};return"string"==typeof t.website?t.website:""},a=[];for(const n of t){a.length&&await new Promise(e=>setTimeout(e,600));const t={email:n.email,userId:null,slug:null,want:n.website,before:null,after:null,status:"pending",note:""};a.push(t);try{const a=s({baseUrl:e,apiKey:""}).signin;let r,i;for(let e=0;e<4;e++){e&&await new Promise(t=>setTimeout(t,1500*e));try{r=await a({email:n.email,password:n.password});break}catch(e){i=e}}if(!r)throw i;const l=r.token||r.accessToken||r.apiKey;let c=r.userId||r.user?.id||r.user?._id,d=r.user?.slug||r.slug;if(!c&&l&&l.includes("."))try{const e=JSON.parse(Buffer.from(l.split(".")[1],"base64").toString());c=e.userId||e.id||e.sub}catch{}if(!l||!c){t.status="auth_failed",t.note="no token/userId";continue}t.userId=c;const u=s({baseUrl:e,apiKey:l});if(!d){const e=await u.profileGet().catch(()=>null);d=e?.user?.slug||e?.slug||e?.data?.slug}if(!d){t.status="apply_failed",t.note="could not resolve slug for gate";continue}t.slug=d;const g=await u.profileBySlug(d).catch(()=>null);t.before=o(g),await u.profileUpdate({website:n.website}),await new Promise(e=>setTimeout(e,800));const p=await u.profileBySlug(d).catch(()=>null);if(t.after=o(p),t.after===n.website){t.status="verified";continue}t.note=`gate failed (got ${JSON.stringify(t.after)}, wanted ${JSON.stringify(n.website)}); rolling back`;try{await u.profileUpdate({website:t.before||""}),t.status=t.before?"rolled_back":"rolled_back_to_empty"}catch(e){t.status="rollback_failed",t.note=`gate failed AND rollback failed: ${e.message}`}}catch(e){t.status="pending"===t.status?"apply_failed":t.status,t.note=t.note||e.message}}const n=require("fs"),r=require("path"),i=require("os"),l=r.join(i.homedir(),".appoiint","ledgers");n.mkdirSync(l,{recursive:!0});const c=r.join(l,`website-${Date.now()}.json`),d={at:(new Date).toISOString(),kind:"website",rows:a,path:c};return n.writeFileSync(c,JSON.stringify(d,null,2)),{ledger:d,allVerified:a.every(e=>"verified"===e.status)}}module.exports={run:run};
|
|
1
|
+
const{getFlag:getFlag,positionalArgs:positionalArgs}=require("../config"),fmt=require("../format");async function run(e,t,s,o){const a=positionalArgs(t),n=a[0];if("tenant-seed"===n)return require("./tenantSeed").run(o,t,s);if("email-override"===n)return require("./emailOverride").run(o,t,s);if("ice"===n||"turn"===n)return require("./ice").run(o,t,s);if("tenants"===n){const t=await e.adminTenants();if(s)return console.log(JSON.stringify(t,null,2));const o=t.tenants||t.data||t;return Array.isArray(o)&&o.length?(console.log(`\n[1mTenants (${o.length})[0m\n`),void o.forEach(e=>{const t=e.plan||"free",s=e.suspended?"[31msuspended[0m":"[32mactive[0m";console.log(` ${(e.slug||e.name||"?").padEnd(20)} ${t.padEnd(12)} ${s} ${e.email||""}`)})):fmt.info("No tenants.")}if("tenant-view"===n){const t=a[1];t||(fmt.err("Usage: apo admin tenant-view SLUG"),process.exit(1));const o=await e.adminTenantView(t);if(s)return console.log(JSON.stringify(o,null,2));const n=o.tenant||o.data||o;return console.log(`\n[1m${n.name||n.slug}[0m`),console.log(` Slug: ${n.slug}`),console.log(` Plan: ${n.plan||"free"}`),console.log(` Email: ${n.email||"—"}`),console.log(" Status: "+(n.suspended?"suspended":"active")),console.log(` Created: ${n.createdAt||"—"}`),void(n.stats&&(console.log(` Rooms: ${n.stats.rooms||0}`),console.log(` Bookings: ${n.stats.bookings||0}`),console.log(` Users: ${n.stats.users||0}`)))}if("tenant-create"===n){const o=a[1],n=getFlag(t,"plan")||"starter",r=getFlag(t,"email");o||(fmt.err("Usage: apo admin tenant-create SLUG [--plan X] [--email X]"),process.exit(1));const i=await e.adminTenantCreate({slug:o,plan:n,email:r});return s?console.log(JSON.stringify(i,null,2)):(fmt.ok(`Tenant created: ${o} (${n})`),void(i.apiKey&&console.log(` API Key: ${i.apiKey}`)))}if("tenant-plan"===n){const s=a[1],o=getFlag(t,"plan");return s&&o||(fmt.err("Usage: apo admin tenant-plan SLUG --plan X"),process.exit(1)),await e.adminTenantPlan(s,o),void fmt.ok(`Plan changed: ${s} → ${o}`)}if("tenant-verify"===n){const t=a[1];return t||(fmt.err("Usage: apo admin tenant-verify SLUG"),process.exit(1)),await e.adminTenantVerify(t),void fmt.ok(`Payment verified for: ${t}`)}if("tenant-suspend"===n){const t=a[1];return t||(fmt.err("Usage: apo admin tenant-suspend SLUG"),process.exit(1)),await e.adminTenantSuspend(t),void fmt.ok(`Tenant suspended: ${t}`)}if("tenant-activate"===n){const t=a[1];return t||(fmt.err("Usage: apo admin tenant-activate SLUG"),process.exit(1)),await e.adminTenantActivate(t),void fmt.ok(`Tenant activated: ${t}`)}if("tenant-delete"===n){const t=a[1];return t||(fmt.err("Usage: apo admin tenant-delete SLUG"),process.exit(1)),await e.adminTenantDelete(t),void fmt.ok(`Tenant deleted: ${t}`)}if("users"===n){const t=await e.adminUsers();if(s)return console.log(JSON.stringify(t,null,2));const o=t.users||t.data||t;return Array.isArray(o)&&o.length?(console.log(`\n[1mUsers (${o.length})[0m\n`),void o.forEach(e=>{const t=e.role||"user",s=e.locked?"[31mlocked[0m":"[32mactive[0m";console.log(` ${(e.email||"?").padEnd(30)} ${t.padEnd(10)} ${s}`)})):fmt.info("No users.")}if("user-create"===n){const o=getFlag(t,"email"),a=getFlag(t,"password")||getFlag(t,"p"),n=getFlag(t,"tenant"),r=getFlag(t,"role")||"user";o&&a||(fmt.err("Usage: apo admin user-create --email X --password X [--tenant SLUG] [--role X]"),process.exit(1));const i=await e.adminUserCreate({email:o,password:a,tenant:n,role:r});return s?console.log(JSON.stringify(i,null,2)):void fmt.ok(`User created: ${o}`)}if("user-reset"===n){const s=a[1],o=getFlag(t,"password")||getFlag(t,"p");return s&&o||(fmt.err("Usage: apo admin user-reset EMAIL --password NEWPASS"),process.exit(1)),await e.adminUserReset(s,{password:o}),void fmt.ok(`Password reset for: ${s}`)}if("user-delete"===n){const t=a[1];return t||(fmt.err("Usage: apo admin user-delete EMAIL"),process.exit(1)),await e.adminUserDelete(t),void fmt.ok(`User deleted: ${t}`)}if("profile-cover"===n||"profile-avatar"===n){const e="profile-avatar"===n?"avatar":getFlag(t,"type")||"cover",r=(a[2]||a[1],getFlag(t,"password")||getFlag(t,"p")),i=getFlag(t,"tenants"),l=getFlag(t,"tenants-file");let d=[];d=l?require("fs").readFileSync(l,"utf8").split("\n").map(e=>e.trim()).filter(e=>e&&!e.startsWith("#")).map(e=>{const[t,s]=e.split(/[,:\s]+/);return{email:t,password:s||r}}):i?i.split(",").map(e=>({email:e.trim(),password:r})):[{email:a[1],password:r}];const c=i||l?a[1]:a[2];c&&!d.some(e=>!e.email||!e.password)||(fmt.err("Usage (single): apo admin profile-cover EMAIL <URL|file> --password PASS [--type cover|avatar]"),fmt.err("Usage (bulk): apo admin profile-cover <URL|file> --tenants a@x,b@y --password PASS [--type cover|avatar]"),fmt.err(" or: apo admin profile-cover <URL|file> --tenants-file list.txt [--password SHARED]"),process.exit(1));const{ledger:u,allVerified:g}=await coverOrchestrate({baseUrl:o.baseUrl,type:e,source:c,targets:d});if(s)return console.log(JSON.stringify(u,null,2)),process.exit(g?0:1);console.log(`\n[1m${e} rollout — ${d.length} tenant(s)[0m\n`);for(const e of u.rows){const t="verified"===e.status?"[32m✓[0m":e.status.startsWith("rolled_back")?"[33m↩[0m":"[31m✗[0m";console.log(` ${t} ${e.email.padEnd(24)} ${e.status}${e.note?" — "+e.note:""}`)}const p=u.rows.filter(e=>"verified"===e.status).length,f=u.rows.filter(e=>e.status.startsWith("rolled_back")).length,m=u.rows.length-p-f;return console.log(`\n [1m${p} verified, ${f} rolled back, ${m} failed[0m`),console.log(` ledger: ${u.path}\n`),process.exit(g?0:1)}if("profile-website"===n){const e=getFlag(t,"password")||getFlag(t,"p"),n=getFlag(t,"tenants"),r=getFlag(t,"tenants-file");let i=[];i=r?require("fs").readFileSync(r,"utf8").split("\n").map(e=>e.trim()).filter(e=>e&&!e.startsWith("#")).map(t=>{const[s,o,a]=t.split(/[,\s]+/);return{email:s,website:o,password:a||e}}):n?n.split(",").map(t=>{const[s,o]=t.split("=");return{email:s.trim(),website:(o||"").trim(),password:e}}):[{email:a[1],website:a[2],password:e}],i.length&&!i.some(e=>!e.email||!e.website||!e.password)||(fmt.err("Usage (single): apo admin profile-website EMAIL https://site.com --password PASS"),fmt.err("Usage (bulk): apo admin profile-website --tenants a@x=https://x.com,b@y=https://y.com --password SHARED"),fmt.err(" or: apo admin profile-website --tenants-file map.txt (lines: email website [password])"),process.exit(1));const{ledger:l,allVerified:d}=await websiteOrchestrate({baseUrl:o.baseUrl,targets:i});if(s)return console.log(JSON.stringify(l,null,2)),process.exit(d?0:1);console.log(`\n[1mwebsite CTA rollout — ${i.length} tenant(s)[0m\n`);for(const e of l.rows){const t="verified"===e.status?"[32m✓[0m":e.status.startsWith("rolled_back")?"[33m↩[0m":"[31m✗[0m";console.log(` ${t} ${e.email.padEnd(24)} ${e.status}${e.after?" → "+e.after:""}${e.note?" — "+e.note:""}`)}const c=l.rows.filter(e=>"verified"===e.status).length,u=l.rows.filter(e=>e.status.startsWith("rolled_back")).length,g=l.rows.length-c-u;return console.log(`\n [1m${c} verified, ${u} rolled back, ${g} failed[0m`),console.log(` ledger: ${l.path}\n`),process.exit(d?0:1)}if("billing"===n){const t=await e.adminBilling();if(s)return console.log(JSON.stringify(t,null,2));const o=t.data||t;return console.log("\n[1mBilling[0m\n"),console.log(` MRR: $${o.mrr||0}`),console.log(` ARR: $${o.arr||0}`),console.log(` Revenue: $${o.totalRevenue||0}`),console.log(` Fees: $${o.fees||0}`),void console.log(` Payouts: $${o.payouts||0}`)}if("discount"===n){const o=a[1],n=getFlag(t,"plan"),r=getFlag(t,"months")||"1";o||(fmt.err("Usage: apo admin discount SLUG [--plan X] [--months N]"),process.exit(1));const i=await e.adminDiscount({slug:o,plan:n,months:parseInt(r)});return s?console.log(JSON.stringify(i,null,2)):void fmt.ok(`Discount created: ${i.code||"done"}`)}if("costs"===n){const t=a[1];if(t){const o=await e.costsByRoom(t);if(s)return console.log(JSON.stringify(o,null,2));const a=o.data||o;return console.log(`\n[1mCosts: ${t}[0m\n`),console.log(` STT: $${(a.stt?.cost||0).toFixed(6)} (${a.stt?.requests||0} requests, ${a.stt?.provider||"—"})`),console.log(` TTS: $${(a.tts?.cost||0).toFixed(6)} (${a.tts?.requests||0} requests, ${a.tts?.provider||"—"})`),console.log(` LLM: $${(a.llm?.cost||0).toFixed(6)} (${a.llm?.requests||0} requests, ${a.llm?.provider||"—"})`),void console.log(` [1mTotal: $${(a.total||0).toFixed(6)}[0m`)}const o=await e.costs();if(s)return console.log(JSON.stringify(o,null,2));const n=o.data||o;return console.log("\n[1mPlatform Costs[0m\n"),console.log(` Rooms tracked: ${n.rooms||0}`),console.log(` STT total: $${(n.stt||0).toFixed(6)}`),console.log(` TTS total: $${(n.tts||0).toFixed(6)}`),console.log(` LLM total: $${(n.llm||0).toFixed(6)}`),console.log(` [1mTotal: $${(n.total||0).toFixed(6)}[0m`),void(n.byRoom?.length&&(console.log("\n Top rooms:"),n.byRoom.slice(0,10).forEach(e=>{console.log(` ${e.roomId.slice(0,12)}... $${e.total.toFixed(6)} (STT:$${e.stt.toFixed(6)} TTS:$${e.tts.toFixed(6)} LLM:$${e.llm.toFixed(6)})`)})))}if("models"===n){const t=await e.costsModels();if(s)return console.log(JSON.stringify(t,null,2));const o=t.data||t,a=o.models||[];return console.log(`\n[1mSpend by Provider / Model[0m Total: $${(o.total||0).toFixed(6)}\n`),a.length?(console.log(` ${"TYPE".padEnd(5)} ${"PROVIDER".padEnd(14)} ${"MODEL".padEnd(20)} ${"REQ".padStart(6)} ${"IN TOK".padStart(10)} ${"OUT TOK".padStart(10)} ${"COST".padStart(12)}`),void a.forEach(e=>{console.log(` ${(e.type||"").toUpperCase().padEnd(5)} ${(e.provider||"—").padEnd(14)} ${(e.model||"—").padEnd(20)} ${String(e.requests||0).padStart(6)} ${String(e.inputTokens||0).padStart(10)} ${String(e.outputTokens||0).padStart(10)} ${("$"+(e.cost||0).toFixed(6)).padStart(12)}`)})):fmt.info("No usage recorded yet.")}if("users-cost"===n||"usage"===n){const t=await e.costsUsers();if(s)return console.log(JSON.stringify(t,null,2));const o=t.data||t,a=o.users||[];return console.log(`\n[1mSpend by User[0m Total: $${(o.total||0).toFixed(6)}\n`),a.length?(console.log(` ${"USER".padEnd(28)} ${"REQ".padStart(6)} ${"LLM".padStart(12)} ${"STT".padStart(12)} ${"TTS".padStart(12)} ${"TOTAL".padStart(12)}`),void a.forEach(e=>{console.log(` ${String(e.userId||"—").padEnd(28)} ${String(e.requests||0).padStart(6)} ${("$"+(e.llm||0).toFixed(6)).padStart(12)} ${("$"+(e.stt||0).toFixed(6)).padStart(12)} ${("$"+(e.tts||0).toFixed(6)).padStart(12)} ${("$"+(e.cost||0).toFixed(6)).padStart(12)}`)})):fmt.info("No usage recorded yet.")}if("timeseries"===n){const o=getFlag(t,"bucket")||"hour",a=await e.costsTimeseries(o);if(s)return console.log(JSON.stringify(a,null,2));const n=a.data||a,r=n.buckets||[];return console.log(`\n[1mSpend Over Time (${n.bucket||o})[0m\n`),r.length?(console.log(` ${"WHEN".padEnd(20)} ${"LLM".padStart(12)} ${"STT".padStart(12)} ${"TTS".padStart(12)} ${"TOTAL".padStart(12)}`),void r.forEach(e=>{const t=new Date(e.t).toISOString().replace("T"," ").slice(0,16);console.log(` ${t.padEnd(20)} ${("$"+(e.llm||0).toFixed(6)).padStart(12)} ${("$"+(e.stt||0).toFixed(6)).padStart(12)} ${("$"+(e.tts||0).toFixed(6)).padStart(12)} ${("$"+(e.total||0).toFixed(6)).padStart(12)}`)})):fmt.info("No usage recorded yet.")}if("pricing"===n){const t=await e.costsPricing();if(s)return console.log(JSON.stringify(t,null,2));const o=t.data||t;return console.log("\n[1mPricing Table[0m\n"),console.log(" STT:"),Object.entries(o.stt||{}).forEach(([e,t])=>console.log(` ${e.padEnd(16)} $${t.perMinute}/min`)),console.log(" TTS:"),Object.entries(o.tts||{}).forEach(([e,t])=>console.log(` ${e.padEnd(16)} $${t.perChar}/char`)),console.log(" LLM (per MTok, in/out):"),void Object.entries(o.llm||{}).forEach(([e,t])=>{Object.entries(t||{}).forEach(([t,s])=>{if(!s||null==s.perInputMTok)return;const o="_default"===t?`${e} (default)`:`${e}/${t}`;console.log(` ${o.padEnd(28)} $${s.perInputMTok}/$${s.perOutputMTok}`)})})}fmt.err("Usage: apo admin [tenants|tenant-view|tenant-create|tenant-plan|tenant-verify|tenant-suspend|tenant-activate|tenant-delete|tenant-seed|email-override|users|user-create|user-reset|user-delete|profile-cover|profile-avatar|billing|discount|costs|costs ROOM_ID|models|usage|timeseries|pricing|ice]"),process.exit(1)}async function coverOrchestrate({baseUrl:e,type:t,source:s,targets:o}){const{api:a,resolveImageSource:n,uploadProfileImage:r,deleteProfileImage:i}=require("../api"),l=e=>(e&&(e.images||e.user&&e.user.images)||[]).find(e=>e.type===t)||null,d=e=>e&&(e.fileId||e.url)||"",c=await n(s),u=[];for(const s of o){u.length&&await new Promise(e=>setTimeout(e,600));const o={email:s.email,userId:null,before:null,after:null,status:"pending",note:""};u.push(o);try{const u=a({baseUrl:e,apiKey:""}).signin;let g,p;for(let e=0;e<4;e++){e&&await new Promise(t=>setTimeout(t,1500*e));try{g=await u({email:s.email,password:s.password});break}catch(e){p=e}}if(!g)throw p;const f=g.token||g.accessToken||g.apiKey;let m=g.userId||g.user?.id||g.user?._id;if(!m&&f&&f.includes("."))try{const e=JSON.parse(Buffer.from(f.split(".")[1],"base64").toString());m=e.userId||e.id||e.sub}catch{}if(!f||!m){o.status="auth_failed",o.note="no token/userId";continue}o.userId=m;const $=a({baseUrl:e,apiKey:f}),w=l(await $.profileGet().catch(()=>null));o.before=w?w.url:null,await r({baseUrl:e,token:f,userId:m,type:t,buffer:c.buffer,filename:c.filename,mime:c.mime});const S=l(await $.profileGet().catch(()=>null));if(o.after=S?S.url:null,S&&d(S)!==d(w)&&/\/(cover|avatar)\//.test(S.url||"")){o.status="verified";continue}o.note="gate failed (cover did not move); rolling back";try{if(w&&w.url){const s=await n(w.url);await r({baseUrl:e,token:f,userId:m,type:t,buffer:s.buffer,filename:s.filename,mime:s.mime}),o.status="rolled_back"}else await i({baseUrl:e,token:f,userId:m,type:t}),o.status="rolled_back_to_default"}catch(e){o.status="rollback_failed",o.note=`gate failed AND rollback failed: ${e.message}`}}catch(e){o.status="pending"===o.status?"apply_failed":o.status,o.note=o.note||e.message}}const g=require("fs"),p=require("path"),f=require("os"),m=p.join(f.homedir(),".appoiint","ledgers");g.mkdirSync(m,{recursive:!0});const $=p.join(m,`cover-${t}-${Date.now()}.json`),w={at:(new Date).toISOString(),type:t,source:s,rows:u,path:$};return g.writeFileSync($,JSON.stringify(w,null,2)),{ledger:w,allVerified:u.every(e=>"verified"===e.status)}}async function websiteOrchestrate({baseUrl:e,targets:t}){const{api:s}=require("../api"),o=e=>{const t=e&&(e.data||e)||{};return"string"==typeof t.website?t.website:""},a=[];for(const n of t){a.length&&await new Promise(e=>setTimeout(e,600));const t={email:n.email,userId:null,slug:null,want:n.website,before:null,after:null,status:"pending",note:""};a.push(t);try{const a=s({baseUrl:e,apiKey:""}).signin;let r,i;for(let e=0;e<4;e++){e&&await new Promise(t=>setTimeout(t,1500*e));try{r=await a({email:n.email,password:n.password});break}catch(e){i=e}}if(!r)throw i;const l=r.token||r.accessToken||r.apiKey;let d=r.userId||r.user?.id||r.user?._id,c=r.user?.slug||r.slug;if(!d&&l&&l.includes("."))try{const e=JSON.parse(Buffer.from(l.split(".")[1],"base64").toString());d=e.userId||e.id||e.sub}catch{}if(!l||!d){t.status="auth_failed",t.note="no token/userId";continue}t.userId=d;const u=s({baseUrl:e,apiKey:l});if(!c){const e=await u.profileGet().catch(()=>null);c=e?.user?.slug||e?.slug||e?.data?.slug}if(!c){t.status="apply_failed",t.note="could not resolve slug for gate";continue}t.slug=c;const g=await u.profileBySlug(c).catch(()=>null);t.before=o(g),await u.profileUpdate({website:n.website}),await new Promise(e=>setTimeout(e,800));const p=await u.profileBySlug(c).catch(()=>null);if(t.after=o(p),t.after===n.website){t.status="verified";continue}t.note=`gate failed (got ${JSON.stringify(t.after)}, wanted ${JSON.stringify(n.website)}); rolling back`;try{await u.profileUpdate({website:t.before||""}),t.status=t.before?"rolled_back":"rolled_back_to_empty"}catch(e){t.status="rollback_failed",t.note=`gate failed AND rollback failed: ${e.message}`}}catch(e){t.status="pending"===t.status?"apply_failed":t.status,t.note=t.note||e.message}}const n=require("fs"),r=require("path"),i=require("os"),l=r.join(i.homedir(),".appoiint","ledgers");n.mkdirSync(l,{recursive:!0});const d=r.join(l,`website-${Date.now()}.json`),c={at:(new Date).toISOString(),kind:"website",rows:a,path:d};return n.writeFileSync(d,JSON.stringify(c,null,2)),{ledger:c,allVerified:a.every(e=>"verified"===e.status)}}module.exports={run:run};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const{execFileSync:execFileSync}=require("child_process"),{getFlag:getFlag,hasFlag:hasFlag,positionalArgs:positionalArgs}=require("../config"),fmt=require("../format"),DEFAULT_FROM="noreply@appoiint.ai",DEFAULT_NAME="Appoiint";async function call(e,t,r,a){const s=await fetch(t,{method:e,headers:{"Content-Type":"application/json",Accept:"application/json",...r?{Authorization:`Bearer ${r}`}:{}},body:a?JSON.stringify(a):void 0});let o;try{o=await s.json()}catch{o={}}return{status:s.status,data:o}}function sc(e){return execFileSync("sc",e,{encoding:"utf8",stdio:["ignore","pipe","pipe"]})}function parseBundle(e){let t=e;for(const e of["data","secret","value","payload"])t&&"object"==typeof t&&e in t&&(t=t[e]);if("string"==typeof t)try{t=JSON.parse(t)}catch{t={value:t}}t&&"object"==typeof t||(t={value:String(t)});const r=Object.fromEntries(Object.entries(t).map(([e,t])=>[e.toLowerCase(),t])),a=(...e)=>e.map(e=>r[e]).find(e=>null!=e&&""!==e),s=a("smtp_hosts","hosts","host","smtp_host");return{fields:Object.keys(t),host:"string"==typeof s?s.split(",")[0].trim():Array.isArray(s)?s[0]:null,port:parseInt(a("smtp_port","port")||"587",10),user:a("smtp_user","user","username"),pass:a("smtp_pass","pass","password","smtp_password"),from:a("mail_from","from","fromemail","from_email")}}function vaultBundleFromShare(e,t,r){if(!/\/api\/v1\/shares\/access\//.test(e))throw new Error("share URL must contain /api/v1/shares/access/<token>");const a=execFileSync("curl",["-s","-m","30","-X","POST",e,"-H","Content-Type: application/json","-H","Accept: application/json","-d","{}"],{encoding:"utf8"}),s=JSON.parse(a);if(s.error)throw new Error(`share redeem failed: ${s.error} ${s.message||""} (ask the issuer to re-issue)`);let o=s;for(const e of["data","secret","value","payload"])o&&"object"==typeof o&&e in o&&(o=o[e]);const i=sc(["secret",t,"string"==typeof o?o:JSON.stringify(o),"-t","custom"]).match(/secret-[0-9a-f-]{8,}/);if(!i)throw new Error("vault write failed");return r(`share redeemed and vaulted as ${t} -> ${i[0]}`),i[0]}function readVault(e){const t=sc(["secret","show",e]),r=t.match(/\{[\s\S]*\}/);return parseBundle(r?JSON.parse(r[0]):t.trim())}async function run(e,t,r){const a=positionalArgs(t)[1]||"show",s=r?()=>{}:e=>fmt.ok(e);e.adminKey||(fmt.err("No SDK superadmin key (APPOIINT_ADMIN_KEY, adminKey in config, or prod-credentials.json in the cwd / cluster root)."),process.exit(1));const o=String(e.sdkUrl).replace(/\/$/,""),i=async()=>{const t=await call("GET",`${o}/admin/email/override`,e.adminKey),a=(t.data.data||{}).override||{},s={enabled:a.enabled,host:a.host,port:a.port,secure:a.secure,user:a.user,fromEmail:a.fromEmail,fromName:a.fromName,passSet:a.passSet};return r?console.log(JSON.stringify({status:t.status,override:s},null,2)):console.log(`\n ${fmt.bold("SDK email override")} (${o})\n ${Object.entries(s).map(([e,t])=>`${e}=${t}`).join(" ")}\n`),t};try{if("show"===a)return await i();if("disable"===a){const t=await call("PUT",`${o}/admin/email/override`,e.adminKey,{enabled:!1});if(200!==t.status)throw new Error(`PUT -> HTTP ${t.status} ${t.data.error||""}`);return s("override disabled (platform relay again)"),await i()}"set"!==a&&(fmt.err("Usage: apo admin email-override show|set|disable"),process.exit(1));let r=getFlag(t,"from-vault");const n=getFlag(t,"from-share");n&&(r=vaultBundleFromShare(n,getFlag(t,"vault-as")||"tygaemail-relay-appoiint-ai",s)),r||(fmt.err("set needs --from-vault <secretId> or --from-share <url>"),process.exit(1));const l=readVault(r);if(!l.host||!l.user||!l.pass)throw new Error(`bundle ${r} lacks host/user/pass (fields: ${l.fields.join(", ")})`);const c=getFlag(t,"from")||l.from||DEFAULT_FROM,m=getFlag(t,"name")||"Appoiint";s(`using ${r}: ${l.host}:${l.port} as ${l.user.slice(0,5)}… from "${m} <${c}>"`);const p=await call("PUT",`${o}/admin/email/override`,e.adminKey,{enabled:!0,host:l.host,port:l.port,secure:!1,user:l.user,pass:l.pass,fromEmail:c,fromName:m});if(200!==p.status)throw new Error(`PUT /admin/email/override -> HTTP ${p.status} ${p.data.error||p.data.message||""}`);s("override saved");const d=((await i()).data.data||{}).override||{};if(!d.enabled||!d.passSet||d.fromEmail!==c)throw new Error("read-back does not match what was written");if(hasFlag(t,"test")){const r=getFlag(t,"test");s(`magic link requested for ${r} (HTTP ${(await call("POST",`${String(e.baseUrl).replace(/\/$/,"")}/saas/api/v1/sdk/auth/magic/request`,null,{email:r})).status}) — check the From header`)}}catch(e){fmt.err(e.message),process.exit(1)}}module.exports={run:run,parseBundle:parseBundle};
|
package/dist/commands/schema.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
const pkg=require("../../package.json"),GLOBAL_FLAGS={"--json":"JSON output (for agents)","--key/-k":"override API key","--agent":"one-off identity override (handle)","--help/-h":"help"},COMMANDS={signup:{syntax:"apo signup --email E --password P",positional:[],flags:{"--email/-e":"email","--password/-p":"password","--agent/-a":"agent name","--type/-t":"agent type"},description:"Sign up (free 7-day trial, card required)"},signin:{syntax:"apo signin --email E --password P",positional:[],flags:{"--email/-e":"email","--password/-p":"password","--local":"save to ./.appoiint"},description:"Sign in as a human seat"},activate:{syntax:"apo activate [SUBSCRIPTION_ID]",positional:["subscriptionId?"],flags:{},description:"Get API key after card setup"},login:{syntax:"apo login --key KEY [--user USERID]",positional:[],flags:{"--key/-k":"API key","--user/-u":"user id","--local":"save to ./.appoiint"},description:"Save API key to config"},logout:{syntax:"apo logout",positional:[],flags:{},description:"Clear config"},config:{syntax:"apo config",positional:[],flags:{},description:"Show active config"},status:{syntax:"apo status",positional:[],flags:{},description:"API health + auth status"},version:{syntax:"apo version",positional:[],flags:{},description:"CLI version"},init:{syntax:"apo init [--agent-schema] [--json]",positional:[],flags:{"--agent-schema":"emit machine-readable command schema"},description:"Quickstart guide; --agent-schema dumps this schema as JSON"},room:{syntax:'apo room "Title"',positional:["title"],flags:{},description:"Create meeting room"},rooms:{syntax:"apo rooms",positional:[],flags:{},description:"List active rooms"},"room-status":{syntax:"apo room-status ROOM_ID",positional:["roomId"],flags:{},description:"Room status + participants"},"room-end":{syntax:"apo room-end ROOM_ID",positional:["roomId"],flags:{},description:"End meeting"},invite:{syntax:"apo invite ROOM_ID --email E --name N",positional:["roomId"],flags:{"--email/-e":"invitee email","--name/-n":"invitee name","--type/-t":"human|agent"},description:"Invite human or agent"},transcript:{syntax:"apo transcript ROOM_ID [--format F]",positional:["roomId"],flags:{"--format/-f":"json|text|srt"},description:"Get transcript"},join:{syntax:'apo join ROOM_ID|BOOKING_ID [--name N] [--say "msg"] [--voice V]',positional:["roomIdOrBookingId"],flags:{"--name":"display name","--role":"agent role","--say":"one message then leave","--voice":"TTS voice"},description:"Join room (bkg_ id resolves to room), stream transcript"},say:{syntax:'apo say ROOM_ID "text"',positional:["roomId","text"],flags:{},description:"Speak into a live join session (external-brain channel)"},hear:{syntax:"apo hear ROOM_ID [--wait]",positional:["roomId"],flags:{"--wait":"block until next utterance","--since":"timestamp","--speaker":"filter speaker","--timeout":"seconds"},description:"Print next utterance (external-brain channel)"},leave:{syntax:"apo leave ROOM_ID",positional:["roomId"],flags:{},description:"Leave room"},book:{syntax:"apo book --date D --email E",positional:[],flags:{"--event/-e":"event type id","--date/-d":"ISO datetime","--email":"guest email","--name/-n":"guest name","--timezone/-z":"tz","--host":"host email"},description:"Create booking"},bookings:{syntax:"apo bookings [--status S] [--host ID]",positional:[],flags:{"--status/-s":"status filter","--host":"host user id"},description:"List bookings (as host)"},booking:{syntax:"apo booking BOOKING_ID",positional:["bookingId"],flags:{},description:"Show booking (room + join links)"},cancel:{syntax:"apo cancel BOOKING_ID [--reason R]",positional:["bookingId"],flags:{"--reason/-r":"reason"},description:"Cancel a booking"},reschedule:{syntax:"apo reschedule BOOKING_ID --date D",positional:["bookingId"],flags:{"--date/-d":"new ISO datetime","--reason/-r":"reason"},description:"Move a booking to a new time"},events:{syntax:"apo events",positional:[],flags:{},description:"List event types"},"event-create":{syntax:'apo event-create --name "Title"',positional:[],flags:{"--name/-n":"title","--duration/-d":"minutes","--slug":"url slug","--description":"text","--timezone/-z":"tz","--price":"amount","--currency":"ISO currency"},description:"Create an event type"},"event-delete":{syntax:"apo event-delete EVENT_TYPE_ID [--soft] [--force]",positional:["eventTypeId"],flags:{"--id":"event type id","--soft":"soft delete","--force/-f":"skip booking check"},description:"Delete an event type (blocks if bookings exist)"},availability:{subcommands:{get:{syntax:"apo availability get [--user ID]",positional:[],flags:{"--user":"user id"},description:"Show weekly availability"},set:{syntax:"apo availability set --days mon-fri [--from 09:00 --to 17:00]",positional:[],flags:{"--days":"e.g. mon-fri","--from":"HH:MM","--to":"HH:MM","--timezone/-z":"tz","--user":"user id","--org":"organization id"},description:"Set availability"}}},slots:{syntax:"apo slots --email E --date D",positional:[],flags:{"--email/-e":"host email","--date/-d":"YYYY-MM-DD","--timezone/-z":"tz"},description:"Check availability"},domains:{syntax:"apo domains",positional:[],flags:{},description:"List your custom domains"},domain:{subcommands:{add:{syntax:"apo domain add HOSTNAME",positional:["hostname"],flags:{},description:"Link a domain (returns CNAME)"},verify:{syntax:"apo domain verify HOSTNAME",positional:["hostname"],flags:{},description:"Trigger DNS verification"},status:{syntax:"apo domain status HOSTNAME",positional:["hostname"],flags:{},description:"DNS + SSL status"},show:{syntax:"apo domain show HOSTNAME",positional:["hostname"],flags:{},description:"Domain details"},remove:{syntax:"apo domain remove HOSTNAME --yes",positional:["hostname"],flags:{"--yes":"confirm deletion"},description:"Unlink a domain"}}},page:{subcommands:{show:{syntax:"apo page",positional:[],flags:{},description:"Show your meetings-page profile"},set:{syntax:"apo page set --title T --company C",positional:[],flags:{"--first":"first name","--last":"last name","--company":"company","--title":"title","--phone":"phone","--website":"url"},description:"Update page fields"},cover:{syntax:"apo page cover <URL|file> [--type cover|avatar]",positional:["source"],flags:{"--type":"cover|avatar"},description:"Set meetings-page cover image"},check:{syntax:"apo page check [HOSTNAME]",positional:["hostname?"],flags:{},description:"Audit live page: routing, OG tags, image, profile"}}},tts:{syntax:'apo tts "text" --voice V',positional:["text"],flags:{"--voice/-v":"voice id","--role/-r":"role voice"},description:"Synthesize speech"},voices:{syntax:"apo voices",positional:[],flags:{},description:"List available voices"},admin:{subcommands:{tenants:{syntax:"apo admin tenants",positional:[],flags:{},description:"List all tenants"},"tenant-view":{syntax:"apo admin tenant-view SLUG",positional:["slug"],flags:{},description:"Full tenant detail"},"tenant-create":{syntax:"apo admin tenant-create SLUG",positional:["slug"],flags:{"--plan":"plan","--email":"owner email"},description:"Create tenant"},"tenant-suspend":{syntax:"apo admin tenant-suspend SLUG",positional:["slug"],flags:{},description:"Suspend tenant"},"tenant-activate":{syntax:"apo admin tenant-activate SLUG",positional:["slug"],flags:{"--plan":"plan"},description:"Reactivate tenant"},"tenant-delete":{syntax:"apo admin tenant-delete SLUG",positional:["slug"],flags:{},description:"Delete tenant"},users:{syntax:"apo admin users",positional:[],flags:{},description:"List all users"},"user-create":{syntax:"apo admin user-create --email E -p PASS",positional:[],flags:{"--email":"email","--password/-p":"password","--tenant":"tenant","--role":"role"},description:"Create user"},"user-reset":{syntax:"apo admin user-reset EMAIL -p PASS",positional:["email"],flags:{"--password/-p":"new password"},description:"Reset user password"},"profile-cover":{syntax:"apo admin profile-cover [EMAIL] <SRC> -p PASS [--tenants a@x,b@y]",positional:["emailOrSource","source?"],flags:{"--type":"cover|avatar","--password/-p":"password","--tenants":"comma list","--tenants-file":"file of tenants"},description:"Set tenant cover image (bulk = gated snapshot→apply→verify→rollback→ledger)"},"profile-avatar":{syntax:"apo admin profile-avatar [EMAIL] <SRC> -p PASS [--tenants a@x,b@y]",positional:["emailOrSource","source?"],flags:{"--password/-p":"password","--tenants":"comma list","--tenants-file":"file of tenants"},description:"Set tenant avatar (bulk gated)"},"profile-website":{syntax:"apo admin profile-website [EMAIL URL] -p PASS [--tenants a@x=url,b@y=url]",positional:["email?","url?"],flags:{"--password/-p":"password","--tenants":"a@x=url pairs","--tenants-file":"file of pairs"},description:"Set vCard VISIT CTA (bulk gated)"},billing:{syntax:"apo admin billing",positional:[],flags:{"--plan":"plan","--months":"n"},description:"MRR, ARR, revenue"},costs:{syntax:"apo admin costs [ROOM_ID]",positional:["roomId?"],flags:{},description:"Platform or per-meeting cost breakdown"},models:{syntax:"apo admin models",positional:[],flags:{},description:"Spend by provider/model + tokens"},usage:{syntax:"apo admin usage",positional:[],flags:{},description:"Spend by user"},timeseries:{syntax:"apo admin timeseries [--bucket day]",positional:[],flags:{"--bucket":"hour|day"},description:"Spend over time"},pricing:{syntax:"apo admin pricing",positional:[],flags:{},description:"Provider pricing table"},ice:{syntax:"apo admin ice [--relay [--transport tcp|udp] [--hosts IP,IP]] [--url turn:IP:3478 --user U --pass P]",positional:[],flags:{"--relay":"relay datagrams between TURN hosts' relay ports (tests the relay port range; result independent of this host's network)","--transport":"control-channel transport for --relay (default tcp)","--hosts":"with --relay: also relay through these hosts from /stt/config (e.g. a coturn box advertised only as STUN), using the advertised TURN creds","--url":"stun:/turn: url","--user":"turn user","--pass":"turn credential","--timeout":"ms"},description:"Probe live STUN/TURN (control-gated; INCONCLUSIVE ≠ fault)"},profile:{syntax:"apo admin profile EMAIL -p PASS",positional:["email"],flags:{"--password/-p":"password"},description:"Superadmin-orchestrated profile view"}}},stats:{syntax:"apo stats",positional:[],flags:{},description:"Platform overview"},funnel:{syntax:"apo funnel",positional:[],flags:{},description:"Signup funnel"},signups:{syntax:"apo signups [--days 7]",positional:[],flags:{"--days":"window"},description:"Recent signups"},revenue:{syntax:"apo revenue",positional:[],flags:{},description:"Revenue breakdown"},tickets:{syntax:"apo tickets [--status open]",positional:[],flags:{"--status":"filter"},description:"List support tickets"},ticket:{subcommands:{show:{syntax:"apo ticket show NUMBER",positional:["number"],flags:{},description:"Ticket detail"},reply:{syntax:"apo ticket reply NUMBER MESSAGE",positional:["number","message"],flags:{},description:"Reply to ticket"},close:{syntax:"apo ticket close NUMBER",positional:["number"],flags:{},description:"Close ticket"}}},audit:{syntax:"apo audit [export] [--action X] [--limit N] [--format csv]",positional:["export?"],flags:{"--action":"filter","--limit":"n","--format":"json|csv (export)"},description:"Activity trail / export"},helpdesk:{subcommands:{tickets:{syntax:"apo helpdesk tickets [--status S]",positional:[],flags:{"--status":"filter"},description:"List tickets"},ticket:{subcommands:{show:{syntax:"apo helpdesk ticket show ID",positional:["id"],flags:{},description:"Ticket detail + messages"},create:{syntax:'apo helpdesk ticket create "Subject"',positional:["subject"],flags:{"--subject":"subject","--priority":"low|medium|high","--category":"category"},description:"Create ticket"},reply:{syntax:'apo helpdesk ticket reply ID "msg"',positional:["id","message"],flags:{"--message":"body"},description:"Reply to ticket"},close:{syntax:"apo helpdesk ticket close ID",positional:["id"],flags:{},description:"Close ticket"},assign:{syntax:"apo helpdesk ticket assign ID --agent X",positional:["id"],flags:{"--agent":"agent"},description:"Assign ticket"}}},kb:{syntax:"apo helpdesk kb",positional:[],flags:{},description:"List KB articles"},stats:{syntax:"apo helpdesk stats",positional:[],flags:{},description:"Helpdesk statistics"}}},crm:{subcommands:{leads:{syntax:"apo crm leads [--status S]",positional:[],flags:{"--status":"filter"},description:"List leads"},lead:{subcommands:{show:{syntax:"apo crm lead show ID",positional:["id"],flags:{},description:"Lead detail"},create:{syntax:'apo crm lead create "Name" --email X',positional:["name"],flags:{"--name":"name","--email":"email","--source":"source"},description:"Create lead"},convert:{syntax:"apo crm lead convert ID",positional:["id"],flags:{},description:"Convert lead → opportunity"}}},opps:{syntax:"apo crm opps [--stage S]",positional:[],flags:{"--stage":"filter"},description:"List opportunities"},opp:{subcommands:{show:{syntax:"apo crm opp show ID",positional:["id"],flags:{},description:"Opportunity detail"},create:{syntax:'apo crm opp create "Deal" --value X',positional:["name"],flags:{"--name":"name","--value":"amount","--stage":"stage"},description:"Create opportunity"}}},pipelines:{syntax:"apo crm pipelines",positional:[],flags:{},description:"List pipelines"},stats:{syntax:"apo crm stats",positional:[],flags:{},description:"CRM statistics"}}},agent:{subcommands:{create:{syntax:"apo agent create HANDLE",positional:["handle"],flags:{"--type":"agent type","--global":"save to ~/.appoiint"},description:"Create named agent"},list:{syntax:"apo agent list",positional:[],flags:{},description:"List your agents"},use:{syntax:"apo agent use HANDLE | --account",positional:["handle?"],flags:{"--account":"switch back to account identity","--global":"save to ~/.appoiint"},description:"Act as @HANDLE"},whoami:{syntax:"apo agent whoami",positional:[],flags:{},description:"Show active identity"},revoke:{syntax:"apo agent revoke HANDLE",positional:["handle"],flags:{"--global":"save to ~/.appoiint"},description:"Revoke agent key"}}},wall:{syntax:"apo wall",positional:[],flags:{},description:"All threads (DMs + channels) w/ unread badges"},dm:{syntax:'apo dm @AGENT ["msg"] [--watch]',positional:["handle","message?"],flags:{"--message/-m":"body","--watch/-w":"live stream","--limit":"history size"},description:"Read/send a DM"},notifications:{syntax:"apo notifications",positional:[],flags:{},description:"Unread digest across all threads"},channel:{subcommands:{create:{syntax:"apo channel create NAME [--topic T]",positional:["name"],flags:{"--topic":"topic"},description:"Create a named channel"},join:{syntax:"apo channel join NAME",positional:["name"],flags:{},description:"Join a channel"},leave:{syntax:"apo channel leave NAME",positional:["name"],flags:{},description:"Leave a channel"},list:{syntax:"apo channel list",positional:[],flags:{},description:"List channels"},post:{syntax:'apo channel post NAME "msg"',positional:["name","message"],flags:{"--message/-m":"body"},description:"Post to a channel"},history:{syntax:"apo channel history <NAME|@AGENT>",positional:["target"],flags:{"--limit":"n"},description:"Channel or DM history (reading = ack)"},send:{syntax:'apo channel send @AGENT "msg"',positional:["handle","message"],flags:{"--message/-m":"body","--action/-a":"action","--payload":"json payload"},description:"Send 1:1 message"},inbox:{syntax:"apo channel inbox",positional:[],flags:{},description:"Unread messages"},agents:{syntax:"apo channel agents",positional:[],flags:{},description:"List agent contacts"}}},diagnose:{syntax:"apo diagnose ROOM_ID",positional:["roomId"],flags:{},description:"Full room diagnostic"},man:{syntax:"apo man renderer",positional:["page"],flags:{},description:"Manual pages (renderer)"},renderer:{syntax:"apo renderer",positional:[],flags:{},description:"Chat bubble renderer spec"},"mcp-serve":{syntax:"apo mcp-serve",positional:[],flags:{},description:"Run stdio MCP server (95 tools)"},help:{syntax:"apo help",positional:[],flags:{},description:"Full reference"}};function schema(){return{$schema:"appoiint-cli-commands",version:pkg.version,description:"apo CLI command schema. Load this into agent context BEFORE using any command. If a command, subcommand or flag is not listed here, DO NOT USE it — it does not exist. Never invent verbs or flags from memory; re-run `apo init --agent-schema --json` to refresh.",globalFlags:GLOBAL_FLAGS,commands:COMMANDS}}module.exports={schema:schema};
|
|
1
|
+
const pkg=require("../../package.json"),GLOBAL_FLAGS={"--json":"JSON output (for agents)","--key/-k":"override API key","--agent":"one-off identity override (handle)","--help/-h":"help"},COMMANDS={signup:{syntax:"apo signup --email E --password P",positional:[],flags:{"--email/-e":"email","--password/-p":"password","--agent/-a":"agent name","--type/-t":"agent type"},description:"Sign up (free 7-day trial, card required)"},signin:{syntax:"apo signin --email E --password P",positional:[],flags:{"--email/-e":"email","--password/-p":"password","--local":"save to ./.appoiint"},description:"Sign in as a human seat"},activate:{syntax:"apo activate [SUBSCRIPTION_ID]",positional:["subscriptionId?"],flags:{},description:"Get API key after card setup"},login:{syntax:"apo login --key KEY [--user USERID]",positional:[],flags:{"--key/-k":"API key","--user/-u":"user id","--local":"save to ./.appoiint"},description:"Save API key to config"},logout:{syntax:"apo logout",positional:[],flags:{},description:"Clear config"},config:{syntax:"apo config",positional:[],flags:{},description:"Show active config"},status:{syntax:"apo status",positional:[],flags:{},description:"API health + auth status"},version:{syntax:"apo version",positional:[],flags:{},description:"CLI version"},init:{syntax:"apo init [--agent-schema] [--json]",positional:[],flags:{"--agent-schema":"emit machine-readable command schema"},description:"Quickstart guide; --agent-schema dumps this schema as JSON"},room:{syntax:'apo room "Title"',positional:["title"],flags:{},description:"Create meeting room"},rooms:{syntax:"apo rooms",positional:[],flags:{},description:"List active rooms"},"room-status":{syntax:"apo room-status ROOM_ID",positional:["roomId"],flags:{},description:"Room status + participants"},"room-end":{syntax:"apo room-end ROOM_ID",positional:["roomId"],flags:{},description:"End meeting"},invite:{syntax:"apo invite ROOM_ID --email E --name N",positional:["roomId"],flags:{"--email/-e":"invitee email","--name/-n":"invitee name","--type/-t":"human|agent"},description:"Invite human or agent"},transcript:{syntax:"apo transcript ROOM_ID [--format F]",positional:["roomId"],flags:{"--format/-f":"json|text|srt"},description:"Get transcript"},join:{syntax:'apo join ROOM_ID|BOOKING_ID [--name N] [--say "msg"] [--voice V]',positional:["roomIdOrBookingId"],flags:{"--name":"display name","--role":"agent role","--say":"one message then leave","--voice":"TTS voice"},description:"Join room (bkg_ id resolves to room), stream transcript"},say:{syntax:'apo say ROOM_ID "text"',positional:["roomId","text"],flags:{},description:"Speak into a live join session (external-brain channel)"},hear:{syntax:"apo hear ROOM_ID [--wait]",positional:["roomId"],flags:{"--wait":"block until next utterance","--since":"timestamp","--speaker":"filter speaker","--timeout":"seconds"},description:"Print next utterance (external-brain channel)"},leave:{syntax:"apo leave ROOM_ID",positional:["roomId"],flags:{},description:"Leave room"},book:{syntax:"apo book --date D --email E",positional:[],flags:{"--event/-e":"event type id","--date/-d":"ISO datetime","--email":"guest email","--name/-n":"guest name","--timezone/-z":"tz","--host":"host email"},description:"Create booking"},bookings:{syntax:"apo bookings [--status S] [--host ID]",positional:[],flags:{"--status/-s":"status filter","--host":"host user id"},description:"List bookings (as host)"},booking:{syntax:"apo booking BOOKING_ID",positional:["bookingId"],flags:{},description:"Show booking (room + join links)"},cancel:{syntax:"apo cancel BOOKING_ID [--reason R]",positional:["bookingId"],flags:{"--reason/-r":"reason"},description:"Cancel a booking"},reschedule:{syntax:"apo reschedule BOOKING_ID --date D",positional:["bookingId"],flags:{"--date/-d":"new ISO datetime","--reason/-r":"reason"},description:"Move a booking to a new time"},events:{syntax:"apo events",positional:[],flags:{},description:"List event types"},"event-create":{syntax:'apo event-create --name "Title"',positional:[],flags:{"--name/-n":"title","--duration/-d":"minutes","--slug":"url slug","--description":"text","--timezone/-z":"tz","--price":"amount","--currency":"ISO currency"},description:"Create an event type"},"event-delete":{syntax:"apo event-delete EVENT_TYPE_ID [--soft] [--force]",positional:["eventTypeId"],flags:{"--id":"event type id","--soft":"soft delete","--force/-f":"skip booking check"},description:"Delete an event type (blocks if bookings exist)"},availability:{subcommands:{get:{syntax:"apo availability get [--user ID]",positional:[],flags:{"--user":"user id"},description:"Show weekly availability"},set:{syntax:"apo availability set --days mon-fri [--from 09:00 --to 17:00]",positional:[],flags:{"--days":"e.g. mon-fri","--from":"HH:MM","--to":"HH:MM","--timezone/-z":"tz","--user":"user id","--org":"organization id"},description:"Set availability"}}},slots:{syntax:"apo slots --email E --date D",positional:[],flags:{"--email/-e":"host email","--date/-d":"YYYY-MM-DD","--timezone/-z":"tz"},description:"Check availability"},domains:{syntax:"apo domains",positional:[],flags:{},description:"List your custom domains"},domain:{subcommands:{add:{syntax:"apo domain add HOSTNAME",positional:["hostname"],flags:{},description:"Link a domain (returns CNAME)"},verify:{syntax:"apo domain verify HOSTNAME",positional:["hostname"],flags:{},description:"Trigger DNS verification"},status:{syntax:"apo domain status HOSTNAME",positional:["hostname"],flags:{},description:"DNS + SSL status"},show:{syntax:"apo domain show HOSTNAME",positional:["hostname"],flags:{},description:"Domain details"},remove:{syntax:"apo domain remove HOSTNAME --yes",positional:["hostname"],flags:{"--yes":"confirm deletion"},description:"Unlink a domain"}}},page:{subcommands:{show:{syntax:"apo page",positional:[],flags:{},description:"Show your meetings-page profile"},set:{syntax:"apo page set --title T --company C",positional:[],flags:{"--first":"first name","--last":"last name","--company":"company","--title":"title","--phone":"phone","--website":"url"},description:"Update page fields"},cover:{syntax:"apo page cover <URL|file> [--type cover|avatar]",positional:["source"],flags:{"--type":"cover|avatar"},description:"Set meetings-page cover image"},check:{syntax:"apo page check [HOSTNAME]",positional:["hostname?"],flags:{},description:"Audit live page: routing, OG tags, image, profile"}}},tts:{syntax:'apo tts "text" --voice V',positional:["text"],flags:{"--voice/-v":"voice id","--role/-r":"role voice"},description:"Synthesize speech"},voices:{syntax:"apo voices",positional:[],flags:{},description:"List available voices"},admin:{subcommands:{tenants:{syntax:"apo admin tenants",positional:[],flags:{},description:"List all tenants"},"tenant-view":{syntax:"apo admin tenant-view SLUG",positional:["slug"],flags:{},description:"Full tenant detail"},"tenant-create":{syntax:"apo admin tenant-create SLUG",positional:["slug"],flags:{"--plan":"plan","--email":"owner email"},description:"Create tenant"},"tenant-suspend":{syntax:"apo admin tenant-suspend SLUG",positional:["slug"],flags:{},description:"Suspend tenant"},"tenant-activate":{syntax:"apo admin tenant-activate SLUG",positional:["slug"],flags:{"--plan":"plan"},description:"Reactivate tenant"},"tenant-delete":{syntax:"apo admin tenant-delete SLUG",positional:["slug"],flags:{},description:"Delete tenant"},users:{syntax:"apo admin users",positional:[],flags:{},description:"List all users"},"user-create":{syntax:"apo admin user-create --email E -p PASS",positional:[],flags:{"--email":"email","--password/-p":"password","--tenant":"tenant","--role":"role"},description:"Create user"},"user-reset":{syntax:"apo admin user-reset EMAIL -p PASS",positional:["email"],flags:{"--password/-p":"new password"},description:"Reset user password"},"profile-cover":{syntax:"apo admin profile-cover [EMAIL] <SRC> -p PASS [--tenants a@x,b@y]",positional:["emailOrSource","source?"],flags:{"--type":"cover|avatar","--password/-p":"password","--tenants":"comma list","--tenants-file":"file of tenants"},description:"Set tenant cover image (bulk = gated snapshot→apply→verify→rollback→ledger)"},"profile-avatar":{syntax:"apo admin profile-avatar [EMAIL] <SRC> -p PASS [--tenants a@x,b@y]",positional:["emailOrSource","source?"],flags:{"--password/-p":"password","--tenants":"comma list","--tenants-file":"file of tenants"},description:"Set tenant avatar (bulk gated)"},"profile-website":{syntax:"apo admin profile-website [EMAIL URL] -p PASS [--tenants a@x=url,b@y=url]",positional:["email?","url?"],flags:{"--password/-p":"password","--tenants":"a@x=url pairs","--tenants-file":"file of pairs"},description:"Set vCard VISIT CTA (bulk gated)"},billing:{syntax:"apo admin billing",positional:[],flags:{"--plan":"plan","--months":"n"},description:"MRR, ARR, revenue"},costs:{syntax:"apo admin costs [ROOM_ID]",positional:["roomId?"],flags:{},description:"Platform or per-meeting cost breakdown"},models:{syntax:"apo admin models",positional:[],flags:{},description:"Spend by provider/model + tokens"},usage:{syntax:"apo admin usage",positional:[],flags:{},description:"Spend by user"},timeseries:{syntax:"apo admin timeseries [--bucket day]",positional:[],flags:{"--bucket":"hour|day"},description:"Spend over time"},pricing:{syntax:"apo admin pricing",positional:[],flags:{},description:"Provider pricing table"},"tenant-seed":{syntax:'apo admin tenant-seed EMAIL --domain meetings.X.com [--name "Display Name"] [--yes] [--no-vault]',positional:["email"],flags:{"--domain":"custom meetings hostname","--name":"display name","--app-url":"the app deployment whose /sdk/domains proxy registers the domain (default https://appoiint.com when the SDK is prod; a QA/prod mix is refused)","--yes":"provision (default is a dry run)","--reset-password":"existing user: issue a fresh temporary password via the SDK and vault + share it","--no-vault":"skip SecretCarousel vault + shares","--no-domain":"seed the account only; re-run later to attach the domain"},description:"Seed a sister-stack tenant on prod: comped enterprise account, 90d dashboard JWT, domain registered as the tenant, secrets vaulted with one-view shares. Idempotent. Needs APPOIINT_ADMIN_KEY"},"email-override":{syntax:"apo admin email-override show | set --from-vault SECRET_ID [--from EMAIL] [--name NAME] [--test EMAIL] | set --from-share URL [--vault-as NAME] | disable",positional:["action"],flags:{"--from-vault":"SecretCarousel secret id holding the SMTP bundle (SMTP_HOSTS/PORT/USER/PASS, MAIL_FROM)","--from-share":"partner one-view share URL: redeemed straight into the vault, then used","--vault-as":"vault name for --from-share (default tygaemail-relay-appoiint-ai)","--from":"From address (default noreply@appoiint.ai)","--name":"From name (default Appoiint)","--test":"after set, request a magic link for this email to see the sender"},description:"Per-tenant SMTP override on the SDK (PUT /admin/email/override) so SDK-sent mail leaves from our relay/From instead of tyga.run. Secrets never hit disk or stdout"},ice:{syntax:"apo admin ice [--relay [--transport tcp|udp] [--hosts IP,IP]] [--url turn:IP:3478 --user U --pass P]",positional:[],flags:{"--relay":"relay datagrams between TURN hosts' relay ports (tests the relay port range; result independent of this host's network)","--transport":"control-channel transport for --relay (default tcp)","--hosts":"with --relay: also relay through these hosts from /stt/config (e.g. a coturn box advertised only as STUN), using the advertised TURN creds","--url":"stun:/turn: url","--user":"turn user","--pass":"turn credential","--timeout":"ms"},description:"Probe live STUN/TURN (control-gated; INCONCLUSIVE ≠ fault)"},profile:{syntax:"apo admin profile EMAIL -p PASS",positional:["email"],flags:{"--password/-p":"password"},description:"Superadmin-orchestrated profile view"}}},stats:{syntax:"apo stats",positional:[],flags:{},description:"Platform overview"},funnel:{syntax:"apo funnel",positional:[],flags:{},description:"Signup funnel"},signups:{syntax:"apo signups [--days 7]",positional:[],flags:{"--days":"window"},description:"Recent signups"},revenue:{syntax:"apo revenue",positional:[],flags:{},description:"Revenue breakdown"},tickets:{syntax:"apo tickets [--status open]",positional:[],flags:{"--status":"filter"},description:"List support tickets"},ticket:{subcommands:{show:{syntax:"apo ticket show NUMBER",positional:["number"],flags:{},description:"Ticket detail"},reply:{syntax:"apo ticket reply NUMBER MESSAGE",positional:["number","message"],flags:{},description:"Reply to ticket"},close:{syntax:"apo ticket close NUMBER",positional:["number"],flags:{},description:"Close ticket"}}},audit:{syntax:"apo audit [export] [--action X] [--limit N] [--format csv]",positional:["export?"],flags:{"--action":"filter","--limit":"n","--format":"json|csv (export)"},description:"Activity trail / export"},helpdesk:{subcommands:{tickets:{syntax:"apo helpdesk tickets [--status S]",positional:[],flags:{"--status":"filter"},description:"List tickets"},ticket:{subcommands:{show:{syntax:"apo helpdesk ticket show ID",positional:["id"],flags:{},description:"Ticket detail + messages"},create:{syntax:'apo helpdesk ticket create "Subject"',positional:["subject"],flags:{"--subject":"subject","--priority":"low|medium|high","--category":"category"},description:"Create ticket"},reply:{syntax:'apo helpdesk ticket reply ID "msg"',positional:["id","message"],flags:{"--message":"body"},description:"Reply to ticket"},close:{syntax:"apo helpdesk ticket close ID",positional:["id"],flags:{},description:"Close ticket"},assign:{syntax:"apo helpdesk ticket assign ID --agent X",positional:["id"],flags:{"--agent":"agent"},description:"Assign ticket"}}},kb:{syntax:"apo helpdesk kb",positional:[],flags:{},description:"List KB articles"},stats:{syntax:"apo helpdesk stats",positional:[],flags:{},description:"Helpdesk statistics"}}},crm:{subcommands:{leads:{syntax:"apo crm leads [--status S]",positional:[],flags:{"--status":"filter"},description:"List leads"},lead:{subcommands:{show:{syntax:"apo crm lead show ID",positional:["id"],flags:{},description:"Lead detail"},create:{syntax:'apo crm lead create "Name" --email X',positional:["name"],flags:{"--name":"name","--email":"email","--source":"source"},description:"Create lead"},convert:{syntax:"apo crm lead convert ID",positional:["id"],flags:{},description:"Convert lead → opportunity"}}},opps:{syntax:"apo crm opps [--stage S]",positional:[],flags:{"--stage":"filter"},description:"List opportunities"},opp:{subcommands:{show:{syntax:"apo crm opp show ID",positional:["id"],flags:{},description:"Opportunity detail"},create:{syntax:'apo crm opp create "Deal" --value X',positional:["name"],flags:{"--name":"name","--value":"amount","--stage":"stage"},description:"Create opportunity"}}},pipelines:{syntax:"apo crm pipelines",positional:[],flags:{},description:"List pipelines"},stats:{syntax:"apo crm stats",positional:[],flags:{},description:"CRM statistics"}}},agent:{subcommands:{create:{syntax:"apo agent create HANDLE",positional:["handle"],flags:{"--type":"agent type","--global":"save to ~/.appoiint"},description:"Create named agent"},list:{syntax:"apo agent list",positional:[],flags:{},description:"List your agents"},use:{syntax:"apo agent use HANDLE | --account",positional:["handle?"],flags:{"--account":"switch back to account identity","--global":"save to ~/.appoiint"},description:"Act as @HANDLE"},whoami:{syntax:"apo agent whoami",positional:[],flags:{},description:"Show active identity"},revoke:{syntax:"apo agent revoke HANDLE",positional:["handle"],flags:{"--global":"save to ~/.appoiint"},description:"Revoke agent key"}}},wall:{syntax:"apo wall",positional:[],flags:{},description:"All threads (DMs + channels) w/ unread badges"},dm:{syntax:'apo dm @AGENT ["msg"] [--watch]',positional:["handle","message?"],flags:{"--message/-m":"body","--watch/-w":"live stream","--limit":"history size"},description:"Read/send a DM"},notifications:{syntax:"apo notifications",positional:[],flags:{},description:"Unread digest across all threads"},channel:{subcommands:{create:{syntax:"apo channel create NAME [--topic T]",positional:["name"],flags:{"--topic":"topic"},description:"Create a named channel"},join:{syntax:"apo channel join NAME",positional:["name"],flags:{},description:"Join a channel"},leave:{syntax:"apo channel leave NAME",positional:["name"],flags:{},description:"Leave a channel"},list:{syntax:"apo channel list",positional:[],flags:{},description:"List channels"},post:{syntax:'apo channel post NAME "msg"',positional:["name","message"],flags:{"--message/-m":"body"},description:"Post to a channel"},history:{syntax:"apo channel history <NAME|@AGENT>",positional:["target"],flags:{"--limit":"n"},description:"Channel or DM history (reading = ack)"},send:{syntax:'apo channel send @AGENT "msg"',positional:["handle","message"],flags:{"--message/-m":"body","--action/-a":"action","--payload":"json payload"},description:"Send 1:1 message"},inbox:{syntax:"apo channel inbox",positional:[],flags:{},description:"Unread messages"},agents:{syntax:"apo channel agents",positional:[],flags:{},description:"List agent contacts"}}},diagnose:{syntax:"apo diagnose ROOM_ID",positional:["roomId"],flags:{},description:"Full room diagnostic"},man:{syntax:"apo man renderer",positional:["page"],flags:{},description:"Manual pages (renderer)"},renderer:{syntax:"apo renderer",positional:[],flags:{},description:"Chat bubble renderer spec"},"mcp-serve":{syntax:"apo mcp-serve",positional:[],flags:{},description:"Run stdio MCP server (95 tools)"},help:{syntax:"apo help",positional:[],flags:{},description:"Full reference"}};function schema(){return{$schema:"appoiint-cli-commands",version:pkg.version,description:"apo CLI command schema. Load this into agent context BEFORE using any command. If a command, subcommand or flag is not listed here, DO NOT USE it — it does not exist. Never invent verbs or flags from memory; re-run `apo init --agent-schema --json` to refresh.",globalFlags:GLOBAL_FLAGS,commands:COMMANDS}}module.exports={schema:schema};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const crypto=require("crypto"),{execFileSync:execFileSync}=require("child_process"),{getFlag:getFlag,hasFlag:hasFlag,positionalArgs:positionalArgs}=require("../config"),fmt=require("../format"),PLAN="appoiint-enterprise",PASSWORD_SECRET_TYPE="custom",SECRETISH=/pass|hash|token|secret|salt|jwt|key/i;function redact(e){return Array.isArray(e)?e.map(redact):e&&"object"==typeof e?Object.fromEntries(Object.entries(e).map(([e,t])=>[e,SECRETISH.test(e)?"<redacted>":redact(t)])):"string"==typeof e&&/^(eyJ|tc_live_|ak_)/.test(e)?"<redacted>":e}function find(e,...t){if(Array.isArray(e))for(const a of e){const e=find(a,...t);if(e)return e}else if(e&&"object"==typeof e){for(const a of t)if(e[a])return e[a];for(const a of Object.values(e)){const e=find(a,...t);if(e)return e}}return null}async function call(e,t,a,s){const r=await fetch(t,{method:e,headers:{Authorization:`Bearer ${a}`,"Content-Type":"application/json",Accept:"application/json"},body:s?JSON.stringify(s):void 0}),n=await r.text();let o;try{o=JSON.parse(n||"{}")}catch{o={raw:n.slice(0,500)}}return{status:r.status,data:o}}function must(e,{status:t,data:a},s){if(t<200||t>=300||!1===a.success)throw new Error(`${e} -> HTTP ${t}\n${JSON.stringify(redact(a),null,1).slice(0,1500)}`);return s(`${e} (HTTP ${t})`),a}function sc(e){try{return execFileSync("sc",e,{encoding:"utf8",stdio:["ignore","pipe","pipe"]})}catch(t){throw new Error(`sc ${e[0]} failed: ${String(t.stderr||t.stdout||t.message).slice(0,600)}`)}}function findSecretId(e){const t=sc(["secrets","--json"]),a=JSON.parse(t),s=(a.secrets||a.data||(Array.isArray(a)?a:[])).find(t=>t&&t.name===e);return s?s.secretId||s.id||s._id:null}function vault(e,t,a,s){let r;try{const s=sc(["secret",e,t,"-t",a]),n=s.match(/secret-[0-9a-f-]{8,}/);if(!n)throw new Error(`could not read secret id from sc output:\n${s.slice(0,400)}`);r=n[0]}catch(a){if(!/already exists/i.test(a.message))throw a;if(r=findSecretId(e),!r)throw new Error(`${e} already exists in the vault but could not be found by name to update it`);sc(["secret","update",r,"--value",t]),s(`vault: ${e} existed -> updated ${r}`)}if(!sc(["secret","show",r]).includes(t))throw new Error(`vault roundtrip failed for ${e} (${r}) — not sharing it`);return s(`vaulted ${e} -> ${r} (roundtrip ok)`),r}function share(e){const t=sc(["share",e,"--hours","72","--views","1"]),a=t.match(/https?:\/\/\S+/);if(!a)throw new Error(`could not read share url from sc output:\n${t.slice(0,400)}`);return a[0].replace(/[.,)]+$/,"")}async function run(e,t,a){const s=positionalArgs(t),r=String(s[1]||"").toLowerCase(),n=String(getFlag(t,"domain")||"").toLowerCase(),o=hasFlag(t,"yes"),i=a?e=>console.error(`• ${e}`):e=>fmt.ok(e);/^[^@\s]+@[^@\s]+\.[a-z]{2,}$/.test(r)&&/^[a-z0-9][a-z0-9.-]+\.[a-z]{2,}$/.test(n)||(fmt.err('Usage: apo admin tenant-seed EMAIL --domain meetings.example.com [--name "Display Name"] [--yes]'),process.exit(1));const d=e.adminKey;d||(fmt.err("No SDK superadmin key: set APPOIINT_ADMIN_KEY or `adminKey` in ~/.appoiint/config.json."),process.exit(1));const l=String(e.sdkUrl).replace(/\/$/,""),c=String(getFlag(t,"app-url")||process.env.APPOIINT_APP_URL||("https://sdk.tyga.run"===l?"https://appoiint.com":e.baseUrl)).replace(/\/$/,"");"https://sdk.tyga.run"===l===/\.hq\.tyga\.dev$/.test(new URL(c).hostname)&&(fmt.err(`SDK ${l} and app ${c} are different deployments — pass --app-url for the app that uses this SDK.`),process.exit(1));const u=getFlag(t,"name")||r.split("@")[0],p=r.split("@")[1].split(".")[0];try{const s=(must("lookup user",await call("GET",`${l}/admin/users?email=${encodeURIComponent(r)}`,d),i).users||[])[0]||null;if(!o){const e={dryRun:!0,sdk:l,app:c,email:r,name:u,domain:n,plan:PLAN,userExists:Boolean(s),userId:s?find(s,"_id","id","userId"):null};return a?console.log(JSON.stringify(e,null,2)):(console.log(`\n ${fmt.bold("DRY RUN")} — targets: SDK ${l} | app ${c}`),console.log(` ${r} ${s?"exists ("+e.userId+")":"does not exist"}; would comp ${PLAN}, mint a 90d JWT, register ${n}, vault + share.`),void console.log(fmt.dim(" Re-run with --yes to provision.\n")))}let m,f=null,h=null;if(s)if(m=find(s,"_id","id","userId"),hasFlag(t,"reset-password")){const e=must("reset password",await call("POST",`${l}/admin/users/reset-password`,d,{userId:m}),i);if(f=find(e,"temporaryPassword","password"),!f)throw new Error(`reset returned no temporaryPassword:\n${JSON.stringify(redact(e)).slice(0,400)}`);hasFlag(t,"no-vault")||(h=vault(`appoiint-tenant-password-${p}`,f,"custom",i))}else i(`user exists (${m}) — reusing; password unchanged (not re-shared; --reset-password to issue a new one)`);else{f=crypto.randomBytes(15).toString("base64").replace(/[+/=]/g,"")+"!7a",hasFlag(t,"no-vault")||(h=vault(`appoiint-tenant-password-${p}`,f,"custom",i));const[e,...a]=u.split(" "),s=must("create user",await call("POST",`${l}/admin/users`,d,{email:r,password:f,name:u,firstName:e,lastName:a.join(" ")||e,role:"customer"}),i);if(m=find(s,"_id","id","userId"),!m)throw new Error(`no user id in create response:\n${JSON.stringify(redact(s)).slice(0,600)}`)}must(`plan -> ${PLAN}`,await call("POST",`${l}/admin/users/${m}/plan`,d,{planId:PLAN}),i);const g=find(must("mint 90d dashboard JWT",await call("POST",`${l}/admin/users/${m}/token?expiresIn=90d`,d),i),"token","accessToken","jwt");if(!g)throw new Error("no token in mint response");let w=null,$=null;const y=hasFlag(t,"no-domain")?{status:200,data:{data:[{hostname:n,_id:"deferred"}]}}:await call("GET",`${c}/saas/api/v1/sdk/domains?tenantId=${m}&tenantType=user`,g),S=[].concat(find(y.data,"domains")||y.data.data||[]).find(e=>e&&e.hostname===n);if(S&&"deferred"===S._id)i(`domain ${n} deferred (--no-domain) — re-run without it to attach`);else if(S)w=find(S,"_id","id","domainId"),$=(S.dnsVerification||{}).expectedValue||null,i(`domain record exists (${w}) — reusing`);else{const t={hostname:n,domainType:"custom",tenantId:m,tenantType:"user",baseDomain:"appoiint.ai"};let a=await call("POST",`${c}/saas/api/v1/sdk/domains`,g,t);if(a.status>=300||!1===a.data.success){if(!e.apiKey)throw new Error(`register as tenant failed (HTTP ${a.status}) and no operator apo key to fall back on`);i(`register as tenant refused (${a.data.error||a.status}) — registering as operator, then re-homing`),a={status:200,data:must(`register ${n} (operator)`,await call("POST",`${c}/saas/api/v1/sdk/domains`,e.apiKey,t),i)};const s=find(a.data,"_id","id","domainId");must("re-home domain to tenant",await call("PUT",`${c}/saas/api/v1/sdk/domains/${s}`,e.apiKey,{tenantId:m,tenantType:"user"}),i);const r=find(must("read back domain owner",await call("GET",`${c}/saas/api/v1/sdk/domains/${s}`,e.apiKey),i),"tenantId");if(r!==m)throw new Error(`domain ${s} is homed on ${r}, not ${m} — fix before sharing`)}else i(`register ${n} (HTTP ${a.status})`);w=find(a.data,"_id","id","domainId"),$=find(a.data,"expectedValue")}const T={sdk:l,app:c,email:r,name:u,userId:m,plan:PLAN,comped:!0,login:`${c}/user-login`,domain:n,domainId:w,cnameTarget:$,jwtExpires:new Date(Date.now()+7776e6).toISOString().slice(0,10)};if(hasFlag(t,"no-vault")||(h&&(T.passwordVault=h,T.passwordShare=share(h)),T.jwtVault=vault(`appoiint-tenant-jwt-${p}-90d`,g,"api-key",i),T.jwtShare=share(T.jwtVault),i("one-view shares created (72h)")),a)return console.log(JSON.stringify(T,null,2));console.log(`\n ${fmt.bold("Seeded")} ${r} ${fmt.dim(m)}`),console.log(` CNAME ${n} -> ${$||"(not returned — apo domain show)"}`),T.passwordShare&&console.log(` password share ${T.passwordShare}`),T.jwtShare&&console.log(` JWT share ${T.jwtShare}`),console.log("")}catch(e){fmt.err(e.message),process.exit(1)}}module.exports={run:run,redact:redact,find:find};
|
package/dist/config.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
const fs=require("fs"),path=require("path"),os=require("os"),LOCAL_DIR=".appoiint",GLOBAL_DIR=path.join(os.homedir(),".appoiint"),CONFIG_FILE="config.json";function hasFlag(n,e){return n.includes(`--${e}`)||n.includes(`-${e[0]}`)}function getFlag(n,e){const t=n.indexOf(`--${e}`);if(-1!==t&&n[t+1])return n[t+1];const i=n.indexOf(`-${e[0]}`);return-1!==i&&n[i+1]?n[i+1]:null}function readConfigFile(n){try{const e=path.join(n,CONFIG_FILE);if(fs.existsSync(e))return JSON.parse(fs.readFileSync(e,"utf8"))}catch{}return{}}function getLongFlag(n,e){const t=n.indexOf(`--${e}`);return-1!==t&&n[t+1]?n[t+1]:null}function loadConfig(n){const e=getFlag(n,"key")||getFlag(n,"k"),t=process.env.APO_API_KEY||process.env.APPOIINT_API_KEY,i=readConfigFile(LOCAL_DIR),
|
|
1
|
+
const fs=require("fs"),path=require("path"),os=require("os"),LOCAL_DIR=".appoiint",GLOBAL_DIR=path.join(os.homedir(),".appoiint"),CONFIG_FILE="config.json";function hasFlag(n,e){return n.includes(`--${e}`)||n.includes(`-${e[0]}`)}function getFlag(n,e){const t=n.indexOf(`--${e}`);if(-1!==t&&n[t+1])return n[t+1];const i=n.indexOf(`-${e[0]}`);return-1!==i&&n[i+1]?n[i+1]:null}function readConfigFile(n){try{const e=path.join(n,CONFIG_FILE);if(fs.existsSync(e))return JSON.parse(fs.readFileSync(e,"utf8"))}catch{}return{}}function getLongFlag(n,e){const t=n.indexOf(`--${e}`);return-1!==t&&n[t+1]?n[t+1]:null}function ledgerAdminKey(){const n=[process.env.APPOIINT_LEDGER,path.join(process.cwd(),"prod-credentials.json"),path.join(os.homedir(),"Local_Gitea","appoiint-cluster","prod-credentials.json")].filter(Boolean);for(const e of n)try{const n=JSON.parse(fs.readFileSync(e,"utf8")).prod_superadmin_admin_key;if(n)return n}catch{}return null}function loadConfig(n){const e=getFlag(n,"key")||getFlag(n,"k"),t=process.env.APO_API_KEY||process.env.APPOIINT_API_KEY,i=readConfigFile(LOCAL_DIR),s=readConfigFile(GLOBAL_DIR),o={...s,...i},r=o.agents||{},a=getLongFlag(n,"agent")||process.env.APO_AGENT||o.activeAgent||null,l=a?String(a).replace(/^@/,""):null,c=l&&r[l]?r[l]:null;return{apiKey:e||t||c&&c.agentKey||o.apiKey||null,accountApiKey:o.apiKey||null,userId:o.userId||null,paymentIntentId:o.paymentIntentId||null,baseUrl:o.baseUrl||process.env.APPOIINT_BASE_URL||"https://appoiint.com",adminKey:process.env.APPOIINT_ADMIN_KEY||o.adminKey||ledgerAdminKey(),sdkUrl:process.env.APPOIINT_SDK_URL||o.sdkUrl||"https://sdk.tyga.run",agents:r,activeAgent:c?l:null,source:e?"flag":t?"env":c?`agent:@${l}`:i.apiKey?"local":s.apiKey?"global":"none"}}function saveConfig(n,e=!0){const t=e?LOCAL_DIR:GLOBAL_DIR;fs.existsSync(t)||fs.mkdirSync(t,{recursive:!0});const i=path.join(t,CONFIG_FILE),s={...readConfigFile(t),...n};return fs.writeFileSync(i,JSON.stringify(s,null,2)),i}function clearConfig(n=!0){const e=n?LOCAL_DIR:GLOBAL_DIR,t=path.join(e,CONFIG_FILE);fs.existsSync(t)&&fs.unlinkSync(t)}function positionalArgs(n){const e=[];for(let t=0;t<n.length;t++)n[t].startsWith("--")||n[t].startsWith("-")?t++:e.push(n[t]);return e}module.exports={loadConfig:loadConfig,saveConfig:saveConfig,clearConfig:clearConfig,hasFlag:hasFlag,getFlag:getFlag,positionalArgs:positionalArgs};
|
package/dist/main.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
const{loadConfig:loadConfig,hasFlag:hasFlag,getFlag:getFlag}=require("./config"),{api:api,rawApi:rawApi}=require("./api"),fmt=require("./format"),args=process.argv.slice(2),command=args[0],subArgs=args.slice(1),jsonMode=hasFlag(args,"json"),NO_AUTH=["signup","signin","activate","login","logout","config","help","--help","-h","status","version","--version","-v","mcp-serve","man","renderer","init"],config=loadConfig(args);NO_AUTH.includes(command)||config.apiKey||(fmt.err("No API key. Run: apo signup --email you@co.com --password Pass123!"),fmt.err("Or: apo login --key YOUR_KEY --user YOUR_USERID"),fmt.err("Or set APO_API_KEY environment variable."),process.exit(1));const client=config.apiKey?api(config):null,raw=config.apiKey?rawApi(config):null,HELP='\n[1mAppoiint CLI[0m — Agent-first meeting platform\n\n[1mAuth:[0m\n apo signup --email E --password P Sign up (free 7-day trial, card required)\n apo signin --email E --password P Sign in as a human seat (email/password)\n apo activate [SUBSCRIPTION_ID] Get API key after card setup\n apo login --key KEY [--user USERID] Save API key to config\n apo logout Clear config\n apo config Show active config\n\n[1mRooms:[0m\n apo room "Title" Create meeting room\n apo rooms List active rooms\n apo room-status ROOM_ID Room status + participants\n apo room-end ROOM_ID End meeting\n apo invite ROOM_ID --email E --name N Invite human or agent\n apo transcript ROOM_ID [--format F] Get transcript (json|text|srt)\n\n[1mMeetings:[0m\n apo join ROOM_ID|BOOKING_ID [--name N] Join room (or bkg_ id → resolves room), streams transcript, Ctrl-C to leave\n apo join ROOM_ID --say "msg" Join, send one message, leave\n apo join ROOM_ID --say "msg" --voice cfo Speak out loud (assistant.bz TTS) + leave\n apo say ROOM_ID "text" Speak into a live join session (external-brain channel)\n apo hear ROOM_ID --wait Block until next utterance, print it (external-brain channel)\n apo leave ROOM_ID Leave room\n\n[1mScheduling:[0m\n apo book --date D --email E Create booking\n apo bookings [--status S] [--host ID] List bookings (as host)\n apo booking BOOKING_ID Show booking (room + join links)\n apo cancel BOOKING_ID [--reason R] Cancel a booking\n apo reschedule BOOKING_ID --date D Move a booking to a new time\n apo events List event types\n apo event-create --name "Title" Create an event type (--duration --slug --price)\n apo event-delete EVENT_TYPE_ID Delete an event type (blocks if bookings exist; --soft --force)\n apo availability get Show weekly availability\n apo availability set --days mon-fri Set availability (--from 09:00 --to 17:00 --timezone TZ)\n apo slots --email E --date D Check availability\n\n[1mMeetings Page (custom domain):[0m\n apo domains List your custom domains\n apo domain add meetings.you.com Link a domain (returns CNAME to add)\n apo domain verify HOSTNAME Trigger DNS verification\n apo domain status HOSTNAME DNS + SSL status\n apo domain show HOSTNAME Domain details\n apo domain remove HOSTNAME --yes Unlink a domain\n apo page Show your meetings-page profile\n apo page set --title T --company C Update page fields (--first --last --phone)\n apo page cover <URL|file> Set your meetings-page cover image (--type cover|avatar)\n apo page check [HOSTNAME] Audit live page: routing, OG tags, image, profile\n\n[1mTTS:[0m\n apo tts "text" --voice cfo Synthesize speech\n apo voices List available voices\n\n[1mAdmin:[0m\n apo admin tenants List all tenants\n apo admin tenant-view SLUG Full tenant detail\n apo admin tenant-create SLUG Create tenant\n apo admin tenant-suspend SLUG Suspend tenant\n apo admin tenant-activate SLUG Reactivate tenant\n apo admin tenant-delete SLUG Delete tenant\n apo admin users List all users\n apo admin user-reset EMAIL -p PASS Reset user password\n apo admin profile-cover EMAIL <SRC> -p PASS Set one tenant\'s cover image (--type cover|avatar)\n apo admin profile-cover <SRC> --tenants a@x,b@y -p PASS Gated bulk rollout (snapshot→apply→verify→rollback→ledger)\n apo admin profile-website EMAIL https://site.com -p PASS Set the vCard "VISIT <SITE>" CTA (profile.website)\n apo admin profile-website --tenants a@x=https://x.com,b@y=https://y.com -p PASS Gated bulk CTA rollout\n apo admin billing MRR, ARR, revenue\n apo admin costs Platform cost summary (STT/TTS/LLM)\n apo admin costs ROOM_ID Per-meeting cost breakdown\n apo admin models Spend by provider/model + tokens\n apo admin usage Spend by user\n apo admin timeseries [--bucket day] Spend over time (hour|day)\n apo admin pricing Current provider pricing table\n apo admin ice Probe live STUN/TURN (control-gated)\n apo admin ice --url turn:IP:3478 Probe one server ad-hoc (--user --pass)\n apo admin ice --relay Relay data between TURN boxes (tests relay port range)\n\n[1mAnalytics:[0m\n apo stats Platform overview\n apo revenue Revenue breakdown\n apo funnel Signup funnel\n apo signups [--days 7] Recent signups\n\n[1mSupport:[0m\n apo tickets [--status open] List support tickets\n apo ticket show NUMBER Ticket detail\n apo ticket reply NUMBER MESSAGE Reply to ticket\n apo ticket close NUMBER Close ticket\n\n[1mAudit:[0m\n apo audit [--action X] [--limit N] Activity trail\n apo audit export [--format csv] Export audit log\n\n[1mHelpdesk (+$4.99/mo):[0m\n apo helpdesk tickets List tickets\n apo helpdesk ticket show ID Ticket detail + messages\n apo helpdesk ticket create "Subject" Create ticket\n apo helpdesk ticket reply ID "msg" Reply to ticket\n apo helpdesk ticket close ID Close ticket\n apo helpdesk ticket assign ID --agent X Assign ticket\n apo helpdesk kb List KB articles\n apo helpdesk stats Helpdesk statistics\n\n[1mCRM (+$4.99/mo):[0m\n apo crm leads List leads\n apo crm lead show ID Lead detail\n apo crm lead create "Name" --email X Create lead\n apo crm lead convert ID Convert lead → opportunity\n apo crm opps List opportunities\n apo crm opp show ID Opportunity detail\n apo crm opp create "Deal" --value X Create opportunity\n apo crm pipelines List pipelines\n apo crm stats CRM statistics\n\n[1mAgents (multi-agent identities):[0m\n apo agent create ci-bot Create named agent under your account\n apo agent list List your agents (plan quota: 3/5/10/∞)\n apo agent use ci-bot Act as @ci-bot (channels route by handle)\n apo agent use --account Switch back to account identity\n apo agent whoami Show active identity\n apo agent revoke ci-bot Revoke agent key\n --agent HANDLE / APO_AGENT env One-off identity override for any command\n\n[1mMessaging (agent-to-agent):[0m\n apo wall All threads (DMs + channels) w/ unread badges\n apo dm @AGENT ["msg"] [--watch] Read/send a DM (--watch = live stream)\n apo notifications Unread digest across all threads\n apo channel create NAME [--topic T] Create a named channel\n apo channel join|leave NAME Join / leave a channel\n apo channel list List channels (membership + unread)\n apo channel post NAME "msg" Post to a channel\n apo channel history <NAME|@AGENT> Channel or DM history (reading = ack)\n apo channel send @AGENT "msg" Send 1:1 message (legacy form of apo dm)\n apo channel inbox Unread messages\n apo channel agents List agent contacts\n\n[1mDiagnostics:[0m\n apo diagnose ROOM_ID Full room diagnostic (peers, events, relay)\n\n[1mManual:[0m\n apo man renderer Chat bubble renderer capabilities + formats\n apo renderer Same as above (shorthand)\n\n[1mOptions:[0m\n --json Output JSON (for agents)\n --key KEY Override API key\n --help Show this help\n\n[2mhttps://appoiint.com/docs/quickstart.html[0m\n';async function run(){switch(command){case"signup":return require("./commands/auth").signup(subArgs,config.baseUrl);case"signin":return require("./commands/auth").signin(subArgs,config.baseUrl);case"activate":return require("./commands/auth").activate(subArgs,config);case"login":return require("./commands/auth").login(subArgs);case"logout":return require("./commands/auth").logout(subArgs);case"config":return require("./commands/auth").showConfig(config);case"room":return require("./commands/rooms").create(client,subArgs,jsonMode,config);case"rooms":return require("./commands/rooms").list(client,subArgs,jsonMode);case"room-status":return require("./commands/rooms").status(client,subArgs,jsonMode);case"room-end":return require("./commands/rooms").end(client,subArgs,jsonMode);case"invite":return require("./commands/rooms").invite(client,subArgs,jsonMode);case"transcript":return require("./commands/rooms").transcript(client,subArgs,jsonMode);case"join":return require("./commands/meeting").join(client,subArgs,jsonMode,config);case"say":return require("./commands/meeting").say(client,subArgs,jsonMode,config);case"hear":return require("./commands/meeting").hear(client,subArgs,jsonMode,config);case"leave":return subArgs[0]||(fmt.err("Usage: apo leave ROOM_ID"),process.exit(1)),client.leaveMeeting({roomId:subArgs[0],agentId:"cli-agent"}).then(e=>jsonMode?console.log(JSON.stringify(e,null,2)):fmt.ok(`Left room ${subArgs[0]}`));case"book":return require("./commands/bookings").book(client,subArgs,jsonMode);case"bookings":return require("./commands/bookings").bookings(client,subArgs,jsonMode);case"booking":return require("./commands/bookings").bookingShow(client,subArgs,jsonMode);case"cancel":return require("./commands/bookings").cancel(client,subArgs,jsonMode);case"reschedule":return require("./commands/bookings").reschedule(client,subArgs,jsonMode);case"events":return require("./commands/bookings").events(client,subArgs,jsonMode);case"event-create":return require("./commands/bookings").eventCreate(client,subArgs,jsonMode);case"event-delete":return require("./commands/bookings").eventDelete(client,subArgs,jsonMode);case"availability":return require("./commands/bookings").availability(client,subArgs,jsonMode);case"slots":return require("./commands/bookings").slots(client,subArgs,jsonMode);case"domains":return require("./commands/domains").list(client,subArgs,jsonMode);case"domain":return require("./commands/domains").domainRun(client,subArgs,jsonMode);case"page":return require("./commands/domains").pageRun(client,subArgs,jsonMode);case"tts":return require("./commands/tts").synthesize(client,subArgs,jsonMode);case"voices":return require("./commands/tts").voices(client,subArgs,jsonMode);case"status":{const e=await api({baseUrl:config.baseUrl,apiKey:""}).status();return jsonMode?console.log(JSON.stringify(e,null,2)):void fmt.ok(`Appoiint ${e.version||"?"} — ${e.available?"online":"offline"}`)}case"version":case"--version":case"-v":return void console.log(require("../package.json").version);case"admin":return require("./commands/admin").run(client,subArgs,jsonMode,config);case"stats":return require("./commands/stats").run(client,jsonMode);case"funnel":return require("./commands/stats").funnel(client,jsonMode);case"signups":return require("./commands/stats").signups(client,subArgs,jsonMode);case"revenue":return require("./commands/stats").revenue(client,jsonMode);case"tickets":return require("./commands/tickets").list(client,subArgs,jsonMode);case"ticket":const e=subArgs[0];if("show"===e)return require("./commands/tickets").show(client,subArgs.slice(1),jsonMode);if("reply"===e)return require("./commands/tickets").reply(client,subArgs.slice(1),jsonMode);if("close"===e)return require("./commands/tickets").close(client,subArgs.slice(1),jsonMode);fmt.err("Usage: apo ticket [show|reply|close] NUMBER"),process.exit(1);case"audit":return require("./commands/audit").run(client,subArgs,jsonMode);case"helpdesk":return require("./commands/helpdesk").run(client,subArgs,jsonMode,raw);case"crm":return require("./commands/crm").run(client,subArgs,jsonMode,raw);case"agent":return require("./commands/agent").run(config,subArgs,jsonMode);case"channel":return require("./commands/channels").run(client,subArgs,jsonMode);case"wall":return require("./commands/wall").wall(client,subArgs,jsonMode);case"dm":return require("./commands/wall").dm(client,subArgs,jsonMode,config);case"notifications":return require("./commands/wall").notifications(client,subArgs,jsonMode);case"diagnose":return require("./commands/diagnose").run(client,subArgs,jsonMode,config);case"init":return require("./commands/init").run(subArgs);case"man":case"renderer":const n="man"===command?subArgs[0]:command;return"renderer"===n?require("./commands/renderer").run(subArgs.slice("man"===command?1:0),jsonMode):void fmt.err("Unknown manual page: "+(n||"(none)")+". Available: renderer");case"mcp-serve":return require("./mcp").serve();case"help":case"--help":case"-h":case void 0:return void console.log(HELP);default:fmt.err(`Unknown command: ${command}`),console.log(HELP),process.exit(1)}}run().catch(e=>{fmt.err(e.message),process.exit(1)});
|
|
1
|
+
const{loadConfig:loadConfig,hasFlag:hasFlag,getFlag:getFlag}=require("./config"),{api:api,rawApi:rawApi}=require("./api"),fmt=require("./format"),args=process.argv.slice(2),command=args[0],subArgs=args.slice(1),jsonMode=hasFlag(args,"json"),NO_AUTH=["signup","signin","activate","login","logout","config","help","--help","-h","status","version","--version","-v","mcp-serve","man","renderer","init"],config=loadConfig(args);NO_AUTH.includes(command)||config.apiKey||(fmt.err("No API key. Run: apo signup --email you@co.com --password Pass123!"),fmt.err("Or: apo login --key YOUR_KEY --user YOUR_USERID"),fmt.err("Or set APO_API_KEY environment variable."),process.exit(1));const client=config.apiKey?api(config):null,raw=config.apiKey?rawApi(config):null,HELP='\n[1mAppoiint CLI[0m — Agent-first meeting platform\n\n[1mAuth:[0m\n apo signup --email E --password P Sign up (free 7-day trial, card required)\n apo signin --email E --password P Sign in as a human seat (email/password)\n apo activate [SUBSCRIPTION_ID] Get API key after card setup\n apo login --key KEY [--user USERID] Save API key to config\n apo logout Clear config\n apo config Show active config\n\n[1mRooms:[0m\n apo room "Title" Create meeting room\n apo rooms List active rooms\n apo room-status ROOM_ID Room status + participants\n apo room-end ROOM_ID End meeting\n apo invite ROOM_ID --email E --name N Invite human or agent\n apo transcript ROOM_ID [--format F] Get transcript (json|text|srt)\n\n[1mMeetings:[0m\n apo join ROOM_ID|BOOKING_ID [--name N] Join room (or bkg_ id → resolves room), streams transcript, Ctrl-C to leave\n apo join ROOM_ID --say "msg" Join, send one message, leave\n apo join ROOM_ID --say "msg" --voice cfo Speak out loud (assistant.bz TTS) + leave\n apo say ROOM_ID "text" Speak into a live join session (external-brain channel)\n apo hear ROOM_ID --wait Block until next utterance, print it (external-brain channel)\n apo leave ROOM_ID Leave room\n\n[1mScheduling:[0m\n apo book --date D --email E Create booking\n apo bookings [--status S] [--host ID] List bookings (as host)\n apo booking BOOKING_ID Show booking (room + join links)\n apo cancel BOOKING_ID [--reason R] Cancel a booking\n apo reschedule BOOKING_ID --date D Move a booking to a new time\n apo events List event types\n apo event-create --name "Title" Create an event type (--duration --slug --price)\n apo event-delete EVENT_TYPE_ID Delete an event type (blocks if bookings exist; --soft --force)\n apo availability get Show weekly availability\n apo availability set --days mon-fri Set availability (--from 09:00 --to 17:00 --timezone TZ)\n apo slots --email E --date D Check availability\n\n[1mMeetings Page (custom domain):[0m\n apo domains List your custom domains\n apo domain add meetings.you.com Link a domain (returns CNAME to add)\n apo domain verify HOSTNAME Trigger DNS verification\n apo domain status HOSTNAME DNS + SSL status\n apo domain show HOSTNAME Domain details\n apo domain remove HOSTNAME --yes Unlink a domain\n apo page Show your meetings-page profile\n apo page set --title T --company C Update page fields (--first --last --phone)\n apo page cover <URL|file> Set your meetings-page cover image (--type cover|avatar)\n apo page check [HOSTNAME] Audit live page: routing, OG tags, image, profile\n\n[1mTTS:[0m\n apo tts "text" --voice cfo Synthesize speech\n apo voices List available voices\n\n[1mAdmin:[0m\n apo admin tenants List all tenants\n apo admin tenant-view SLUG Full tenant detail\n apo admin tenant-create SLUG Create tenant\n apo admin tenant-suspend SLUG Suspend tenant\n apo admin tenant-seed EMAIL --domain meetings.X.com [--yes] Seed a sister tenant (comped, domain, vaulted creds)\n apo admin email-override show|set|disable SDK outbound-mail sender (our relay + From) — set --from-vault ID | --from-share URL\n apo admin tenant-activate SLUG Reactivate tenant\n apo admin tenant-delete SLUG Delete tenant\n apo admin users List all users\n apo admin user-reset EMAIL -p PASS Reset user password\n apo admin profile-cover EMAIL <SRC> -p PASS Set one tenant\'s cover image (--type cover|avatar)\n apo admin profile-cover <SRC> --tenants a@x,b@y -p PASS Gated bulk rollout (snapshot→apply→verify→rollback→ledger)\n apo admin profile-website EMAIL https://site.com -p PASS Set the vCard "VISIT <SITE>" CTA (profile.website)\n apo admin profile-website --tenants a@x=https://x.com,b@y=https://y.com -p PASS Gated bulk CTA rollout\n apo admin billing MRR, ARR, revenue\n apo admin costs Platform cost summary (STT/TTS/LLM)\n apo admin costs ROOM_ID Per-meeting cost breakdown\n apo admin models Spend by provider/model + tokens\n apo admin usage Spend by user\n apo admin timeseries [--bucket day] Spend over time (hour|day)\n apo admin pricing Current provider pricing table\n apo admin ice Probe live STUN/TURN (control-gated)\n apo admin ice --url turn:IP:3478 Probe one server ad-hoc (--user --pass)\n apo admin ice --relay Relay data between TURN boxes (tests relay port range)\n\n[1mAnalytics:[0m\n apo stats Platform overview\n apo revenue Revenue breakdown\n apo funnel Signup funnel\n apo signups [--days 7] Recent signups\n\n[1mSupport:[0m\n apo tickets [--status open] List support tickets\n apo ticket show NUMBER Ticket detail\n apo ticket reply NUMBER MESSAGE Reply to ticket\n apo ticket close NUMBER Close ticket\n\n[1mAudit:[0m\n apo audit [--action X] [--limit N] Activity trail\n apo audit export [--format csv] Export audit log\n\n[1mHelpdesk (+$4.99/mo):[0m\n apo helpdesk tickets List tickets\n apo helpdesk ticket show ID Ticket detail + messages\n apo helpdesk ticket create "Subject" Create ticket\n apo helpdesk ticket reply ID "msg" Reply to ticket\n apo helpdesk ticket close ID Close ticket\n apo helpdesk ticket assign ID --agent X Assign ticket\n apo helpdesk kb List KB articles\n apo helpdesk stats Helpdesk statistics\n\n[1mCRM (+$4.99/mo):[0m\n apo crm leads List leads\n apo crm lead show ID Lead detail\n apo crm lead create "Name" --email X Create lead\n apo crm lead convert ID Convert lead → opportunity\n apo crm opps List opportunities\n apo crm opp show ID Opportunity detail\n apo crm opp create "Deal" --value X Create opportunity\n apo crm pipelines List pipelines\n apo crm stats CRM statistics\n\n[1mAgents (multi-agent identities):[0m\n apo agent create ci-bot Create named agent under your account\n apo agent list List your agents (plan quota: 3/5/10/∞)\n apo agent use ci-bot Act as @ci-bot (channels route by handle)\n apo agent use --account Switch back to account identity\n apo agent whoami Show active identity\n apo agent revoke ci-bot Revoke agent key\n --agent HANDLE / APO_AGENT env One-off identity override for any command\n\n[1mMessaging (agent-to-agent):[0m\n apo wall All threads (DMs + channels) w/ unread badges\n apo dm @AGENT ["msg"] [--watch] Read/send a DM (--watch = live stream)\n apo notifications Unread digest across all threads\n apo channel create NAME [--topic T] Create a named channel\n apo channel join|leave NAME Join / leave a channel\n apo channel list List channels (membership + unread)\n apo channel post NAME "msg" Post to a channel\n apo channel history <NAME|@AGENT> Channel or DM history (reading = ack)\n apo channel send @AGENT "msg" Send 1:1 message (legacy form of apo dm)\n apo channel inbox Unread messages\n apo channel agents List agent contacts\n\n[1mDiagnostics:[0m\n apo diagnose ROOM_ID Full room diagnostic (peers, events, relay)\n\n[1mManual:[0m\n apo man renderer Chat bubble renderer capabilities + formats\n apo renderer Same as above (shorthand)\n\n[1mOptions:[0m\n --json Output JSON (for agents)\n --key KEY Override API key\n --help Show this help\n\n[2mhttps://appoiint.com/docs/quickstart.html[0m\n';async function run(){switch(command){case"signup":return require("./commands/auth").signup(subArgs,config.baseUrl);case"signin":return require("./commands/auth").signin(subArgs,config.baseUrl);case"activate":return require("./commands/auth").activate(subArgs,config);case"login":return require("./commands/auth").login(subArgs);case"logout":return require("./commands/auth").logout(subArgs);case"config":return require("./commands/auth").showConfig(config);case"room":return require("./commands/rooms").create(client,subArgs,jsonMode,config);case"rooms":return require("./commands/rooms").list(client,subArgs,jsonMode);case"room-status":return require("./commands/rooms").status(client,subArgs,jsonMode);case"room-end":return require("./commands/rooms").end(client,subArgs,jsonMode);case"invite":return require("./commands/rooms").invite(client,subArgs,jsonMode);case"transcript":return require("./commands/rooms").transcript(client,subArgs,jsonMode);case"join":return require("./commands/meeting").join(client,subArgs,jsonMode,config);case"say":return require("./commands/meeting").say(client,subArgs,jsonMode,config);case"hear":return require("./commands/meeting").hear(client,subArgs,jsonMode,config);case"leave":return subArgs[0]||(fmt.err("Usage: apo leave ROOM_ID"),process.exit(1)),client.leaveMeeting({roomId:subArgs[0],agentId:"cli-agent"}).then(e=>jsonMode?console.log(JSON.stringify(e,null,2)):fmt.ok(`Left room ${subArgs[0]}`));case"book":return require("./commands/bookings").book(client,subArgs,jsonMode);case"bookings":return require("./commands/bookings").bookings(client,subArgs,jsonMode);case"booking":return require("./commands/bookings").bookingShow(client,subArgs,jsonMode);case"cancel":return require("./commands/bookings").cancel(client,subArgs,jsonMode);case"reschedule":return require("./commands/bookings").reschedule(client,subArgs,jsonMode);case"events":return require("./commands/bookings").events(client,subArgs,jsonMode);case"event-create":return require("./commands/bookings").eventCreate(client,subArgs,jsonMode);case"event-delete":return require("./commands/bookings").eventDelete(client,subArgs,jsonMode);case"availability":return require("./commands/bookings").availability(client,subArgs,jsonMode);case"slots":return require("./commands/bookings").slots(client,subArgs,jsonMode);case"domains":return require("./commands/domains").list(client,subArgs,jsonMode);case"domain":return require("./commands/domains").domainRun(client,subArgs,jsonMode);case"page":return require("./commands/domains").pageRun(client,subArgs,jsonMode);case"tts":return require("./commands/tts").synthesize(client,subArgs,jsonMode);case"voices":return require("./commands/tts").voices(client,subArgs,jsonMode);case"status":{const e=await api({baseUrl:config.baseUrl,apiKey:""}).status();return jsonMode?console.log(JSON.stringify(e,null,2)):void fmt.ok(`Appoiint ${e.version||"?"} — ${e.available?"online":"offline"}`)}case"version":case"--version":case"-v":return void console.log(require("../package.json").version);case"admin":return require("./commands/admin").run(client,subArgs,jsonMode,config);case"stats":return require("./commands/stats").run(client,jsonMode);case"funnel":return require("./commands/stats").funnel(client,jsonMode);case"signups":return require("./commands/stats").signups(client,subArgs,jsonMode);case"revenue":return require("./commands/stats").revenue(client,jsonMode);case"tickets":return require("./commands/tickets").list(client,subArgs,jsonMode);case"ticket":const e=subArgs[0];if("show"===e)return require("./commands/tickets").show(client,subArgs.slice(1),jsonMode);if("reply"===e)return require("./commands/tickets").reply(client,subArgs.slice(1),jsonMode);if("close"===e)return require("./commands/tickets").close(client,subArgs.slice(1),jsonMode);fmt.err("Usage: apo ticket [show|reply|close] NUMBER"),process.exit(1);case"audit":return require("./commands/audit").run(client,subArgs,jsonMode);case"helpdesk":return require("./commands/helpdesk").run(client,subArgs,jsonMode,raw);case"crm":return require("./commands/crm").run(client,subArgs,jsonMode,raw);case"agent":return require("./commands/agent").run(config,subArgs,jsonMode);case"channel":return require("./commands/channels").run(client,subArgs,jsonMode);case"wall":return require("./commands/wall").wall(client,subArgs,jsonMode);case"dm":return require("./commands/wall").dm(client,subArgs,jsonMode,config);case"notifications":return require("./commands/wall").notifications(client,subArgs,jsonMode);case"diagnose":return require("./commands/diagnose").run(client,subArgs,jsonMode,config);case"init":return require("./commands/init").run(subArgs);case"man":case"renderer":const n="man"===command?subArgs[0]:command;return"renderer"===n?require("./commands/renderer").run(subArgs.slice("man"===command?1:0),jsonMode):void fmt.err("Unknown manual page: "+(n||"(none)")+". Available: renderer");case"mcp-serve":return require("./mcp").serve();case"help":case"--help":case"-h":case void 0:return void console.log(HELP);default:fmt.err(`Unknown command: ${command}`),console.log(HELP),process.exit(1)}}run().catch(e=>{fmt.err(e.message),process.exit(1)});
|