@closedloop-ai/cli 0.3.2 → 0.3.3
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/README.md +1 -1
- package/dist/closedloop.js +3 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -49,7 +49,7 @@ The npm package is `@closedloop-ai/cli`; users of the earlier `cl` CLI can updat
|
|
|
49
49
|
- `closedloop products`: your product structure, the products, areas (parts of a product) and features (parts of the product that exist, never planned work) your insights are filed against, with insight counts, then the products your company research suggested and has not had confirmed. As JSON it is the API's answer, whose `structure` can be edited and saved.
|
|
50
50
|
- `products confirm [name...]` adds the suggested products (all, or the ones named) to the structure; `products save --file <structure.json>` (or `--file -` for standard input) saves a structure of your own, either the whole `closedloop products --json` answer or only its `structure`. Products, areas and features left out are archived, never deleted. `--check` only checks.
|
|
51
51
|
- Both check the structure first and list every change. In a terminal the CLI asks you to type `yes`; anyone else adds `--yes`, which an agent does only after its user agreed. Without it nothing is saved and the result's `instructions` say what to ask. Saving needs an owner or admin; the next nightly recalculation then re-places needs and insights, charged in credits.
|
|
52
|
-
- `closedloop competitors`: the competitors ClosedLoop AI watches for in your insights, the same list as Settings, Company, Competitors in the app: each with its website, whether it is a direct competitor, how many times customers mentioned it, and where it came from (
|
|
52
|
+
- `closedloop competitors`: the competitors ClosedLoop AI watches for in your insights, the same list as Settings, Company, Competitors in the app: each with its website, whether it is a direct competitor, how many times customers mentioned it, and where it came from (AI research, mentioned by customers, or manual). Insights that name one count as competitor mentions, shown on opportunities, features, customer profiles and the weekly brief. As JSON it is the API's `competitors` and `can_change`.
|
|
53
53
|
- `competitors add <name> [--website <url>] [--description <text>]` adds one as a direct competitor (adding one that was removed restores it); `competitors edit <name-or-id> [--name <name>] [--website <url>] [--description <text>]` changes only what you give (an empty value clears the website or description); `competitors remove <name-or-id>` marks it as not a competitor and takes it off the list, keeping the customer mentions behind it. A name matches in any case; one that matches none or several is refused with the candidates.
|
|
54
54
|
- A removal asks you to type `yes` in a terminal; anyone else adds `--yes`, which an agent does only after its user agreed. Without it nothing is removed and the result's `instructions` say what to ask. Adding, editing and removing need the right to change the workspace's settings (an owner or admin).
|
|
55
55
|
- `closedloop ingest [text]`: sends your own data in (feedback, notes, survey answers, transcripts) for the pipeline to turn into insights.
|
package/dist/closedloop.js
CHANGED
|
@@ -25,7 +25,7 @@ Expecting one of '${r.join("', '")}'`);return this._lifeCycleHooks[e]?this._life
|
|
|
25
25
|
`),this._exit(0,"commander.version",e)}),this}description(e,n){return e===void 0&&n===void 0?this._description:(this._description=e,n&&(this._argsDescription=n),this)}summary(e){return e===void 0?this._summary:(this._summary=e,this)}alias(e){if(e===void 0)return this._aliases[0];let n=this;if(this.commands.length!==0&&this.commands[this.commands.length-1]._executableHandler&&(n=this.commands[this.commands.length-1]),e===n._name)throw new Error("Command alias can't be the same as its name");let r=this.parent?._findCommand(e);if(r){let i=[r.name()].concat(r.aliases()).join("|");throw new Error(`cannot add alias '${e}' to command '${this.name()}' as already have command '${i}'`)}return n._aliases.push(e),this}aliases(e){return e===void 0?this._aliases:(e.forEach(n=>this.alias(n)),this)}usage(e){if(e===void 0){if(this._usage)return this._usage;let n=this.registeredArguments.map(r=>jt(r));return[].concat(this.options.length||this._helpOption!==null?"[options]":[],this.commands.length?"[command]":[],this.registeredArguments.length?n:[]).join(" ")}return this._usage=e,this}name(e){return e===void 0?this._name:(this._name=e,this)}helpGroup(e){return e===void 0?this._helpGroupHeading??"":(this._helpGroupHeading=e,this)}commandsGroup(e){return e===void 0?this._defaultCommandGroup??"":(this._defaultCommandGroup=e,this)}optionsGroup(e){return e===void 0?this._defaultOptionGroup??"":(this._defaultOptionGroup=e,this)}_initOptionGroup(e){this._defaultOptionGroup&&!e.helpGroupHeading&&e.helpGroup(this._defaultOptionGroup)}_initCommandGroup(e){this._defaultCommandGroup&&!e.helpGroup()&&e.helpGroup(this._defaultCommandGroup)}nameFromFilename(e){return this._name=ge.basename(e,ge.extname(e)),this}executableDir(e){return e===void 0?this._executableDir:(this._executableDir=e,this)}helpInformation(e){let n=this.createHelp(),r=this._getOutputContext(e);n.prepareContext({error:r.error,helpWidth:r.helpWidth,outputHasColors:r.hasColors});let i=n.formatHelp(this,n);return r.hasColors?i:this._outputConfiguration.stripColor(i)}_getOutputContext(e){e=e||{};let n=!!e.error,r,i,o;return n?(r=a=>this._outputConfiguration.writeErr(a),i=this._outputConfiguration.getErrHasColors(),o=this._outputConfiguration.getErrHelpWidth()):(r=a=>this._outputConfiguration.writeOut(a),i=this._outputConfiguration.getOutHasColors(),o=this._outputConfiguration.getOutHelpWidth()),{error:n,write:a=>(i||(a=this._outputConfiguration.stripColor(a)),r(a)),hasColors:i,helpWidth:o}}outputHelp(e){let n;typeof e=="function"&&(n=e,e=void 0);let r=this._getOutputContext(e),i={error:r.error,write:r.write,command:this};this._getCommandAndAncestors().reverse().forEach(s=>s.emit("beforeAllHelp",i)),this.emit("beforeHelp",i);let o=this.helpInformation({error:r.error});if(n&&(o=n(o),typeof o!="string"&&!Buffer.isBuffer(o)))throw new Error("outputHelp callback must return a string or a Buffer");r.write(o),this._getHelpOption()?.long&&this.emit(this._getHelpOption().long),this.emit("afterHelp",i),this._getCommandAndAncestors().forEach(s=>s.emit("afterAllHelp",i))}helpOption(e,n){return typeof e=="boolean"?(e?(this._helpOption===null&&(this._helpOption=void 0),this._defaultOptionGroup&&this._initOptionGroup(this._getHelpOption())):this._helpOption=null,this):(this._helpOption=this.createOption(e??"-h, --help",n??"display help for command"),(e||n)&&this._initOptionGroup(this._helpOption),this)}_getHelpOption(){return this._helpOption===void 0&&this.helpOption(void 0,void 0),this._helpOption}addHelpOption(e){return this._helpOption=e,this._initOptionGroup(e),this}help(e){this.outputHelp(e);let n=Number(T.exitCode??0);n===0&&e&&typeof e!="function"&&e.error&&(n=1),this._exit(n,"commander.help","(outputHelp)")}addHelpText(e,n){let r=["beforeAll","before","after","afterAll"];if(!r.includes(e))throw new Error(`Unexpected value for position to addHelpText.
|
|
26
26
|
Expecting one of '${r.join("', '")}'`);let i=`${e}Help`;return this.on(i,o=>{let s;typeof n=="function"?s=n({error:o.error,command:o.command}):s=n,s&&o.write(`${s}
|
|
27
27
|
`)}),this}_outputHelpIfRequested(e){let n=this._getHelpOption();n&&e.find(i=>n.is(i))&&(this.outputHelp(),this._exit(0,"commander.helpDisplayed","(outputHelp)"))}};function Hr(t){return t.map(e=>{if(!e.startsWith("--inspect"))return e;let n,r="127.0.0.1",i="9229",o;return(o=e.match(/^(--inspect(-brk)?)$/))!==null?n=o[1]:(o=e.match(/^(--inspect(-brk|-port)?)=([^:]+)$/))!==null?(n=o[1],/^\d+$/.test(o[3])?i=o[3]:r=o[3]):(o=e.match(/^(--inspect(-brk|-port)?)=([^:]+):(\d+)$/))!==null&&(n=o[1],r=o[3],i=o[4]),n&&i!=="0"?`${n}=${r}:${parseInt(i)+1}`:e})}function Gr(){if(T.env.NO_COLOR||T.env.FORCE_COLOR==="0"||T.env.FORCE_COLOR==="false")return!1;if(T.env.FORCE_COLOR||T.env.CLICOLOR_FORCE!==void 0)return!0}var up=new Ve;var g={Success:0,Failure:1,Usage:2},Ea=/^[a-z][a-z0-9_]*$/,p=class extends Error{code;exitCode;hint;httpStatus;constructor(e,n,r,i,o){if(!Ea.test(e))throw new Error(`CliError code must be snake_case: '${e}'`);if(r===g.Success)throw new Error(`CliError '${e}' cannot use the success exit code`);super(n),this.name="CliError",this.code=e,this.exitCode=r,this.hint=i,this.httpStatus=o}};function E(t){return new Intl.DateTimeFormat(void 0,{dateStyle:"medium",timeStyle:"short"}).format(new Date(t))}function he(t,e){return e?`\x1B]8;;${t}\x07${t}\x1B]8;;\x07`:t}function P(t){return new Intl.DateTimeFormat(void 0,{dateStyle:"medium"}).format(new Date(t))}function Wt(t,e){try{return new Intl.DateTimeFormat(void 0,{dateStyle:"medium",timeZone:e}).format(new Date(t))}catch{return P(t)}}function ht(t){let[e,n]=t.split("-").map(Number);return new Intl.DateTimeFormat(void 0,{month:"long",year:"numeric",timeZone:"UTC"}).format(new Date(Date.UTC(e,n-1,1)))}function k(t){return new Intl.NumberFormat(void 0,{maximumFractionDigits:0}).format(t)}function Be(t){let e=Math.round(Math.max(0,t)/6e4);if(e<1)return"under a minute";if(e<60)return`${e} min`;let n=Math.floor(e/60);if(n<48){let r=e%60;return r>=5?`${n} h ${r} min`:`${n} h`}return`${Math.round(n/24)} days`}function H(t){return new Intl.DateTimeFormat(void 0,{dateStyle:"medium",timeZone:"UTC"}).format(new Date(t))}import{randomUUID as qr}from"node:crypto";import{chmodSync as Ra,closeSync as Wn,cpSync as Ia,existsSync as Ye,fstatSync as Pa,fsyncSync as xa,lstatSync as Mn,mkdirSync as Gt,mkdtempSync as Na,openSync as Jr,readFileSync as La,readdirSync as Da,renameSync as Vr,rmSync as qt,writeFileSync as Br}from"node:fs";import{basename as Yr,dirname as Mt,join as z,win32 as Hn}from"node:path";var ja="https://api.closedloop.sh";function Jt(t){let e=t.env.CLOSEDLOOP_API_URL?.trim(),n=new URL(e||ja),r=["localhost","127.0.0.1","[::1]"].includes(n.hostname);if(n.protocol!=="https:"&&!(n.protocol==="http:"&&r))throw new Error("CLOSEDLOOP_API_URL must use https (http only for localhost)");if(n.username||n.password)throw new Error("CLOSEDLOOP_API_URL must not contain a user name or password");return n.origin}function Ce(t){let e=t.env.CLOSEDLOOP_CONFIG_DIR?.trim();if(e)return e;let n=t.env.XDG_CONFIG_HOME?.trim();if(n)return z(n,"closedloop");let r=t.platform??process.platform;return r==="darwin"?z(t.homeDir,"Library","Application Support","closedloop"):r==="win32"?Hn.join(t.env.LOCALAPPDATA?.trim()||Hn.join(t.homeDir,"AppData","Local"),"closedloop"):z(t.homeDir,".config","closedloop")}function Ht(t,e,n){(t.platform??process.platform)!=="win32"&&Ra(e,n)}function Kr(t){return t.env.CLOSEDLOOP_CONFIG_DIR?.trim()||t.env.XDG_CONFIG_HOME?.trim()?null:(t.platform??process.platform)==="win32"?Hn.join(t.homeDir,".config","closedloop"):z(t.homeDir,".config","closedloop")}function Fa(t){let e=Ce(t),n=Kr(t);if(n===null||e===n||Ye(e)||!Ye(n))return;let r;try{Gt(Mt(e),{recursive:!0}),r=Na(z(Mt(e),`.${Yr(e)}-migration-`)),Ia(n,r,{recursive:!0,filter:i=>{let o=Mn(i);if(!o.isDirectory()&&!o.isFile())throw new Error("legacy config contains a non-regular entry");return!0}}),Ht(t,r,448);for(let i of Da(r,{recursive:!0})){let o=z(r,String(i));Ht(t,o,Mn(o).isDirectory()?448:384)}Ye(e)||Vr(r,e)}catch{throw new p("config_migration_failed","your saved ClosedLoop AI settings could not be moved to the new location",g.Failure,"Set CLOSEDLOOP_CONFIG_DIR to your existing config folder and repeat the command. Your original files were kept.")}finally{r&&qt(r,{recursive:!0,force:!0})}}function zr(t,e){let n,r=new Int32Array(new SharedArrayBuffer(4));for(let o=0;o<5;o++)try{n=Jr(t,"wx",384);break}catch(s){if(s.code!=="EEXIST")throw s;o<4&&Atomics.wait(r,0,0,25)}if(n===void 0)throw new p("config_busy","another CLI process is updating the saved state",g.Failure,`Try again after other CLI commands finish. If no CLI process is running, inspect and remove ${t}.`);let i;try{return i=Pa(n),Br(n,JSON.stringify({pid:process.pid,owner:qr()})),e()}finally{try{if(i&&Ye(t)){let o=Mn(t);o.dev===i.dev&&o.ino===i.ino&&qt(t)}}finally{Wn(n)}}}function Vt(t,e){let n=Ce(t);try{return Gt(Mt(n),{recursive:!0}),zr(z(Mt(n),`.${Yr(n)}.lock`),()=>(Fa(t),e()))}catch(r){throw r instanceof p?r:new p("config_write_failed","the saved config folder could not be updated",g.Failure,`Check permissions and free space for ${n}, then repeat the command.`)}}function Xr(t,e){let n=z(Ce(t),e);try{return JSON.parse(La(n,"utf8"))}catch(r){if(r.code==="ENOENT")return null;throw r instanceof SyntaxError?new p("config_invalid",`the saved ${e} file is not valid JSON`,g.Failure,`Back up and remove ${n}, then repeat the command${e==="credentials.json"?" or run `closedloop login` to sign in again":""}.`):new p("config_unreadable",`the saved ${e} file could not be read`,g.Failure,`Check the permissions for ${n} and CLOSEDLOOP_CONFIG_DIR, then repeat the command.`)}}function te(t,e){let n=Kr(t);return n!==null&&n!==Ce(t)&&!Ye(Ce(t))&&Ye(n)&&Vt(t,()=>{}),Xr(t,e)}function Qr(t,e,n){let r=Ce(t),i=z(r,`.${e}.${qr()}.tmp`),o;try{let s=`${JSON.stringify(n,null,2)}
|
|
28
|
-
`;Gt(r,{recursive:!0,mode:448}),Ht(t,r,448),o=Jr(i,"wx",384),Br(o,s),xa(o),Wn(o),o=void 0,Vr(i,z(r,e))}catch{throw new p("config_write_failed",`the saved ${e} file could not be updated`,g.Failure,`Check the permissions and free space for ${r}, then repeat the command. The previous file was kept.`)}finally{o!==void 0&&Wn(o),qt(i,{force:!0})}}function Zr(t,e){try{qt(z(Ce(t),e),{force:!0})}catch{throw new p("config_write_failed",`the saved ${e} file could not be removed`,g.Failure,"Check the config folder permissions or CLOSEDLOOP_CONFIG_DIR, then repeat the command.")}}var ei=["credentials.json","pending-login.json","pending-browser-login.json"];function ne(t,e){return Vt(t,()=>{let n=Ce(t);return Gt(n,{recursive:!0,mode:448}),Ht(t,n,448),zr(z(n,".credentials.json.lock"),()=>e({read:r=>Xr(t,r),write:(r,i)=>Qr(t,r,i),remove:r=>Zr(t,r)}))})}function Ke(t,e,n){ei.includes(e)?ne(t,r=>r.write(e,n)):Vt(t,()=>Qr(t,e,n))}function ti(t,e){ei.includes(e)?ne(t,n=>n.remove(e)):Vt(t,()=>Zr(t,e))}var re="0.3.2";var Bt="npm install -g @closedloop-ai/cli@latest",qn=/^(\d{1,6})\.(\d{1,6})\.(\d{1,6})$/,ni="update-notice.json",Ua=1440*60*1e3;function ft(t){return typeof t=="string"&&qn.test(t)}function ri(t,e){let n=qn.exec(t),r=qn.exec(e);if(!n||!r)throw new Error("compareVersions needs two x.y.z versions");for(let i=1;i<=3;i++)if(Number(n[i])!==Number(r[i]))return Number(n[i])-Number(r[i]);return 0}function ii(t,e){t.release&&ft(e)&&(t.release.latest===null||ri(e,t.release.latest)>0)&&(t.release.latest=e)}function Jn(t){let e=t.release?.latest??null;return e===null||!ft(re)?null:ri(e,re)>0?e:null}function Gn(t){return t!==void 0&&t!==""&&t!=="0"&&t!=="false"}function oi(t,e){let n=Jn(t);if(n===null||!e)return null;let{env:r}=t;if(Gn(r.CI)||Gn(r.NO_UPDATE_NOTIFIER)||Gn(r.CLOSEDLOOP_NO_UPDATE_NOTIFIER))return null;let i=t.now?.()??Date.now(),o=null;try{o=te(t,ni)}catch{}if(o&&o.version===n&&typeof o.shown_at=="number"&&i>=o.shown_at&&i-o.shown_at<Ua)return null;try{Ke(t,ni,{version:n,shown_at:i})}catch{}return`ClosedLoop AI CLI ${n} is out (you have ${re}). Update: ${Bt}`}var Wa=7e4,Vn=/[\u0000-\u0008\u000B-\u001F\u007F-\u009F\u061C\u200E\u200F\u202A-\u202E\u2066-\u2069]/g,Ma=(t,e)=>{if(typeof e=="string")return e.replace(Vn,"");if(e===null||typeof e!="object"||Array.isArray(e))return e;let n=Object.keys(e);return n.some(r=>r!==r.replace(Vn,""))?Object.fromEntries(n.map(r=>[r.replace(Vn,""),e[r]])):e};async function Yt(t,e,n,r,i={}){if(!r.startsWith("/"))throw new Error("an API path starts with /");let o,s,a=AbortSignal.timeout(Wa);try{o=await t.fetch(`${e}${r}`,{method:n,headers:{...i.headers,Accept:"application/json","X-ClosedLoop-Client":"cli","X-ClosedLoop-CLI-Version":re,...i.payload!==void 0&&{"Content-Type":"application/json"},...i.token&&{Authorization:`Bearer ${i.token}`}},...i.payload!==void 0&&{body:JSON.stringify(i.payload)},signal:a}),s=await o.text()}catch(u){throw u instanceof Error&&u.name==="TimeoutError"||a.aborted&&a.reason?.name==="TimeoutError"?new p("api_timeout","ClosedLoop AI took too long to answer",g.Failure,"Try again; if you sent data, check its status before resending it."):new p("api_unreachable",`could not reach ClosedLoop AI at ${e}`,g.Failure,"Check your connection, or CLOSEDLOOP_API_URL if you set it.")}let l={};try{let u=JSON.parse(s,Ma);u!==null&&typeof u=="object"&&!Array.isArray(u)&&(l=u)}catch{}if(o.status===426&&l.code==="upgrade_required")throw Ga(l);if(ii(t,o.headers.get("X-ClosedLoop-CLI-Latest")),o.status===429)throw Ha(t,o.headers.get("Retry-After"),l);return{status:o.status,body:l,text:s}}function Ha(t,e,n){let r=typeof n.code=="string"?n.code.toLowerCase():"",i=r==="rate_limit_exceeded"?"rate_limited":r,o=e!==null&&/^\d+(?:\.\d+)?$/.test(e.trim())?Number(e):e!==null?(Date.parse(e)-(t.now?.()??Date.now()))/1e3:NaN,s=Math.max(0,Math.ceil(o)),a=Number.isFinite(s)?`Try again in ${s} second${s===1?"":"s"}.`:"Wait a few minutes, then run it again.";return new p(/^[a-z][a-z0-9_]*$/.test(i)?i:"rate_limited",typeof n.error=="string"&&n.error?n.error:"too many requests to ClosedLoop AI",g.Failure,a,429)}function Ga(t){let e=ft(t.minimum_version)?t.minimum_version:"a newer version";return new p("upgrade_required",`this ClosedLoop AI CLI (${re}) is too old for the API; ${e} or later is needed`,g.Failure,ft(t.latest_version)?`Update to ${t.latest_version}: ${Bt}`:`Update: ${Bt}`)}function Re(t,e,n,r){return Yt(t,e,"POST",n,{payload:r})}var ze="credentials.json",qa=["password","email_code","google","microsoft","okta","saml"];function Kt(t){return qa.includes(t)?t:null}var Bn="Run `closedloop login` to sign in.",x='Sign in to the app with your email address. An account made in the CLI has no password: choose "Email me a code" and the app emails you a sign-in code.';function Xe(){return new p("session_changed","your saved sign-in changed while this command was running",g.Failure,"Repeat the command. If you signed out, run `closedloop login` to sign in again.")}function zt(t){let e=te(t,ze);if(e===null||typeof e!="object"||Array.isArray(e))return null;let n=r=>typeof r=="string"&&r.trim()!==""&&r.trim()===r;if(!n(e.api_url)||!n(e.email)||!n(e.access_token)||!n(e.refresh_token))return null;try{if(Jt({...t,env:{CLOSEDLOOP_API_URL:e.api_url}})!==e.api_url)return null}catch{return null}return e}function $(t){let e=zt(t);if(e===null)throw new p("not_signed_in","you are not signed in on this computer",g.Failure,Bn);return e}function ai(t,e,n,r,i){if(!e.access_token||!e.refresh_token)throw new Error("a sign-in needs both tokens");let o=new Date(t.now?.()??Date.now()).toISOString();ne(t,s=>{if(i&&JSON.stringify(s.read(i.file))!==JSON.stringify(i.value))throw Xe();s.write(ze,{...e,signed_in_at:o,sign_in_method:n,signed_in_with:r,saved_at:o}),s.remove("pending-login.json"),s.remove("pending-browser-login.json")})}function Yn(t){let e=t.split(".");if(e.length!==3)return null;try{let n=JSON.parse(Buffer.from(e[1],"base64url").toString("utf8")),{iat:r,exp:i}=n??{};return typeof r!="number"||typeof i!="number"||!Number.isFinite(r)||!Number.isFinite(i)||r<0||i<=r||i*1e3>864e13?null:{issuedAt:r,expiresAt:i}}catch{return null}}function li(t){let e=Yn(t.refresh_token)??Yn(t.access_token);return e?new Date(e.expiresAt*1e3).toISOString():null}function Ja(t,e){let n=Yn(t);return n!==null&&e/1e3>=n.issuedAt+(n.expiresAt-n.issuedAt)/2}async function Va(t,e){let n=zt(t);if(!n||n.api_url!==e.api_url||n.email!==e.email||n.refresh_token!==e.refresh_token)throw Xe();let r=JSON.stringify(n),i=await Yt(t,e.api_url,"POST","/api/auth/refresh",{payload:{refreshToken:e.refresh_token}}),{accessToken:o,refreshToken:s}=i.body;if(i.status!==200||typeof o!="string"||!o.trim()||typeof s!="string"||!s.trim())return null;let a={access_token:o,refresh_token:s,saved_at:new Date(t.now?.()??Date.now()).toISOString()};return ne(t,l=>{if(JSON.stringify(l.read(ze))!==r)throw Xe();l.write(ze,{...e,...a})}),a}async function si(t,e){let n=t.pendingSessionRefreshes??=new Map,r=JSON.stringify([e.api_url,e.email,e.refresh_token]),i=n.get(r);i||(i=Va(t,e),n.set(r,i));try{let o=await i;return o?(Object.assign(e,o),!0):!1}finally{n.get(r)===i&&n.delete(r)}}async function v(t,e,n,r,i,o){let s=()=>Yt(t,e.api_url,n,r,{token:e.access_token,payload:i,headers:o}),a=null;if(Ja(e.access_token,t.now?.()??Date.now()))try{a=await si(t,e)}catch(c){if(!(c instanceof p))throw c;let d=["api_unreachable","api_timeout"].includes(c.code),m=c.httpStatus===429;if(!d&&!m)throw c;a=!1}let l=await s();if(l.status!==401)return l;if(a!==null||!await si(t,e))throw new p("session_expired","your saved sign-in has expired or was signed out",g.Failure,Bn);let u=await s();if(u.status===401)throw new p("session_expired","your saved sign-in has expired or was signed out",g.Failure,Bn);return u}function Kn(t,e,n){return v(t,e,"GET",n)}function C(t,e,n){let r=typeof t.body.code=="string"?t.body.code.toLowerCase():"",i=typeof t.body.error=="string"&&t.body.error?t.body.error:n;return new p(/^[a-z][a-z0-9_]*$/.test(r)?r:e,i,g.Failure)}var Ba=["can_manage_integrations","can_manage_billing","can_manage_members","can_manage_team_settings","can_view_analytics"];async function F(t,e){let n=await v(t,e,"GET","/api/team-info"),r=n.body.teamInfo;if(n.status!==200)throw C(n,"api_error","Your workspace could not be loaded. Try again.");if(typeof r?.team_id!="string"||!r.team_id)throw new p("no_workspace","you are not in a workspace yet",g.Failure,`Create or join one in the app. ${x}`);let i=Object.fromEntries(Ba.map(o=>[o,r.permissions?.[o]===!0]));return{id:r.team_id,name:typeof r.team_name=="string"?r.team_name:"your workspace",role:typeof r.role=="string"?r.role:null,permissions:i}}async function Ie(t,e){return(await F(t,e)).id}var Ya={can_manage_integrations:"manage integrations",can_manage_billing:"manage billing"};async function Qe(t,e,n,r){let i=await F(t,e);if(!i.permissions[n])throw new p(n==="can_manage_integrations"?"integration_manager_required":"billing_manager_required",`you cannot ${r}: it needs the right to ${Ya[n]} in ${i.name}`,g.Failure,`Ask an owner or admin of ${i.name} to give you that right, or to do it for you.`);return i}var ke=new Set(["queued","importing","stalled"]),Ka=7200,ui=30,za=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,di=t=>`${t[0].toUpperCase()}${t.slice(1)}`;function mi(t){if(!t.progress)return null;let{unit:e,done:n,total:r,failed:i}=t.progress;return`${k(n)} of ${k(r)} ${e}${t.percent!==null?` (${t.percent}%)`:""}${i?`, ${k(i)} failed`:""}`}function pi(t){return t.from&&t.to?`${H(t.from)} to ${H(t.to)}${t.window?` (${t.window})`:""}`:t.window}function gi(t,e){if(t.state==="stalled")return t.last_activity_at?`no progress since ${E(t.last_activity_at)}`:"no progress";if(t.state==="queued")return t.created_at?`waiting to start since ${E(t.created_at)}`:"waiting to start";if(t.state==="importing")return t.estimated_finish_at?`about ${Be(Date.parse(t.estimated_finish_at)-e)} left (around ${E(t.estimated_finish_at)})`:t.started_at?`started ${E(t.started_at)}; too early to estimate`:"too early to estimate";if(t.finished_at){let n=t.started_at??t.created_at,r=n?Date.parse(t.finished_at)-Date.parse(n):NaN,i=!Number.isFinite(r)||r<0?"":t.started_at?` (took ${Be(r)})`:` (${Be(r)} after it was requested)`;return`${t.state==="done"?"finished":"ended"} ${E(t.finished_at)}${i}`}return null}function Xa(t,e){let n=di(t.state);return t.state==="failed"||t.state==="stalled"?e.red(n):t.state==="importing"||t.state==="queued"?e.orange(n):t.state==="done"?e.green(n):e.dim(n)}function Xt(t,e,n){let r=n.enabled&&ke.has(t.state)&&t.percent!==null?`${n.orange(n.bar(t.percent/100,12))} `:"";return[Xa(t,n),`${r}${[t.source,pi(t),mi(t),gi(t,e)].filter(Boolean).join(n.dim(" \xB7 "))}`].join(" ")}async function hi(t,e,n){let r=await v(t.runtime,e,"GET",`/api/cli/backfills?limit=${n}`);if(r.status!==200||!Array.isArray(r.body.backfills))throw C(r,"api_error","Backfills could not be loaded. Try again.");return r.body.backfills}async function Qa(t,e){let n=e===void 0?20:Number(e);if(!Number.isInteger(n)||n<1||n>100)throw new p("invalid_option","--limit must be a whole number from 1 to 100",g.Usage);let r=$(t.runtime),i=await hi(t,r,n),o=Date.now();t.print.result({backfills:i},s=>{if(s.backfills.length===0)return"No backfills in this workspace yet.";let a=s.backfills.map(u=>Xt(u,o,t.print.style)),l=s.backfills.some(u=>ke.has(u.state))?`
|
|
28
|
+
`;Gt(r,{recursive:!0,mode:448}),Ht(t,r,448),o=Jr(i,"wx",384),Br(o,s),xa(o),Wn(o),o=void 0,Vr(i,z(r,e))}catch{throw new p("config_write_failed",`the saved ${e} file could not be updated`,g.Failure,`Check the permissions and free space for ${r}, then repeat the command. The previous file was kept.`)}finally{o!==void 0&&Wn(o),qt(i,{force:!0})}}function Zr(t,e){try{qt(z(Ce(t),e),{force:!0})}catch{throw new p("config_write_failed",`the saved ${e} file could not be removed`,g.Failure,"Check the config folder permissions or CLOSEDLOOP_CONFIG_DIR, then repeat the command.")}}var ei=["credentials.json","pending-login.json","pending-browser-login.json"];function ne(t,e){return Vt(t,()=>{let n=Ce(t);return Gt(n,{recursive:!0,mode:448}),Ht(t,n,448),zr(z(n,".credentials.json.lock"),()=>e({read:r=>Xr(t,r),write:(r,i)=>Qr(t,r,i),remove:r=>Zr(t,r)}))})}function Ke(t,e,n){ei.includes(e)?ne(t,r=>r.write(e,n)):Vt(t,()=>Qr(t,e,n))}function ti(t,e){ei.includes(e)?ne(t,n=>n.remove(e)):Vt(t,()=>Zr(t,e))}var re="0.3.3";var Bt="npm install -g @closedloop-ai/cli@latest",qn=/^(\d{1,6})\.(\d{1,6})\.(\d{1,6})$/,ni="update-notice.json",Ua=1440*60*1e3;function ft(t){return typeof t=="string"&&qn.test(t)}function ri(t,e){let n=qn.exec(t),r=qn.exec(e);if(!n||!r)throw new Error("compareVersions needs two x.y.z versions");for(let i=1;i<=3;i++)if(Number(n[i])!==Number(r[i]))return Number(n[i])-Number(r[i]);return 0}function ii(t,e){t.release&&ft(e)&&(t.release.latest===null||ri(e,t.release.latest)>0)&&(t.release.latest=e)}function Jn(t){let e=t.release?.latest??null;return e===null||!ft(re)?null:ri(e,re)>0?e:null}function Gn(t){return t!==void 0&&t!==""&&t!=="0"&&t!=="false"}function oi(t,e){let n=Jn(t);if(n===null||!e)return null;let{env:r}=t;if(Gn(r.CI)||Gn(r.NO_UPDATE_NOTIFIER)||Gn(r.CLOSEDLOOP_NO_UPDATE_NOTIFIER))return null;let i=t.now?.()??Date.now(),o=null;try{o=te(t,ni)}catch{}if(o&&o.version===n&&typeof o.shown_at=="number"&&i>=o.shown_at&&i-o.shown_at<Ua)return null;try{Ke(t,ni,{version:n,shown_at:i})}catch{}return`ClosedLoop AI CLI ${n} is out (you have ${re}). Update: ${Bt}`}var Wa=7e4,Vn=/[\u0000-\u0008\u000B-\u001F\u007F-\u009F\u061C\u200E\u200F\u202A-\u202E\u2066-\u2069]/g,Ma=(t,e)=>{if(typeof e=="string")return e.replace(Vn,"");if(e===null||typeof e!="object"||Array.isArray(e))return e;let n=Object.keys(e);return n.some(r=>r!==r.replace(Vn,""))?Object.fromEntries(n.map(r=>[r.replace(Vn,""),e[r]])):e};async function Yt(t,e,n,r,i={}){if(!r.startsWith("/"))throw new Error("an API path starts with /");let o,s,a=AbortSignal.timeout(Wa);try{o=await t.fetch(`${e}${r}`,{method:n,headers:{...i.headers,Accept:"application/json","X-ClosedLoop-Client":"cli","X-ClosedLoop-CLI-Version":re,...i.payload!==void 0&&{"Content-Type":"application/json"},...i.token&&{Authorization:`Bearer ${i.token}`}},...i.payload!==void 0&&{body:JSON.stringify(i.payload)},signal:a}),s=await o.text()}catch(u){throw u instanceof Error&&u.name==="TimeoutError"||a.aborted&&a.reason?.name==="TimeoutError"?new p("api_timeout","ClosedLoop AI took too long to answer",g.Failure,"Try again; if you sent data, check its status before resending it."):new p("api_unreachable",`could not reach ClosedLoop AI at ${e}`,g.Failure,"Check your connection, or CLOSEDLOOP_API_URL if you set it.")}let l={};try{let u=JSON.parse(s,Ma);u!==null&&typeof u=="object"&&!Array.isArray(u)&&(l=u)}catch{}if(o.status===426&&l.code==="upgrade_required")throw Ga(l);if(ii(t,o.headers.get("X-ClosedLoop-CLI-Latest")),o.status===429)throw Ha(t,o.headers.get("Retry-After"),l);return{status:o.status,body:l,text:s}}function Ha(t,e,n){let r=typeof n.code=="string"?n.code.toLowerCase():"",i=r==="rate_limit_exceeded"?"rate_limited":r,o=e!==null&&/^\d+(?:\.\d+)?$/.test(e.trim())?Number(e):e!==null?(Date.parse(e)-(t.now?.()??Date.now()))/1e3:NaN,s=Math.max(0,Math.ceil(o)),a=Number.isFinite(s)?`Try again in ${s} second${s===1?"":"s"}.`:"Wait a few minutes, then run it again.";return new p(/^[a-z][a-z0-9_]*$/.test(i)?i:"rate_limited",typeof n.error=="string"&&n.error?n.error:"too many requests to ClosedLoop AI",g.Failure,a,429)}function Ga(t){let e=ft(t.minimum_version)?t.minimum_version:"a newer version";return new p("upgrade_required",`this ClosedLoop AI CLI (${re}) is too old for the API; ${e} or later is needed`,g.Failure,ft(t.latest_version)?`Update to ${t.latest_version}: ${Bt}`:`Update: ${Bt}`)}function Re(t,e,n,r){return Yt(t,e,"POST",n,{payload:r})}var ze="credentials.json",qa=["password","email_code","google","microsoft","okta","saml"];function Kt(t){return qa.includes(t)?t:null}var Bn="Run `closedloop login` to sign in.",x='Sign in to the app with your email address. An account made in the CLI has no password: choose "Email me a code" and the app emails you a sign-in code.';function Xe(){return new p("session_changed","your saved sign-in changed while this command was running",g.Failure,"Repeat the command. If you signed out, run `closedloop login` to sign in again.")}function zt(t){let e=te(t,ze);if(e===null||typeof e!="object"||Array.isArray(e))return null;let n=r=>typeof r=="string"&&r.trim()!==""&&r.trim()===r;if(!n(e.api_url)||!n(e.email)||!n(e.access_token)||!n(e.refresh_token))return null;try{if(Jt({...t,env:{CLOSEDLOOP_API_URL:e.api_url}})!==e.api_url)return null}catch{return null}return e}function $(t){let e=zt(t);if(e===null)throw new p("not_signed_in","you are not signed in on this computer",g.Failure,Bn);return e}function ai(t,e,n,r,i){if(!e.access_token||!e.refresh_token)throw new Error("a sign-in needs both tokens");let o=new Date(t.now?.()??Date.now()).toISOString();ne(t,s=>{if(i&&JSON.stringify(s.read(i.file))!==JSON.stringify(i.value))throw Xe();s.write(ze,{...e,signed_in_at:o,sign_in_method:n,signed_in_with:r,saved_at:o}),s.remove("pending-login.json"),s.remove("pending-browser-login.json")})}function Yn(t){let e=t.split(".");if(e.length!==3)return null;try{let n=JSON.parse(Buffer.from(e[1],"base64url").toString("utf8")),{iat:r,exp:i}=n??{};return typeof r!="number"||typeof i!="number"||!Number.isFinite(r)||!Number.isFinite(i)||r<0||i<=r||i*1e3>864e13?null:{issuedAt:r,expiresAt:i}}catch{return null}}function li(t){let e=Yn(t.refresh_token)??Yn(t.access_token);return e?new Date(e.expiresAt*1e3).toISOString():null}function Ja(t,e){let n=Yn(t);return n!==null&&e/1e3>=n.issuedAt+(n.expiresAt-n.issuedAt)/2}async function Va(t,e){let n=zt(t);if(!n||n.api_url!==e.api_url||n.email!==e.email||n.refresh_token!==e.refresh_token)throw Xe();let r=JSON.stringify(n),i=await Yt(t,e.api_url,"POST","/api/auth/refresh",{payload:{refreshToken:e.refresh_token}}),{accessToken:o,refreshToken:s}=i.body;if(i.status!==200||typeof o!="string"||!o.trim()||typeof s!="string"||!s.trim())return null;let a={access_token:o,refresh_token:s,saved_at:new Date(t.now?.()??Date.now()).toISOString()};return ne(t,l=>{if(JSON.stringify(l.read(ze))!==r)throw Xe();l.write(ze,{...e,...a})}),a}async function si(t,e){let n=t.pendingSessionRefreshes??=new Map,r=JSON.stringify([e.api_url,e.email,e.refresh_token]),i=n.get(r);i||(i=Va(t,e),n.set(r,i));try{let o=await i;return o?(Object.assign(e,o),!0):!1}finally{n.get(r)===i&&n.delete(r)}}async function v(t,e,n,r,i,o){let s=()=>Yt(t,e.api_url,n,r,{token:e.access_token,payload:i,headers:o}),a=null;if(Ja(e.access_token,t.now?.()??Date.now()))try{a=await si(t,e)}catch(c){if(!(c instanceof p))throw c;let d=["api_unreachable","api_timeout"].includes(c.code),m=c.httpStatus===429;if(!d&&!m)throw c;a=!1}let l=await s();if(l.status!==401)return l;if(a!==null||!await si(t,e))throw new p("session_expired","your saved sign-in has expired or was signed out",g.Failure,Bn);let u=await s();if(u.status===401)throw new p("session_expired","your saved sign-in has expired or was signed out",g.Failure,Bn);return u}function Kn(t,e,n){return v(t,e,"GET",n)}function C(t,e,n){let r=typeof t.body.code=="string"?t.body.code.toLowerCase():"",i=typeof t.body.error=="string"&&t.body.error?t.body.error:n;return new p(/^[a-z][a-z0-9_]*$/.test(r)?r:e,i,g.Failure)}var Ba=["can_manage_integrations","can_manage_billing","can_manage_members","can_manage_team_settings","can_view_analytics"];async function F(t,e){let n=await v(t,e,"GET","/api/team-info"),r=n.body.teamInfo;if(n.status!==200)throw C(n,"api_error","Your workspace could not be loaded. Try again.");if(typeof r?.team_id!="string"||!r.team_id)throw new p("no_workspace","you are not in a workspace yet",g.Failure,`Create or join one in the app. ${x}`);let i=Object.fromEntries(Ba.map(o=>[o,r.permissions?.[o]===!0]));return{id:r.team_id,name:typeof r.team_name=="string"?r.team_name:"your workspace",role:typeof r.role=="string"?r.role:null,permissions:i}}async function Ie(t,e){return(await F(t,e)).id}var Ya={can_manage_integrations:"manage integrations",can_manage_billing:"manage billing"};async function Qe(t,e,n,r){let i=await F(t,e);if(!i.permissions[n])throw new p(n==="can_manage_integrations"?"integration_manager_required":"billing_manager_required",`you cannot ${r}: it needs the right to ${Ya[n]} in ${i.name}`,g.Failure,`Ask an owner or admin of ${i.name} to give you that right, or to do it for you.`);return i}var ke=new Set(["queued","importing","stalled"]),Ka=7200,ui=30,za=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,di=t=>`${t[0].toUpperCase()}${t.slice(1)}`;function mi(t){if(!t.progress)return null;let{unit:e,done:n,total:r,failed:i}=t.progress;return`${k(n)} of ${k(r)} ${e}${t.percent!==null?` (${t.percent}%)`:""}${i?`, ${k(i)} failed`:""}`}function pi(t){return t.from&&t.to?`${H(t.from)} to ${H(t.to)}${t.window?` (${t.window})`:""}`:t.window}function gi(t,e){if(t.state==="stalled")return t.last_activity_at?`no progress since ${E(t.last_activity_at)}`:"no progress";if(t.state==="queued")return t.created_at?`waiting to start since ${E(t.created_at)}`:"waiting to start";if(t.state==="importing")return t.estimated_finish_at?`about ${Be(Date.parse(t.estimated_finish_at)-e)} left (around ${E(t.estimated_finish_at)})`:t.started_at?`started ${E(t.started_at)}; too early to estimate`:"too early to estimate";if(t.finished_at){let n=t.started_at??t.created_at,r=n?Date.parse(t.finished_at)-Date.parse(n):NaN,i=!Number.isFinite(r)||r<0?"":t.started_at?` (took ${Be(r)})`:` (${Be(r)} after it was requested)`;return`${t.state==="done"?"finished":"ended"} ${E(t.finished_at)}${i}`}return null}function Xa(t,e){let n=di(t.state);return t.state==="failed"||t.state==="stalled"?e.red(n):t.state==="importing"||t.state==="queued"?e.orange(n):t.state==="done"?e.green(n):e.dim(n)}function Xt(t,e,n){let r=n.enabled&&ke.has(t.state)&&t.percent!==null?`${n.orange(n.bar(t.percent/100,12))} `:"";return[Xa(t,n),`${r}${[t.source,pi(t),mi(t),gi(t,e)].filter(Boolean).join(n.dim(" \xB7 "))}`].join(" ")}async function hi(t,e,n){let r=await v(t.runtime,e,"GET",`/api/cli/backfills?limit=${n}`);if(r.status!==200||!Array.isArray(r.body.backfills))throw C(r,"api_error","Backfills could not be loaded. Try again.");return r.body.backfills}async function Qa(t,e){let n=e===void 0?20:Number(e);if(!Number.isInteger(n)||n<1||n>100)throw new p("invalid_option","--limit must be a whole number from 1 to 100",g.Usage);let r=$(t.runtime),i=await hi(t,r,n),o=Date.now();t.print.result({backfills:i},s=>{if(s.backfills.length===0)return"No backfills in this workspace yet.";let a=s.backfills.map(u=>Xt(u,o,t.print.style)),l=s.backfills.some(u=>ke.has(u.state))?`
|
|
29
29
|
Progress covers importing; insights appear as the imported records are analysed.`:"";return`${a.join(`
|
|
30
30
|
`)}${l}`})}async function ci(t,e,n){if(za.test(n)){let o=await v(t.runtime,e,"GET",`/api/cli/backfills/${n.toLowerCase()}`);if(o.status===404)throw new p("backfill_not_found","there is no backfill with that id in your workspace",g.Failure);if(o.status!==200||typeof o.body.id!="string")throw C(o,"api_error","The backfill could not be loaded. Try again.");return o.body}let r=n.trim().toLowerCase(),i=(await hi(t,e,100)).find(o=>o.slug===r||o.source.toLowerCase()===r);if(!i)throw new p("backfill_not_found",`there is no backfill of "${n}" in your workspace`,g.Failure,"See them with: closedloop backfills");return i}function Za(t,e){let n=[`${t.source} backfill: ${t.state}`],r=pi(t);r&&n.push(`Covers: ${r}`);let i=mi(t);i&&n.push(`Progress: ${i}`),t.created_at&&n.push(`Requested: ${E(t.created_at)}`),t.started_at&&n.push(`Started: ${E(t.started_at)}`),t.last_activity_at&&ke.has(t.state)&&n.push(`Last activity: ${E(t.last_activity_at)}`);let o=gi(t,e);return o&&n.push(di(o)+"."),ke.has(t.state)&&n.push("This covers importing; insights appear as the imported records are analysed."),n.push(`id ${t.id}`),n.join(`
|
|
31
31
|
`)}async function el(t,e,n){let r=$(t.runtime),i=await ci(t,r,e);if(n&&ke.has(i.state)){let o="",s=t.print.spinner("Checking every 30 seconds\u2026");try{for(let a=0;a<Ka&&ke.has(i.state);a+=ui){let l=Xt(i,Date.now(),t.print.style);l!==o&&(s.stop(),t.print.note(l)),o=l,s.update("Checking every 30 seconds\u2026"),await t.runtime.sleep(ui*1e3),i=await ci(t,r,i.id)}}finally{s.stop()}}t.print.result(i,o=>Za(o,Date.now()))}var fi=(t,e)=>{t.command("backfills").alias("backfill").description("History imports: their state, progress and an estimate of when they finish.").option("--limit <n>","How many, up to 100 (default 20).").action(async r=>Qa(e,r.limit)).command("show").argument("<which>","A backfill id, or a source for its latest backfill, e.g. gong.").description("One backfill in detail.").option("--watch","Keep checking every 30 seconds until it finishes (up to 2 hours).").action(async(r,i)=>el(e,r,i.watch===!0))};import{existsSync as Ti,readFileSync as nl,statSync as Ei}from"node:fs";var w=t=>t!==null&&typeof t=="object"&&!Array.isArray(t),zn=t=>Array.isArray(t)?t.filter(w):[];function Se(t){let{success:e,...n}=t;return n}function yt(t,e){let n=Array.isArray(t.body.problems)?t.body.problems:Array.isArray(t.body.errors)?t.body.errors:null;if(t.status!==400||!n||!n.length)return null;let r=n.map(s=>{if(typeof s=="string")return s;let{path:a,field:l,message:u}=s??{},c=typeof a=="string"?a:typeof l=="string"?l:"",d=typeof u=="string"?u:JSON.stringify(s);return c?`${c}: ${d}`:d});if(n.map(s=>w(s)&&typeof s.path=="string"?s.path:"").every(s=>s==="name"||s==="description"))return new p("invalid_input",`the request was not accepted; fix ${r.length===1?"this":`these ${r.length}`}:
|
|
@@ -48,8 +48,8 @@ The research text appears here once it is done.`;if(typeof n.text!="string"||!n.
|
|
|
48
48
|
ClosedLoop AI did not send the research text. Try again later.`;let r=n.researched_url??t.website,i=n.researched_at?`, ${E(n.researched_at)}`:"";return`What the company research found${r?` (from ${r}${i})`:""}:
|
|
49
49
|
|
|
50
50
|
${n.text.trim()}`}async function Xi(t,e,n,r){let i=n,o=t.print.spinner("Checking every 30 seconds\u2026");try{for(let s=0;s<_l&&bl.has(i.research.state);s+=zi)o.update(`${tt(i,Date.now())} Checking every 30 seconds\u2026`),await t.runtime.sleep(zi*1e3),i=await Le(t.runtime,e,r)}finally{o.stop()}return i}async function Sl(t,e,n){let{runtime:r,interactive:i}=t,o=n.domains===!1?null:Zi(String(n.domains));if(o!==null&&o.length===0)throw new p("invalid_domains","--domains needs the company's email domains, e.g. --domains acme.example",g.Usage);let s=n.confirmedByUser===!0;if(!s&&i){let a=o===null?'Without email domains every colleague counts as a customer and the results will be wrong. Type "yes" to confirm your company has none: ':`Are these your colleagues' email domains: ${et(o)}? Type "yes" to save them: `;s=eo(await r.prompt(a))}if(!s){let a=await Le(r,e),l=o===null?"that the company has no email domain of its own":`that their colleagues' email domains are ${et(o)}`,u=o===null?Qi:`closedloop company --domains ${o.join(",")} --confirmed-by-user`,c=i?"Nothing was saved.":`Nothing was saved: only the person who uses this workspace can confirm the email domains. Has your user confirmed ${l}? ${rr} If they have not, ask them first. Once they say yes, run: ${u}`;return{profile:a,saved:!1,instructions:c}}return{profile:await nr(r,e,o),saved:!0,instructions:null}}async function Al(t,e){let n=$(t.runtime);if(e.website!==void 0&&!e.website.trim())throw new p("invalid_website","--website needs the company website, e.g. https://acme.example",g.Usage);if(e.confirmedByUser&&e.domains===void 0)throw new p("invalid_options","--confirmed-by-user goes with --domains or --no-domains",g.Usage);if(e.research&&(e.website!==void 0||e.domains!==void 0))throw new p("invalid_options","--research only shows the research; set the website or the email domains without it",g.Usage);if(e.research){let l=await Le(t.runtime,n,!0);e.watch&&(l=await Xi(t,n,l,!0)),t.print.result(l,u=>kl(u,Date.now()));return}let r=e.website!==void 0?await sr(t.runtime,n,e.website):await Le(t.runtime,n),i=!1,o=null;if(e.domains!==void 0){let l=await Sl(t,n,e);({profile:r}=l),i=l.saved,o=l.instructions}e.watch&&(r=await Xi(t,n,r,!1)),!o&&!t.interactive&&(o=St(r));let s=r.research.state==="ready"?await Cl(t.runtime,n):null,a={...r,email_domains_saved:i,instructions:o,products_suggested:s};t.print.result(a,l=>{let u=[];return e.website!==void 0&&u.push(`Website saved. ${Ct}`),l.email_domains_saved?u.push("Email domains saved."):e.domains!==void 0&&l.instructions&&u.push(l.instructions),u.push(ar(l,Date.now(),l.products_suggested)),u.join(`
|
|
51
|
-
`)})}var no=(t,e)=>{t.command("company").description("Your company website and the research of it, which comes before any analysis.").option("--website <url>","Set the company website, e.g. https://acme.example (an owner or admin).").option("--domains <list>","Confirm your colleagues' email domains, e.g. acme.example,acme.io (an owner or admin).").option("--no-domains","Confirm your company has no email domain of its own; colleagues then count as customers.").option("--confirmed-by-user","The person using this workspace confirmed the domains. An agent adds it only after its user said yes.").option("--watch","Keep checking every 30 seconds until the research is done (up to an hour).").option("--research","Show everything the company research found: the whole research text.").action(async n=>Al(e,n))};var Ol="Insights that name one of these competitors count as competitor mentions, shown on opportunities, features, customer profiles and the weekly brief.",ro="closedloop competitors add <name> [--website <url>]",Tl="Changing competitors needs the right to change the workspace's settings (an owner or admin).",El="It is then marked as not a competitor; the customer mentions behind it are kept, and adding it again restores it.",Rl={researched:"
|
|
52
|
-
`)}async function xl(t){let e=$(t.runtime),n=await F(t.runtime,e),r=await ur(t.runtime,e,n.id);t.print.result({competitors:r,can_change:n.permissions.can_manage_team_settings},Pl)}async function Nl(t,e,n){let r=e.trim();if(!r)throw new p("missing_name",`give the competitor's name, e.g. closedloop competitors add "Acme Rival"`,g.Usage);let i=$(t.runtime),{id:o}=await F(t.runtime,i),s={competitor_company:r,is_direct_competitor:!0};n.website?.trim()&&(s.product_website=n.website.trim()),n.description?.trim()&&(s.description=n.description.trim());let a=await v(t.runtime,i,"POST",mn(o),s);if(a.status!==200)throw dn(a,"The competitor could not be added. Try again.",!0);let l=lr(a.body.competitor);t.print.result({competitor:l},u=>`Added ${u.competitor.competitor_company} to your competitors.`)}async function Ll(t,e,n){if(n.name===void 0&&n.website===void 0&&n.description===void 0)throw new p("nothing_to_change","say what to change: --name, --website or --description",g.Usage);if(n.name!==void 0&&!n.name.trim())throw new p("missing_name","--name needs the new name; a competitor always has one",g.Usage);let r=$(t.runtime),{id:i}=await F(t.runtime,r),o=oo(await ur(t.runtime,r,i),e),s={};n.name!==void 0&&(s.competitor_company=n.name.trim()),n.website!==void 0&&(s.product_website=n.website.trim()||null),n.description!==void 0&&(s.description=n.description.trim()||null);let a=await v(t.runtime,r,"PUT",mn(i,o.id),s);if(a.status!==200)throw dn(a,"The competitor could not be saved. Try again.",!0);let l=lr(a.body.competitor);t.print.result({competitor:l},u=>`Saved: ${so(u.competitor)}`)}async function Dl(t,e,n){let{runtime:r,interactive:i,print:o}=t,s=$(r),{id:a}=await F(r,s),l=oo(await ur(r,s,a),e),u=l.competitor_company,c=n;if(!c&&i&&(o.note(`${u} will be marked as not a competitor and moved out of this list. The customer mentions behind it are kept, and adding it again restores it.`),c=Il(await r.prompt(`Type "yes" to remove ${u}: `))),!c){let m=i?"Nothing was removed.":`Nothing was removed: only the person who uses this workspace can remove a competitor. Has your user agreed to remove ${u} from the competitor list? ${El} If they have not, ask them first. Once they say yes, run: closedloop competitors remove ${io(e.trim())} --yes`;o.result({status:"not_removed",competitor:l,instructions:m},f=>f.instructions);return}let d=await v(r,s,"DELETE",mn(a,l.id));if(d.status!==200)throw dn(d,"The competitor could not be removed. Try again.",!0);o.result({status:"removed",competitor:l,instructions:null},m=>`Removed ${m.competitor.competitor_company} from your competitors. The customer mentions behind it are kept; to restore it, run: closedloop competitors add ${io(m.competitor.competitor_company)}`)}var ao=(t,e)=>{let n=t.command("competitors").description("The competitors ClosedLoop AI watches for in your insights: the ones
|
|
51
|
+
`)})}var no=(t,e)=>{t.command("company").description("Your company website and the research of it, which comes before any analysis.").option("--website <url>","Set the company website, e.g. https://acme.example (an owner or admin).").option("--domains <list>","Confirm your colleagues' email domains, e.g. acme.example,acme.io (an owner or admin).").option("--no-domains","Confirm your company has no email domain of its own; colleagues then count as customers.").option("--confirmed-by-user","The person using this workspace confirmed the domains. An agent adds it only after its user said yes.").option("--watch","Keep checking every 30 seconds until the research is done (up to an hour).").option("--research","Show everything the company research found: the whole research text.").action(async n=>Al(e,n))};var Ol="Insights that name one of these competitors count as competitor mentions, shown on opportunities, features, customer profiles and the weekly brief.",ro="closedloop competitors add <name> [--website <url>]",Tl="Changing competitors needs the right to change the workspace's settings (an owner or admin).",El="It is then marked as not a competitor; the customer mentions behind it are kept, and adding it again restores it.",Rl={researched:"AI research",researched_and_mentioned:"AI research, mentioned by customers",mentioned:"mentioned by customers",edited:"manual"},nt=t=>t.trim().toLowerCase(),Il=t=>/^(y|yes)$/i.test(t.trim()),io=t=>/^[\w.@-]+$/.test(t)?t:`"${t.replace(/(["\\$`])/g,"\\$1")}"`;function dn(t,e,n){let r=t.status===403,i=C(t,r?"access_denied":"api_error",e);return r&&n?new p(i.code,i.message,g.Failure,Tl):i}var mn=(t,e)=>`/api/competitors/${encodeURIComponent(t)}${e?`/${encodeURIComponent(e)}`:""}`;function lr(t){let e=t;if(!e||typeof e.id!="string"||typeof e.competitor_company!="string")throw new p("api_error","ClosedLoop AI sent an unexpected reply",g.Failure);return e}async function ur(t,e,n){let r=await v(t,e,"GET",mn(n));if(r.status!==200)throw dn(r,"The competitors could not be loaded. Try again.",!1);if(!Array.isArray(r.body.competitors))throw new p("api_error","ClosedLoop AI sent an unexpected reply",g.Failure);return r.body.competitors.map(lr)}function oo(t,e){let n=e.trim(),r=t.find(a=>a.id.toLowerCase()===n.toLowerCase());if(r)return r;let i=t.filter(a=>nt(a.competitor_company)===nt(n));if(i.length===1)return i[0];if(i.length>1)throw new p("ambiguous_competitor",`"${n}" matches more than one competitor: ${i.map(a=>`${a.competitor_company} (${a.id})`).join(", ")}`,g.Usage,"Name it by its id instead.");let o=n?t.filter(a=>nt(a.competitor_company).includes(nt(n))):[],s=o.length>0?`; competitors with that in their name: ${o.map(a=>a.competitor_company).join(", ")}`:"";throw new p("unknown_competitor",`"${n}" is not on your competitor list${s}`,g.Usage,"See the list with: closedloop competitors")}function so(t){let e=[t.competitor_product&&nt(t.competitor_product)!==nt(t.competitor_company)?`${t.competitor_company} (${t.competitor_product})`:t.competitor_company];t.product_website&&e.push(t.product_website),t.is_direct_competitor===!0&&e.push("direct"),t.is_direct_competitor===!1&&e.push("indirect"),typeof t.mention_count=="number"&&t.mention_count>0&&e.push(`${k(t.mention_count)} mention${t.mention_count===1?"":"s"}`);let n=t.provenance?Rl[t.provenance]:void 0;return n&&e.push(n),e.join(" \xB7 ")}function Pl(t){let e=[Ol,""];return t.competitors.length===0?e.push(`No competitors yet. Your company research adds the ones it finds; add your own with: ${ro}`):(e.push(...t.competitors.map(n=>` ${so(n)}`),""),e.push(`${k(t.competitors.length)} competitor${t.competitors.length===1?"":"s"}. Add one: ${ro}. Change one: closedloop competitors edit <name> or remove <name>.`)),t.can_change||e.push("Only someone who can change the workspace's settings (an owner or admin) can change this list."),e.join(`
|
|
52
|
+
`)}async function xl(t){let e=$(t.runtime),n=await F(t.runtime,e),r=await ur(t.runtime,e,n.id);t.print.result({competitors:r,can_change:n.permissions.can_manage_team_settings},Pl)}async function Nl(t,e,n){let r=e.trim();if(!r)throw new p("missing_name",`give the competitor's name, e.g. closedloop competitors add "Acme Rival"`,g.Usage);let i=$(t.runtime),{id:o}=await F(t.runtime,i),s={competitor_company:r,is_direct_competitor:!0};n.website?.trim()&&(s.product_website=n.website.trim()),n.description?.trim()&&(s.description=n.description.trim());let a=await v(t.runtime,i,"POST",mn(o),s);if(a.status!==200)throw dn(a,"The competitor could not be added. Try again.",!0);let l=lr(a.body.competitor);t.print.result({competitor:l},u=>`Added ${u.competitor.competitor_company} to your competitors.`)}async function Ll(t,e,n){if(n.name===void 0&&n.website===void 0&&n.description===void 0)throw new p("nothing_to_change","say what to change: --name, --website or --description",g.Usage);if(n.name!==void 0&&!n.name.trim())throw new p("missing_name","--name needs the new name; a competitor always has one",g.Usage);let r=$(t.runtime),{id:i}=await F(t.runtime,r),o=oo(await ur(t.runtime,r,i),e),s={};n.name!==void 0&&(s.competitor_company=n.name.trim()),n.website!==void 0&&(s.product_website=n.website.trim()||null),n.description!==void 0&&(s.description=n.description.trim()||null);let a=await v(t.runtime,r,"PUT",mn(i,o.id),s);if(a.status!==200)throw dn(a,"The competitor could not be saved. Try again.",!0);let l=lr(a.body.competitor);t.print.result({competitor:l},u=>`Saved: ${so(u.competitor)}`)}async function Dl(t,e,n){let{runtime:r,interactive:i,print:o}=t,s=$(r),{id:a}=await F(r,s),l=oo(await ur(r,s,a),e),u=l.competitor_company,c=n;if(!c&&i&&(o.note(`${u} will be marked as not a competitor and moved out of this list. The customer mentions behind it are kept, and adding it again restores it.`),c=Il(await r.prompt(`Type "yes" to remove ${u}: `))),!c){let m=i?"Nothing was removed.":`Nothing was removed: only the person who uses this workspace can remove a competitor. Has your user agreed to remove ${u} from the competitor list? ${El} If they have not, ask them first. Once they say yes, run: closedloop competitors remove ${io(e.trim())} --yes`;o.result({status:"not_removed",competitor:l,instructions:m},f=>f.instructions);return}let d=await v(r,s,"DELETE",mn(a,l.id));if(d.status!==200)throw dn(d,"The competitor could not be removed. Try again.",!0);o.result({status:"removed",competitor:l,instructions:null},m=>`Removed ${m.competitor.competitor_company} from your competitors. The customer mentions behind it are kept; to restore it, run: closedloop competitors add ${io(m.competitor.competitor_company)}`)}var ao=(t,e)=>{let n=t.command("competitors").description("The competitors ClosedLoop AI watches for in your insights: the ones AI research found, the ones customers mention and the ones added by hand. Shows them; add, edit or remove one (someone who can change the workspace's settings).").action(async()=>xl(e));n.command("add").argument("<name>","The competitor's name.").description("Add a competitor, as a direct competitor. Adding one that was removed restores it.").option("--website <url>","Its website, e.g. https://rival.example.").option("--description <text>","What it does, in a sentence.").action(async(r,i)=>Nl(e,r,i)),n.command("edit").argument("<name-or-id>","The competitor, by name or id.").description("Change a competitor's name, website or description. Only what you give changes; an empty --website or --description clears it.").option("--name <name>","Its new name.").option("--website <url>","Its website.").option("--description <text>","What it does, in a sentence.").action(async(r,i)=>Ll(e,r,i)),n.command("remove").argument("<name-or-id>","The competitor, by name or id.").description("Remove a competitor from the list: it is marked as not a competitor, its customer mentions are kept, and adding it again restores it. Asks before removing.").option("--yes","Remove without asking. An agent adds it only after its user agreed.").action(async(r,i)=>Dl(e,r,i.yes===!0))};function Q(t,e=process.platform){return e==="win32"?`'${t.replace(/['‘-‛]/g,"$&$&")}'`:`'${t.replace(/'/g,"'\\''")}'`}function pn(t,e,n=process.platform){let r=JSON.stringify(t);if(n!=="win32")return e(Q(r,n));let i=r.replace(/[^\x20-\x7e]/g,o=>`\\u${o.charCodeAt(0).toString(16).padStart(4,"0")}`);return`${Q(i,n)} | ${e("-")}`}var lo={event:"dated records, so a past value can be computed",customer_history:"from the customer history, so a past value can be computed",crm_history:"from the CRM's change history, so a past value can be computed",none:"none: a past value cannot be computed"},jl=(t,e)=>Object.prototype.hasOwnProperty.call(t,e),Z=t=>typeof t=="string"?t:"",cr=t=>typeof t!="string"?String(t):/^\d{4}-\d{2}-\d{2}$/.test(t)?H(`${t}T00:00:00Z`):P(t);function Fl(t){if(typeof t.empty_share=="number")return`${Math.round(t.empty_share*100)}%`;let e=t.record_count,n=t.empty_count;return typeof e!="number"||typeof n!="number"||e<=0?null:`${Math.round(n/e*100)}%`}function Ul(t,e){return typeof t.distinct_count=="number"?Math.max(0,t.distinct_count-e):0}var oe=t=>t!=null,Wl=t=>t.hidden!==!0&&(t.kind==="number"&&![t.min,t.max,t.p10,t.p90].some(oe)||t.kind==="date"&&![t.first,t.last,t.p10,t.p90].some(oe)),uo=t=>String(t).replace(/\s+/g," ").trim(),dr=["customer","deal","person","insight","context","conversation","usage"],mr="When two fields fit a request, ask the user which one they mean before writing the rule; never pick one silently.";function Ml(t){let e=r=>typeof r=="number"?O(r):cr(r),n=typeof t.zero_share=="number"&&t.zero_share>0?[`${Math.round(t.zero_share*100)}% are 0`]:[];return oe(t.median)?[...n,`${n.length?"other values: ":""}10th percentile ${e(t.p10)}`,`median ${e(t.median)}`,`90th percentile ${e(t.p90)}`]:n}function mo(t,e){let n=Z(e.label)&&e.label!==e.key?` \xB7 ${e.label}`:"";return`${t.bold(Z(e.key))} ${t.dim(`${Z(e.type)}${n}`)}`}function Hl(t){return w(t.guide)&&w(t.guide.preview)&&t.guide.preview.kind==="empty"}function Gl(t,e,n,r){if(!e.length)return n?`No fields match "${n}".`:r?"No fields hold values yet.":"No fields yet.";let i=new Map;for(let s of e){let a=Z(s.entity)||"other";i.set(a,[...i.get(a)??[],s])}return`${[...i.entries()].map(([s,a])=>[t.bold(s),...a.map(l=>` ${mo(t,l)}`)].join(`
|
|
53
53
|
`)).join(`
|
|
54
54
|
|
|
55
55
|
`)}
|