@closedloop-ai/cli 0.3.3 → 0.3.4
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 +28 -10
- package/dist/closedloop.js +137 -135
- package/package.json +1 -1
package/dist/closedloop.js
CHANGED
|
@@ -1,176 +1,178 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
var
|
|
3
|
-
`)}displayWidth(
|
|
2
|
+
var B=class extends Error{constructor(t,n,o){super(o),Error.captureStackTrace(this,this.constructor),this.name=this.constructor.name,this.code=n,this.exitCode=t,this.nestedError=void 0}},je=class extends B{constructor(t){super(1,"commander.invalidArgument",t),Error.captureStackTrace(this,this.constructor),this.name=this.constructor.name}};var Tt=class{constructor(t,n){switch(this.description=n||"",this.variadic=!1,this.parseArg=void 0,this.defaultValue=void 0,this.defaultValueDescription=void 0,this.argChoices=void 0,t[0]){case"<":this.required=!0,this._name=t.slice(1,-1);break;case"[":this.required=!1,this._name=t.slice(1,-1);break;default:this.required=!0,this._name=t;break}this._name.endsWith("...")&&(this.variadic=!0,this._name=this._name.slice(0,-3))}name(){return this._name}_collectValue(t,n){return n===this.defaultValue||!Array.isArray(n)?[t]:(n.push(t),n)}default(t,n){return this.defaultValue=t,this.defaultValueDescription=n,this}argParser(t){return this.parseArg=t,this}choices(t){return this.argChoices=t.slice(),this.parseArg=(n,o)=>{if(!this.argChoices.includes(n))throw new je(`Allowed choices are ${this.argChoices.join(", ")}.`);return this.variadic?this._collectValue(n,o):n},this}argRequired(){return this.required=!0,this}argOptional(){return this.required=!1,this}};function en(e){let t=e.name()+(e.variadic===!0?"...":"");return e.required?"<"+t+">":"["+t+"]"}import{EventEmitter as kl}from"node:events";import uo from"node:child_process";import we from"node:path";import nn from"node:fs";import T from"node:process";import{stripVTControlCharacters as Sl}from"node:util";import{stripVTControlCharacters as $l}from"node:util";var Te=class{constructor(){this.helpWidth=void 0,this.minWidthToWrap=40,this.sortSubcommands=!1,this.sortOptions=!1,this.showGlobalOptions=!1}prepareContext(t){this.helpWidth=this.helpWidth??t.helpWidth??80}visibleCommands(t){let n=t.commands.filter(i=>!i._hidden),o=t._getHelpCommand();return o&&!o._hidden&&n.push(o),this.sortSubcommands&&n.sort((i,r)=>i.name().localeCompare(r.name())),n}compareOptions(t,n){let o=i=>i.short?i.short.replace(/^-/,""):i.long.replace(/^--/,"");return o(t).localeCompare(o(n))}visibleOptions(t){let n=t.options.filter(i=>!i.hidden),o=t._getHelpOption();if(o&&!o.hidden){let i=o.short&&t._findOption(o.short),r=o.long&&t._findOption(o.long);!i&&!r?n.push(o):o.long&&!r?n.push(t.createOption(o.long,o.description)):o.short&&!i&&n.push(t.createOption(o.short,o.description))}return this.sortOptions&&n.sort(this.compareOptions),n}visibleGlobalOptions(t){if(!this.showGlobalOptions)return[];let n=[];for(let o=t.parent;o;o=o.parent){let i=o.options.filter(r=>!r.hidden);n.push(...i)}return this.sortOptions&&n.sort(this.compareOptions),n}visibleArguments(t){return t._argsDescription&&t.registeredArguments.forEach(n=>{n.description=n.description||t._argsDescription[n.name()]||""}),t.registeredArguments.find(n=>n.description)?t.registeredArguments:[]}subcommandTerm(t){let n=t.registeredArguments.map(o=>en(o)).join(" ");return t._name+(t._aliases[0]?"|"+t._aliases[0]:"")+(t.options.length?" [options]":"")+(n?" "+n:"")}optionTerm(t){return t.flags}argumentTerm(t){return t.name()}longestSubcommandTermLength(t,n){return n.visibleCommands(t).reduce((o,i)=>Math.max(o,this.displayWidth(n.styleSubcommandTerm(n.subcommandTerm(i)))),0)}longestOptionTermLength(t,n){return n.visibleOptions(t).reduce((o,i)=>Math.max(o,this.displayWidth(n.styleOptionTerm(n.optionTerm(i)))),0)}longestGlobalOptionTermLength(t,n){return n.visibleGlobalOptions(t).reduce((o,i)=>Math.max(o,this.displayWidth(n.styleOptionTerm(n.optionTerm(i)))),0)}longestArgumentTermLength(t,n){return n.visibleArguments(t).reduce((o,i)=>Math.max(o,this.displayWidth(n.styleArgumentTerm(n.argumentTerm(i)))),0)}commandUsage(t){let n=t._name;t._aliases[0]&&(n=n+"|"+t._aliases[0]);let o="";for(let i=t.parent;i;i=i.parent)o=i.name()+" "+o;return o+n+" "+t.usage()}commandDescription(t){return t.description()}subcommandDescription(t){return t.summary()||t.description()}optionDescription(t){let n=[];if(t.argChoices&&n.push(`choices: ${t.argChoices.map(o=>JSON.stringify(o)).join(", ")}`),t.defaultValue!==void 0&&(t.required||t.optional||t.isBoolean()&&typeof t.defaultValue=="boolean")&&n.push(`default: ${t.defaultValueDescription||JSON.stringify(t.defaultValue)}`),t.presetArg!==void 0&&t.optional&&n.push(`preset: ${JSON.stringify(t.presetArg)}`),t.envVar!==void 0&&n.push(`env: ${t.envVar}`),n.length>0){let o=`(${n.join(", ")})`;return t.description?`${t.description} ${o}`:o}return t.description}argumentDescription(t){let n=[];if(t.argChoices&&n.push(`choices: ${t.argChoices.map(o=>JSON.stringify(o)).join(", ")}`),t.defaultValue!==void 0&&n.push(`default: ${t.defaultValueDescription||JSON.stringify(t.defaultValue)}`),n.length>0){let o=`(${n.join(", ")})`;return t.description?`${t.description} ${o}`:o}return t.description}formatItemList(t,n,o){return n.length===0?[]:[o.styleTitle(t),...n,""]}groupItems(t,n,o){let i=new Map;return t.forEach(r=>{let s=o(r);i.has(s)||i.set(s,[])}),n.forEach(r=>{let s=o(r);i.has(s)||i.set(s,[]),i.get(s).push(r)}),i}formatHelp(t,n){let o=n.padWidth(t,n),i=n.helpWidth??80;function r(d,m){return n.formatItem(d,o,m,n)}let s=[`${n.styleTitle("Usage:")} ${n.styleUsage(n.commandUsage(t))}`,""],a=n.commandDescription(t);a.length>0&&(s=s.concat([n.boxWrap(n.styleCommandDescription(a),i),""]));let l=n.visibleArguments(t).map(d=>r(n.styleArgumentTerm(n.argumentTerm(d)),n.styleArgumentDescription(n.argumentDescription(d))));if(s=s.concat(this.formatItemList("Arguments:",l,n)),this.groupItems(t.options,n.visibleOptions(t),d=>d.helpGroupHeading??"Options:").forEach((d,m)=>{let h=d.map(f=>r(n.styleOptionTerm(n.optionTerm(f)),n.styleOptionDescription(n.optionDescription(f))));s=s.concat(this.formatItemList(m,h,n))}),n.showGlobalOptions){let d=n.visibleGlobalOptions(t).map(m=>r(n.styleOptionTerm(n.optionTerm(m)),n.styleOptionDescription(n.optionDescription(m))));s=s.concat(this.formatItemList("Global Options:",d,n))}return this.groupItems(t.commands,n.visibleCommands(t),d=>d.helpGroup()||"Commands:").forEach((d,m)=>{let h=d.map(f=>r(n.styleSubcommandTerm(n.subcommandTerm(f)),n.styleSubcommandDescription(n.subcommandDescription(f))));s=s.concat(this.formatItemList(m,h,n))}),s.join(`
|
|
3
|
+
`)}displayWidth(t){return $l(t).length}styleTitle(t){return t}styleUsage(t){return t.split(" ").map(n=>n==="[options]"?this.styleOptionText(n):n==="[command]"?this.styleSubcommandText(n):n[0]==="["||n[0]==="<"?this.styleArgumentText(n):this.styleCommandText(n)).join(" ")}styleCommandDescription(t){return this.styleDescriptionText(t)}styleOptionDescription(t){return this.styleDescriptionText(t)}styleSubcommandDescription(t){return this.styleDescriptionText(t)}styleArgumentDescription(t){return this.styleDescriptionText(t)}styleDescriptionText(t){return t}styleOptionTerm(t){return this.styleOptionText(t)}styleSubcommandTerm(t){return t.split(" ").map(n=>n==="[options]"?this.styleOptionText(n):n[0]==="["||n[0]==="<"?this.styleArgumentText(n):this.styleSubcommandText(n)).join(" ")}styleArgumentTerm(t){return this.styleArgumentText(t)}styleOptionText(t){return t}styleArgumentText(t){return t}styleSubcommandText(t){return t}styleCommandText(t){return t}padWidth(t,n){return Math.max(n.longestOptionTermLength(t,n),n.longestGlobalOptionTermLength(t,n),n.longestSubcommandTermLength(t,n),n.longestArgumentTermLength(t,n))}preformatted(t){return/\n[^\S\r\n]/.test(t)}formatItem(t,n,o,i){let s=" ".repeat(2);if(!o)return s+t;let a=t.padEnd(n+t.length-i.displayWidth(t)),l=2,c=(this.helpWidth??80)-n-l-2,d;return c<this.minWidthToWrap||i.preformatted(o)?d=o:d=i.boxWrap(o,c).replace(/\n/g,`
|
|
4
4
|
`+" ".repeat(n+l)),s+a+" ".repeat(l)+d.replace(/\n/g,`
|
|
5
|
-
${s}`)}boxWrap(
|
|
6
|
-
`)}};var
|
|
5
|
+
${s}`)}boxWrap(t,n){if(n<this.minWidthToWrap)return t;let o=t.split(/\r\n|\n/),i=/[\s]*[^\s]+/g,r=[];return o.forEach(s=>{let a=s.match(i);if(a===null){r.push("");return}let l=[a.shift()],u=this.displayWidth(l[0]);a.forEach(c=>{let d=this.displayWidth(c);if(u+d<=n){l.push(c),u+=d;return}r.push(l.join(""));let m=c.trimStart();l=[m],u=this.displayWidth(m)}),r.push(l.join(""))}),r.join(`
|
|
6
|
+
`)}};var ot=class{constructor(t,n){this.flags=t,this.description=n||"",this.required=t.includes("<"),this.optional=t.includes("["),this.variadic=/\w\.\.\.[>\]]$/.test(t),this.mandatory=!1;let o=vl(t);this.short=o.shortFlag,this.long=o.longFlag,this.negate=!1,this.long&&(this.negate=this.long.startsWith("--no-")),this.defaultValue=void 0,this.defaultValueDescription=void 0,this.presetArg=void 0,this.envVar=void 0,this.parseArg=void 0,this.hidden=!1,this.argChoices=void 0,this.conflictsWith=[],this.implied=void 0,this.helpGroupHeading=void 0}default(t,n){return this.defaultValue=t,this.defaultValueDescription=n,this}preset(t){return this.presetArg=t,this}conflicts(t){return this.conflictsWith=this.conflictsWith.concat(t),this}implies(t){let n=t;return typeof t=="string"&&(n={[t]:!0}),this.implied=Object.assign(this.implied||{},n),this}env(t){return this.envVar=t,this}argParser(t){return this.parseArg=t,this}makeOptionMandatory(t=!0){return this.mandatory=!!t,this}hideHelp(t=!0){return this.hidden=!!t,this}_collectValue(t,n){return n===this.defaultValue||!Array.isArray(n)?[t]:(n.push(t),n)}choices(t){return this.argChoices=t.slice(),this.parseArg=(n,o)=>{if(!this.argChoices.includes(n))throw new je(`Allowed choices are ${this.argChoices.join(", ")}.`);return this.variadic?this._collectValue(n,o):n},this}name(){return this.long?this.long.replace(/^--/,""):this.short.replace(/^-/,"")}attributeName(){return this.negate?Ti(this.name().replace(/^no-/,"")):Ti(this.name())}helpGroup(t){return this.helpGroupHeading=t,this}is(t){return this.short===t||this.long===t}isBoolean(){return!this.required&&!this.optional&&!this.negate}},tn=class{constructor(t){this.positiveOptions=new Map,this.negativeOptions=new Map,this.dualOptions=new Set,t.forEach(n=>{n.negate?this.negativeOptions.set(n.attributeName(),n):this.positiveOptions.set(n.attributeName(),n)}),this.negativeOptions.forEach((n,o)=>{this.positiveOptions.has(o)&&this.dualOptions.add(o)})}valueFromOption(t,n){let o=n.attributeName();if(!this.dualOptions.has(o))return!0;let i=this.negativeOptions.get(o).presetArg,r=i!==void 0?i:!1;return n.negate===(r===t)}};function Ti(e){return e.split("-").reduce((t,n)=>t+n[0].toUpperCase()+n.slice(1))}function vl(e){let t,n,o=/^-[^-]$/,i=/^--[^-]/,r=e.split(/[ |,]+/).concat("guard");if(o.test(r[0])&&(t=r.shift()),i.test(r[0])&&(n=r.shift()),!t&&o.test(r[0])&&(t=r.shift()),!t&&i.test(r[0])&&(t=n,n=r.shift()),r[0].startsWith("-")){let s=r[0],a=`option creation failed due to '${s}' in option flags '${e}'`;throw/^-[^-][^-]/.test(s)?new Error(`${a}
|
|
7
7
|
- a short flag is a single dash and a single character
|
|
8
8
|
- either use a single dash and a single character (for a short flag)
|
|
9
|
-
- or use a double dash for a long option (and can have two, like '--ws, --workspace')`):
|
|
9
|
+
- or use a double dash for a long option (and can have two, like '--ws, --workspace')`):o.test(s)?new Error(`${a}
|
|
10
10
|
- too many short flags`):i.test(s)?new Error(`${a}
|
|
11
11
|
- too many long flags`):new Error(`${a}
|
|
12
|
-
- unrecognised flag format`)}if(
|
|
13
|
-
(Did you mean one of ${
|
|
14
|
-
(Did you mean ${
|
|
15
|
-
- specify the name in Command constructor or using .name()`);return n=n||{},n.isDefault&&(this._defaultCommandName=
|
|
16
|
-
Expecting one of '${
|
|
17
|
-
- already used by option '${n.flags}'`)}this._initOptionGroup(
|
|
18
|
-
- either make a new Command for each call to parse, or stop storing options as properties`);this._name=this._savedState._name,this._scriptPath=null,this.rawArgs=[],this._optionValues={...this._savedState._optionValues},this._optionValueSources={...this._savedState._optionValueSources},this.args=[],this.processedArgs=[]}_checkForMissingExecutable(
|
|
19
|
-
- if '${
|
|
12
|
+
- unrecognised flag format`)}if(t===void 0&&n===void 0)throw new Error(`option creation failed due to no flags found in '${e}'.`);return{shortFlag:t,longFlag:n}}function Cl(e,t){if(Math.abs(e.length-t.length)>3)return Math.max(e.length,t.length);let n=[];for(let o=0;o<=e.length;o++)n[o]=[o];for(let o=0;o<=t.length;o++)n[0][o]=o;for(let o=1;o<=t.length;o++)for(let i=1;i<=e.length;i++){let r;e[i-1]===t[o-1]?r=0:r=1,n[i][o]=Math.min(n[i-1][o]+1,n[i][o-1]+1,n[i-1][o-1]+r),i>1&&o>1&&e[i-1]===t[o-2]&&e[i-2]===t[o-1]&&(n[i][o]=Math.min(n[i][o],n[i-2][o-2]+1))}return n[e.length][t.length]}function lo(e,t){if(!t||t.length===0)return"";t=Array.from(new Set(t));let n=e.startsWith("--");n&&(e=e.slice(2),t=t.map(s=>s.slice(2)));let o=[],i=3,r=.4;return t.forEach(s=>{if(s.length<=1)return;let a=Cl(e,s),l=Math.max(e.length,s.length);(l-a)/l>r&&(a<i?(i=a,o=[s]):a===i&&o.push(s))}),o.sort((s,a)=>s.localeCompare(a)),n&&(o=o.map(s=>`--${s}`)),o.length>1?`
|
|
13
|
+
(Did you mean one of ${o.join(", ")}?)`:o.length===1?`
|
|
14
|
+
(Did you mean ${o[0]}?)`:""}var it=class e extends kl{constructor(t){super(),this.commands=[],this.options=[],this.parent=null,this._allowUnknownOption=!1,this._allowExcessArguments=!1,this.registeredArguments=[],this._args=this.registeredArguments,this.args=[],this.rawArgs=[],this.processedArgs=[],this._scriptPath=null,this._name=t||"",this._optionValues={},this._optionValueSources={},this._storeOptionsAsProperties=!1,this._actionHandler=null,this._executableHandler=!1,this._executableFile=null,this._executableDir=null,this._defaultCommandName=null,this._exitCallback=null,this._aliases=[],this._combineFlagAndOptionalValue=!0,this._description="",this._summary="",this._argsDescription=void 0,this._enablePositionalOptions=!1,this._passThroughOptions=!1,this._lifeCycleHooks={},this._showHelpAfterError=!1,this._showSuggestionAfterError=!0,this._savedState=null,this._outputConfiguration={writeOut:n=>T.stdout.write(n),writeErr:n=>T.stderr.write(n),outputError:(n,o)=>o(n),getOutHelpWidth:()=>T.stdout.isTTY?T.stdout.columns:void 0,getErrHelpWidth:()=>T.stderr.isTTY?T.stderr.columns:void 0,getOutHasColors:()=>Ei()??(T.stdout.isTTY&&T.stdout.hasColors?.()),getErrHasColors:()=>Ei()??(T.stderr.isTTY&&T.stderr.hasColors?.()),stripColor:n=>Sl(n)},this._hidden=!1,this._helpOption=void 0,this._addImplicitHelpCommand=void 0,this._helpCommand=void 0,this._helpConfiguration={},this._helpGroupHeading=void 0,this._defaultCommandGroup=void 0,this._defaultOptionGroup=void 0}copyInheritedSettings(t){return this._outputConfiguration=t._outputConfiguration,this._helpOption=t._helpOption,this._helpCommand=t._helpCommand,this._helpConfiguration=t._helpConfiguration,this._exitCallback=t._exitCallback,this._storeOptionsAsProperties=t._storeOptionsAsProperties,this._combineFlagAndOptionalValue=t._combineFlagAndOptionalValue,this._allowExcessArguments=t._allowExcessArguments,this._enablePositionalOptions=t._enablePositionalOptions,this._showHelpAfterError=t._showHelpAfterError,this._showSuggestionAfterError=t._showSuggestionAfterError,this}_getCommandAndAncestors(){let t=[];for(let n=this;n;n=n.parent)t.push(n);return t}command(t,n,o){let i=n,r=o;typeof i=="object"&&i!==null&&(r=i,i=null),r=r||{};let[,s,a]=t.match(/([^ ]+) *(.*)/),l=this.createCommand(s);return i&&(l.description(i),l._executableHandler=!0),r.isDefault&&(this._defaultCommandName=l._name),l._hidden=!!(r.noHelp||r.hidden),l._executableFile=r.executableFile||null,a&&l.arguments(a),this._registerCommand(l),l.parent=this,l.copyInheritedSettings(this),i?this:l}createCommand(t){return new e(t)}createHelp(){return Object.assign(new Te,this.configureHelp())}configureHelp(t){return t===void 0?this._helpConfiguration:(this._helpConfiguration=t,this)}configureOutput(t){return t===void 0?this._outputConfiguration:(this._outputConfiguration={...this._outputConfiguration,...t},this)}showHelpAfterError(t=!0){return typeof t!="string"&&(t=!!t),this._showHelpAfterError=t,this}showSuggestionAfterError(t=!0){return this._showSuggestionAfterError=!!t,this}addCommand(t,n){if(!t._name)throw new Error(`Command passed to .addCommand() must have a name
|
|
15
|
+
- specify the name in Command constructor or using .name()`);return n=n||{},n.isDefault&&(this._defaultCommandName=t._name),(n.noHelp||n.hidden)&&(t._hidden=!0),this._registerCommand(t),t.parent=this,t._checkForBrokenPassThrough(),this}createArgument(t,n){return new Tt(t,n)}argument(t,n,o,i){let r=this.createArgument(t,n);return typeof o=="function"?r.default(i).argParser(o):r.default(o),this.addArgument(r),this}arguments(t){return t.trim().split(/ +/).forEach(n=>{this.argument(n)}),this}addArgument(t){let n=this.registeredArguments.slice(-1)[0];if(n?.variadic)throw new Error(`only the last argument can be variadic '${n.name()}'`);if(t.required&&t.defaultValue!==void 0&&t.parseArg===void 0)throw new Error(`a default value for a required argument is never used: '${t.name()}'`);return this.registeredArguments.push(t),this}helpCommand(t,n){if(typeof t=="boolean")return this._addImplicitHelpCommand=t,t&&this._defaultCommandGroup&&this._initCommandGroup(this._getHelpCommand()),this;let o=t??"help [command]",[,i,r]=o.match(/([^ ]+) *(.*)/),s=n??"display help for command",a=this.createCommand(i);return a.helpOption(!1),r&&a.arguments(r),s&&a.description(s),this._addImplicitHelpCommand=!0,this._helpCommand=a,(t||n)&&this._initCommandGroup(a),this}addHelpCommand(t,n){return typeof t!="object"?(this.helpCommand(t,n),this):(this._addImplicitHelpCommand=!0,this._helpCommand=t,this._initCommandGroup(t),this)}_getHelpCommand(){return this._addImplicitHelpCommand??(this.commands.length&&!this._actionHandler&&!this._findCommand("help"))?(this._helpCommand===void 0&&this.helpCommand(void 0,void 0),this._helpCommand):null}hook(t,n){let o=["preSubcommand","preAction","postAction"];if(!o.includes(t))throw new Error(`Unexpected value for event passed to hook : '${t}'.
|
|
16
|
+
Expecting one of '${o.join("', '")}'`);return this._lifeCycleHooks[t]?this._lifeCycleHooks[t].push(n):this._lifeCycleHooks[t]=[n],this}exitOverride(t){return t?this._exitCallback=t:this._exitCallback=n=>{if(n.code!=="commander.executeSubCommandAsync")throw n},this}_exit(t,n,o){this._exitCallback&&this._exitCallback(new B(t,n,o)),T.exit(t)}action(t){let n=o=>{let i=this.registeredArguments.length,r=o.slice(0,i);return this._storeOptionsAsProperties?r[i]=this:r[i]=this.opts(),r.push(this),t.apply(this,r)};return this._actionHandler=n,this}createOption(t,n){return new ot(t,n)}_callParseArg(t,n,o,i){try{return t.parseArg(n,o)}catch(r){if(r.code==="commander.invalidArgument"){let s=`${i} ${r.message}`;this.error(s,{exitCode:r.exitCode,code:r.code})}throw r}}_registerOption(t){let n=t.short&&this._findOption(t.short)||t.long&&this._findOption(t.long);if(n){let o=t.long&&this._findOption(t.long)?t.long:t.short;throw new Error(`Cannot add option '${t.flags}'${this._name&&` to command '${this._name}'`} due to conflicting flag '${o}'
|
|
17
|
+
- already used by option '${n.flags}'`)}this._initOptionGroup(t),this.options.push(t)}_registerCommand(t){let n=i=>[i.name()].concat(i.aliases()),o=n(t).find(i=>this._findCommand(i));if(o){let i=n(this._findCommand(o)).join("|"),r=n(t).join("|");throw new Error(`cannot add command '${r}' as already have command '${i}'`)}this._initCommandGroup(t),this.commands.push(t)}addOption(t){this._registerOption(t);let n=t.name(),o=t.attributeName();t.defaultValue!==void 0&&this.setOptionValueWithSource(o,t.defaultValue,"default");let i=(r,s,a)=>{r==null&&t.presetArg!==void 0&&(r=t.presetArg);let l=this.getOptionValue(o);r!==null&&t.parseArg?r=this._callParseArg(t,r,l,s):r!==null&&t.variadic&&(r=t._collectValue(r,l)),r==null&&(t.negate?r=!1:t.isBoolean()||t.optional?r=!0:r=""),this.setOptionValueWithSource(o,r,a)};return this.on("option:"+n,r=>{let s=`error: option '${t.flags}' argument '${r}' is invalid.`;i(r,s,"cli")}),t.envVar&&this.on("optionEnv:"+n,r=>{let s=`error: option '${t.flags}' value '${r}' from env '${t.envVar}' is invalid.`;i(r,s,"env")}),this}_optionEx(t,n,o,i,r){if(typeof n=="object"&&n instanceof ot)throw new Error("To add an Option object use addOption() instead of option() or requiredOption()");let s=this.createOption(n,o);if(s.makeOptionMandatory(!!t.mandatory),typeof i=="function")s.default(r).argParser(i);else if(i instanceof RegExp){let a=i;i=(l,u)=>{let c=a.exec(l);return c?c[0]:u},s.default(r).argParser(i)}else s.default(i);return this.addOption(s)}option(t,n,o,i){return this._optionEx({},t,n,o,i)}requiredOption(t,n,o,i){return this._optionEx({mandatory:!0},t,n,o,i)}combineFlagAndOptionalValue(t=!0){return this._combineFlagAndOptionalValue=!!t,this}allowUnknownOption(t=!0){return this._allowUnknownOption=!!t,this}allowExcessArguments(t=!0){return this._allowExcessArguments=!!t,this}enablePositionalOptions(t=!0){return this._enablePositionalOptions=!!t,this}passThroughOptions(t=!0){return this._passThroughOptions=!!t,this._checkForBrokenPassThrough(),this}_checkForBrokenPassThrough(){if(this.parent&&this._passThroughOptions&&!this.parent._enablePositionalOptions)throw new Error(`passThroughOptions cannot be used for '${this._name}' without turning on enablePositionalOptions for parent command(s)`)}storeOptionsAsProperties(t=!0){if(this.options.length)throw new Error("call .storeOptionsAsProperties() before adding options");if(Object.keys(this._optionValues).length)throw new Error("call .storeOptionsAsProperties() before setting option values");return this._storeOptionsAsProperties=!!t,this}getOptionValue(t){return this._storeOptionsAsProperties?this[t]:this._optionValues[t]}setOptionValue(t,n){return this.setOptionValueWithSource(t,n,void 0)}setOptionValueWithSource(t,n,o){return this._storeOptionsAsProperties?this[t]=n:this._optionValues[t]=n,this._optionValueSources[t]=o,this}getOptionValueSource(t){return this._optionValueSources[t]}getOptionValueSourceWithGlobals(t){let n;return this._getCommandAndAncestors().forEach(o=>{o.getOptionValueSource(t)!==void 0&&(n=o.getOptionValueSource(t))}),n}_prepareUserArgs(t,n){if(t!==void 0&&!Array.isArray(t))throw new Error("first parameter to parse must be array or undefined");if(n=n||{},t===void 0&&n.from===void 0){T.versions?.electron&&(n.from="electron");let i=T.execArgv??[];(i.includes("-e")||i.includes("--eval")||i.includes("-p")||i.includes("--print"))&&(n.from="eval")}t===void 0&&(t=T.argv),this.rawArgs=t.slice();let o;switch(n.from){case void 0:case"node":this._scriptPath=t[1],o=t.slice(2);break;case"electron":T.defaultApp?(this._scriptPath=t[1],o=t.slice(2)):o=t.slice(1);break;case"user":o=t.slice(0);break;case"eval":o=t.slice(1);break;default:throw new Error(`unexpected parse option { from: '${n.from}' }`)}return!this._name&&this._scriptPath&&this.nameFromFilename(this._scriptPath),this._name=this._name||"program",o}parse(t,n){this._prepareForParse();let o=this._prepareUserArgs(t,n);return this._parseCommand([],o),this}async parseAsync(t,n){this._prepareForParse();let o=this._prepareUserArgs(t,n);return await this._parseCommand([],o),this}_prepareForParse(){this._savedState===null?(this.options.filter(t=>t.negate&&t.defaultValue===void 0&&this.getOptionValue(t.attributeName())===void 0).forEach(t=>{let n=t.long.replace(/^--no-/,"--");this._findOption(n)||this.setOptionValueWithSource(t.attributeName(),!0,"default")}),this.saveStateBeforeParse()):this.restoreStateBeforeParse()}saveStateBeforeParse(){this._savedState={_name:this._name,_optionValues:{...this._optionValues},_optionValueSources:{...this._optionValueSources}}}restoreStateBeforeParse(){if(this._storeOptionsAsProperties)throw new Error(`Can not call parse again when storeOptionsAsProperties is true.
|
|
18
|
+
- either make a new Command for each call to parse, or stop storing options as properties`);this._name=this._savedState._name,this._scriptPath=null,this.rawArgs=[],this._optionValues={...this._savedState._optionValues},this._optionValueSources={...this._savedState._optionValueSources},this.args=[],this.processedArgs=[]}_checkForMissingExecutable(t,n,o){if(nn.existsSync(t))return;let i=n?`searched for local subcommand relative to directory '${n}'`:"no directory for search for local subcommand, use .executableDir() to supply a custom directory",r=`'${t}' does not exist
|
|
19
|
+
- if '${o}' is not meant to be an executable command, remove description parameter from '.command()' and use '.description()' instead
|
|
20
20
|
- if the default executable name is not suitable, use the executableFile option to supply a custom name or path
|
|
21
|
-
- ${i}`;throw new Error(
|
|
21
|
+
- ${i}`;throw new Error(r)}_executeSubCommand(t,n){n=n.slice();let o=[".js",".ts",".tsx",".mjs",".cjs"];function i(c,d){let m=we.resolve(c,d);if(nn.existsSync(m))return m;if(o.includes(we.extname(d)))return;let h=o.find(f=>nn.existsSync(`${m}${f}`));if(h)return`${m}${h}`}this._checkForMissingMandatoryOptions(),this._checkForConflictingOptions();let r=t._executableFile||`${this._name}-${t._name}`,s=this._executableDir||"";if(this._scriptPath){let c;try{c=nn.realpathSync(this._scriptPath)}catch{c=this._scriptPath}s=we.resolve(we.dirname(c),s)}if(s){let c=i(s,r);if(!c&&!t._executableFile&&this._scriptPath){let d=we.basename(this._scriptPath,we.extname(this._scriptPath));d!==this._name&&(c=i(s,`${d}-${t._name}`))}r=c||r}let a=o.includes(we.extname(r)),l;T.platform!=="win32"?a?(n.unshift(r),n=Ri(T.execArgv).concat(n),l=uo.spawn(T.argv[0],n,{stdio:"inherit"})):l=uo.spawn(r,n,{stdio:"inherit"}):(this._checkForMissingExecutable(r,s,t._name),n.unshift(r),n=Ri(T.execArgv).concat(n),l=uo.spawn(T.execPath,n,{stdio:"inherit"})),l.killed||["SIGUSR1","SIGUSR2","SIGTERM","SIGINT","SIGHUP"].forEach(d=>{T.on(d,()=>{l.killed===!1&&l.exitCode===null&&l.kill(d)})});let u=this._exitCallback;l.on("close",c=>{c=c??1,u?u(new B(c,"commander.executeSubCommandAsync","(close)")):T.exit(c)}),l.on("error",c=>{if(c.code==="ENOENT")this._checkForMissingExecutable(r,s,t._name);else if(c.code==="EACCES")throw new Error(`'${r}' not executable`);if(!u)T.exit(1);else{let d=new B(1,"commander.executeSubCommandAsync","(error)");d.nestedError=c,u(d)}}),this.runningCommand=l}_dispatchSubcommand(t,n,o){let i=this._findCommand(t);i||this.help({error:!0}),i._prepareForParse();let r;return r=this._chainOrCallSubCommandHook(r,i,"preSubcommand"),r=this._chainOrCall(r,()=>{if(i._executableHandler)this._executeSubCommand(i,n.concat(o));else return i._parseCommand(n,o)}),r}_dispatchHelpCommand(t){t||this.help();let n=this._findCommand(t);return n&&!n._executableHandler&&n.help(),this._dispatchSubcommand(t,[],[this._getHelpOption()?.long??this._getHelpOption()?.short??"--help"])}_checkNumberOfArguments(){this.registeredArguments.forEach((t,n)=>{t.required&&this.args[n]==null&&this.missingArgument(t.name())}),!(this.registeredArguments.length>0&&this.registeredArguments[this.registeredArguments.length-1].variadic)&&this.args.length>this.registeredArguments.length&&this._excessArguments(this.args)}_processArguments(){let t=(o,i,r)=>{let s=i;if(i!==null&&o.parseArg){let a=`error: command-argument value '${i}' is invalid for argument '${o.name()}'.`;s=this._callParseArg(o,i,r,a)}return s};this._checkNumberOfArguments();let n=[];this.registeredArguments.forEach((o,i)=>{let r=o.defaultValue;o.variadic?i<this.args.length?(r=this.args.slice(i),o.parseArg&&(r=r.reduce((s,a)=>t(o,a,s),o.defaultValue))):r===void 0&&(r=[]):i<this.args.length&&(r=this.args[i],o.parseArg&&(r=t(o,r,o.defaultValue))),n[i]=r}),this.processedArgs=n}_chainOrCall(t,n){return t?.then&&typeof t.then=="function"?t.then(()=>n()):n()}_chainOrCallHooks(t,n){let o=t,i=[];return this._getCommandAndAncestors().reverse().filter(r=>r._lifeCycleHooks[n]!==void 0).forEach(r=>{r._lifeCycleHooks[n].forEach(s=>{i.push({hookedCommand:r,callback:s})})}),n==="postAction"&&i.reverse(),i.forEach(r=>{o=this._chainOrCall(o,()=>r.callback(r.hookedCommand,this))}),o}_chainOrCallSubCommandHook(t,n,o){let i=t;return this._lifeCycleHooks[o]!==void 0&&this._lifeCycleHooks[o].forEach(r=>{i=this._chainOrCall(i,()=>r(this,n))}),i}_parseCommand(t,n){let o=this.parseOptions(n);if(this._parseOptionsEnv(),this._parseOptionsImplied(),t=t.concat(o.operands),n=o.unknown,this.args=t.concat(n),t&&this._findCommand(t[0]))return this._dispatchSubcommand(t[0],t.slice(1),n);if(this._getHelpCommand()&&t[0]===this._getHelpCommand().name())return this._dispatchHelpCommand(t[1]);if(this._defaultCommandName)return this._outputHelpIfRequested(n),this._dispatchSubcommand(this._defaultCommandName,t,n);this.commands.length&&this.args.length===0&&!this._actionHandler&&!this._defaultCommandName&&this.help({error:!0}),this._outputHelpIfRequested(o.unknown),this._checkForMissingMandatoryOptions(),this._checkForConflictingOptions();let i=()=>{o.unknown.length>0&&this.unknownOption(o.unknown[0])},r=`command:${this.name()}`;if(this._actionHandler){i(),this._processArguments();let s;return s=this._chainOrCallHooks(s,"preAction"),s=this._chainOrCall(s,()=>this._actionHandler(this.processedArgs)),this.parent&&(s=this._chainOrCall(s,()=>{this.parent.emit(r,t,n)})),s=this._chainOrCallHooks(s,"postAction"),s}if(this.parent?.listenerCount(r))i(),this._processArguments(),this.parent.emit(r,t,n);else if(t.length){if(this._findCommand("*"))return this._dispatchSubcommand("*",t,n);this.listenerCount("command:*")?this.emit("command:*",t,n):this.commands.length?this.unknownCommand():(i(),this._processArguments())}else this.commands.length?(i(),this.help({error:!0})):(i(),this._processArguments())}_findCommand(t){if(t)return this.commands.find(n=>n._name===t||n._aliases.includes(t))}_findOption(t){return this.options.find(n=>n.is(t))}_checkForMissingMandatoryOptions(){this._getCommandAndAncestors().forEach(t=>{t.options.forEach(n=>{n.mandatory&&t.getOptionValue(n.attributeName())===void 0&&t.missingMandatoryOptionValue(n)})})}_checkForConflictingLocalOptions(){let t=this.options.filter(o=>{let i=o.attributeName();return this.getOptionValue(i)===void 0?!1:this.getOptionValueSource(i)!=="default"});t.filter(o=>o.conflictsWith.length>0).forEach(o=>{let i=t.find(r=>o.conflictsWith.includes(r.attributeName()));i&&this._conflictingOption(o,i)})}_checkForConflictingOptions(){this._getCommandAndAncestors().forEach(t=>{t._checkForConflictingLocalOptions()})}parseOptions(t){let n=[],o=[],i=n;function r(c){return c.length>1&&c[0]==="-"}let s=c=>/^-(\d+|\d*\.\d+)(e[+-]?\d+)?$/.test(c)?!this._getCommandAndAncestors().some(d=>d.options.map(m=>m.short).some(m=>/^-\d$/.test(m))):!1,a=null,l=null,u=0;for(;u<t.length||l;){let c=l??t[u++];if(l=null,c==="--"){i===o&&i.push(c),i.push(...t.slice(u));break}if(a&&(!r(c)||s(c))){this.emit(`option:${a.name()}`,c);continue}if(a=null,r(c)){let d=this._findOption(c);if(d){if(d.required){let m=t[u++];m===void 0&&this.optionMissingArgument(d),this.emit(`option:${d.name()}`,m)}else if(d.optional){let m=null;u<t.length&&(!r(t[u])||s(t[u]))&&(m=t[u++]),this.emit(`option:${d.name()}`,m)}else this.emit(`option:${d.name()}`);a=d.variadic?d:null;continue}}if(c.length>2&&c[0]==="-"&&c[1]!=="-"){let d=this._findOption(`-${c[1]}`);if(d){d.required||d.optional&&this._combineFlagAndOptionalValue?this.emit(`option:${d.name()}`,c.slice(2)):(this.emit(`option:${d.name()}`),l=`-${c.slice(2)}`);continue}}if(/^--[^=]+=/.test(c)){let d=c.indexOf("="),m=this._findOption(c.slice(0,d));if(m&&(m.required||m.optional)){this.emit(`option:${m.name()}`,c.slice(d+1));continue}}if(i===n&&r(c)&&!(this.commands.length===0&&s(c))&&(i=o),(this._enablePositionalOptions||this._passThroughOptions)&&n.length===0&&o.length===0){if(this._findCommand(c)){n.push(c),o.push(...t.slice(u));break}else if(this._getHelpCommand()&&c===this._getHelpCommand().name()){n.push(c,...t.slice(u));break}else if(this._defaultCommandName){o.push(c,...t.slice(u));break}}if(this._passThroughOptions){i.push(c,...t.slice(u));break}i.push(c)}return{operands:n,unknown:o}}opts(){if(this._storeOptionsAsProperties){let t={},n=this.options.length;for(let o=0;o<n;o++){let i=this.options[o].attributeName();t[i]=i===this._versionOptionName?this._version:this[i]}return t}return this._optionValues}optsWithGlobals(){return this._getCommandAndAncestors().reduce((t,n)=>Object.assign(t,n.opts()),{})}error(t,n){this._outputConfiguration.outputError(`${t}
|
|
22
22
|
`,this._outputConfiguration.writeErr),typeof this._showHelpAfterError=="string"?this._outputConfiguration.writeErr(`${this._showHelpAfterError}
|
|
23
23
|
`):this._showHelpAfterError&&(this._outputConfiguration.writeErr(`
|
|
24
|
-
`),this.outputHelp({error:!0}));let
|
|
25
|
-
`),this._exit(0,"commander.version",
|
|
26
|
-
Expecting one of '${
|
|
27
|
-
`)}),this}_outputHelpIfRequested(
|
|
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))?`
|
|
24
|
+
`),this.outputHelp({error:!0}));let o=n||{},i=o.exitCode||1,r=o.code||"commander.error";this._exit(i,r,t)}_parseOptionsEnv(){this.options.forEach(t=>{if(t.envVar&&t.envVar in T.env){let n=t.attributeName();(this.getOptionValue(n)===void 0||["default","config","env"].includes(this.getOptionValueSource(n)))&&(t.required||t.optional?this.emit(`optionEnv:${t.name()}`,T.env[t.envVar]):this.emit(`optionEnv:${t.name()}`))}})}_parseOptionsImplied(){let t=new tn(this.options),n=o=>this.getOptionValue(o)!==void 0&&!["default","implied"].includes(this.getOptionValueSource(o));this.options.filter(o=>o.implied!==void 0&&n(o.attributeName())&&t.valueFromOption(this.getOptionValue(o.attributeName()),o)).forEach(o=>{Object.keys(o.implied).filter(i=>!n(i)).forEach(i=>{this.setOptionValueWithSource(i,o.implied[i],"implied")})})}missingArgument(t){let n=`error: missing required argument '${t}'`;this.error(n,{code:"commander.missingArgument"})}optionMissingArgument(t){let n=`error: option '${t.flags}' argument missing`;this.error(n,{code:"commander.optionMissingArgument"})}missingMandatoryOptionValue(t){let n=`error: required option '${t.flags}' not specified`;this.error(n,{code:"commander.missingMandatoryOptionValue"})}_conflictingOption(t,n){let o=s=>{let a=s.attributeName(),l=this.getOptionValue(a),u=this.options.find(d=>d.negate&&a===d.attributeName()),c=this.options.find(d=>!d.negate&&a===d.attributeName());return u&&(u.presetArg===void 0&&l===!1||u.presetArg!==void 0&&l===u.presetArg)?u:c||s},i=s=>{let a=o(s),l=a.attributeName();return this.getOptionValueSource(l)==="env"?`environment variable '${a.envVar}'`:`option '${a.flags}'`},r=`error: ${i(t)} cannot be used with ${i(n)}`;this.error(r,{code:"commander.conflictingOption"})}unknownOption(t){if(this._allowUnknownOption)return;let n="";if(t.startsWith("--")&&this._showSuggestionAfterError){let i=[],r=this;do{let s=r.createHelp().visibleOptions(r).filter(a=>a.long).map(a=>a.long);i=i.concat(s),r=r.parent}while(r&&!r._enablePositionalOptions);n=lo(t,i)}let o=`error: unknown option '${t}'${n}`;this.error(o,{code:"commander.unknownOption"})}_excessArguments(t){if(this._allowExcessArguments)return;let n=this.registeredArguments.length,o=n===1?"":"s",i=t.length,r=this.parent?` for '${this.name()}'`:"",s=t.join(", "),a=`error: too many arguments${r}. Expected ${n} argument${o} but got ${i}: ${s}.`;this.error(a,{code:"commander.excessArguments"})}unknownCommand(){let t=this.args[0],n="";if(this._showSuggestionAfterError){let i=[];this.createHelp().visibleCommands(this).forEach(r=>{i.push(r.name()),r.alias()&&i.push(r.alias())}),n=lo(t,i)}let o=`error: unknown command '${t}'${n}`;this.error(o,{code:"commander.unknownCommand"})}version(t,n,o){if(t===void 0)return this._version;this._version=t,n=n||"-V, --version",o=o||"output the version number";let i=this.createOption(n,o);return this._versionOptionName=i.attributeName(),this._registerOption(i),this.on("option:"+i.name(),()=>{this._outputConfiguration.writeOut(`${t}
|
|
25
|
+
`),this._exit(0,"commander.version",t)}),this}description(t,n){return t===void 0&&n===void 0?this._description:(this._description=t,n&&(this._argsDescription=n),this)}summary(t){return t===void 0?this._summary:(this._summary=t,this)}alias(t){if(t===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]),t===n._name)throw new Error("Command alias can't be the same as its name");let o=this.parent?._findCommand(t);if(o){let i=[o.name()].concat(o.aliases()).join("|");throw new Error(`cannot add alias '${t}' to command '${this.name()}' as already have command '${i}'`)}return n._aliases.push(t),this}aliases(t){return t===void 0?this._aliases:(t.forEach(n=>this.alias(n)),this)}usage(t){if(t===void 0){if(this._usage)return this._usage;let n=this.registeredArguments.map(o=>en(o));return[].concat(this.options.length||this._helpOption!==null?"[options]":[],this.commands.length?"[command]":[],this.registeredArguments.length?n:[]).join(" ")}return this._usage=t,this}name(t){return t===void 0?this._name:(this._name=t,this)}helpGroup(t){return t===void 0?this._helpGroupHeading??"":(this._helpGroupHeading=t,this)}commandsGroup(t){return t===void 0?this._defaultCommandGroup??"":(this._defaultCommandGroup=t,this)}optionsGroup(t){return t===void 0?this._defaultOptionGroup??"":(this._defaultOptionGroup=t,this)}_initOptionGroup(t){this._defaultOptionGroup&&!t.helpGroupHeading&&t.helpGroup(this._defaultOptionGroup)}_initCommandGroup(t){this._defaultCommandGroup&&!t.helpGroup()&&t.helpGroup(this._defaultCommandGroup)}nameFromFilename(t){return this._name=we.basename(t,we.extname(t)),this}executableDir(t){return t===void 0?this._executableDir:(this._executableDir=t,this)}helpInformation(t){let n=this.createHelp(),o=this._getOutputContext(t);n.prepareContext({error:o.error,helpWidth:o.helpWidth,outputHasColors:o.hasColors});let i=n.formatHelp(this,n);return o.hasColors?i:this._outputConfiguration.stripColor(i)}_getOutputContext(t){t=t||{};let n=!!t.error,o,i,r;return n?(o=a=>this._outputConfiguration.writeErr(a),i=this._outputConfiguration.getErrHasColors(),r=this._outputConfiguration.getErrHelpWidth()):(o=a=>this._outputConfiguration.writeOut(a),i=this._outputConfiguration.getOutHasColors(),r=this._outputConfiguration.getOutHelpWidth()),{error:n,write:a=>(i||(a=this._outputConfiguration.stripColor(a)),o(a)),hasColors:i,helpWidth:r}}outputHelp(t){let n;typeof t=="function"&&(n=t,t=void 0);let o=this._getOutputContext(t),i={error:o.error,write:o.write,command:this};this._getCommandAndAncestors().reverse().forEach(s=>s.emit("beforeAllHelp",i)),this.emit("beforeHelp",i);let r=this.helpInformation({error:o.error});if(n&&(r=n(r),typeof r!="string"&&!Buffer.isBuffer(r)))throw new Error("outputHelp callback must return a string or a Buffer");o.write(r),this._getHelpOption()?.long&&this.emit(this._getHelpOption().long),this.emit("afterHelp",i),this._getCommandAndAncestors().forEach(s=>s.emit("afterAllHelp",i))}helpOption(t,n){return typeof t=="boolean"?(t?(this._helpOption===null&&(this._helpOption=void 0),this._defaultOptionGroup&&this._initOptionGroup(this._getHelpOption())):this._helpOption=null,this):(this._helpOption=this.createOption(t??"-h, --help",n??"display help for command"),(t||n)&&this._initOptionGroup(this._helpOption),this)}_getHelpOption(){return this._helpOption===void 0&&this.helpOption(void 0,void 0),this._helpOption}addHelpOption(t){return this._helpOption=t,this._initOptionGroup(t),this}help(t){this.outputHelp(t);let n=Number(T.exitCode??0);n===0&&t&&typeof t!="function"&&t.error&&(n=1),this._exit(n,"commander.help","(outputHelp)")}addHelpText(t,n){let o=["beforeAll","before","after","afterAll"];if(!o.includes(t))throw new Error(`Unexpected value for position to addHelpText.
|
|
26
|
+
Expecting one of '${o.join("', '")}'`);let i=`${t}Help`;return this.on(i,r=>{let s;typeof n=="function"?s=n({error:r.error,command:r.command}):s=n,s&&r.write(`${s}
|
|
27
|
+
`)}),this}_outputHelpIfRequested(t){let n=this._getHelpOption();n&&t.find(i=>n.is(i))&&(this.outputHelp(),this._exit(0,"commander.helpDisplayed","(outputHelp)"))}};function Ri(e){return e.map(t=>{if(!t.startsWith("--inspect"))return t;let n,o="127.0.0.1",i="9229",r;return(r=t.match(/^(--inspect(-brk)?)$/))!==null?n=r[1]:(r=t.match(/^(--inspect(-brk|-port)?)=([^:]+)$/))!==null?(n=r[1],/^\d+$/.test(r[3])?i=r[3]:o=r[3]):(r=t.match(/^(--inspect(-brk|-port)?)=([^:]+):(\d+)$/))!==null&&(n=r[1],o=r[3],i=r[4]),n&&i!=="0"?`${n}=${o}:${parseInt(i)+1}`:t})}function Ei(){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 vg=new it;import{createHash as Wl}from"node:crypto";import{readdirSync as Gi}from"node:fs";import{join as Ml}from"node:path";import{createHash as Ol,randomUUID as Ii}from"node:crypto";import{chmodSync as Tl,closeSync as co,cpSync as Rl,existsSync as Fe,fstatSync as El,fsyncSync as Il,lstatSync as mo,mkdirSync as fo,mkdtempSync as Pl,openSync as Pi,readFileSync as xi,readdirSync as xl,renameSync as Ni,rmSync as rn,writeFileSync as Li}from"node:fs";import{basename as yo,dirname as rt,join as _e,win32 as po}from"node:path";var g={Success:0,Failure:1,Usage:2},Al=/^[a-z][a-z0-9_]*$/,p=class extends Error{code;exitCode;hint;httpStatus;constructor(t,n,o,i,r){if(!Al.test(t))throw new Error(`CliError code must be snake_case: '${t}'`);if(o===g.Success)throw new Error(`CliError '${t}' cannot use the success exit code`);super(n),this.name="CliError",this.code=t,this.exitCode=o,this.hint=i,this.httpStatus=r}};var Nl="https://api.closedloop.sh",Q={us:"https://api.closedloop.sh",eu:"https://eu.api.closedloop.sh"};function Ue(e){return e===Q.us?"us":e===Q.eu?"eu":null}function sn(e){let t=e.env.CLOSEDLOOP_API_URL?.trim(),n=new URL(t||Nl),o=["localhost","127.0.0.1","[::1]"].includes(n.hostname);if(n.protocol!=="https:"&&!(n.protocol==="http:"&&o))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 Z(e){let t=e.env.CLOSEDLOOP_CONFIG_DIR?.trim();if(t)return t;let n=e.env.XDG_CONFIG_HOME?.trim();if(n)return _e(n,"closedloop");let o=e.platform??process.platform;return o==="darwin"?_e(e.homeDir,"Library","Application Support","closedloop"):o==="win32"?po.join(e.env.LOCALAPPDATA?.trim()||po.join(e.homeDir,"AppData","Local"),"closedloop"):_e(e.homeDir,".config","closedloop")}function on(e,t,n){(e.platform??process.platform)!=="win32"&&Tl(t,n)}function go(e,t){let n=Z(e);fo(t,{recursive:!0,mode:448}),on(e,n,448),t!==n&&on(e,t,448)}function Di(e){return e.env.CLOSEDLOOP_CONFIG_DIR?.trim()||e.env.XDG_CONFIG_HOME?.trim()?null:(e.platform??process.platform)==="win32"?po.join(e.homeDir,".config","closedloop"):_e(e.homeDir,".config","closedloop")}function Ll(e){let t=Z(e),n=Di(e);if(n===null||t===n||Fe(t)||!Fe(n))return;let o;try{fo(rt(t),{recursive:!0}),o=Pl(_e(rt(t),`.${yo(t)}-migration-`)),Rl(n,o,{recursive:!0,filter:i=>{let r=mo(i);if(!r.isDirectory()&&!r.isFile())throw new Error("legacy config contains a non-regular entry");return!0}}),on(e,o,448);for(let i of xl(o,{recursive:!0})){let r=_e(o,String(i));on(e,r,mo(r).isDirectory()?448:384)}Fe(t)||Ni(o,t)}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{o&&rn(o,{recursive:!0,force:!0})}}function ji(e){let t,n=new Int32Array(new SharedArrayBuffer(4));for(let s=0;s<5;s++)try{t=Pi(e,"wx",384);break}catch(a){if(a.code!=="EEXIST")throw a;s<4&&Atomics.wait(n,0,0,25)}if(t===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 ${e}.`);let o=t,i,r=()=>{try{if(i&&Fe(e)){let s=mo(e);s.dev===i.dev&&s.ino===i.ino&&rn(e)}}finally{co(o)}};try{i=El(o),Li(o,JSON.stringify({pid:process.pid,owner:Ii()}))}catch(s){throw r(),s}return r}function Dl(e,t){let n=ji(e);try{return t()}finally{n()}}function an(e,t){let n=Z(e);try{return fo(rt(n),{recursive:!0}),Dl(_e(rt(n),`.${yo(n)}.lock`),()=>(Ll(e),t()))}catch(o){throw o instanceof p?o: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 Rt(e,t){return _e(Z(e),...t.split("/"))}function Fi(e,t){let n=Rt(e,t);try{return JSON.parse(xi(n,"utf8"))}catch(o){if(o.code==="ENOENT")return null;throw o instanceof SyntaxError?new p("config_invalid",`the saved ${t} file is not valid JSON`,g.Failure,`Back up and remove ${n}, then repeat the command${t==="credentials.json"?" or run `closedloop login` to sign in again":""}.`):new p("config_unreadable",`the saved ${t} file could not be read`,g.Failure,`Check the permissions for ${n} and CLOSEDLOOP_CONFIG_DIR, then repeat the command.`)}}function wo(e){let t=Di(e);t!==null&&t!==Z(e)&&!Fe(Z(e))&&Fe(t)&&an(e,()=>{})}function Re(e,t){return wo(e),Fi(e,t)}function _o(e,t){let n=Rt(e,t);try{return Ol("sha256").update(xi(n)).digest("hex")}catch(o){if(o.code==="ENOENT")return null;throw new p("config_unreadable",`the saved ${t} file could not be read`,g.Failure,`Check the permissions for ${n} and CLOSEDLOOP_CONFIG_DIR, then repeat the command.`)}}function Ui(e,t,n){let o=Rt(e,t),i=rt(o),r=_e(i,`.${yo(o)}.${Ii()}.tmp`),s;try{let a=`${JSON.stringify(n,null,2)}
|
|
28
|
+
`;go(e,i),s=Pi(r,"wx",384),Li(s,a),Il(s),co(s),s=void 0,Ni(r,o)}catch{throw new p("config_write_failed",`the saved ${t} file could not be updated`,g.Failure,`Check the permissions and free space for ${i}, then repeat the command. The previous file was kept.`)}finally{s!==void 0&&co(s),rn(r,{force:!0})}}function jl(e,t){try{rn(Rt(e,t),{force:!0})}catch{throw new p("config_write_failed",`the saved ${t} file could not be removed`,g.Failure,"Check the config folder permissions or CLOSEDLOOP_CONFIG_DIR, then repeat the command.")}}var Fl=["credentials.json","pending-login.json","pending-browser-login.json"];function ho(e,t,n){go(e,Z(e));let o=[];try{for(let i of t){let r=Rt(e,i);go(e,rt(r)),o.push(ji(r))}return n({read:i=>Fi(e,i),write:(i,r)=>Ui(e,i,r),remove:i=>jl(e,i)})}finally{for(let i of o.reverse())i()}}function Ul(e,t){return an(e,()=>ho(e,[".credentials.json.lock"],t))}function bo(e,t,n,o=!1){if(o||!Fe(Z(e)))return an(e,()=>ho(e,t,n));try{return ho(e,t,n)}catch(i){throw i instanceof p?i:new p("config_write_failed","the saved config folder could not be updated",g.Failure,`Check permissions and free space for ${Z(e)}, then repeat the command.`)}}function Wi(e,t,n){Fl.includes(t)?Ul(e,o=>o.write(t,n)):an(e,()=>Ui(e,t,n))}var at="general.json",N="credentials.json",$e="pending-login.json",ie="pending-browser-login.json",Hl="working-goal.json",Et="accounts",qi=".general.json.lock",Gl=".credentials.json.lock",ql=/^[^\s\p{C}@<>,;:"()\\[\]]+@(?:[^\s\p{C}@<>,;:"()\\[\].]+\.)+[^\s\p{C}@<>,;:"()\\[\].]+$/u;function ut(e){let t=e?.trim().toLowerCase();return t&&t.length<=320&&ql.test(t)?t:null}function be(e){if(typeof e!="string")return null;let t=e.trim().toLowerCase();return t.length>0&&t.length<=320&&t.includes("@")&&!/[\s\p{C}]/u.test(t)?t:null}function lt(e){return Wl("sha256").update(e.trim().toLowerCase()).digest("hex").slice(0,16)}function ge(e){return`${Et}/${lt(e)}.json`}function Ji(e){return`${Et}/.${lt(e)}.lock`}function st(e){return e.replace(/\.json$/,"").replace(/-/g,"_")}function Jl(e){return[N,$e,ie,Hl].includes(e)||/^last-[a-z0-9-]+\.json$/.test(e)}function We(e){return e!==null&&typeof e=="object"&&!Array.isArray(e)}function Vi(e){return e==="us"||e==="eu"?e:null}function ln(e){return We(e)?e:{}}function Co(e,t){return We(e)&&typeof e.email=="string"&&e.email===t?e:null}function Bi(e){let t;try{t=Gi(Ml(Z(e),Et))}catch(o){if(o.code==="ENOENT")return[];throw new p("config_unreadable","the saved accounts could not be read",g.Failure,"Check the permissions for the config folder and CLOSEDLOOP_CONFIG_DIR, then repeat the command.")}let n=[];for(let o of t.filter(i=>/^[0-9a-f]{16}\.json$/.test(i)).sort()){let i;try{i=Re(e,`${Et}/${o}`)}catch(s){if(s instanceof p&&s.code==="config_invalid")continue;throw s}let r=We(i)?be(i.email):null;r!==null&&r===i?.email&&ge(r)===`${Et}/${o}`&&n.push(r)}return n.sort()}function Mi(e,t){let n=_o(e,t);if(n===null)return null;try{return{value:Re(e,t),fingerprint:n}}catch(o){if(o instanceof p&&o.code==="config_invalid")return null;throw o}}function Hi(e,t,n){let o=We(n.migrated)?n.migrated:{},i={moves:[],signedIn:null,region:null},r=t.includes(N)?Mi(e,N):null;if(r!==null){let a=We(r.value)?be(r.value.email):null;o[N]===r.fingerprint?i.moves.push({file:N,fingerprint:r.fingerprint,to:null}):a!==null&&(i.signedIn=a,i.region=Vi(r.value.region),i.moves.push({file:N,fingerprint:r.fingerprint,to:a,value:r.value}))}let s=i.signedIn??be(n.active_account);for(let a of t.filter(l=>l!==N)){let l=Mi(e,a);if(l===null)continue;if(o[a]===l.fingerprint){i.moves.push({file:a,fingerprint:l.fingerprint,to:null});continue}let u=null;a===$e?u=We(l.value)?be(l.value.email):null:a===ie?u=s??"settings":u=s,u!==null&&i.moves.push({file:a,fingerprint:l.fingerprint,to:u,value:l.value})}return i}function vo(e){try{return Gi(Z(e)).filter(Jl).sort()}catch(t){if(t.code==="ENOENT"||t.code==="ENOTDIR")return[];throw new p("config_unreadable","the saved config folder could not be read",g.Failure,"Check the permissions for the config folder and CLOSEDLOOP_CONFIG_DIR, then repeat the command.")}}function $o(e){return[...new Set(e.moves.map(t=>t.to).filter(t=>t!==null&&t!=="settings"))].sort()}function un(e){wo(e);let t=vo(e);if(t.length===0)return;let n=Hi(e,t,ln(Re(e,at)));if(n.moves.length===0)return;let o=$o(n),i=[Gl,qi,...[...o].sort((r,s)=>lt(r).localeCompare(lt(s))).map(Ji)];bo(e,i,r=>{let s=ln(r.read(at)),a=Hi(e,vo(e),s);if(a.moves.length===0)return;if($o(a).some(c=>!o.includes(c)))throw new p("config_busy","another CLI process is updating the saved state",g.Failure,"Try again after other CLI commands finish.");for(let c of $o(a)){let d=Co(r.read(ge(c)),c)??{email:c};for(let m of a.moves.filter(h=>h.to===c))d[st(m.file)]=m.value;r.write(ge(c),d)}let l={...We(s.migrated)?s.migrated:{}};for(let c of a.moves)l[c.file]=c.fingerprint;let u=a.moves.find(c=>c.to==="settings");r.write(at,{...s,...u&&{pending_browser_login:u.value},...a.signedIn!==null&&{active_account:a.signedIn,last_email:a.signedIn},...a.region!==null&&{last_region:a.region},migrated:l});for(let c of a.moves)r.remove(c.file)},!0)}function Vl(e,t){for(let n of vo(e))t.remove(n)}function Me(e){return un(e),ln(Re(e,at))}function cn(e,t,n){un(e);let i=Co(Re(e,ge(t)),t)?.[st(n)];return i===void 0?null:i}function It(e){return un(e),Bi(e)}function Ee(e){if(e.account)return e.account;let t=e.accountOption??(e.env.CLOSEDLOOP_ACCOUNT?.trim()||void 0),n;if(t!==void 0){let o=be(t);if(o===null)throw new p("invalid_account","the account must be the email address of an account signed in on this computer",g.Usage,"Run `closedloop accounts` to see the accounts signed in on this computer.");n={email:o,explicit:!0}}else n={email:be(Me(e).active_account),explicit:!1};return e.account=n,n}function Y(e,t,n){un(e);let o=[...new Set((t.accounts==="all"?Bi(e):t.accounts).map(r=>r.trim().toLowerCase()))].sort((r,s)=>lt(r).localeCompare(lt(s))),i=[...t.settings?[qi]:[],...o.map(Ji)];return bo(e,i,r=>{let s=l=>{let u=l.trim().toLowerCase();if(!o.includes(u))throw new Error("an account file was changed without its lock");return u},a=l=>Co(r.read(ge(l)),l);return n({settings:()=>ln(r.read(at)),setSettings:l=>{if(!t.settings)throw new Error("general.json was changed without its lock");r.write(at,l)},read:(l,u)=>{let c=a(s(l))?.[st(u)];return c===void 0?null:c},write:(l,u,c)=>{let d=s(l);r.write(ge(d),{...a(d)??{email:d},[st(u)]:c})},remove:(l,u)=>{let c=s(l),d=a(c);if(d===null||!(st(u)in d))return;let{[st(u)]:m,...h}=d;Object.keys(h).length===1?r.remove(ge(c)):r.write(ge(c),h)},forget:l=>r.remove(ge(s(l))),emails:()=>o.filter(l=>_o(e,ge(l))!==null).sort(),removeLeftovers:()=>Vl(e,r)})})}function dn(e,t,n){return t!==null?Y(e,{accounts:[t]},o=>n({read:i=>o.read(t,i),write:(i,r)=>o.write(t,i,r),remove:i=>o.remove(t,i)})):Y(e,{accounts:[],settings:!0},o=>{let i=r=>{if(r!==ie)throw new Error("only a browser sign-in is kept without an account")};return n({read:r=>{i(r);let s=o.settings().pending_browser_login;return s===void 0?null:s},write:(r,s)=>{i(r),o.setSettings({...o.settings(),pending_browser_login:s})},remove:r=>{i(r);let{pending_browser_login:s,...a}=o.settings();o.setSettings(a)}})})}function ko(e,t,n){if(t!==null)return cn(e,t,n);if(n!==ie)return null;let o=Me(e).pending_browser_login;return o===void 0?null:o}function mn(e,t,n){Y(e,{accounts:[],settings:!0},o=>{let i=o.settings();o.setSettings({...i,last_email:t,...n!==null&&{last_region:n}})})}function pn(e,t){let{email:n}=Ee(e);return n===null?null:cn(e,n,t)}function gn(e,t,n){let{email:o}=Ee(e);o!==null&&Y(e,{accounts:[o]},i=>{i.read(o,N)!==null&&i.write(o,t,n)})}function Yi(e,t){let{email:n}=Ee(e);n!==null&&Y(e,{accounts:[n]},o=>o.remove(n,t))}function hn(e){return Vi(e.region)??Ue(e.api_url)}function ee(e,t=process.platform){return t==="win32"?`'${e.replace(/['‘-‛]/g,"$&$&")}'`:`'${e.replace(/'/g,"'\\''")}'`}function So(e,t=process.platform){return/^[a-z0-9@._+-]+$/i.test(e)?e:ee(e,t)}function Ie(e,t=process.platform){if(e===null)return"closedloop login";let n=So(e,t);return n.startsWith("-")?`closedloop login --email ${n}`:`closedloop login ${n}`}function fn(e,t,n=process.platform){let o=JSON.stringify(e);if(n!=="win32")return t(ee(o,n));let i=o.replace(/[^\x20-\x7e]/g,r=>`\\u${r.charCodeAt(0).toString(16).padStart(4,"0")}`);return`${ee(i,n)} | ${t("-")}`}var re="0.3.4";var yn="npm install -g @closedloop-ai/cli@latest",Oo=/^(\d{1,6})\.(\d{1,6})\.(\d{1,6})$/,Ki="update-notice.json",Bl=1440*60*1e3;function Pt(e){return typeof e=="string"&&Oo.test(e)}function zi(e,t){let n=Oo.exec(e),o=Oo.exec(t);if(!n||!o)throw new Error("compareVersions needs two x.y.z versions");for(let i=1;i<=3;i++)if(Number(n[i])!==Number(o[i]))return Number(n[i])-Number(o[i]);return 0}function Xi(e,t){e.release&&Pt(t)&&(e.release.latest===null||zi(t,e.release.latest)>0)&&(e.release.latest=t)}function To(e){let t=e.release?.latest??null;return t===null||!Pt(re)?null:zi(t,re)>0?t:null}function Ao(e){return e!==void 0&&e!==""&&e!=="0"&&e!=="false"}function Qi(e,t){let n=To(e);if(n===null||!t)return null;let{env:o}=e;if(Ao(o.CI)||Ao(o.NO_UPDATE_NOTIFIER)||Ao(o.CLOSEDLOOP_NO_UPDATE_NOTIFIER))return null;let i=e.now?.()??Date.now(),r=null;try{r=Re(e,Ki)}catch{}if(r&&r.version===n&&typeof r.shown_at=="number"&&i>=r.shown_at&&i-r.shown_at<Bl)return null;try{Wi(e,Ki,{version:n,shown_at:i})}catch{}return`ClosedLoop AI CLI ${n} is out (you have ${re}). Update: ${yn}`}var Yl=7e4,Ro=/[\u0000-\u0008\u000B-\u001F\u007F-\u009F\u061C\u200E\u200F\u202A-\u202E\u2066-\u2069]/g,Kl=(e,t)=>{if(typeof t=="string")return t.replace(Ro,"");if(t===null||typeof t!="object"||Array.isArray(t))return t;let n=Object.keys(t);return n.some(o=>o!==o.replace(Ro,""))?Object.fromEntries(n.map(o=>[o.replace(Ro,""),t[o]])):t};async function wn(e,t,n,o,i={}){if(!o.startsWith("/"))throw new Error("an API path starts with /");let r,s,a=AbortSignal.timeout(Yl);try{r=await e.fetch(`${t}${o}`,{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 r.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 ${t}`,g.Failure,"Check your connection, or CLOSEDLOOP_API_URL if you set it.")}let l={};try{let u=JSON.parse(s,Kl);u!==null&&typeof u=="object"&&!Array.isArray(u)&&(l=u)}catch{}if(r.status===426&&l.code==="upgrade_required")throw Xl(l);if(Xi(e,r.headers.get("X-ClosedLoop-CLI-Latest")),r.status===429)throw zl(e,r.headers.get("Retry-After"),l);return{status:r.status,body:l,text:s}}function zl(e,t,n){let o=typeof n.code=="string"?n.code.toLowerCase():"",i=o==="rate_limit_exceeded"?"rate_limited":o,r=t!==null&&/^\d+(?:\.\d+)?$/.test(t.trim())?Number(t):t!==null?(Date.parse(t)-(e.now?.()??Date.now()))/1e3:NaN,s=Math.max(0,Math.ceil(r)),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 Xl(e){let t=Pt(e.minimum_version)?e.minimum_version:"a newer version";return new p("upgrade_required",`this ClosedLoop AI CLI (${re}) is too old for the API; ${t} or later is needed`,g.Failure,Pt(e.latest_version)?`Update to ${e.latest_version}: ${yn}`:`Update: ${yn}`)}function He(e,t,n,o){return wn(e,t,"POST",n,{payload:o})}var Ql=["password","email_code","google","microsoft","okta","saml"];function _n(e){return Ql.includes(e)?e:null}var Eo="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 ct(){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 dt(e,t,n){if(t===null||typeof t!="object"||Array.isArray(t))return null;let o=t,i=r=>typeof r=="string"&&r.trim()!==""&&r.trim()===r;if(!i(o.api_url)||!i(o.email)||!i(o.access_token)||!i(o.refresh_token)||o.email.toLowerCase()!==n)return null;try{if(sn({...e,env:{CLOSEDLOOP_API_URL:o.api_url}})!==o.api_url)return null}catch{return null}return o}function Po(e,t){let n=t.trim().toLowerCase();return dt(e,cn(e,n,N),n)}function Zl(e){let{email:t}=Ee(e);return t===null?null:Po(e,t)}function xo(e){let t=[];for(let n of It(e)){let o=Po(e,n);o!==null&&t.push({email:n,session:o})}return t}function $(e){let t=Zl(e);if(t!==null)return t;let{email:n,explicit:o}=Ee(e);if(o&&n!==null)throw new p("account_not_signed_in",`${n} is not signed in on this computer`,g.Failure,`Run \`${Ie(n,e.platform)}\` to sign it in, or \`closedloop accounts\` to see the accounts signed in here.`);let i=xo(e).map(r=>r.email);throw new p("not_signed_in","you are not signed in on this computer",g.Failure,i.length===0?Eo:`Run \`closedloop use <email>\` to use one of the accounts signed in here (${i.join(", ")}), or \`closedloop login\` to sign in.`)}function er(e,t,n,o,i){if(!t.access_token||!t.refresh_token)throw new Error("a sign-in needs both tokens");let r=t.email.trim().toLowerCase(),s=new Date(e.now?.()??Date.now()).toISOString(),a=i?.account??null,l=hn(t);Y(e,{accounts:a!==null&&a!==r?[r,a]:[r],settings:!0},u=>{if(i){let m=a===null?u.settings().pending_browser_login??null:u.read(a,i.file);if(JSON.stringify(m)!==JSON.stringify(i.value))throw ct()}u.write(r,N,{...t,signed_in_at:s,sign_in_method:n,signed_in_with:o,saved_at:s}),u.remove(r,$e),u.remove(r,ie),a!==null&&a!==r&&i&&u.remove(a,i.file);let{pending_browser_login:c,...d}=u.settings();u.setSettings({...d,...a!==null&&c!==void 0&&{pending_browser_login:c},active_account:r,last_email:r,...l!==null&&{last_region:l}})}),e.account={email:r,explicit:!1}}function Io(e){let t=e.split(".");if(t.length!==3)return null;try{let n=JSON.parse(Buffer.from(t[1],"base64url").toString("utf8")),{iat:o,exp:i}=n??{};return typeof o!="number"||typeof i!="number"||!Number.isFinite(o)||!Number.isFinite(i)||o<0||i<=o||i*1e3>864e13?null:{issuedAt:o,expiresAt:i}}catch{return null}}function tr(e){let t=Io(e.refresh_token)??Io(e.access_token);return t?new Date(t.expiresAt*1e3).toISOString():null}function eu(e,t){let n=Io(e);return n!==null&&t/1e3>=n.issuedAt+(n.expiresAt-n.issuedAt)/2}async function tu(e,t){let n=t.email.toLowerCase(),o=Po(e,n);if(!o||o.api_url!==t.api_url||o.email!==t.email||o.refresh_token!==t.refresh_token)throw ct();let i=JSON.stringify(o),r=await wn(e,t.api_url,"POST","/api/auth/refresh",{payload:{refreshToken:t.refresh_token}}),{accessToken:s,refreshToken:a}=r.body;if(r.status!==200||typeof s!="string"||!s.trim()||typeof a!="string"||!a.trim())return null;let l={access_token:s,refresh_token:a,saved_at:new Date(e.now?.()??Date.now()).toISOString()};return Y(e,{accounts:[n]},u=>{if(JSON.stringify(u.read(n,N))!==i)throw ct();u.write(n,N,{...t,...l})}),l}async function Zi(e,t){let n=e.pendingSessionRefreshes??=new Map,o=JSON.stringify([t.api_url,t.email,t.refresh_token]),i=n.get(o);i||(i=tu(e,t),n.set(o,i));try{let r=await i;return r?(Object.assign(t,r),!0):!1}finally{n.get(o)===i&&n.delete(o)}}async function v(e,t,n,o,i,r){let s=()=>wn(e,t.api_url,n,o,{token:t.access_token,payload:i,headers:r}),a=null;if(eu(t.access_token,e.now?.()??Date.now()))try{a=await Zi(e,t)}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 Zi(e,t))throw new p("session_expired","your saved sign-in has expired or was signed out",g.Failure,Eo);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,Eo);return u}function No(e,t,n){return v(e,t,"GET",n)}function nr(e,t,n){let o=t.team&&typeof t.team.id=="string"&&typeof t.team.name=="string"?{id:t.team.id,name:t.team.name}:null;return{email:e,region:hn(t),workspace:o,api_url:t.api_url,active:e===n}}function nu(e){let t=[e.region?.toUpperCase()??null,e.workspace?.name??null].filter(Boolean).join(", ");return`${e.active?"*":" "} ${e.email}${t?` (${t})`:""}${e.active?" active":""}`}var or=(e,{print:t,runtime:n})=>{e.command("accounts").description("List the accounts signed in on this computer, and which one is active. Run one command as another of them with --as <email> or CLOSEDLOOP_ACCOUNT.").action(()=>{let o=Me(n),i=be(o.active_account),r=xo(n).map(a=>nr(a.email,a.session,i)),s={accounts:r,active_account:r.some(a=>a.active)?i:null};t.result(s,a=>{if(a.accounts.length===0)return"No account is signed in on this computer. Sign in with: closedloop login <email>";let l=a.accounts.map(nu);return a.active_account===null&&l.push("No account is active. Choose one with: closedloop use <email>"),l.join(`
|
|
29
|
+
`)})}),e.command("use").description("Make one of the accounts signed in on this computer the active one.").argument("<email>","The email of the account.").action(o=>{let i=be(o);if(i===null)throw new p("invalid_account","the account must be the email address of an account signed in on this computer",g.Usage,"Run `closedloop accounts` to see the accounts signed in on this computer.");let r=Y(n,{accounts:[i],settings:!0},a=>{let l=dt(n,a.read(i,N),i);return l!==null&&a.setSettings({...a.settings(),active_account:i}),l});if(r===null)throw new p("account_not_signed_in",`${i} is not signed in on this computer`,g.Failure,`Run \`${Ie(i,n.platform)}\` to sign it in, or \`closedloop accounts\` to see the accounts signed in here.`);let s={status:"active",...nr(i,r,i)};t.result(s,a=>`Now using ${a.email}${a.region?` (${a.region.toUpperCase()})`:""}.`)})};function R(e){return new Intl.DateTimeFormat(void 0,{dateStyle:"medium",timeStyle:"short"}).format(new Date(e))}function ve(e,t){return t?`\x1B]8;;${e}\x07${e}\x1B]8;;\x07`:e}function P(e){return new Intl.DateTimeFormat(void 0,{dateStyle:"medium"}).format(new Date(e))}function bn(e,t){try{return new Intl.DateTimeFormat(void 0,{dateStyle:"medium",timeZone:t}).format(new Date(e))}catch{return P(e)}}function xt(e){let[t,n]=e.split("-").map(Number);return new Intl.DateTimeFormat(void 0,{month:"long",year:"numeric",timeZone:"UTC"}).format(new Date(Date.UTC(t,n-1,1)))}function k(e){return new Intl.NumberFormat(void 0,{maximumFractionDigits:0}).format(e)}function mt(e){let t=Math.round(Math.max(0,e)/6e4);if(t<1)return"under a minute";if(t<60)return`${t} min`;let n=Math.floor(t/60);if(n<48){let o=t%60;return o>=5?`${n} h ${o} min`:`${n} h`}return`${Math.round(n/24)} days`}function q(e){return new Intl.DateTimeFormat(void 0,{dateStyle:"medium",timeZone:"UTC"}).format(new Date(e))}function C(e,t,n){let o=typeof e.body.code=="string"?e.body.code.toLowerCase():"",i=typeof e.body.error=="string"&&e.body.error?e.body.error:n;return new p(/^[a-z][a-z0-9_]*$/.test(o)?o:t,i,g.Failure)}var ou=["can_manage_integrations","can_manage_billing","can_manage_members","can_manage_team_settings","can_view_analytics"];async function W(e,t){let n=await v(e,t,"GET","/api/team-info"),o=n.body.teamInfo;if(n.status!==200)throw C(n,"api_error","Your workspace could not be loaded. Try again.");if(typeof o?.team_id!="string"||!o.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(ou.map(r=>[r,o.permissions?.[r]===!0]));return{id:o.team_id,name:typeof o.team_name=="string"?o.team_name:"your workspace",role:typeof o.role=="string"?o.role:null,permissions:i}}async function Ge(e,t){return(await W(e,t)).id}var iu={can_manage_integrations:"manage integrations",can_manage_billing:"manage billing"};async function pt(e,t,n,o){let i=await W(e,t);if(!i.permissions[n])throw new p(n==="can_manage_integrations"?"integration_manager_required":"billing_manager_required",`you cannot ${o}: it needs the right to ${iu[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 Pe=new Set(["queued","importing","stalled"]),ru=7200,ir=30,su=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,sr=e=>`${e[0].toUpperCase()}${e.slice(1)}`;function ar(e){if(!e.progress)return null;let{unit:t,done:n,total:o,failed:i}=e.progress;return`${k(n)} of ${k(o)} ${t}${e.percent!==null?` (${e.percent}%)`:""}${i?`, ${k(i)} failed`:""}`}function lr(e){return e.from&&e.to?`${q(e.from)} to ${q(e.to)}${e.window?` (${e.window})`:""}`:e.window}function ur(e,t){if(e.state==="stalled")return e.last_activity_at?`no progress since ${R(e.last_activity_at)}`:"no progress";if(e.state==="queued")return e.created_at?`waiting to start since ${R(e.created_at)}`:"waiting to start";if(e.state==="importing")return e.estimated_finish_at?`about ${mt(Date.parse(e.estimated_finish_at)-t)} left (around ${R(e.estimated_finish_at)})`:e.started_at?`started ${R(e.started_at)}; too early to estimate`:"too early to estimate";if(e.finished_at){let n=e.started_at??e.created_at,o=n?Date.parse(e.finished_at)-Date.parse(n):NaN,i=!Number.isFinite(o)||o<0?"":e.started_at?` (took ${mt(o)})`:` (${mt(o)} after it was requested)`;return`${e.state==="done"?"finished":"ended"} ${R(e.finished_at)}${i}`}return null}function au(e,t){let n=sr(e.state);return e.state==="failed"||e.state==="stalled"?t.red(n):e.state==="importing"||e.state==="queued"?t.orange(n):e.state==="done"?t.green(n):t.dim(n)}function $n(e,t,n){let o=n.enabled&&Pe.has(e.state)&&e.percent!==null?`${n.orange(n.bar(e.percent/100,12))} `:"";return[au(e,n),`${o}${[e.source,lr(e),ar(e),ur(e,t)].filter(Boolean).join(n.dim(" \xB7 "))}`].join(" ")}async function cr(e,t,n){let o=await v(e.runtime,t,"GET",`/api/cli/backfills?limit=${n}`);if(o.status!==200||!Array.isArray(o.body.backfills))throw C(o,"api_error","Backfills could not be loaded. Try again.");return o.body.backfills}async function lu(e,t){let n=t===void 0?20:Number(t);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 o=$(e.runtime),i=await cr(e,o,n),r=Date.now();e.print.result({backfills:i},s=>{if(s.backfills.length===0)return"No backfills in this workspace yet.";let a=s.backfills.map(u=>$n(u,r,e.print.style)),l=s.backfills.some(u=>Pe.has(u.state))?`
|
|
29
30
|
Progress covers importing; insights appear as the imported records are analysed.`:"";return`${a.join(`
|
|
30
|
-
`)}${l}`})}async function
|
|
31
|
-
`)}async function
|
|
32
|
-
${
|
|
33
|
-
`)}`,g.Failure);let
|
|
34
|
-
${
|
|
35
|
-
`)}`,g.Failure,"The fields a rule can name: closedloop fields list; one field's values: closedloop fields show <key>")}async function V(t,e,n,r){let i=$(t.runtime),o=await v(t.runtime,i,e,n,r);if(o.status===429&&typeof o.body.code!="string")throw new p("rate_limited",typeof o.body.error=="string"&&o.body.error?o.body.error:"too many requests; try again in a few minutes",g.Failure,"Wait a few minutes, then run it again.");return o}var B=t=>t.status===200||t.status===201;function de(t){return w(t)&&w(t.current_version)?t.current_version:{}}function Ae(t){return w(t)&&typeof t.version=="number"?t.version:null}async function yi(t,e){let n=new URLSearchParams;e.q&&n.set("q",e.q),e.entity&&n.set("entity",e.entity);let r=n.toString(),i=await V(t,"GET",`/api/fields${r?`?${r}`:""}`);if(!B(i)||!Array.isArray(i.body.fields))throw C(i,"api_error","The fields could not be loaded. Try again.");return zn(i.body.fields)}async function wi(t,e){let n=await V(t,"GET",`/api/fields/${encodeURIComponent(e)}`);if(n.status===404)throw new p("field_not_found",`there is no field ${e} in this workspace`,g.Failure,"Find its key: closedloop fields search <words>");if(!B(n)||!w(n.body.field))throw C(n,"api_error","The field could not be loaded. Try again.");return n.body.field}async function _i(t,e){let n=await V(t,"POST","/api/segments/preview",{rule:e}),r=yt(n,"rule");if(r)throw r;if(!B(n)||typeof n.body.count!="number")throw C(n,"api_error","The preview could not be made. Try again.");return Se(n.body)}async function bi(t,e,n,r){let i=await V(t,"POST","/api/segments",{name:e,rule:n,...r!==void 0&&{description:r}}),o=yt(i,"rule");if(o)throw o;if(i.status===409&&i.body.code==="NAME_TAKEN")throw new p("segment_exists",typeof i.body.error=="string"&&i.body.error?i.body.error:`a segment named "${e}" already exists`,g.Failure,"Pick another name, or see yours: closedloop segments list");if(!B(i)||!w(i.body.segment)||typeof i.body.segment.id!="string")throw C(i,"api_error","The segment could not be saved. Try again.");return i.body.segment}async function $i(t,e){try{let n=await V(t,"GET",`/api/segments/${encodeURIComponent(e)}`),r=B(n)?de(n.body.segment).rule:null;return w(r)?r:null}catch(n){if(n instanceof p)return null;throw n}}async function vi(t){let e=await V(t,"GET","/api/segments");if(!B(e)||!Array.isArray(e.body.segments))throw C(e,"api_error","The segments could not be loaded. Try again.");return zn(e.body.segments)}async function Xn(t,e,n){let r=await V(t,"POST","/api/metrics/preview",{segment:e,calc:n}),i=yt(r,"metric");if(i)throw i;if(!B(r)||!("value"in r.body))throw C(r,"api_error","The preview could not be made. Try again.");return Se(r.body)}async function Ci(t,e,n,r,i){let o=await V(t,"POST","/api/metrics",{name:e,segment:n,calc:r,...i!==void 0&&{description:i}}),s=yt(o,"metric");if(s)throw s;if(o.status===409&&o.body.code==="NAME_TAKEN")throw new p("metric_exists",typeof o.body.error=="string"&&o.body.error?o.body.error:`a metric named "${e}" already exists`,g.Failure,"Pick another name, or see yours: closedloop metrics list");if(!B(o)||!w(o.body.metric)||typeof o.body.metric.id!="string")throw C(o,"api_error","The metric could not be saved. Try again.");return o.body.metric}async function Qt(t){let e=await V(t,"GET","/api/metrics");if(!B(e)||!Array.isArray(e.body.metrics))throw C(e,"api_error","The metrics could not be loaded. Try again.");return zn(e.body.metrics)}async function ki(t,e,n){let r=await V(t,"DELETE",`/api/metrics/${encodeURIComponent(e)}`,n===void 0?{}:{reason:n}),i=typeof r.body.error=="string"&&r.body.error?r.body.error:null;if(r.status===404)throw new p("not_found",i??"There is no such metric.",g.Failure,"See them: closedloop metrics list");if(r.status===409){let s=r.body.code==="ARCHIVED";throw new p(s?"metric_deleted":"metric_in_use",i??(s?"this metric is already deleted":"this metric is in use"),g.Failure)}let o=yt(r,"metric");if(o)throw o;if(!B(r)||!w(r.body.metric)||!Array.isArray(r.body.removed))throw C(r,"api_error","The metric could not be deleted. Try again.");return Se(r.body)}async function Si(t,e,n){try{let r=await V(t,"GET",`/api/metrics/${encodeURIComponent(e)}?runs_limit=1`);if(!B(r)||!Array.isArray(r.body.versions))return null;let i=r.body.versions.find(o=>w(o)&&o.version===n);return w(i)&&w(i.calc)?i.calc:null}catch(r){if(r instanceof p)return null;throw r}}async function Ai(t,e){let n=await V(t,"GET",`/api/metrics/${encodeURIComponent(e)}?runs_limit=1000`);if(n.status===404){let r=typeof n.body.error=="string"&&n.body.error?n.body.error:"There is no such metric.";throw new p("not_found",r,g.Failure,"See them: closedloop metrics list")}if(!B(n)||!w(n.body.metric)||!Array.isArray(n.body.versions)||!Array.isArray(n.body.runs))throw C(n,"api_error","The metric could not be loaded. Try again.");return Se(n.body)}async function Qn(t,e,n,r){let i=new URLSearchParams({as_of:n});r!==void 0&&i.set("version",String(r));let o=await V(t,"GET",`/api/metrics/${encodeURIComponent(e)}/value?${i.toString()}`);if(o.status===404){let s=typeof o.body.error=="string"&&o.body.error?o.body.error:"There is no such metric.";throw new p("not_found",s,g.Failure,"See them: closedloop metrics list")}if(!B(o))throw C(o,"api_error","The value could not be loaded. Try again.");return Se(o.body)}var tl=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;function Oi(t,e){let n=$(t);return{api_url:new URL(n.api_url).origin,user_id:typeof n.user_id=="string"?n.user_id:null,email:n.email.toLowerCase(),team_id:e??null}}async function Oe(t,e,n){let{runtime:r}=t,i=Oi(r),o;try{o=te(r,e)}catch{return null}if(!o||typeof o!="object"||Array.isArray(o))return null;let s=o.owner;if(!s||s.api_url!==i.api_url||s.user_id!==i.user_id||s.email!==i.email)return null;let a=Array.isArray(o.ids)?o.ids[n-1]:null;return typeof a!="string"||!tl.test(a)?o:s.team_id!==(await wt(t)).id?null:o}function wt(t){return t.workspace??=F(t.runtime,$(t.runtime)),t.workspace}async function fe(t,e,n){let r=await wt(t),{runtime:i}=t;Ke(i,e,{...n,owner:Oi(i,r.id),at:new Date(i.now?.()??Date.now()).toISOString()})}var rl=1024*1024,Ri=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,Pe=(t,e,n)=>new p(t,e,g.Usage,n);function _t(t){let e=t.trim().replace(/^'/,"");return/^[{[]/.test(e)&&!e.includes('"')&&/[{,]\s*[A-Za-z_][\w.]*\s*:/.test(e)}function Zt(t,e=process.platform,n=t){let r=e==="win32"?`On Windows, PowerShell 5.1 and Command Prompt remove the double quotes from JSON typed in a command. Save the ${t} in a file and give its path: --${n} ${t}.json`:`Put the JSON in single quotes, --${n} '{...}', or save it in a file and give its path: --${n} ${t}.json`;return Pe(`invalid_${t}`,`the ${t} given is not valid JSON: the shell removed its double quotes`,r)}var il=[{option:"rule",kind:"rule"},{option:"segment",kind:"segment"},{option:"calc",kind:"calc"},{option:"file",kind:"structure",command:["products","save"]}];function ol(t,e,n){if(n===void 0)return!0;let r=t.slice(0,e).filter(i=>!i.startsWith("-"));return n.every((i,o)=>r[o]===i)}function Di(t){for(let e=0;e<t.length;e++)for(let{option:n,kind:r,command:i}of il){let o=t[e]===`--${n}`?t[e+1]:t[e].startsWith(`--${n}=`)?t[e].slice(n.length+3):void 0;if(o!==void 0&&_t(o)&&ol(t,e,i))return{option:n,kind:r}}return null}async function Te(t,e,n,r,i=rl,o=n){let s,a,l=e.trim(),u=l.startsWith("{")||l.startsWith("[");if(l==="-")s=await t.runtime.readStdin(i),a="standard input";else if(u)s=l,a=`the ${n} given`;else{if(!Ti(l)&&_t(l))throw Zt(n,t.runtime.platform,o);if(!Ti(l)||!Ei(l).isFile())throw Pe("file_not_found",`there is no file ${l}`,r);if(Ei(l).size>i)throw Pe("file_too_large",`${l} is larger than ${Math.round(i/(1024*1024))} MB`);s=nl(l,"utf8").replace(/^\uFEFF/,""),a=l}if(!s.trim())throw Pe(`invalid_${n}`,`${a} is empty`,r);let c;try{c=JSON.parse(s)}catch{throw u&&_t(s)?Zt(n,t.runtime.platform,o):Pe(`invalid_${n}`,`${a} is not valid JSON; check its syntax`,r)}if(!w(c))throw Pe(`invalid_${n}`,`${a} is not a JSON object`,r);return c}function tn(t,e,n){return fe(t,e,{ids:n})}async function ie(t,e,n,r,i){let o=e.trim();if(Ri.test(o))return o.toLowerCase();if(!/^\d{1,3}$/.test(o))return null;let s=await Oe(t,n,Number(o)),a=Array.isArray(s?.ids)?s.ids:[],l=Number(o),u=a[l-1];if(l>=1&&l<=a.length&&typeof u=="string"&&Ri.test(u))return u.toLowerCase();throw Pe("unknown_number",a.length?`the last ${r} list had ${a.length}, not ${l}`:`there is no recent ${r} list to pick a number from`,`List them first: ${i}`)}var Ii={customer:["customer","customers"],deal:["deal","deals"],person:["person","people"],insight:["insight","insights"],context:["context record","context records"],conversation:["conversation","conversations"],usage:["usage week","usage weeks"]},nn=(t,e)=>Object.prototype.hasOwnProperty.call(t,e);function U(t,e=2){return typeof t!="string"||!nn(Ii,t)?e===1?"member":"members":Ii[t][e===1?0:1]}function O(t){return typeof t!="number"||!Number.isFinite(t)?String(t):Number.isInteger(t)?k(t):new Intl.NumberFormat(void 0,{maximumFractionDigits:2}).format(t)}function N(t){return typeof t=="string"?JSON.stringify(t):Array.isArray(t)?t.map(N).join(", "):t==null?"empty":typeof t=="number"?O(t):typeof t=="object"?JSON.stringify(t):String(t)}function xe(t,e){switch(t){case"exists":return"has a value";case"is_empty":return"is empty";case"within_last":return`within the last ${N(e)} days`;case"within_period":return`within this ${typeof e=="string"?e:N(e)} so far`;case"between":return Array.isArray(e)&&e.length===2?`between ${N(e[0])} and ${N(e[1])}`:`between ${N(e)}`;case"not_in":return`not in ${N(e)}`;default:return`${String(t)} ${N(e)}`}}var Pi={">=":"at least",">":"more than","<=":"at most","<":"fewer than","=":"exactly"},xi={count_distinct:"distinct values of",sum:"sum of",avg:"average of",min:"smallest",max:"largest",median:"median of"};function ji(t){if(typeof t!="string")return String(t);let e=/^p(\d{1,2})$/.exec(t);return e?`${e[1]}th percentile of`:nn(xi,t)?xi[t]:t}function Fi(t){let e=w(t)&&Array.isArray(t.includes)?t.includes.filter(w):[];return new Map(e.filter(n=>typeof n.version_id=="string"&&typeof n.name=="string").map(n=>[n.version_id,n.name]))}function bt(t,e=new Map){if(typeof t=="string")return t;if(!w(t))return"";let n=i=>bt(i,e),r=(i,o)=>{let s=n(i),a=o==="and"?"or":"and";return w(i)&&Array.isArray(i[a])?`(${s})`:s};if(Array.isArray(t.and))return t.and.map(i=>r(i,"and")).join(" and ");if(Array.isArray(t.or))return t.or.map(i=>r(i,"or")).join(" or ");if("not"in t)return`not (${n(t.not)})`;if(typeof t.segment=="string")return`in segment ${JSON.stringify(e.get(t.segment)??t.segment)}`;if(typeof t.field=="string")return`${t.field} ${xe(t.op,t.value)}`;if(typeof t.has=="string"){let i=w(t.count)?t.count:null;return`has ${typeof t.min=="number"&&t.min!==1?`at least ${N(t.min)} ${U(t.has)}`:i&&typeof i.op=="string"&&nn(Pi,i.op)?`${Pi[i.op]} ${N(i.value)} ${U(t.has)}`:`${/^[aeiou]/.test(U(t.has,1))?"an":"a"} ${U(t.has,1)}`}${t.where?` where ${n(t.where)}`:""}`}if(w(t.subtotal)){let i=t.subtotal,o=i.fn==="count"?`count of ${U(i.of)}`:`${ji(i.fn)} ${String(i.field??U(i.of))}`,s=i.where?` over ${U(i.of)} where ${n(i.where)}`:"";return`${o}${s} ${xe(t.op,t.value)}`}if(typeof t.moved=="string"){let i=t.from!==void 0?` from ${N(t.from)}`:"";return`${t.moved} moved into ${N(t.into)}${i} ${Ni(t)}`}if(w(t.change)){let i=t.change,o=w(i.start)?` (start ${xe(i.start.op,i.start.value)})`:"";return`change of ${String(i.field)} ${Ni(i)} ${xe(t.op,t.value)}${o}`}if(t.usage==="event"){let i=t.within_last!==void 0?` in the last ${N(t.within_last)} days`:"";return`usage event ${N(t.event)} ${String(t.measure??"count")}${i} ${xe(t.op,t.value)}`}return JSON.stringify(t)}function Ni(t){return t.within_period!==void 0?xe("within_period",t.within_period):xe("within_last",t.within_last)}function Ui(t,e=new Map){return w(t)?t.where===void 0?`all ${U(t.of)}`:bt(t.where,e):"no rule"}function en(t){if(!w(t))return"no calculation";if(t.fn==="ratio")return`share: ${en(t.numerator)} / ${en(t.denominator)}`;let e=t.fn==="count"?"count":`${ji(t.fn)} ${String(t.field)}`;return t.where?`${e} where ${bt(t.where)}`:e}function $t(t){if(!w(t)||t.fn!=="ratio"||!w(t.numerator)||!w(t.denominator))return!1;let[e,n]=[t.numerator,t.denominator];return e.fn==="count"&&n.fn==="count"||e.fn==="sum"&&n.fn==="sum"&&e.field===n.field}function Ne(t,e,n,r){if(typeof e!="number"||!Number.isFinite(e))return null;if(w(t)&&t.fn==="ratio"){let i=typeof n=="number"&&typeof r=="number"?` (${O(n)} / ${O(r)})`:"";return $t(t)?`${(e*100).toFixed(1)}%${i}`:`${O(e)}${i}`}return O(e)}var Li={crm:"CRM",insights:"insights",usage:"product usage"},rn=t=>typeof t=="string"&&nn(Li,t)?Li[t]:String(t);function vt(t){let e=Array.isArray(t)?t.filter(n=>typeof n=="string"):[];return e.length?`the data is late (${e.map(rn).join(", ")}), so this number may be out of date`:null}function on(t){if(typeof t=="string")return t;if(!w(t))return JSON.stringify(t);if(typeof t.message=="string")return t.message;let e=JSON.stringify(t),r=(Array.isArray(t.suggestions)?t.suggestions:[]).map(i=>w(i)?`${N(i.value)}${typeof i.count=="number"?` (${k(i.count)})`:""}`:N(i));return r.length?`${e} Closest: ${r.join(", ")}.`:e}function Wi(t){return Array.isArray(t)?t.filter(w).filter(e=>typeof e.field=="string"&&typeof e.instead_of=="string"):[]}var Zn=t=>typeof t=="number"?k(t):"no number";function sn(t,e){let n=Wi(t);return n.length?`If the request did not say which field it means, ask the user to choose before saving: ${n.map(i=>`${String(i.instead_of)} gives ${Zn(e)}, ${String(i.field)} gives ${Zn(i.value)}${typeof i.reason=="string"&&i.reason?` (${i.reason})`:""}`).join("; ")}.`:null}function an(t,e){return Wi(e).map(n=>t.dim(`With ${String(n.field)} instead of ${String(n.instead_of)}: ${n.value===null||n.value===void 0?`no number${typeof n.reason=="string"&&n.reason?` (${n.reason})`:""}`:Zn(n.value)}. They are often confused; say which one you mean.`))}var tr="Insights are filed against the products, areas and features you confirm here.",Gi="An area is a part of a product; a feature is a part of the product that exists, never planned work.",ye="closedloop products confirm",un="closedloop products save --file <structure.json>",sl=10*1024*1024,ln="Start from the current structure: closedloop products --json > structure.json";function al(t){let e=t.structure;if(!e||!Array.isArray(e.products)||!Array.isArray(e.areas)||!Array.isArray(e.features))throw new p("api_error","ClosedLoop AI sent an unexpected reply",g.Failure);let n=Array.isArray(t.suggested_products)?t.suggested_products:[];return{...t,structure:e,suggested_products:n,can_change:t.can_change===!0}}async function Ze(t,e){let n=await v(t,e,"GET","/api/product-structure");if(n.status!==200)throw C(n,"api_error","The product structure could not be loaded. Try again.");return al(n.body)}var qi=t=>typeof t=="number"?` \xB7 ${k(t)} insight${t===1?"":"s"}`:"",er=(t,e)=>(t??[]).map(n=>`${e}Feature: ${n.name}${qi(n.insight_count)}`),Mi=(t,e)=>(t??[]).flatMap(n=>[`${e}Area: ${n.name}${qi(n.insight_count)}`,...er(n.features,`${e} `)]);function ll(t){let{structure:e,suggested_products:n}=t,r=[tr];if(e.products.length+e.areas.length+e.features.length===0&&n.length===0)return r.push("",`No products yet. Suggestions appear once your company research is done; see: closedloop company. Or add your own: ${un}`),r.join(`
|
|
36
|
-
`);for(let
|
|
37
|
-
`)}async function
|
|
38
|
-
`);return new p("invalid_structure",`nothing was saved; fix ${
|
|
39
|
-
${
|
|
40
|
-
`),d={...
|
|
41
|
-
${c}`);return}let m=
|
|
42
|
-
${c}`),
|
|
31
|
+
`)}${l}`})}async function rr(e,t,n){if(su.test(n)){let r=await v(e.runtime,t,"GET",`/api/cli/backfills/${n.toLowerCase()}`);if(r.status===404)throw new p("backfill_not_found","there is no backfill with that id in your workspace",g.Failure);if(r.status!==200||typeof r.body.id!="string")throw C(r,"api_error","The backfill could not be loaded. Try again.");return r.body}let o=n.trim().toLowerCase(),i=(await cr(e,t,100)).find(r=>r.slug===o||r.source.toLowerCase()===o);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 uu(e,t){let n=[`${e.source} backfill: ${e.state}`],o=lr(e);o&&n.push(`Covers: ${o}`);let i=ar(e);i&&n.push(`Progress: ${i}`),e.created_at&&n.push(`Requested: ${R(e.created_at)}`),e.started_at&&n.push(`Started: ${R(e.started_at)}`),e.last_activity_at&&Pe.has(e.state)&&n.push(`Last activity: ${R(e.last_activity_at)}`);let r=ur(e,t);return r&&n.push(sr(r)+"."),Pe.has(e.state)&&n.push("This covers importing; insights appear as the imported records are analysed."),n.push(`id ${e.id}`),n.join(`
|
|
32
|
+
`)}async function cu(e,t,n){let o=$(e.runtime),i=await rr(e,o,t);if(n&&Pe.has(i.state)){let r="",s=e.print.spinner("Checking every 30 seconds\u2026");try{for(let a=0;a<ru&&Pe.has(i.state);a+=ir){let l=$n(i,Date.now(),e.print.style);l!==r&&(s.stop(),e.print.note(l)),r=l,s.update("Checking every 30 seconds\u2026"),await e.runtime.sleep(ir*1e3),i=await rr(e,o,i.id)}}finally{s.stop()}}e.print.result(i,r=>uu(r,Date.now()))}var dr=(e,t)=>{e.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 o=>lu(t,o.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(o,i)=>cu(t,o,i.watch===!0))};import{existsSync as Cr,readFileSync as mu,statSync as kr}from"node:fs";var w=e=>e!==null&&typeof e=="object"&&!Array.isArray(e),Lo=e=>Array.isArray(e)?e.filter(w):[];function xe(e){let{success:t,...n}=e;return n}function Nt(e,t){let n=Array.isArray(e.body.problems)?e.body.problems:Array.isArray(e.body.errors)?e.body.errors:null;if(e.status!==400||!n||!n.length)return null;let o=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 ${o.length===1?"this":`these ${o.length}`}:
|
|
33
|
+
${o.join(`
|
|
34
|
+
`)}`,g.Failure);let r=`the ${t} was not accepted; fix ${o.length===1?"this":`these ${o.length}`}:`;return new p(t==="rule"?"invalid_rule":"invalid_metric",`${r}
|
|
35
|
+
${o.join(`
|
|
36
|
+
`)}`,g.Failure,"The fields a rule can name: closedloop fields list; one field's values: closedloop fields show <key>")}async function K(e,t,n,o){let i=$(e.runtime),r=await v(e.runtime,i,t,n,o);if(r.status===429&&typeof r.body.code!="string")throw new p("rate_limited",typeof r.body.error=="string"&&r.body.error?r.body.error:"too many requests; try again in a few minutes",g.Failure,"Wait a few minutes, then run it again.");return r}var z=e=>e.status===200||e.status===201;function he(e){return w(e)&&w(e.current_version)?e.current_version:{}}function Ne(e){return w(e)&&typeof e.version=="number"?e.version:null}async function mr(e,t){let n=new URLSearchParams;t.q&&n.set("q",t.q),t.entity&&n.set("entity",t.entity);let o=n.toString(),i=await K(e,"GET",`/api/fields${o?`?${o}`:""}`);if(!z(i)||!Array.isArray(i.body.fields))throw C(i,"api_error","The fields could not be loaded. Try again.");return Lo(i.body.fields)}async function pr(e,t){let n=await K(e,"GET",`/api/fields/${encodeURIComponent(t)}`);if(n.status===404)throw new p("field_not_found",`there is no field ${t} in this workspace`,g.Failure,"Find its key: closedloop fields search <words>");if(!z(n)||!w(n.body.field))throw C(n,"api_error","The field could not be loaded. Try again.");return n.body.field}async function gr(e,t){let n=await K(e,"POST","/api/segments/preview",{rule:t}),o=Nt(n,"rule");if(o)throw o;if(!z(n)||typeof n.body.count!="number")throw C(n,"api_error","The preview could not be made. Try again.");return xe(n.body)}async function hr(e,t,n,o){let i=await K(e,"POST","/api/segments",{name:t,rule:n,...o!==void 0&&{description:o}}),r=Nt(i,"rule");if(r)throw r;if(i.status===409&&i.body.code==="NAME_TAKEN")throw new p("segment_exists",typeof i.body.error=="string"&&i.body.error?i.body.error:`a segment named "${t}" already exists`,g.Failure,"Pick another name, or see yours: closedloop segments list");if(!z(i)||!w(i.body.segment)||typeof i.body.segment.id!="string")throw C(i,"api_error","The segment could not be saved. Try again.");return i.body.segment}async function fr(e,t){try{let n=await K(e,"GET",`/api/segments/${encodeURIComponent(t)}`),o=z(n)?he(n.body.segment).rule:null;return w(o)?o:null}catch(n){if(n instanceof p)return null;throw n}}async function yr(e){let t=await K(e,"GET","/api/segments");if(!z(t)||!Array.isArray(t.body.segments))throw C(t,"api_error","The segments could not be loaded. Try again.");return Lo(t.body.segments)}async function Do(e,t,n){let o=await K(e,"POST","/api/metrics/preview",{segment:t,calc:n}),i=Nt(o,"metric");if(i)throw i;if(!z(o)||!("value"in o.body))throw C(o,"api_error","The preview could not be made. Try again.");return xe(o.body)}async function wr(e,t,n,o,i){let r=await K(e,"POST","/api/metrics",{name:t,segment:n,calc:o,...i!==void 0&&{description:i}}),s=Nt(r,"metric");if(s)throw s;if(r.status===409&&r.body.code==="NAME_TAKEN")throw new p("metric_exists",typeof r.body.error=="string"&&r.body.error?r.body.error:`a metric named "${t}" already exists`,g.Failure,"Pick another name, or see yours: closedloop metrics list");if(!z(r)||!w(r.body.metric)||typeof r.body.metric.id!="string")throw C(r,"api_error","The metric could not be saved. Try again.");return r.body.metric}async function vn(e){let t=await K(e,"GET","/api/metrics");if(!z(t)||!Array.isArray(t.body.metrics))throw C(t,"api_error","The metrics could not be loaded. Try again.");return Lo(t.body.metrics)}async function _r(e,t,n){let o=await K(e,"DELETE",`/api/metrics/${encodeURIComponent(t)}`,n===void 0?{}:{reason:n}),i=typeof o.body.error=="string"&&o.body.error?o.body.error:null;if(o.status===404)throw new p("not_found",i??"There is no such metric.",g.Failure,"See them: closedloop metrics list");if(o.status===409){let s=o.body.code==="ARCHIVED";throw new p(s?"metric_deleted":"metric_in_use",i??(s?"this metric is already deleted":"this metric is in use"),g.Failure)}let r=Nt(o,"metric");if(r)throw r;if(!z(o)||!w(o.body.metric)||!Array.isArray(o.body.removed))throw C(o,"api_error","The metric could not be deleted. Try again.");return xe(o.body)}async function br(e,t,n){try{let o=await K(e,"GET",`/api/metrics/${encodeURIComponent(t)}?runs_limit=1`);if(!z(o)||!Array.isArray(o.body.versions))return null;let i=o.body.versions.find(r=>w(r)&&r.version===n);return w(i)&&w(i.calc)?i.calc:null}catch(o){if(o instanceof p)return null;throw o}}async function $r(e,t){let n=await K(e,"GET",`/api/metrics/${encodeURIComponent(t)}?runs_limit=1000`);if(n.status===404){let o=typeof n.body.error=="string"&&n.body.error?n.body.error:"There is no such metric.";throw new p("not_found",o,g.Failure,"See them: closedloop metrics list")}if(!z(n)||!w(n.body.metric)||!Array.isArray(n.body.versions)||!Array.isArray(n.body.runs))throw C(n,"api_error","The metric could not be loaded. Try again.");return xe(n.body)}async function jo(e,t,n,o){let i=new URLSearchParams({as_of:n});o!==void 0&&i.set("version",String(o));let r=await K(e,"GET",`/api/metrics/${encodeURIComponent(t)}/value?${i.toString()}`);if(r.status===404){let s=typeof r.body.error=="string"&&r.body.error?r.body.error:"There is no such metric.";throw new p("not_found",s,g.Failure,"See them: closedloop metrics list")}if(!z(r))throw C(r,"api_error","The value could not be loaded. Try again.");return xe(r.body)}var du=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;function vr(e,t){let n=$(e);return{api_url:new URL(n.api_url).origin,user_id:typeof n.user_id=="string"?n.user_id:null,email:n.email.toLowerCase(),team_id:t??null}}async function Le(e,t,n){let{runtime:o}=e,i=vr(o),r;try{r=pn(o,t)}catch{return null}if(!r||typeof r!="object"||Array.isArray(r))return null;let s=r.owner;if(!s||s.api_url!==i.api_url||s.user_id!==i.user_id||s.email!==i.email)return null;let a=Array.isArray(r.ids)?r.ids[n-1]:null;return typeof a!="string"||!du.test(a)?r:s.team_id!==(await Lt(e)).id?null:r}function Lt(e){return e.workspace??=W(e.runtime,$(e.runtime)),e.workspace}async function Ce(e,t,n){let o=await Lt(e),{runtime:i}=e;gn(i,t,{...n,owner:vr(i,o.id),at:new Date(i.now?.()??Date.now()).toISOString()})}var pu=1024*1024,Sr=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,qe=(e,t,n)=>new p(e,t,g.Usage,n);function Dt(e){let t=e.trim().replace(/^'/,"");return/^[{[]/.test(t)&&!t.includes('"')&&/[{,]\s*[A-Za-z_][\w.]*\s*:/.test(t)}function Cn(e,t=process.platform,n=e){let o=t==="win32"?`On Windows, PowerShell 5.1 and Command Prompt remove the double quotes from JSON typed in a command. Save the ${e} in a file and give its path: --${n} ${e}.json`:`Put the JSON in single quotes, --${n} '{...}', or save it in a file and give its path: --${n} ${e}.json`;return qe(`invalid_${e}`,`the ${e} given is not valid JSON: the shell removed its double quotes`,o)}var gu=[{option:"rule",kind:"rule"},{option:"segment",kind:"segment"},{option:"calc",kind:"calc"},{option:"file",kind:"structure",command:["products","save"]}];function hu(e,t,n){if(n===void 0)return!0;let o=e.slice(0,t).filter(i=>!i.startsWith("-"));return n.every((i,r)=>o[r]===i)}function Ir(e){for(let t=0;t<e.length;t++)for(let{option:n,kind:o,command:i}of gu){let r=e[t]===`--${n}`?e[t+1]:e[t].startsWith(`--${n}=`)?e[t].slice(n.length+3):void 0;if(r!==void 0&&Dt(r)&&hu(e,t,i))return{option:n,kind:o}}return null}async function De(e,t,n,o,i=pu,r=n){let s,a,l=t.trim(),u=l.startsWith("{")||l.startsWith("[");if(l==="-")s=await e.runtime.readStdin(i),a="standard input";else if(u)s=l,a=`the ${n} given`;else{if(!Cr(l)&&Dt(l))throw Cn(n,e.runtime.platform,r);if(!Cr(l)||!kr(l).isFile())throw qe("file_not_found",`there is no file ${l}`,o);if(kr(l).size>i)throw qe("file_too_large",`${l} is larger than ${Math.round(i/(1024*1024))} MB`);s=mu(l,"utf8").replace(/^\uFEFF/,""),a=l}if(!s.trim())throw qe(`invalid_${n}`,`${a} is empty`,o);let c;try{c=JSON.parse(s)}catch{throw u&&Dt(s)?Cn(n,e.runtime.platform,r):qe(`invalid_${n}`,`${a} is not valid JSON; check its syntax`,o)}if(!w(c))throw qe(`invalid_${n}`,`${a} is not a JSON object`,o);return c}function Sn(e,t,n){return Ce(e,t,{ids:n})}async function se(e,t,n,o,i){let r=t.trim();if(Sr.test(r))return r.toLowerCase();if(!/^\d{1,3}$/.test(r))return null;let s=await Le(e,n,Number(r)),a=Array.isArray(s?.ids)?s.ids:[],l=Number(r),u=a[l-1];if(l>=1&&l<=a.length&&typeof u=="string"&&Sr.test(u))return u.toLowerCase();throw qe("unknown_number",a.length?`the last ${o} list had ${a.length}, not ${l}`:`there is no recent ${o} list to pick a number from`,`List them first: ${i}`)}var Ar={customer:["customer","customers"],deal:["deal","deals"],person:["person","people"],insight:["insight","insights"],context:["context record","context records"],conversation:["conversation","conversations"],usage:["usage week","usage weeks"]},An=(e,t)=>Object.prototype.hasOwnProperty.call(e,t);function M(e,t=2){return typeof e!="string"||!An(Ar,e)?t===1?"member":"members":Ar[e][t===1?0:1]}function O(e){return typeof e!="number"||!Number.isFinite(e)?String(e):Number.isInteger(e)?k(e):new Intl.NumberFormat(void 0,{maximumFractionDigits:2}).format(e)}function L(e){return typeof e=="string"?JSON.stringify(e):Array.isArray(e)?e.map(L).join(", "):e==null?"empty":typeof e=="number"?O(e):typeof e=="object"?JSON.stringify(e):String(e)}function Je(e,t){switch(e){case"exists":return"has a value";case"is_empty":return"is empty";case"within_last":return`within the last ${L(t)} days`;case"within_period":return`within this ${typeof t=="string"?t:L(t)} so far`;case"between":return Array.isArray(t)&&t.length===2?`between ${L(t[0])} and ${L(t[1])}`:`between ${L(t)}`;case"not_in":return`not in ${L(t)}`;default:return`${String(e)} ${L(t)}`}}var Or={">=":"at least",">":"more than","<=":"at most","<":"fewer than","=":"exactly"},Tr={count_distinct:"distinct values of",sum:"sum of",avg:"average of",min:"smallest",max:"largest",median:"median of"};function Pr(e){if(typeof e!="string")return String(e);let t=/^p(\d{1,2})$/.exec(e);return t?`${t[1]}th percentile of`:An(Tr,e)?Tr[e]:e}function xr(e){let t=w(e)&&Array.isArray(e.includes)?e.includes.filter(w):[];return new Map(t.filter(n=>typeof n.version_id=="string"&&typeof n.name=="string").map(n=>[n.version_id,n.name]))}function jt(e,t=new Map){if(typeof e=="string")return e;if(!w(e))return"";let n=i=>jt(i,t),o=(i,r)=>{let s=n(i),a=r==="and"?"or":"and";return w(i)&&Array.isArray(i[a])?`(${s})`:s};if(Array.isArray(e.and))return e.and.map(i=>o(i,"and")).join(" and ");if(Array.isArray(e.or))return e.or.map(i=>o(i,"or")).join(" or ");if("not"in e)return`not (${n(e.not)})`;if(typeof e.segment=="string")return`in segment ${JSON.stringify(t.get(e.segment)??e.segment)}`;if(typeof e.field=="string")return`${e.field} ${Je(e.op,e.value)}`;if(typeof e.has=="string"){let i=w(e.count)?e.count:null;return`has ${typeof e.min=="number"&&e.min!==1?`at least ${L(e.min)} ${M(e.has)}`:i&&typeof i.op=="string"&&An(Or,i.op)?`${Or[i.op]} ${L(i.value)} ${M(e.has)}`:`${/^[aeiou]/.test(M(e.has,1))?"an":"a"} ${M(e.has,1)}`}${e.where?` where ${n(e.where)}`:""}`}if(w(e.subtotal)){let i=e.subtotal,r=i.fn==="count"?`count of ${M(i.of)}`:`${Pr(i.fn)} ${String(i.field??M(i.of))}`,s=i.where?` over ${M(i.of)} where ${n(i.where)}`:"";return`${r}${s} ${Je(e.op,e.value)}`}if(typeof e.moved=="string"){let i=e.from!==void 0?` from ${L(e.from)}`:"";return`${e.moved} moved into ${L(e.into)}${i} ${Rr(e)}`}if(w(e.change)){let i=e.change,r=w(i.start)?` (start ${Je(i.start.op,i.start.value)})`:"";return`change of ${String(i.field)} ${Rr(i)} ${Je(e.op,e.value)}${r}`}if(e.usage==="event"){let i=e.within_last!==void 0?` in the last ${L(e.within_last)} days`:"";return`usage event ${L(e.event)} ${String(e.measure??"count")}${i} ${Je(e.op,e.value)}`}return JSON.stringify(e)}function Rr(e){return e.within_period!==void 0?Je("within_period",e.within_period):Je("within_last",e.within_last)}function Nr(e,t=new Map){return w(e)?e.where===void 0?`all ${M(e.of)}`:jt(e.where,t):"no rule"}function kn(e){if(!w(e))return"no calculation";if(e.fn==="ratio")return`share: ${kn(e.numerator)} / ${kn(e.denominator)}`;let t=e.fn==="count"?"count":`${Pr(e.fn)} ${String(e.field)}`;return e.where?`${t} where ${jt(e.where)}`:t}function Ft(e){if(!w(e)||e.fn!=="ratio"||!w(e.numerator)||!w(e.denominator))return!1;let[t,n]=[e.numerator,e.denominator];return t.fn==="count"&&n.fn==="count"||t.fn==="sum"&&n.fn==="sum"&&t.field===n.field}function Ve(e,t,n,o){if(typeof t!="number"||!Number.isFinite(t))return null;if(w(e)&&e.fn==="ratio"){let i=typeof n=="number"&&typeof o=="number"?` (${O(n)} / ${O(o)})`:"";return Ft(e)?`${(t*100).toFixed(1)}%${i}`:`${O(t)}${i}`}return O(t)}var Er={crm:"CRM",insights:"insights",usage:"product usage"},On=e=>typeof e=="string"&&An(Er,e)?Er[e]:String(e);function Ut(e){let t=Array.isArray(e)?e.filter(n=>typeof n=="string"):[];return t.length?`the data is late (${t.map(On).join(", ")}), so this number may be out of date`:null}function Tn(e){if(typeof e=="string")return e;if(!w(e))return JSON.stringify(e);if(typeof e.message=="string")return e.message;let t=JSON.stringify(e),o=(Array.isArray(e.suggestions)?e.suggestions:[]).map(i=>w(i)?`${L(i.value)}${typeof i.count=="number"?` (${k(i.count)})`:""}`:L(i));return o.length?`${t} Closest: ${o.join(", ")}.`:t}function Lr(e){return Array.isArray(e)?e.filter(w).filter(t=>typeof t.field=="string"&&typeof t.instead_of=="string"):[]}var Fo=e=>typeof e=="number"?k(e):"no number";function Rn(e,t){let n=Lr(e);return n.length?`If the request did not say which field it means, ask the user to choose before saving: ${n.map(i=>`${String(i.instead_of)} gives ${Fo(t)}, ${String(i.field)} gives ${Fo(i.value)}${typeof i.reason=="string"&&i.reason?` (${i.reason})`:""}`).join("; ")}.`:null}function En(e,t){return Lr(t).map(n=>e.dim(`With ${String(n.field)} instead of ${String(n.instead_of)}: ${n.value===null||n.value===void 0?`no number${typeof n.reason=="string"&&n.reason?` (${n.reason})`:""}`:Fo(n.value)}. They are often confused; say which one you mean.`))}var Wo="Insights are filed against the products, areas and features you confirm here.",Fr="An area is a part of a product; a feature is a part of the product that exists, never planned work.",ke="closedloop products confirm",Pn="closedloop products save --file <structure.json>",fu=10*1024*1024,In="Start from the current structure: closedloop products --json > structure.json";function yu(e){let t=e.structure;if(!t||!Array.isArray(t.products)||!Array.isArray(t.areas)||!Array.isArray(t.features))throw new p("api_error","ClosedLoop AI sent an unexpected reply",g.Failure);let n=Array.isArray(e.suggested_products)?e.suggested_products:[];return{...e,structure:t,suggested_products:n,can_change:e.can_change===!0}}async function gt(e,t){let n=await v(e,t,"GET","/api/product-structure");if(n.status!==200)throw C(n,"api_error","The product structure could not be loaded. Try again.");return yu(n.body)}var Ur=e=>typeof e=="number"?` \xB7 ${k(e)} insight${e===1?"":"s"}`:"",Uo=(e,t)=>(e??[]).map(n=>`${t}Feature: ${n.name}${Ur(n.insight_count)}`),Dr=(e,t)=>(e??[]).flatMap(n=>[`${t}Area: ${n.name}${Ur(n.insight_count)}`,...Uo(n.features,`${t} `)]);function wu(e){let{structure:t,suggested_products:n}=e,o=[Wo];if(t.products.length+t.areas.length+t.features.length===0&&n.length===0)return o.push("",`No products yet. Suggestions appear once your company research is done; see: closedloop company. Or add your own: ${Pn}`),o.join(`
|
|
37
|
+
`);for(let r of t.products)o.push("",`Product: ${r.name}`,...Dr(r.areas," "),...Uo(r.features," "));return t.areas.length+t.features.length>0&&o.push("","Under no product",...Dr(t.areas," "),...Uo(t.features," ")),t.products.length===0&&o.push("","No products confirmed yet."),n.length>0&&o.push("",`Suggested by your company research, not confirmed yet: ${n.map(r=>r.name).join(", ")}. Confirm them with: ${ke}`),e.can_change||o.push("","Only an owner or admin of your workspace can change it."),o.join(`
|
|
38
|
+
`)}async function _u(e){let t=$(e.runtime);e.print.result(await gt(e.runtime,t),wu)}var bu={product_area:["area","product"],area_feature:["feature","area"],product_feature:["feature","product"]};function $u(e,t){if(e.action==="link"||e.action==="unlink"){let[o,i]=bu[e.level]??["item","item"];return e.action==="link"?`Put ${o} "${e.child}" under ${i} "${e.parent}"`:`Take ${o} "${e.child}" out of ${i} "${e.parent}"`}let n=`${e.level} "${e.name}"`;switch(e.action){case"create":return`Add ${n}`;case"restore":return e.id&&t.has(e.id)?`Confirm the suggested ${n}`:`Restore ${n}`;case"update":return`Change ${n}${e.fields?.length?` (${e.fields.map(o=>o.replace(/_/g," ")).join(", ")})`:""}`;case"archive":return`Archive ${n}`;default:return`${e.action} ${n}`}}function Wr(e){if(e.length===0)return new p("invalid_structure","the structure has problems; nothing was saved",g.Failure,In);let t=e.map(n=>`${n.path}: ${n.message}`).join(`
|
|
39
|
+
`);return new p("invalid_structure",`nothing was saved; fix ${e.length===1?"this":`these ${e.length}`}:
|
|
40
|
+
${t}`,g.Failure,In)}async function vu(e,t,n){let o=await v(e,t,"POST","/api/product-structure/check",{structure:n});if(o.status!==200||typeof o.body.valid!="boolean")throw C(o,"api_error","The product structure could not be checked. Try again.");let i=o.body;if(!i.valid||typeof i.check_token!="string")throw Wr(Array.isArray(i.problems)?i.problems:[]);return{...i,changes:Array.isArray(i.changes)?i.changes:[],warnings:Array.isArray(i.warnings)?i.warnings:[]}}var Cu={structure_changed:"Run the same command again: it checks the structure as it is now.",recalculation_running:"Run the same command again in a few minutes.",owner_or_admin_required:"Ask an owner or admin of your workspace to save it."};async function ku(e,t,n,o){let i=await v(e,t,"POST","/api/product-structure/save",{structure:n,check_token:o});if(i.status===400&&Array.isArray(i.body.problems))throw Wr(i.body.problems);if(i.status!==200||typeof i.body.status!="string"){let s=C(i,"api_error","The product structure could not be saved. Try again."),a=Cu[s.code];throw a?new p(s.code,s.message,s.exitCode,a):s}let r=i.body;return{status:r.status,changes:Array.isArray(r.changes)?r.changes:[],warnings:Array.isArray(r.warnings)?r.warnings:[],recalculation:typeof r.recalculation=="string"?r.recalculation:null}}var Su=e=>/^(y|yes)$/i.test(e.trim());async function Mr(e,t,n,o){let{runtime:i,interactive:r,print:s}=e,a=await vu(i,t,n),l=typeof a.estimated_credits=="number"?a.estimated_credits:null,u=(y,_)=>[...y.map(S=>` ${$u(S,o.suggestedIds)}`),..._.map(S=>` Note: ${S.message}`)],c=[...u(a.changes,a.warnings),...l?[`After a save, the next nightly recalculation re-places needs and insights, estimated at ${k(l)} credits.`]:[]].join(`
|
|
41
|
+
`),d={...o.extra,changes:a.changes,warnings:a.warnings,estimated_credits:l};if(a.changes.length===0){s.result({...d,status:"unchanged",recalculation:null,instructions:null},()=>"Nothing to change: the product structure is already like this. Nothing was saved.");return}if(o.checkOnly){s.result({...d,status:"checked",recalculation:null,instructions:null},()=>`Checked, nothing saved. Saving it would:
|
|
42
|
+
${c}`);return}let m=o.yes,h=!1;if(!m&&r&&(s.note(`Saving it would:
|
|
43
|
+
${c}`),h=!0,m=Su(await i.prompt('Save these changes? Type "yes" to save: '))),!m){let y=r?"Nothing was saved.":`Nothing was saved: only the person who uses this workspace can agree to its product structure. Has your user agreed to the changes listed here? Insights are filed against the products, areas and features confirmed here. If they have not, show them the changes and ask first. Once they say yes, run: ${o.again} --yes`;s.result({...d,status:"not_saved",recalculation:null,instructions:y},_=>h?_.instructions:`Saving it would:
|
|
43
44
|
${c}
|
|
44
|
-
${
|
|
45
|
-
`)})}var
|
|
46
|
-
`)}async function
|
|
47
|
-
The research text appears here once it is done.`;if(typeof n.text!="string"||!n.text.trim())return`${
|
|
48
|
-
ClosedLoop AI did not send the research text. Try again later.`;let
|
|
49
|
-
|
|
50
|
-
${n.text.trim()}`}async function
|
|
51
|
-
`)})}var
|
|
52
|
-
`)}async function
|
|
45
|
+
${_.instructions}`);return}let f=await ku(i,t,n,a.check_token);s.result({...d,status:f.status,changes:f.changes,warnings:f.warnings,recalculation:f.recalculation,instructions:null},y=>{if(y.status!=="saved")return"Nothing changed, so nothing was saved.";let _=h?["Saved the product structure."]:["Saved the product structure:",...u(y.changes,y.warnings)];return y.recalculation&&_.push(y.recalculation),_.join(`
|
|
46
|
+
`)})}var Hr=e=>/^[\w.@-]+$/.test(e)?e:`"${e.replace(/(["\\$`])/g,"\\$1")}"`,jr=e=>e.trim().toLowerCase();async function Au(e,t,n){let o=$(e.runtime),i=await gt(e.runtime,o),r=i.suggested_products;if(r.length===0){let u=i.structure.products.length===0?`Nothing is suggested yet. Suggestions appear once your company research is done; see: closedloop company. Or add your own: ${Pn}`:"Nothing is suggested right now. See your products with: closedloop products";e.print.result({status:"nothing_suggested",confirmed:[],changes:[],instructions:null},()=>u);return}let s=[];for(let u of t){let c=r.find(d=>jr(d.name)===jr(u)||d.id===u.trim());if(!c)throw new p("unknown_product",`"${u}" is not a suggested product; the suggested ones are: ${r.map(d=>d.name).join(", ")}`,g.Usage,`Confirm them all with: ${ke}`);s.includes(c)||s.push(c)}let a=t.length===0?r:s,l={...i.structure,products:[...i.structure.products,...a.map(({id:u,name:c,description:d})=>({id:u,name:c,description:d}))]};await Mr(e,o,l,{yes:n,checkOnly:!1,again:t.length===0?ke:`${ke} ${t.map(Hr).join(" ")}`,suggestedIds:new Set(a.map(u=>u.id)),extra:{confirmed:a.map(u=>u.name)}})}async function Ou(e,t){if(t.file===void 0||!t.file.trim())throw new p("missing_file","give the structure with --file <path>, or --file - to read standard input",g.Usage,In);let n=await De(e,t.file,"structure",In,fu,"file"),o=w(n.structure)?n.structure:n,i=Array.isArray(n.suggested_products)?n.suggested_products.filter(w):[],r=$(e.runtime);await Mr(e,r,o,{yes:t.yes===!0,checkOnly:t.check===!0,again:`closedloop products save --file ${Hr(t.file.trim())}`,suggestedIds:new Set(i.map(s=>String(s.id))),extra:{}})}var Gr=(e,t)=>{let n=e.command("products").description("Your product structure: your products, their areas (the parts of a product) and features (parts of the product that exist, never planned work). Insights are filed against them. Shows it; confirm the products your company research suggested, or save your own. See also: closedloop glossary").action(async()=>_u(t));n.command("confirm").argument("[name...]","The suggested products to confirm, by name; all of them when left out.").description("Confirm the products your company research suggested, so insights are filed against them. Shows the changes and asks before saving (an owner or admin).").option("--yes","Save without asking. An agent adds it only after its user agreed to the changes.").action(async(o,i)=>Au(t,o,i.yes===!0)),n.command("save").description("Save a product structure from a JSON file: the output of `closedloop products --json`, edited, or only its structure: { products: [{ id?, name, description?, areas: [{ id?, name, description?, owner_email?, features: [{ id?, name, description? }] }], features: [...] }], areas: [...], features: [...] }. Products, areas and features left out are archived, never deleted; a field or list left out of an item keeps what is saved, and an empty list removes its items. Shows the changes and asks before saving (an owner or admin).").option("--file <path>","The JSON file, or - to read standard input.").option("--check","Only check it: show the changes or problems and save nothing.").option("--yes","Save without asking. An agent adds it only after its user agreed to the changes.").action(async o=>Ou(t,o))};var te="closedloop company --website <url>",Wt="ClosedLoop AI reads your website to learn your company, its products and its market. That is the basis for the suggested product structure and for analysing your insights, and it usually takes a few minutes.",qr="It reads your website to learn your company and usually takes a few minutes.",Tu="closedloop company --research";var Br="closedloop company --no-domains --confirmed-by-user",Ho="Without them ClosedLoop AI cannot tell colleagues from customers, so everything a colleague says on a call or in an email would be analysed as customer feedback and the results would be wrong.",Ru=3600,Jr=30,Eu=new Set(["waiting","researching","retrying"]);function Go(e){let t=e.research;if(!t||typeof t.state!="string")throw new p("api_error","ClosedLoop AI sent an unexpected reply",g.Failure);return e}async function Be(e,t,n=!1){let o=await v(e,t,"GET",n?"/api/cli/company?include=research":"/api/cli/company");if(o.status!==200)throw C(o,"api_error","The company profile could not be loaded. Try again.");return Go(o.body)}async function Ye(e,t){try{return await Be(e,t)}catch(n){if(n instanceof p)return null;throw n}}function xn(e){return!!(e?.email_domains&&!e.email_domains.confirmed)}function Mt(e){return!e||!e.analysis_waiting?null:e.research.state==="no_website"?`Nothing is analysed until your company website is set: ${te}`:e.research.state!=="ready"?"It is analysed once your company research is done; see: closedloop company":xn(e)?"Nothing is analysed until your company's email domains are confirmed; see: closedloop company":"It is analysed soon; see: closedloop company"}var ht=e=>e.join(", ");function Ht(e){if(!xn(e))return null;let t=e.email_domains.suggested,n=t.length>0?`Ask your user whether their colleagues' work email addresses end in ${ht(t.map(i=>`@${i}`))}, and whether there are others.`:"Ask your user which domains their colleagues' work email addresses end in (the part after the @).",o=t.length>0?t.join(","):"<domain,...>";return[`ClosedLoop AI needs your user to confirm their company's email domains before it analyses anything. ${Ho}`,n,`When they have answered, run: closedloop company --domains ${o} --confirmed-by-user (with the domains they gave).`,`If their company has no email domain of its own (everyone uses a personal address such as Gmail), explain to them that every colleague will then count as a customer and the results will be wrong, and ask them to confirm they understand. Only after they say yes, run: ${Br}`,"Do not run either command, and do not add --confirmed-by-user, without your user's answer."].join(" ")}function qo(e){let t=e.email_domains;return t?t.confirmed?t.domains.length>0?`Email domains: ${ht(t.domains)}.`:"Email domains: none (confirmed); colleagues count as customers.":`Email domains: not confirmed yet, so nothing is analysed.${t.suggested.length>0?` Suggested: ${ht(t.suggested)}.`:""} Confirm with: closedloop company --domains ${t.suggested.length>0?t.suggested.join(","):"<domain,...>"}`:null}async function Mo(e,t,n){let i=await v(e,t,"PUT","/api/cli/company/domains",n===null?{no_domains:!0,confirmed_by_user:!0}:{domains:n,confirmed_by_user:!0});if(i.status!==200)throw C(i,"api_error","The email domains could not be saved. Try again.");return Go(i.body)}function Yr(e){return e.split(/[\s,]+/).map(t=>t.trim().replace(/^@/,"").toLowerCase()).filter(Boolean)}var Kr=e=>/^(y|yes)$/i.test(e.trim());async function zr(e,t,n){let{runtime:o,print:i}=e,r=n.email_domains?.suggested??[];i.note(`Your colleagues' email domains tell ClosedLoop AI who is a colleague and who is a customer. ${Ho}`);let s=r.length>0?`Your company's email domains, separated by commas [${ht(r)}]: `:`Your company's email domains, separated by commas (e.g. acme.example), or "none" if it has none: `,a=(await o.prompt(s)).trim();if(/^none$/i.test(a)||!a&&r.length===0){if(!a)return null;let u=await o.prompt('Without email domains every colleague counts as a customer and the results will be wrong. Type "yes" to confirm your company has none: ');return Kr(u)?Mo(o,t,null):null}let l=a?Yr(a):r;return Mo(o,t,l)}async function Jo(e,t,n){let o=await v(e,t,"PUT","/api/cli/company/website",{website:n.trim()});if(o.status!==200)throw C(o,"api_error","The website could not be saved. Try again.");return Go(o.body)}function ft(e,t){let n=e.research;switch(n.state){case"no_website":return`No company website yet. Your data is imported, but nothing is analysed until ClosedLoop AI has researched your company. Set it with: ${te}`;case"waiting":return`Company research: waiting to start. ${qr} Your data is analysed once it is done.`;case"researching":return`Company research: in progress${n.started_at?`, started ${mt(t-Date.parse(n.started_at))} ago`:""}. ${qr} Your data is analysed once it is done.`;case"retrying":return`Company research: the last try failed${n.next_try_at?`; it tries again around ${R(n.next_try_at)}`:""}. Your data is analysed once it is done.`;case"failed":return`Company research keeps failing${n.failed_at?` (last at ${R(n.failed_at)})`:""}; it is tried again about half an hour after each failure. If the address is wrong, set the right one: ${te}. Your data is analysed once research is done.`;case"ready":return`Company research: done${n.researched_at?` (${R(n.researched_at)})`:""}.${e.analysis_waiting?"":" Your data is being analysed."}`;default:return`Company research: ${String(n.state)}. ${e.analysis_waiting?"Your data is analysed once it is done.":"Your data is being analysed."}`}}function Iu(e){let t=e.research.state==="ready"?e.research.summary:null;if(!t)return[];let n=[];return t.description&&n.push(`What it learned: ${t.description}`),t.covers.length>0&&n.push(`It also covers: ${t.covers.join(", ")}.`),n.push(`See all of it: ${Tu}`),n}function Pu(e){return e===null||e<=0?null:e===1?`Next: confirm the product it suggested: ${ke}`:`Next: confirm the ${e} products it suggested: ${ke}`}function Vo(e,t,n=null){let o=[];e.website&&o.push(`Website: ${e.website}`),o.push(ft(e,t));let i=qo(e);i&&o.push(i),e.research.state==="ready"&&e.research.researched_url&&e.website&&e.research.researched_url!==e.website&&o.push(`The profile was researched from ${e.research.researched_url}.`),o.push(...Iu(e));let r=e.research.state==="ready"?Pu(n):null;return r&&o.push(r),o.join(`
|
|
47
|
+
`)}async function xu(e,t){try{return(await gt(e,t)).suggested_products.length}catch(n){if(n instanceof p)return null;throw n}}function Nu(e,t){let n=e.research;if(n.state!=="ready")return`${ft(e,t)}
|
|
48
|
+
The research text appears here once it is done.`;if(typeof n.text!="string"||!n.text.trim())return`${ft(e,t)}
|
|
49
|
+
ClosedLoop AI did not send the research text. Try again later.`;let o=n.researched_url??e.website,i=n.researched_at?`, ${R(n.researched_at)}`:"";return`What the company research found${o?` (from ${o}${i})`:""}:
|
|
50
|
+
|
|
51
|
+
${n.text.trim()}`}async function Vr(e,t,n,o){let i=n,r=e.print.spinner("Checking every 30 seconds\u2026");try{for(let s=0;s<Ru&&Eu.has(i.research.state);s+=Jr)r.update(`${ft(i,Date.now())} Checking every 30 seconds\u2026`),await e.runtime.sleep(Jr*1e3),i=await Be(e.runtime,t,o)}finally{r.stop()}return i}async function Lu(e,t,n){let{runtime:o,interactive:i}=e,r=n.domains===!1?null:Yr(String(n.domains));if(r!==null&&r.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=r===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: ${ht(r)}? Type "yes" to save them: `;s=Kr(await o.prompt(a))}if(!s){let a=await Be(o,t),l=r===null?"that the company has no email domain of its own":`that their colleagues' email domains are ${ht(r)}`,u=r===null?Br:`closedloop company --domains ${r.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}? ${Ho} If they have not, ask them first. Once they say yes, run: ${u}`;return{profile:a,saved:!1,instructions:c}}return{profile:await Mo(o,t,r),saved:!0,instructions:null}}async function Du(e,t){let n=$(e.runtime);if(t.website!==void 0&&!t.website.trim())throw new p("invalid_website","--website needs the company website, e.g. https://acme.example",g.Usage);if(t.confirmedByUser&&t.domains===void 0)throw new p("invalid_options","--confirmed-by-user goes with --domains or --no-domains",g.Usage);if(t.research&&(t.website!==void 0||t.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(t.research){let l=await Be(e.runtime,n,!0);t.watch&&(l=await Vr(e,n,l,!0)),e.print.result(l,u=>Nu(u,Date.now()));return}let o=t.website!==void 0?await Jo(e.runtime,n,t.website):await Be(e.runtime,n),i=!1,r=null;if(t.domains!==void 0){let l=await Lu(e,n,t);({profile:o}=l),i=l.saved,r=l.instructions}t.watch&&(o=await Vr(e,n,o,!1)),!r&&!e.interactive&&(r=Ht(o));let s=o.research.state==="ready"?await xu(e.runtime,n):null,a={...o,email_domains_saved:i,instructions:r,products_suggested:s};e.print.result(a,l=>{let u=[];return t.website!==void 0&&u.push(`Website saved. ${Wt}`),l.email_domains_saved?u.push("Email domains saved."):t.domains!==void 0&&l.instructions&&u.push(l.instructions),u.push(Vo(l,Date.now(),l.products_suggested)),u.join(`
|
|
52
|
+
`)})}var Xr=(e,t)=>{e.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=>Du(t,n))};var ju="Insights that name one of these competitors count as competitor mentions, shown on opportunities, features, customer profiles and the weekly brief.",Qr="closedloop competitors add <name> [--website <url>]",Fu="Changing competitors needs the right to change the workspace's settings (an owner or admin).",Uu="It is then marked as not a competitor; the customer mentions behind it are kept, and adding it again restores it.",Wu={researched:"AI research",researched_and_mentioned:"AI research, mentioned by customers",mentioned:"mentioned by customers",edited:"manual"},yt=e=>e.trim().toLowerCase(),Mu=e=>/^(y|yes)$/i.test(e.trim()),Zr=e=>/^[\w.@-]+$/.test(e)?e:`"${e.replace(/(["\\$`])/g,"\\$1")}"`;function Nn(e,t,n){let o=e.status===403,i=C(e,o?"access_denied":"api_error",t);return o&&n?new p(i.code,i.message,g.Failure,Fu):i}var Ln=(e,t)=>`/api/competitors/${encodeURIComponent(e)}${t?`/${encodeURIComponent(t)}`:""}`;function Bo(e){let t=e;if(!t||typeof t.id!="string"||typeof t.competitor_company!="string")throw new p("api_error","ClosedLoop AI sent an unexpected reply",g.Failure);return t}async function Yo(e,t,n){let o=await v(e,t,"GET",Ln(n));if(o.status!==200)throw Nn(o,"The competitors could not be loaded. Try again.",!1);if(!Array.isArray(o.body.competitors))throw new p("api_error","ClosedLoop AI sent an unexpected reply",g.Failure);return o.body.competitors.map(Bo)}function es(e,t){let n=t.trim(),o=e.find(a=>a.id.toLowerCase()===n.toLowerCase());if(o)return o;let i=e.filter(a=>yt(a.competitor_company)===yt(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 r=n?e.filter(a=>yt(a.competitor_company).includes(yt(n))):[],s=r.length>0?`; competitors with that in their name: ${r.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 ts(e){let t=[e.competitor_product&&yt(e.competitor_product)!==yt(e.competitor_company)?`${e.competitor_company} (${e.competitor_product})`:e.competitor_company];e.product_website&&t.push(e.product_website),e.is_direct_competitor===!0&&t.push("direct"),e.is_direct_competitor===!1&&t.push("indirect"),typeof e.mention_count=="number"&&e.mention_count>0&&t.push(`${k(e.mention_count)} mention${e.mention_count===1?"":"s"}`);let n=e.provenance?Wu[e.provenance]:void 0;return n&&t.push(n),t.join(" \xB7 ")}function Hu(e){let t=[ju,""];return e.competitors.length===0?t.push(`No competitors yet. Your company research adds the ones it finds; add your own with: ${Qr}`):(t.push(...e.competitors.map(n=>` ${ts(n)}`),""),t.push(`${k(e.competitors.length)} competitor${e.competitors.length===1?"":"s"}. Add one: ${Qr}. Change one: closedloop competitors edit <name> or remove <name>.`)),e.can_change||t.push("Only someone who can change the workspace's settings (an owner or admin) can change this list."),t.join(`
|
|
53
|
+
`)}async function Gu(e){let t=$(e.runtime),n=await W(e.runtime,t),o=await Yo(e.runtime,t,n.id);e.print.result({competitors:o,can_change:n.permissions.can_manage_team_settings},Hu)}async function qu(e,t,n){let o=t.trim();if(!o)throw new p("missing_name",`give the competitor's name, e.g. closedloop competitors add "Acme Rival"`,g.Usage);let i=$(e.runtime),{id:r}=await W(e.runtime,i),s={competitor_company:o,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(e.runtime,i,"POST",Ln(r),s);if(a.status!==200)throw Nn(a,"The competitor could not be added. Try again.",!0);let l=Bo(a.body.competitor);e.print.result({competitor:l},u=>`Added ${u.competitor.competitor_company} to your competitors.`)}async function Ju(e,t,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 o=$(e.runtime),{id:i}=await W(e.runtime,o),r=es(await Yo(e.runtime,o,i),t),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(e.runtime,o,"PUT",Ln(i,r.id),s);if(a.status!==200)throw Nn(a,"The competitor could not be saved. Try again.",!0);let l=Bo(a.body.competitor);e.print.result({competitor:l},u=>`Saved: ${ts(u.competitor)}`)}async function Vu(e,t,n){let{runtime:o,interactive:i,print:r}=e,s=$(o),{id:a}=await W(o,s),l=es(await Yo(o,s,a),t),u=l.competitor_company,c=n;if(!c&&i&&(r.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=Mu(await o.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? ${Uu} If they have not, ask them first. Once they say yes, run: closedloop competitors remove ${Zr(t.trim())} --yes`;r.result({status:"not_removed",competitor:l,instructions:m},h=>h.instructions);return}let d=await v(o,s,"DELETE",Ln(a,l.id));if(d.status!==200)throw Nn(d,"The competitor could not be removed. Try again.",!0);r.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 ${Zr(m.competitor.competitor_company)}`)}var ns=(e,t)=>{let n=e.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()=>Gu(t));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(o,i)=>qu(t,o,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(o,i)=>Ju(t,o,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(o,i)=>Vu(t,o,i.yes===!0))};var os={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"},Bu=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),ne=e=>typeof e=="string"?e:"",Ko=e=>typeof e!="string"?String(e):/^\d{4}-\d{2}-\d{2}$/.test(e)?q(`${e}T00:00:00Z`):P(e);function Yu(e){if(typeof e.empty_share=="number")return`${Math.round(e.empty_share*100)}%`;let t=e.record_count,n=e.empty_count;return typeof t!="number"||typeof n!="number"||t<=0?null:`${Math.round(n/t*100)}%`}function Ku(e,t){return typeof e.distinct_count=="number"?Math.max(0,e.distinct_count-t):0}var ae=e=>e!=null,zu=e=>e.hidden!==!0&&(e.kind==="number"&&![e.min,e.max,e.p10,e.p90].some(ae)||e.kind==="date"&&![e.first,e.last,e.p10,e.p90].some(ae)),is=e=>String(e).replace(/\s+/g," ").trim(),zo=["customer","deal","person","insight","context","conversation","usage"],Xo="When two fields fit a request, ask the user which one they mean before writing the rule; never pick one silently.";function Xu(e){let t=o=>typeof o=="number"?O(o):Ko(o),n=typeof e.zero_share=="number"&&e.zero_share>0?[`${Math.round(e.zero_share*100)}% are 0`]:[];return ae(e.median)?[...n,`${n.length?"other values: ":""}10th percentile ${t(e.p10)}`,`median ${t(e.median)}`,`90th percentile ${t(e.p90)}`]:n}function ss(e,t){let n=ne(t.label)&&t.label!==t.key?` \xB7 ${t.label}`:"";return`${e.bold(ne(t.key))} ${e.dim(`${ne(t.type)}${n}`)}`}function Qu(e){return w(e.guide)&&w(e.guide.preview)&&e.guide.preview.kind==="empty"}function Zu(e,t,n,o){if(!t.length)return n?`No fields match "${n}".`:o?"No fields hold values yet.":"No fields yet.";let i=new Map;for(let s of t){let a=ne(s.entity)||"other";i.set(a,[...i.get(a)??[],s])}return`${[...i.entries()].map(([s,a])=>[e.bold(s),...a.map(l=>` ${ss(e,l)}`)].join(`
|
|
53
54
|
`)).join(`
|
|
54
55
|
|
|
55
56
|
`)}
|
|
56
57
|
|
|
57
|
-
${
|
|
58
|
-
${
|
|
59
|
-
`)}async function
|
|
60
|
-
`)))};import{existsSync as
|
|
61
|
-
`)}var
|
|
62
|
-
`)}async function
|
|
58
|
+
${e.dim(Xo)}
|
|
59
|
+
${e.dim("Read one in full: closedloop fields show <key>")}`}function ec(e,t,n){let o=[`${ss(e,t)}${e.dim(` \xB7 ${ne(t.entity)}`)}`];ne(t.id)&&t.id!==t.key&&o.push(e.dim(`id ${ne(t.id)}`)),ne(t.description)&&o.push(ne(t.description)),t.example!==void 0&&t.example!==null&&o.push(e.dim(`e.g. ${JSON.stringify(t.example)}`)),typeof t.history=="string"&&o.push(e.dim(`history: ${Bu(os,t.history)?os[t.history]:t.history}`));let i=Array.isArray(t.confused_with)?t.confused_with.filter(l=>typeof l=="string"):[];i.length&&o.push(`Often confused with ${i.join(", ")}. When a request does not say which one it means, ask which, showing the number with each (a preview gives both).`),(t.pii==="email"||t.pii==="phone"||t.pii===!0)&&o.push(e.dim(`It holds ${t.pii==="phone"?"phone numbers":t.pii==="email"?"email addresses":"email addresses or phone numbers"}, so only their pattern is shown.`));let r=w(t.profile)?t.profile:null;if(r){let l=Array.isArray(r.values)?r.values.filter(w):[];if(typeof r.pattern=="string")o.push("",`Values look like ${r.pattern}.`);else if(r.kind==="text")o.push("",`Free text${typeof r.distinct_count=="number"?` with ${O(r.distinct_count)} different values`:""}; no values are kept.`);else if(l.length){let c=Math.max(...l.map(m=>is(m.value).length)),d=Math.max(...l.map(m=>O(m.count).length));if(o.push("","Values:",...l.map(m=>` ${is(m.value).padEnd(c)} ${O(m.count).padStart(d)}`)),typeof r.hidden_values!="number"){let m=Ku(r,l.length);m>0&&o.push(e.dim(` and ${O(m)} more`))}}else if(r.hidden===!0)o.push("",`Not shown: fewer than ${O(r.hidden_below)} customers hold the values at one end of the range.`);else if(ae(r.p10)||ae(r.p90)||typeof r.zero_share=="number"&&r.zero_share>0)o.push("",Xu(r).join(" \xB7 "));else if(zu(r))o.push("","no values yet: no record has one.");else if(ae(r.min)||ae(r.max)){let c=[`smallest ${O(r.min)}`,...ae(r.median)?[`median ${O(r.median)}`]:[],...ae(r.p90)?[`90th percentile ${O(r.p90)}`]:[],`largest ${O(r.max)}`];o.push("",c.join(" \xB7 "))}else(ae(r.first)||ae(r.last))&&o.push("",`first ${Ko(r.first)} \xB7 last ${Ko(r.last)}`);typeof r.hidden_values=="number"&&r.hidden_values>0&&o.push(e.dim(`${O(r.hidden_values)} values held by fewer than ${O(r.hidden_below)} customers are not shown.`));let u=Yu(r);u!==null&&o.push(`${k(r.empty_count)} of ${k(r.record_count)} records have no value (${u}).`)}let a={of:!ne(t.entity)||t.entity==="usage"?"customer":ne(t.entity),where:{field:ne(t.key),op:"exists"}};return o.push("",e.dim(`Use it in a rule: ${fn(a,l=>`closedloop segments preview --rule ${l}`,n)}`)),o.join(`
|
|
60
|
+
`)}async function rs(e,t,n){if(t!==void 0&&!zo.includes(t))throw new p("invalid_entity",`--entity must be one of ${zo.join(", ")}`,g.Usage);let o=await mr(e,{entity:t,q:n}),i=n===void 0?o.filter(s=>!Qu(s)):o,r={filter:{entity:t??null,q:n??null},fields:i,ask:Xo,next:"closedloop fields show <key>"};e.print.result(r,s=>Zu(e.print.style,s.fields,n,o.length-i.length))}async function tc(e,t){let o={field:await pr(e,t),next:"closedloop segments preview --rule <file|json|->"};e.print.result(o,i=>ec(e.print.style,i.field,e.runtime.platform))}var as=(e,t)=>{let n=e.command("fields").alias("field").description(`The named fields a segment or a metric is written in: what each means, a preview of this workspace's values and how to filter on it. ${Xo}`),o=`Only one kind of record: ${zo.join(", ")}.`;n.command("list").description("Every field that holds values, one line each: its key, type and label, by the kind of record it describes.").option("--entity <entity>",o).action(async i=>rs(t,i.entity,void 0)),n.command("search").argument("<words...>","Words in the field's key, label or description, e.g. property type.").description("The fields whose key, label or description matches the words.").option("--entity <entity>",o).action(async(i,r)=>rs(t,r.entity,i.join(" ").trim())),n.command("show").argument("<key>","A field key, e.g. customer.country or customer.crm.Property_Type__c.").description("One field in full: what it means, every value with how many records have it, and how many have none.").action(async i=>tc(t,i))};var nc=[{term:"Product structure",definition:"The products, areas and features a team confirms (closedloop products). Insights are filed against them."},{term:"Product",definition:"One of the canonical Products a team owns. Products, the Areas under them and the Features under those are how the product is organised."},{term:"Area",definition:"A part of a Product. Areas and Features are the parts an Insight is filed against."},{term:"Feature",definition:"A part of the product that exists (Checkout, Reporting), never planned work. It sits under an Area or a Product, and Insights are filed against it."},{term:"Insight",definition:"One source-backed product problem, request, bug or improvement extracted from customer evidence. One conversation can produce several."},{term:"Opportunity",definition:"A customer problem, clustered from related Insights and split into Needs; can span products. Shows customer impact and whether delivery work covers it. The top of the discovery layer."},{term:"Need",definition:"One specific need inside an Opportunity, discovered by sub-clustering and backed by Insights. Has one place in the product layer: the Feature holding most of its Insights, else that Area, else that Product."},{term:"Customer",definition:"The person or account that gave feedback."},{term:"Competitor",definition:"A third party customers mention that competes with your product (closedloop competitors). Insights that name one count as competitor mentions."}],ls=(e,t)=>{e.command("glossary").description("What the words mean: product structure, product, area, feature, insight, opportunity, need, customer and competitor.").action(()=>t.print.result({terms:nc},n=>n.terms.map(o=>`${t.print.style.bold(o.term)}: ${o.definition}`).join(`
|
|
61
|
+
`)))};import{existsSync as zc,readFileSync as Xc,statSync as Qc}from"node:fs";var oc=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,Gt='A formula combines saved metrics, e.g. "[Accounts churned this quarter] / [Paying accounts]", "#2 * 12" or "([MRR] - [MRR]@90d) / [MRR]@90d" (@90d: its value 90 days earlier); name a metric in brackets, by its number in the last metrics list (#2) or by its id.',le=e=>new p("invalid_formula",e,g.Usage,Gt),cs={"+":"add","-":"subtract","*":"multiply","/":"divide"},us={add:"+",subtract:"-",multiply:"*",divide:"/"};function ic(e){let t=[],n=0,o=()=>!t.length||t[t.length-1].kind==="op"||t[t.length-1].kind==="open";for(;n<e.length;){let i=e[n];if(/\s/.test(i)){n+=1;continue}let r=null;if(i==="["){let a=e.indexOf("]",n+1);if(a<0)throw le(`a metric name opened with [ at ${n+1} is not closed with ]`);let l=e.slice(n+1,a).trim();if(!l)throw le(`an empty metric name [] at ${n+1}`);r={kind:"name",text:l},n=a+1}else if(i==="#"){let a=/^#(\d{1,3})/.exec(e.slice(n));if(!a)throw le(`# at ${n+1} must be followed by a number from the last metrics list, e.g. #2`);r={kind:"number",text:a[1]},n+=a[0].length}else oc.test(e.slice(n,n+36))&&(r={kind:"id",text:e.slice(n,n+36).toLowerCase()},n+=36);if(r){let a=/^\s*@\s*(\d+)\s*d\b/i.exec(e.slice(n));if(!a&&/^\s*@/.test(e.slice(n)))throw le(`an earlier value is written @<days>d, e.g. @90d (at ${n+1})`);let l=a?Number(a[1]):void 0;if(l===0)throw le("@0d is the value on the same day: leave it out");t.push({kind:"metric",name:r,...l!==void 0&&{days:l}}),a&&(n+=a[0].length);continue}if(/^\d+(?:\.\d+)?|^\.\d+/.exec(e.slice(n))||i==="-"&&o()&&/^-\s*(\d|\.\d)/.test(e.slice(n))){let a=/^-?\s*(\d+(?:\.\d+)?|\.\d+)/.exec(e.slice(n));t.push({kind:"num",value:Number(a[0].replace(/\s+/g,""))}),n+=a[0].length;continue}if(i in cs){t.push({kind:"op",value:i}),n+=1;continue}if(i==="("){t.push({kind:"open"}),n+=1;continue}if(i===")"){t.push({kind:"close"}),n+=1;continue}throw le(`'${i}' at ${n+1} is not part of a formula`)}return t}function rc(e){let t=0,n=()=>{let a=e[t];if(!a)throw le("the formula ends where a metric or a number is expected");if(t+=1,a.kind==="num")return a.value;if(a.kind==="metric")return{metric:a.name,...a.days!==void 0&&{days_ago:a.days}};if(a.kind==="open"){let l=r();if(e[t]?.kind!=="close")throw le("a ( is not closed with )");return t+=1,l}throw le(`a metric or a number is expected where ${a.kind==="op"?a.value:")"} is`)},o=(a,l)=>{let u=a();for(;e[t]?.kind==="op"&&l.includes(e[t].value);){let c=cs[e[t].value];t+=1,u={fn:"formula",op:c,args:[u,a()]}}return u},i=()=>o(n,"*/"),r=()=>o(i,"+-"),s=r();if(t<e.length)throw le("the formula goes on after it is complete; join the parts with + - * or /");return s}function ds(e){let t=rc(ic(e));if(typeof t=="number"||!("fn"in t))throw le("a formula combines at least two things with + - * or /");return t}function ms(e){let t=[],n=[e];for(;n.length;){let o=n.pop();typeof o!="number"&&("metric"in o?t.push(o.metric):n.push(o.args[1],o.args[0]))}return t}function Qo(e,t){let n=o=>typeof o=="number"?o:"metric"in o?{metric:t(o.metric),...o.days_ago!==void 0&&{days_ago:o.days_ago}}:Qo(o,t);return{fn:"formula",op:e.op,args:[n(e.args[0]),n(e.args[1])]}}var fe=e=>w(e)&&e.fn==="formula";function Dn(e,t=new Map){if(typeof e=="number")return String(e);if(!w(e))return"?";if(typeof e.metric=="string"){let r=t.get(e.metric)??e.metric.slice(0,8),s=typeof e.days_ago=="number"?`@${e.days_ago}d`:"";return`[${r}]${s}`}let n=Array.isArray(e.args)?e.args:[],o=r=>fe(r)?`(${Dn(r,t)})`:Dn(r,t),i=typeof e.op=="string"&&e.op in us?us[e.op]:String(e.op);return`${o(n[0])} ${i} ${o(n[1])}`}function Se(e,t,n,o){if(typeof t!="number"||!Number.isFinite(t))return null;let i=t!==0&&Math.abs(t)<1?String(Number(t.toPrecision(3))):Number.isInteger(t)?k(t):new Intl.NumberFormat(void 0,{maximumFractionDigits:2}).format(t),r=a=>Number.isInteger(a)?k(a):String(Number(a.toPrecision(6))),s=w(e)&&e.op==="divide"&&typeof n=="number"&&typeof o=="number"?` (${r(n)} / ${r(o)})`:"";return`${i}${s}`}import{existsSync as cc}from"node:fs";var Zo="\u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588";function qt(e,t,n){return o=>n&&o?`\x1B[${e}m${o}\x1B[${t}m`:o}function ei(e){let t=e.color===!0;return{enabled:t,width:Math.max(60,Math.min(100,e.columns??100)),bold:qt(1,22,t),dim:qt(2,22,t),red:qt(31,39,t),orange:qt(33,39,t),green:qt(32,39,t),bar:(n,o)=>{let i=Number.isFinite(n)?Math.min(1,Math.max(0,n)):0,r=Math.round(i*o);return`${"\u2588".repeat(r)}${"\u2591".repeat(o-r)}`},sparkline:n=>{let o=Math.max(0,...n);return n.map(i=>Zo[o>0?Math.min(Zo.length-1,Math.round(i/o*(Zo.length-1))):0]).join("")}}}function jn(e,t){return t<=1?e.slice(0,Math.max(0,t)):e.length<=t?e:`${e.slice(0,t-1).trimEnd()}\u2026`}function Fn(e,t,n){let o=e.split(/\s+/).filter(Boolean),i=[],r="";for(let s of o){let a=t-(i.length===0?0:n.length);r&&r.length+1+s.length>a?(i.push(r),r=s):r=r?`${r} ${s}`:s}return r&&i.push(r),i.map((s,a)=>a===0?s:`${n}${s}`).join(`
|
|
62
|
+
`)}var ni="last-segments.json",Jt='A rule is JSON: {"of":"customer","where":{"field":"customer.country","op":"in","value":["United States"]}}; a saved segment is included by its name, {"segment":"Groups"}; see closedloop fields list',ps="<file|json|->";async function gs(e,t){if(t===void 0)throw new p("missing_rule","give the rule with --rule <file|json|->",g.Usage,Jt);return De(e,t,"rule",Jt)}function sc(e,t,n,o){let i=typeof t.entity=="string"?t.entity:n.of,r=typeof t.count=="number"?t.count:0,s=[`${k(r)} ${M(i,r)} ${r===1?"matches":"match"}.`],a=Array.isArray(t.steps)?t.steps.filter(w):[],l=a.length&&typeof t.total=="number"?[{condition:null,count:t.total},...a]:a;if(l.length){let f=l.map(b=>b.condition===null||b.condition===void 0?`all ${M(i)}`:jt(b.condition)),y=l.map(b=>typeof b.count=="number"?k(b.count):"?"),_=Math.min(Math.max(...f.map(b=>b.length)),e.width-16),S=Math.max(...y.map(b=>b.length));s.push("",...f.map((b,F)=>` ${jn(b,_).padEnd(_)} ${y[F].padStart(S)}`))}let c=(Array.isArray(t.examples)?t.examples.filter(w):[]).map(f=>[f.name,f.label,f.title,f.customer_id,f.id].find(y=>typeof y=="string")).filter(Boolean);(c.length||typeof t.insights_in_scope=="number")&&s.push(""),c.length&&s.push(`Examples: ${c.join(", ")}`),typeof t.insights_in_scope=="number"&&s.push(`${k(t.insights_in_scope)} insight${t.insights_in_scope===1?"":"s"} in scope.`);let d=Array.isArray(t.warnings)?t.warnings:[],m=Ut(t.stale);(d.length||m)&&s.push("",...m?[e.orange(`Warning: ${m}.`)]:[],...d.map(f=>e.orange(`Warning: ${Tn(f)}`)));let h=En(e,t.alternatives);return h.length&&s.push("",...h),s.push("",e.dim(o)),s.join(`
|
|
63
|
+
`)}async function ac(e,t){let n=await gs(e,t),o=await gr(e,n),i=Rn(o.alternatives,o.count),r={...o,rule:n,...i?{ask:i}:{},next:`closedloop segments define <name> --rule ${ps}`};e.print.result(r,s=>sc(e.print.style,s,n,"Save it: closedloop segments define <name> --rule <the same rule>"))}function ti(e){let t=w(e)&&Array.isArray(e.includes)?e.includes.filter(w):[];return t.length?t.map(n=>{let o=n.newer_version===!0&&typeof n.current_version=="number"?`; version ${n.current_version} is newer`:"",i=n.archived===!0?"; archived":"";return`${String(n.name)} (version ${String(n.version)}${o}${i})`}).join(", "):null}async function lc(e,t,n){let o=await gs(e,n.rule),i=await hr(e,t,o,n.description),r=String(i.id),s=he(i),a={segment:i,rule:w(s.rule)?s.rule:o,next:`closedloop metrics preview --segment ${r} --calc count`};e.print.result(a,l=>{let u=typeof i.entity=="string"?i.entity:l.rule.of,c=Ne(s),d=c===null?"":`version ${c}, `,m=ti(s);return`Saved the segment "${String(i.name??t)}" (${d}${M(u)}).${m?`
|
|
63
64
|
It includes ${m}.`:""}
|
|
64
|
-
${
|
|
65
|
-
`)}),c=
|
|
65
|
+
${e.print.style.dim(`Measure it: ${l.next}`)}`})}async function uc(e){let t=await yr(e);await Sn(e,ni,t.map(o=>o.id));let n={segments:t,next:"closedloop metrics define <name> --segment <number|id|name> --calc <calc>"};e.print.result(n,o=>{let i=e.print.style;if(!o.segments.length)return`No segments yet. Define one: closedloop segments define <name> --rule ${ps}`;let r=String(o.segments.length).length,s=" ".repeat(r+2),a=new Map;for(let d of o.segments){let m=he(d).includes;for(let h of Array.isArray(m)?m.filter(w):[]){let f=String(h.segment_id);a.set(f,[...a.get(f)??[],String(d.name)])}}let l=o.segments.map((d,m)=>{let h=he(d),f=Ne(h),y=f===null?"":` \xB7 version ${f}`,_=ti(h),S=a.get(String(d.id))??[];return[`${String(m+1).padStart(r)} ${i.bold(String(d.name))}${i.dim(` \xB7 ${M(d.entity)}${y}`)}`,`${s}${Nr(h.rule,xr(h))}`,..._?[`${s}includes ${_}`]:[],...S.length?[`${s}included in ${S.join(", ")}`]:[],`${s}${i.dim(`id ${String(d.id)}`)}`].join(`
|
|
66
|
+
`)}),c=o.segments.some(d=>/is newer/.test(ti(he(d))??""))?`
|
|
66
67
|
${i.dim("A segment keeps the version it included; save its rule again to use the newer one.")}`:"";return`${l.join(`
|
|
67
68
|
|
|
68
69
|
`)}${c}
|
|
69
70
|
|
|
70
|
-
${i.dim("Measure one: closedloop metrics preview --segment <number> --calc count")}`})}var
|
|
71
|
-
`)})}async function
|
|
72
|
-
`)})}async function
|
|
73
|
-
`+
|
|
71
|
+
${i.dim("Measure one: closedloop metrics preview --segment <number> --calc count")}`})}var hs=(e,t)=>{let n=e.command("segments").alias("segment").description("Who is in scope: customers, deals, people, insights, context or conversations, described as a rule over named fields."),o=`The rule: a JSON file, inline JSON, or - for standard input. ${Jt}.`;n.command("define").argument("<name>",`The segment's name, e.g. "paying US hotels". Other rules include it by this name.`).description("Save a rule as a named segment (version 1). A segment in use is never changed in place: a change makes a new version.").option("--rule <rule>",o).option("--description <text>","What the segment is for.").action(async(i,r)=>lc(t,i,r)),n.command("preview").description("Count a rule without saving it: how many match, how many are left after each condition, examples, insights in scope and warnings.").option("--rule <rule>",o).action(async i=>ac(t,i.rule)),n.command("list").description("Your workspace's segments, numbered, each with its rule in words, the segments it includes and the ones that include it.").action(async()=>uc(t))};var Ke="last-metrics.json",Vt='A calculation is count, or JSON such as {"fn":"sum","field":"customer.open_pipeline"} or {"fn":"ratio","numerator":{"fn":"count","where":{...}},"denominator":{"fn":"count"}}',H=(e,t,n)=>new p(e,t,g.Usage,n),oi=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,dc=e=>{let t=e.trim();return t!==""&&t!=="-"&&!/^[{[]/.test(t)&&!Dt(t)&&!/^\d{1,3}$/.test(t)&&!oi.test(t)&&!cc(t)};async function mc(e,t){let n=ds(t),o=ms(n),i=new Map;if(o.some(a=>a.kind==="name"))for(let a of await vn(e))typeof a.name=="string"&&typeof a.id=="string"&&i.set(a.name.toLowerCase(),a.id);let r=new Map;for(let a of o)a.kind==="number"&&r.set(a.text,await se(e,a.text,Ke,"metrics","closedloop metrics list"));return Qo(n,a=>{if(a.kind==="id")return a.text;if(a.kind==="number")return r.get(a.text);let l=i.get(a.text.toLowerCase());if(!l)throw H("unknown_metric",`no metric in this workspace is named "${a.text}"`,"See their names: closedloop metrics list");return l})}async function ws(e,t){if(t.formula!==void 0){if(t.calc!==void 0)throw H("formula_and_calc","give --formula or --calc, not both",Gt);if(t.segment!==void 0)throw H("formula_has_no_segment","a formula has no segment: it combines saved metrics, each over its own",Gt);return{segment:void 0,calc:await mc(e,t.formula)}}if(t.segment===void 0&&t.calc!==void 0&&!/^[a-z_0-9]+$/i.test(t.calc.trim())){let s=await De(e,t.calc,"calc",Vt);if(fe(s))return{segment:void 0,calc:s}}if(t.segment===void 0)throw H("missing_segment","give the segment with --segment <number|id|name|file|json|->","Your segments: closedloop segments list");if(t.calc===void 0)throw H("missing_calc","give the calculation with --calc <calc>",Vt);if(t.segment.trim()==="-"&&t.calc.trim()==="-")throw H("stdin_twice","only one of --segment and --calc can come from standard input");let o=(dc(t.segment)?t.segment.trim():await se(e,t.segment,ni,"segments","closedloop segments list"))??await De(e,t.segment,"segment",Jt),i=t.calc.trim(),r;if(i==="count")r={fn:"count"};else{if(/^[a-z_0-9]+$/i.test(i))throw H("invalid_calc",`'${i}' is not a calculation`,Vt);r=await De(e,i,"calc",Vt)}return{segment:o,calc:r}}var _s=async(e,t)=>t===void 0?null:typeof t!="string"?t:oi.test(t)?fr(e,t):null,Un=e=>String(e).replace(/\s*\(?\b(failed|not_measured):\s*[a-z_]+\)?/g,"").trim();function bs(e){let t={measured:"measured",rebuilt:"rebuilt from the history",not_available:"no value"};return(Array.isArray(e)?e.filter(w):[]).map(n=>{let o=typeof n.name=="string"?n.name:String(n.metric),i=typeof n.days_ago=="number"&&n.days_ago>0?` ${n.days_ago} days earlier (${Ae(n.as_of)})`:"",r=typeof n.basis=="string"&&Object.prototype.hasOwnProperty.call(t,n.basis)?t[n.basis]:String(n.basis),s=typeof n.value=="number"?O(n.value):`no value${typeof n.reason=="string"&&Un(n.reason)?` (${Un(n.reason)})`:""}`;return` ${o}${i}: ${s}${typeof n.value=="number"?`, ${r}`:""}`})}async function pc(e,t,n){let o=await Do(e,void 0,t),i=n.formula!==void 0?`--formula ${ee(n.formula,e.runtime.platform)}`:"--calc <the same formula>",r={...o,calc:w(o.calc)?o.calc:t,next:`closedloop metrics define <name> ${i}`};e.print.result(r,s=>{let a=e.print.style,l=Se(s.calc,s.value,s.numerator,s.denominator),u=[l!==null?`${l} on ${Ae(s.as_of)}.`:`No value: ${s.reason===void 0||s.reason===null?"an input has no value":Un(s.reason)}.`,...bs(s.inputs)],c=Ut(s.stale);return c&&u.push(a.orange(`Warning: ${c}.`)),u.push("",a.dim(`Save it: ${s.next}`)),u.join(`
|
|
72
|
+
`)})}async function gc(e,t){let{segment:n,calc:o}=await ws(e,t);if(n===void 0)return pc(e,o,t);let i=await Do(e,n,o),r=await _s(e,n),s=typeof n=="string"?oi.test(n)?n:ee(n,e.runtime.platform):"<the same segment>",a=Rn(i.alternatives,i.value),l={...i,segment:n,rule:r,calc:o,...a?{ask:a}:{},next:`closedloop metrics define <name> --segment ${s} --calc <calc>`};e.print.result(l,u=>{let c=e.print.style,d=typeof u.member_count=="number"?u.member_count:null,m=M(typeof u.entity=="string"?u.entity:u.rule?.of,d??2),h=d===null?"":u.calc.fn==="count"&&u.value===d?` ${m}`:` over ${k(d)} ${m}`,f=Ve(u.calc,u.value,u.numerator,u.denominator),y=[f!==null?`${f}${h}.`:u.calc.fn==="ratio"?"No value: there is nothing to divide by (the denominator is 0).":`No value${h}: none of them has one.`],_=typeof u.missing_count=="number"?u.missing_count:0;_>0&&d!==null&&typeof u.calc.field=="string"&&y.push(`${k(_)} of ${k(d)} have no value for ${u.calc.field}, so they are left out.`);let S=Array.isArray(u.warnings)?u.warnings:[],b=Ut(u.stale);return b&&y.push(c.orange(`Warning: ${b}.`)),y.push(...S.map(F=>c.orange(`Warning: ${Tn(F)}`))),y.push(...En(c,u.alternatives)),y.push("",c.dim(`Save it: closedloop metrics define <name> --segment ${s} --calc <the same calc>`)),y.join(`
|
|
73
|
+
`)})}async function hc(e,t,n){let{segment:o,calc:i}=await ws(e,n),r=await wr(e,t,o,i,n.description),s=String(r.id),a=he(r),l={metric:r,rule:await _s(e,o),calc:w(a.calc)?a.calc:i,next:`closedloop goals metrics add <goal> --metric ${s} --period month`};e.print.result(l,u=>{let c=w(r.segment)&&typeof r.segment.name=="string"?r.segment.name:null,d=o===void 0?", each metric it uses pinned at its current version":typeof o=="string"?"":c?` with its own segment "${c}"`:" with its own segment, named after it",m=Ne(a),h=m===null?"":` (version ${m})`;return`Saved the metric "${String(r.name??t)}"${h}${d}.
|
|
74
|
+
`+e.print.style.dim(`It is measured every night. Measure a goal on it: ${u.next}`)})}function fc(e){let t=w(e.sources_fresh_as_of)?e.sources_fresh_as_of:{};return(typeof e.reason=="string"?e.reason.replace(/^stale:\s*/,"").split(/,\s*/).filter(Boolean):[]).map(i=>typeof t[i]=="string"&&!Number.isNaN(Date.parse(t[i]))?`${On(i)} last updated ${P(t[i])}`:`${On(i)} late`).join(", ")}function yc(e,t,n,o={}){let i=e.print.style;if(!w(n))return"not measured yet";let r=typeof n.as_of=="string"?/^\d{4}-\d{2}-\d{2}$/.test(n.as_of)?q(`${n.as_of}T00:00:00Z`):P(n.as_of):"an unknown day",s=typeof n.reason=="string"?n.reason.replace(/^(stale|failed|not_measured):\s*/,""):"",a=s?`: ${s}`:"";if(n.status==="not_measured"||n.status==="failed")return`no value on ${r}`;let l=typeof n.version=="number"?n.version:null,u=Ne(o),c=l!==null&&u!==null&&l!==u,m=`${(c?typeof n.value=="number"?O(n.value):null:fe(t)?Se(t,n.value,n.numerator,n.denominator):Ve(t,n.value,n.numerator,n.denominator))??"no value"}${c?` (version ${l})`:""}`,h=typeof n.basis=="string"?` \xB7 ${n.basis}`:"";if(n.status!=="stale")return`${m} on ${r}${h}`;let f=fc(n);return`${m} on ${r}${h} \xB7 ${i.orange(`stale${f?`: ${f}`:a}`)}`}async function wc(e){let t=await vn(e);await Sn(e,Ke,t.map(o=>o.id));let n={metrics:t,next:"closedloop goals metrics add <goal> --metric <number|id|name> --period month"};e.print.result(n,o=>{let i=e.print.style;if(!o.metrics.length)return"No metrics defined yet. Define one: closedloop metrics define <name> --segment <segment> --calc <calc>";let r=String(o.metrics.length).length,s=" ".repeat(r+2),a=new Map(o.metrics.map(u=>[String(u.id),String(u.name)]));return`${o.metrics.map((u,c)=>{let d=he(u),m=Ne(d),h=m===null?"":` \xB7 version ${m}`,f=typeof u.goals_using=="number"&&u.goals_using>0?u.goals_using:0,y=f?` \xB7 measured by ${f} goal metric${f===1?"":"s"}`:"";return[`${String(c+1).padStart(r)} ${i.bold(String(u.name))}${i.dim(`${h}${y}`)}`,`${s}${fe(d.calc)?`formula: ${Dn(d.calc,a)}`:kn(d.calc)}`,`${s}${yc(e,d.calc,u.latest_run,d)}`,`${s}${i.dim(`id ${String(u.id)}`)}`].join(`
|
|
74
75
|
`)}).join(`
|
|
75
76
|
|
|
76
77
|
`)}
|
|
77
78
|
|
|
78
79
|
${i.dim("Measure a goal on one: closedloop goals metrics add <goal> --metric <number> --period month")}
|
|
79
|
-
`+i.dim("Delete one: closedloop metrics delete <number>")})}var
|
|
80
|
-
`)})}var
|
|
81
|
-
`)})}async function
|
|
82
|
-
`);a.push(
|
|
83
|
-
`)}function
|
|
84
|
-
`)}var
|
|
80
|
+
`+i.dim("Delete one: closedloop metrics delete <number>")})}var Wn=/^\d{4}-\d{2}-\d{2}$/,Ae=e=>typeof e=="string"&&Wn.test(e)?q(`${e}T00:00:00Z`):String(e),fs={measured:"measured that night",rebuilt:"computed now from the history"};async function _c(e,t,n,o){let i=await se(e,t,Ke,"metrics","closedloop metrics list");if(i===null)throw H("invalid_id",`'${t}' is not a metric id or a number from the last metrics list`,"List them: closedloop metrics list");if(n===void 0||!Wn.test(n))throw H("invalid_day","--as-of must be a day before today, YYYY-MM-DD");if(o!==void 0&&!/^[1-9][0-9]*$/.test(o))throw H("invalid_version","--metric-version must be a version number");let r=await jo(e,i,n,o===void 0?void 0:Number(o)),a=typeof r.member_count=="number"&&r.value===r.member_count&&r.numerator===null&&!Array.isArray(r.inputs)&&typeof r.version=="number"?await br(e,i,r.version):null,l={...r,...a?{calc:a}:{},next:"closedloop metrics list"};e.print.result(l,u=>{let c=e.print.style,d=Ae(u.as_of);if(u.basis==="not_available")return`No value on ${d}: ${String(u.reason??"there is no history to read it from")}.`;let m=Array.isArray(u.inputs),h=(m?Se({op:u.denominator===null?null:"divide"},u.value,u.numerator,u.denominator):null)??(typeof u.value=="number"?O(u.value):"no value"),f=w(u.calc)&&u.calc.fn==="count"&&u.member_count===u.value,y=typeof u.member_count=="number"&&!f?` over ${k(u.member_count)}`:"",_=m&&u.basis==="rebuilt"?"worked out now from the values of the metrics it uses":typeof u.basis=="string"&&Object.prototype.hasOwnProperty.call(fs,u.basis)?fs[u.basis]:String(u.basis),S=[`${h}${y} on ${d} (version ${String(u.version)}), ${_}.`,...m?bs(u.inputs):[]];return u.basis==="rebuilt"&&!m&&S.push(c.dim("Read from the CRM history and the dated records; it is not stored.")),S.join(`
|
|
81
|
+
`)})}var bc=(e,t)=>new Date(Date.parse(`${e}T00:00:00Z`)-t*864e5).toISOString().slice(0,10),wt=e=>typeof e=="number"&&Number.isFinite(e)?e:null,ys={rate_limited:"too many requests for now",preview_busy:"the workspace is busy with other previews",preview_timeout:"working out past values takes too long on this workspace",internal_error:"the API did not answer",api_error:"the API did not answer",api_unreachable:"the API did not answer"};async function $c(e,t,n){let o=n.days??"30";if(!/^[1-9][0-9]*$/.test(o))throw H("invalid_days","--days must be a whole number of days, 1 or more");let i=n.every??"day";if(i!=="day"&&i!=="week")throw H("invalid_step","--every must be day or week");if(n.metricVersion!==void 0&&!/^[1-9][0-9]*$/.test(n.metricVersion))throw H("invalid_version","--metric-version must be a version number");let r=await se(e,t,Ke,"metrics","closedloop metrics list")??await $s(e,t),s=await $r(e,r),a=s.metric,l=s.versions.filter(w),u=n.metricVersion===void 0?Ne(he(a)):Number(n.metricVersion),c=l.find(A=>A.version===u);if(!c||typeof c.id!="string")throw H("unknown_version",`this metric has no version ${String(u)}`,"See its versions: closedloop metrics list");let d=typeof s.today=="string"&&Wn.test(s.today)?s.today:null;if(!d)throw new p("api_error","The API did not say which day it is. Try again.",g.Failure);let m=i==="week"?7:1,h=new Map,f=null;for(let A of s.runs.filter(w))A.version_id!==c.id||A.basis!=="measured"||typeof A.as_of!="string"||(A.status==="ok"||A.status==="stale"?h.set(A.as_of,A):A.as_of===d&&(f=A));let y=[],_=null,S=null,b=0;for(let A=0;A<Number(o);A+=m){let J=bc(d,A),V=h.get(J);if(V){y.push({day:J,value:wt(V.value),numerator:wt(V.numerator),denominator:wt(V.denominator),basis:"measured",status:String(V.status),reason:typeof V.reason=="string"?V.reason:null});continue}if(J===d){y.push(f?{day:J,value:null,numerator:null,denominator:null,basis:"measured",status:String(f.status),reason:typeof f.reason=="string"?f.reason:null}:{day:J,value:null,numerator:null,denominator:null,basis:"not_measured",status:null,reason:"the next nightly run measures it"});continue}if(_)continue;if(S){b+=1;continue}let U;try{U=await jo(e,r,J,u??void 0)}catch(tt){if(!(tt instanceof p)||!Object.prototype.hasOwnProperty.call(ys,tt.code))throw tt;S={day:J,reason:ys[tt.code]},b+=1;continue}y.push({day:J,value:wt(U.value),numerator:wt(U.numerator),denominator:wt(U.denominator),basis:String(U.basis),status:typeof U.status=="string"?U.status:null,reason:typeof U.reason=="string"?U.reason:null}),U.basis==="not_available"&&typeof U.history_from=="string"&&Wn.test(U.history_from)&&(_=U.history_from)}y.reverse();let F=y.filter(A=>A.value!==null),oe=F[0],G=F[F.length-1],At=oe&&G&&oe!==G?{from:{day:oe.day,value:oe.value},to:{day:G.day,value:G.value},delta:G.value-oe.value}:null,et=c.calc,ao={metric_id:r,name:a.name,version:u,today:d,time_zone:s.time_zone??null,every:i,days:y,change:At,history_from:_,complete:S===null,...S?{stopped:S,not_fetched:b}:{}};e.print.result(ao,A=>{let J=e.print.style,V=(E,ye,nt)=>(fe(et)?Se(et,E,ye,nt):Ve(et,E,ye,nt))??"no value",U=Math.max(...A.days.map(E=>Ae(E.day).length),0),tt=E=>E.basis==="measured"?E.status==="stale"?"measured, stale":"measured":"worked out from the history",Ot=[`${String(A.name)} (version ${String(A.version)}), ${A.every==="week"?"weekly":"daily"} over the last ${o} days:`];for(let E of A.days){let ye=E.reason?Un(E.reason):"",nt=E.basis==="not_measured"?`not measured yet (${E.reason})`:E.value===null?`no value${ye?`: ${ye}`:""}`:`${V(E.value,E.numerator,E.denominator)} (${tt(E)})`;Ot.push(`${Ae(E.day).padEnd(U)} ${nt}`)}if(A.change){let E=A.change.delta,ye=E>0?"+":E<0?"-":"",nt=Ft(et)?`${ye}${Math.abs(E*100).toFixed(1)} points`:`${ye}${O(Math.abs(E))}${A.change.from.value?` (${ye}${Math.abs(E/A.change.from.value*100).toFixed(1)}%)`:""}`;Ot.push("",`From ${V(A.change.from.value,null,null)} on ${Ae(A.change.from.day)} to ${V(A.change.to.value,null,null)} on ${Ae(A.change.to.day)}: ${nt}.`)}return A.history_from&&Ot.push(J.dim(`No values can be worked out before ${Ae(A.history_from)}, where the history starts.`)),S&&Ot.push(J.orange(`Stopped at ${Ae(S.day)}: ${S.reason}; ${b} earlier day${b===1?"":"s"} without a measured value ${b===1?"was":"were"} not fetched.`)),Ot.join(`
|
|
82
|
+
`)})}async function $s(e,t){let n=t.trim().toLowerCase(),o=(await vn(e)).find(i=>typeof i.name=="string"&&i.name.trim().toLowerCase()===n);if(!o||typeof o.id!="string")throw H("unknown_metric",`no metric in this workspace is named "${t.trim()}"`,"See their names: closedloop metrics list");return o.id}async function vc(e,t,n){let o=await se(e,t,Ke,"metrics","closedloop metrics list")??await $s(e,t);if(!n.yes){if(!e.interactive)throw new p("confirmation_required","deleting a metric needs confirmation",g.Usage,`Run it again with --yes: closedloop metrics delete ${ee(t,e.runtime.platform)} --yes`);if((await e.runtime.prompt("Delete this metric? It comes off each of your goals that measures it (a key result takes its indicators); its history stays. Type yes to confirm: ")).trim().toLowerCase()!=="yes"){e.print.result({status:"cancelled",id:o},()=>"Nothing changed.");return}}let i=await _r(e,o,n.reason),r=i.removed.filter(w),s={status:"deleted",id:o,archived_at:w(i.metric)?i.metric.archived_at:null,removed:r};e.print.result(s,a=>{let l=a.removed.map(c=>{let d=Array.isArray(c.kpi_ids)?c.kpi_ids.length:0;return`${String(c.title)} (${d} metric${d===1?"":"s"})`});return`Deleted the metric.${l.length?` It came off ${l.length} goal${l.length===1?"":"s"}: ${l.join(", ")}.`:""} Its history stays.`})}function vs(e,t){let n="The segment: its name, a number from the last segments list, a segment id, or its rule as a JSON file, inline JSON or - for standard input.",o=`The calculation: count, or JSON (inline, a file, or -). ${Vt}.`,i=`A formula over saved metrics instead of a segment and a calculation. ${Gt}`;e.command("define").argument("<name>",`The metric's name, e.g. "share that turned on payments".`).description("Save a calculation over a segment, or a formula over saved metrics, as a named metric (version 1). It is measured every night; a change makes a new version.").option("--segment <segment>",n).option("--calc <calc>",o).option("--formula <formula>",i).option("--description <text>","What the metric shows.").action(async(r,s)=>hc(t,r,s)),e.command("preview").description("Measure a calculation over a segment, or a formula over saved metrics, now without saving it: the value, a share's totals, and how many members have no value.").option("--segment <segment>",n).option("--calc <calc>",o).option("--formula <formula>",i).action(async r=>gc(t,r)),e.command("list").description("Your workspace's metrics, numbered, each with its calculation, segment and latest measured value.").action(async()=>wc(t)),e.command("value").argument("<metric>","A number from the last metrics list, or a metric id.").description("The metric's value on a past day: the number measured that night, else computed from the history; says when it cannot be.").option("--as-of <day>","The day, YYYY-MM-DD, before today.").option("--metric-version <number>","Which version of the metric; the current one by default.").action(async(r,s)=>_c(t,r,s.asOf,s.metricVersion)),e.command("history").argument("<metric>","A number from the last metrics list, a metric id, or its name.").description("The metric day by day, oldest first: the value measured that night, else worked out from the history, else why there is none; and the change from the first value to the last.").option("--days <n>","How many days back, today included (default 30).").option("--every <step>","day (the default) or week: one value a week, on the same weekday as today.").option("--metric-version <number>","Which version of the metric; the current one by default.").action(async(r,s)=>$c(t,r,s)),e.command("delete").argument("<metric>","A number from the last metrics list, a metric id, or its name.").description("Delete a metric: it comes off each of your goals that measures it (a key result takes its indicators), and its history stays. Refused when a teammate's goal measures it, when it is an active goal's only key result, or when a formula uses it.").option("--reason <text>","Why, kept in each goal's history.").option("--yes","Skip the confirmation.").action(async(r,s)=>vc(t,r,s))}var Cs="last-metrics.json",Mn=(e,t,n)=>new p(e,t,g.Usage,n),Cc=/^\d{4}-\d{2}-\d{2}$/,ue=e=>typeof e=="string"&&Cc.test(e)?q(`${e}T00:00:00Z`):String(e),I=e=>typeof e=="number"&&Number.isFinite(e)?e:null;function As(e){return e===7?"week":e===1?"day":`${e} days`}function _t(e){return e===7?"weekly":e===1?"daily":`${e}-day`}function kc(e,t){if(t===7)return`about ${e} week${e===1?"":"s"}`;let n=e*t;return`about ${n} day${n===1?"":"s"}`}var ks=e=>`${e>0?"+":""}${O(e)}`;function bt(e,t){let n=I(e.every_days)??7,o=As(n),i=I(e.lag_points),r=I(e.correlation),s=I(e.moves)??0,a=I(e.followed)??0,l=i!==null?kc(i,n):"",u=e.direction==="opposite";if(e.verdict==="leads")return`Leads: it moved ${l} before the key result${u?", in the other direction (when it rose, the key result fell)":""}, ${a} of ${s} times (correlation of ${_t(n)} changes ${r===null?"?":r.toFixed(2)}).`;if(e.verdict==="not_enough_history"){let d=I(e.points)??0,m=e.history_from?` from ${ue(e.history_from)}`:"",h=w(e.history_ends)?e.history_ends:null,f=h?.series==="indicator"?"the indicator":"the key result",y=h?h.basis==="not_reached"?` The ${o} of ${ue(h.day)} was not read in time; try again.`:h.basis==="failed"?` ${f[0].toUpperCase()}${f.slice(1)} could not be read for ${ue(h.day)}${typeof h.reason=="string"?` (${h.reason})`:""}.`:typeof h.reason=="string"?` For ${f}, ${h.reason.replace(/^not read: /,"")}.`:"":"";return`Not enough history to tell: ${d} ${_t(n)} point${d===1?"":"s"}${m}; at least ${String(e.needed_points)} are needed.${y}`}let c=I(t.min_correlation);switch(e.reason){case"moves_together":return`No lead: it changes in the same ${o} as the key result, not before it (correlation ${r===null?"?":r.toFixed(2)}).`;case"weak":return`No lead: its changes match the key result's later changes only weakly (best: correlation ${r===null?"none":r.toFixed(2)}${l?` ${l} later`:""}; a lead needs ${c??.5} or more either way).`;case"too_few_moves":return`No lead shown: it made only ${s} clear move${s===1?"":"s"} in this time; at least ${String(t.min_moves??3)} are needed.`;case"not_followed_often":return`No lead: the key result followed only ${a} of its ${s} moves ${l} later; a lead needs two of every three.`;case"indicator_flat":return"No lead: the indicator did not change in this time.";case"key_result_flat":return"No lead: the key result did not change in this time, so nothing can be shown to lead it.";default:return"No lead found."}}function Sc(e){let t=Array.isArray(e.learnings)?e.learnings.filter(w):[];if(!t.length)return[];let n=As(I(e.every_days)??7);return["When the key result did not follow:",...t.map(o=>{let i=I(o.indicator_from)??0,r=I(o.indicator_to)??0,s=(I(o.key_result_to)??0)-(I(o.key_result_from)??0),a=I(o.key_result_usual_change)??0;return` ${ue(o.from_day)} to ${ue(o.day)}: the indicator ${r>=i?"rose":"fell"} from ${O(i)} to ${O(r)}; from ${ue(o.key_result_from_day)} to ${ue(o.key_result_day)} the key result changed by ${ks(s)} (usually ${ks(a)} a ${n}).`})]}function Ac(e){let t=Array.isArray(e)?e.filter(w):[],n=t.filter(i=>i.basis==="measured"&&I(i.value)!==null).length,o=t.filter(i=>i.basis==="rebuilt"&&I(i.value)!==null).length;return[n?`${n} measured`:"",o?`${o} rebuilt from the history`:""].filter(Boolean).join(", ")}function Oc(e){let t=Array.isArray(e)?e.filter(w):[];if(!t.length||!t.every(o=>o.basis==="not_read"))return null;let n=typeof t[0].reason=="string"?t[0].reason.replace(/^not read: /,""):"";return n?`${n[0].toUpperCase()}${n.slice(1)}, so the indicator was not read.`:"The indicator was not read."}function qn(e){switch(e){case"cut_short":return"it took longer than ClosedLoop AI's background runs allow";case"interrupted":return"the background run stopped while computing it";case"rule_invalid":return"a rule it reads can no longer be used as written";case"point_timeout":return"a point of its history took too long to read";case"point_failed":return"a point of its history could not be read";default:return`a read failed${typeof e=="string"?` (${e})`:""}`}}function Tc(e){return e.status==="failed"?`Could not be finished: ${qn(e.error_code)}. Ask again tomorrow.`:e.status==="running"?"Being computed now.":"Queued: ClosedLoop AI computes it in the background, usually within minutes."}var ii=e=>Array.isArray(e.checks)?e.checks.filter(w):[],Hn=e=>w(e.counts)?(I(e.counts.queued)??0)+(I(e.counts.running)??0):0,Gn=e=>w(e.indicator)&&typeof e.indicator.name=="string"?e.indicator.name:"the indicator";function Rc(e,t,n){let o=e.print.style,i=w(t.rules)?t.rules:{},r=w(t.key_result)&&typeof t.key_result.name=="string"?t.key_result.name:"the metric",s=ii(t).find(y=>w(y.indicator)&&y.indicator.id===n)??ii(t)[0];if(!s)return`No lead check of this pair yet. Ask for it: closedloop metrics leads ${String(w(t.key_result)?t.key_result.id:"")} --indicator ${n}`;let a=[o.bold(`Does "${Gn(s)}" lead "${r}"?`)],l=w(s.check)?s.check:null;if(s.status!=="done"||!l)return[...a,Tc(s)].join(`
|
|
83
|
+
`);a.push(bt(l,i));let u=w(s.series)?s.series:{},c=Oc(u.indicator);c&&a.push(c);let d=y=>Array.isArray(y)?y.filter(w).map(_=>I(_.value)).filter(_=>_!==null):[],m=I(l.points)??0;if(m&&l.history_from){let y=Ac(u.key_result);a.push(o.dim(`${m} ${_t(I(l.every_days)??7)} points from ${ue(l.history_from)}${y?` (key result: ${y})`:""}; checked for ${ue(s.as_of)}.`))}let h=d(u.indicator),f=d(u.key_result);return h.length>1&&f.length>1&&a.push(o.dim(`indicator ${o.sparkline(h)}`),o.dim(`key result ${o.sparkline(f)}`)),a.push(...Sc(l)),a.join(`
|
|
84
|
+
`)}function Ec(e,t){let n=t.map(i=>w(i.check)?i.check:{});if(!n.length)return null;if(n.every(i=>i.verdict==="not_enough_history"&&w(i.history_ends)&&i.history_ends.series==="key_result")){let i=n[0],r=w(i.history_ends)?i.history_ends:{},s=typeof r.reason=="string"?` (${r.reason})`:"";return`Not enough history of "${e}" to find what leads it: ${String(i.points)} ${_t(I(i.every_days)??7)} points${i.history_from?` from ${ue(i.history_from)}`:""}${s}; at least ${String(i.needed_points)} are needed.`}return n.every(i=>i.reason==="key_result_flat")?`"${e}" did not change over the last ${String(n[0].points)} ${_t(I(n[0].every_days)??7)} points, so nothing can be shown to lead it.`:null}function Ic(e,t,n){let o=e.print.style;if(typeof t.not_supported=="string")return t.not_supported;let i=w(t.rules)?t.rules:{},r=w(t.key_result)&&typeof t.key_result.name=="string"?t.key_result.name:"this metric",s=ii(t).filter(f=>w(f.indicator)&&f.indicator.kind==="candidate"),a=s.filter(f=>f.status==="done"&&w(f.check)),l=Ec(r,a),u=[];if(l)u.push(l);else{let f=Array.isArray(t.suggestions)?t.suggestions.filter(w):[],y=a.find(b=>w(b.check)&&b.check.history_from),_=y&&w(y.check)?o.dim(` (${String(y.check.points)} ${_t(I(y.check.every_days)??7)} points from ${ue(y.check.history_from)})`):"";u.push(o.bold(`Indicators that led "${r}" on this workspace's history`)+_),f.length||u.push(a.length?"None of the candidates checked so far led it.":"No candidate has been checked yet."),f.forEach((b,F)=>{let oe=w(b.check)?b.check:{},G=w(b.indicator)?b.indicator:{};u.push(`${F+1} ${o.bold(String(G.name))}`,` ${bt(oe,i)}`,o.dim(` Save it: ${fn(G.rule,At=>`closedloop metrics define ${ee(`${r}: ${String(G.name)}`,e.runtime.platform)} --segment ${At} --calc count`,e.runtime.platform)}`))});let S=a.filter(b=>w(b.check)&&b.check.verdict!=="leads");if(S.length){u.push("","Checked, no lead found:");for(let b of S)u.push(` ${Gn(b)}: ${bt(b.check,i)}`)}}let c=s.filter(f=>f.status==="failed");if(c.length){u.push("",o.orange("Could not be finished:"));for(let f of c)u.push(o.orange(` ${Gn(f)}: ${qn(f.error_code)}`))}let d=s.filter(f=>f.status==="queued"||f.status==="running");d.length&&u.push("",`Still being checked (${d.length}): ${d.map(Gn).join("; ")}.`,o.dim(`ClosedLoop AI checks them in the background. See them: closedloop metrics leads ${n} --wait`));let m=Array.isArray(t.skipped)?t.skipped.filter(w):[];if(m.length){u.push("",o.dim("Not checked, no data for them:"));for(let f of m)u.push(o.dim(` ${String(f.name)}: ${String(f.reason)}`))}let h=Array.isArray(t.unknown)?t.unknown.filter(f=>typeof f=="string"):[];return h.length&&u.push("",o.orange(`Unknown candidates: ${h.join(", ")}.`)),u.join(`
|
|
85
|
+
`)}var Ss=15e3,ri=30*6e4;async function Pc(e,t,n,o){let i=o,r=e.print.spinner(`Waiting for ClosedLoop AI to finish ${Hn(i)} lead check${Hn(i)===1?"":"s"} (up to ${ri/6e4} minutes)`);try{for(let s=0;s<ri/Ss&&Hn(i)>0;s+=1){await e.runtime.sleep(Ss);let a=await v(e.runtime,t,"GET",n);if(a.status!==200)throw C(a,"api_error","The lead checks could not be read. Try again.");i={...xe(a.body),...xc(o,["queued","skipped","unknown","not_supported"])}}}finally{r.stop()}return i}var xc=(e,t)=>Object.fromEntries(t.filter(n=>e[n]!==void 0).map(n=>[n,e[n]]));async function Nc(e,t,n){let o=await se(e,t,Cs,"metrics","closedloop metrics list");if(o===null)throw Mn("invalid_id",`'${t}' is not a metric id or a number from the last metrics list`,"List them: closedloop metrics list");if(n.indicator!==void 0&&n.candidates!==void 0)throw Mn("indicator_and_candidates","give --indicator or --candidates, not both");let i=n.indicator===void 0?void 0:await se(e,n.indicator,Cs,"metrics","closedloop metrics list");if(i===null)throw Mn("invalid_id",`'${n.indicator}' is not a metric id or a number from the last metrics list`,"List them: closedloop metrics list");let r=n.metricVersion;if(r!==void 0&&!/^[1-9][0-9]*$/.test(r))throw Mn("invalid_version","--metric-version must be a version number");let s={...i&&{indicator:i},...n.candidates&&{candidates:n.candidates},...r&&{version:Number(r)}},a=$(e.runtime),l=await v(e.runtime,a,"POST",`/api/metrics/${o}/leads`,s);if(l.status===404){let h=typeof l.body.error=="string"&&l.body.error?l.body.error:"There is no such metric.";throw new p("not_found",h,g.Failure,"See them: closedloop metrics list")}if(l.status!==200)throw C(l,"api_error","The lead check could not be asked for. Try again.");let u=xe(l.body),c=new URLSearchParams({...i&&{indicator:i},...r&&{version:r}}).toString();n.wait&&(u=await Pc(e,a,`/api/metrics/${o}/leads${c?`?${c}`:""}`,u));let d=`${i?` --indicator ${n.indicator}`:""}${r?` --metric-version ${r}`:""}`,m=Hn(u)>0?`closedloop metrics leads ${t}${d} --wait`:i?"closedloop metrics list":`closedloop metrics leads ${t} --indicator <metric>`;e.print.result({...u,next:m},h=>i?Rc(e,h,i):Ic(e,h,t))}function Os(e,t){e.command("leads").argument("<metric>","The key result: a number from the last metrics list, or a metric id.").description("Whether an indicator's history moves before this metric's, and when it moved and the metric did not follow; without --indicator, the indicators that led it on this workspace's history. ClosedLoop AI computes the checks in the background; this asks for them and shows what is known.").option("--indicator <metric>","The indicator to check: a number from the last metrics list, or a metric id.").option("--candidates <key...>","Only these candidates (keys from an earlier answer).").option("--metric-version <number>","Which version of the key result (a goal's key result stays on the version the goal pins); the current one if not given.").option("--wait",`Wait until the checks are done (up to ${ri/6e4} minutes).`).action(async(n,o)=>Nc(t,n,o))}var Es="last-kpis.json",Ts=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,Lc=/^\d{4}-\d{2}-\d{2}$/,Jn="A target is one line: >=1400, <=0.3, between 40 and 60, +500, +10% or, for a share, +5pts.",Bt=(e,t,n)=>new p(e,t,g.Usage,n),Dc=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),Xe=(e,t,n=t)=>Dc(e,t)?e[t]:n,jc={week:"weekly",month:"monthly",quarter:"quarterly"},Fc={cli:"through the CLI",app:"in the app",mcp:"through an AI assistant",api:"through the API",job:"automatically"},Is={key_result:"key result",indicator:"indicator"},ai={key_result:"as a key result",indicator:"as an indicator"},ce=e=>typeof e=="string"&&Lc.test(e)?q(`${e}T00:00:00Z`):String(e??"an unknown day"),Ps=e=>new Date(Date.parse(`${e}T00:00:00Z`)-864e5).toISOString().slice(0,10),Yt=e=>e.period==="days"?`every ${e.period_days} days`:Xe(jc,e.period),ze=(e,t)=>t===null?"no value":Ft(e)?`${(t*100).toFixed(1)}%`:fe(e)?Se({},t,null,null):O(t);function li(e){let t=e.written,n=t.value<0?"-":"+",o=Math.abs(t.value)>0&&Math.abs(t.value)<1?String(Number(Math.abs(t.value).toPrecision(3))):O(Math.abs(t.value));return t.as==="percent"?`${n}${o}%`:t.as==="points"?`${n}${o} points`:`${n}${o}`}function xs(e,t){return t.condition==="between"?t.low===null||t.high===null?null:`between ${ze(e,t.low)} and ${ze(e,t.high)}`:t.condition==="at_least"?t.low===null?null:`at least ${ze(e,t.low)}`:t.high===null?null:`at most ${ze(e,t.high)}`}function ui(e){let t=e.target;if(!t)return"no target yet";let n=e.data_series.calc,o=xs(n,t);if(t.written.as==="number")return o??"a target without a bound";let i=e.baseline&&e.baseline.value!==null?`from ${ze(n,e.baseline.value)} on ${ce(e.baseline.as_of)}`:"from the baseline";return o?`${o} (${li(t)} ${i})`:`${li(t)} from the baseline, once it is measured`}function Ns(e,t){let n=t.target,o=n?xs(t.calc,n)??`${li(n)} from the baseline, once it is measured`:"no target yet",i=t.current,r=i?(fe(t.calc)?Se(t.calc,i.value,i.numerator,i.denominator):Ve(t.calc,i.value,i.numerator,i.denominator))??"no value":null,s=i?`now ${r} on ${ce(i.as_of)}${i.status==="stale"?` ${e.orange("(stale)")}`:""}`:"not measured yet";return`${t.name}: ${o}, ${Yt(t)} \xB7 ${s}`}function ci(e,t=!1){return!e.baseline&&t?"baseline measured when the goal is activated":e.baseline?`baseline ${ze(e.data_series.calc,e.baseline.value)} on ${ce(e.baseline.as_of)}`:"baseline not measured yet (it is measured tonight)"}function Ls(e,t,n=!1){if(!t.current)return"not measured yet";let o=t.current;return`now ${(fe(t.data_series.calc)?Se(t.data_series.calc,o.value,o.numerator,o.denominator):Ve(t.data_series.calc,o.value,o.numerator,o.denominator))??"no value"} on ${ce(o.as_of)}${o.status==="stale"?` ${e.orange("(stale: a source it reads is late)")}`:""}`+(n?e.dim(" (a preview: periods start when the goal is activated)"):"")}function Uc(e,t,n){let o=`${ce(n.start)} to ${ce(Ps(n.end))}`;if(n.value===null||n.value===void 0)return`${o}: ${n.status==="failed"||n.status==="not_measured"?"no value":`not measured yet (the run of ${ce(n.end)} settles it)`}`;let i=n.result==="met"?e.green("met"):n.result==="missed"?e.red("missed"):"not judged yet";return`${o}: ${ze(t.data_series.calc,n.value)}, ${i}${n.status==="stale"?e.dim(" (stale)"):""}`}function Wc(e,t,n){return t?t.status==="done"&&t.check?`lead check of ${ce(t.as_of)}: ${bt(t.check,n)}`:t.status==="failed"?e.orange(`lead check of ${ce(t.as_of)} could not be finished: ${qn(t.error_code)}`):e.dim(`lead check ${t.status==="running"?"being computed now":"queued"}: ClosedLoop AI computes it in the background; list the goal again later`):e.dim("lead check: none (an archived metric is not checked)")}function Mc(e,t,n,o,i={},r=!1){let s=" ".repeat(n.length+2),a=t.role==="indicator"?`indicator of ${t.key_result_id&&o.has(t.key_result_id)?o.get(t.key_result_id):"a key result"}`:"key result",l=t.data_series,u=`version ${l.version}${l.current_version!==null&&l.current_version!==l.version?` (the metric is at version ${l.current_version})`:""}`,d=[`${n} ${e.bold(l.name)}${e.dim(` \xB7 ${a} \xB7 ${Yt(t)} \xB7 ${u}`)}`+(t.archived_at?e.dim(` \xB7 archived ${q(t.archived_at)}`):""),`${s}target ${ui(t)} \xB7 ${ci(t,r)}`,`${s}${Ls(e,t,r)}`];for(let m of t.periods)d.push(`${s}${Uc(e,t,m)}`);if(t.current_period&&d.push(`${s}${e.dim(`this period: ${ce(t.current_period.start)} to ${ce(Ps(t.current_period.end))}`)}`),t.role==="indicator"&&t.lead_check!==void 0&&d.push(`${s}${Wc(e,t.lead_check,i)}`),t.role==="key_result"&&t.suggested_indicators?.length){d.push(`${s}suggested indicators (they led it on this workspace's history):`);for(let m of t.suggested_indicators)d.push(`${s} ${e.bold(typeof m.indicator.name=="string"?m.indicator.name:"an indicator")}: ${m.check?bt(m.check,i):""}`);d.push(`${s}${e.dim(`save one as a metric: closedloop metrics leads ${l.id}${l.current_version!==null&&l.current_version!==l.version?` --metric-version ${l.version}`:""}`)}`)}return d.push(`${s}${e.dim(`id ${t.id}`)}`),d}function di(e,t,n={},o=!1){let i=String(t.length).length,r=new Map(t.map((s,a)=>[s.id,String(a+1)]));return t.map((s,a)=>Mc(e,s,String(a+1).padStart(i),r,n,o).join(`
|
|
85
86
|
`)).join(`
|
|
86
87
|
|
|
87
|
-
`)}async function
|
|
88
|
-
${
|
|
89
|
-
`)}`,g.Failure,`Your saved metrics: closedloop metrics list. ${
|
|
90
|
-
|
|
91
|
-
${a.dim(`Days and periods are counted in ${s.time_zone}; a period is settled by the value measured the night after it ends.`)}`:"";return`${
|
|
92
|
-
`));return}let d=a.body.kpi;if(a.status!==201||!d||typeof d.id!="string")throw
|
|
93
|
-
`)})}async function
|
|
94
|
-
`))}async function
|
|
95
|
-
`)})}var
|
|
96
|
-
|
|
97
|
-
${m.dim(`${
|
|
98
|
-
`)}${y}`})}function
|
|
99
|
-
`).map(
|
|
100
|
-
`);function
|
|
101
|
-
`)),
|
|
102
|
-
|
|
103
|
-
${d.dim(`Showing the first ${c.goals.length}.`)}`:"";return`${
|
|
104
|
-
|
|
105
|
-
`)}${
|
|
106
|
-
|
|
107
|
-
${d.dim("Read one: closedloop goals show <number>")}`})}async function
|
|
108
|
-
${
|
|
109
|
-
${
|
|
110
|
-
`)}`:`A draft, ready to activate: closedloop goals activate ${c.id}`),c.values&&c.values.length&&(m.push("","Values:",...c.values.slice(0,10).map(
|
|
111
|
-
`)})}async function
|
|
112
|
-
`);let c=(m,f,
|
|
113
|
-
`)})}async function
|
|
88
|
+
`)}async function mi(e,t,n){let o=n.trim();if(Ts.test(o))return o.toLowerCase();if(/^\d{1,3}$/.test(o)){let i=await Le(e,Es,Number(o)),r=i?.goal_id===t&&Array.isArray(i.ids)?i.ids:[],s=Number(o),a=r[s-1];if(s>=1&&s<=r.length&&typeof a=="string"&&Ts.test(a))return a.toLowerCase();throw Bt("unknown_number",r.length?`the last metric list of this goal had ${r.length}, not ${s}`:"there is no recent metric list of this goal to pick a number from","List them first: closedloop goals metrics list <goal>")}throw Bt("invalid_id",`'${n}' is not a goal metric id or a number from the last goals metrics list`)}function $t(e,t){return e.status===404?new p("goal_not_found","there is no goal (or goal metric) with that id you can see",g.Failure):e.status===403?new p("not_your_goal","only the goal's owner changes or measures its metrics",g.Failure):C(e,"api_error",t)}function Ds(e,t){if(e.status!==400||!Array.isArray(e.body.problems))return null;let n=e.body.problems,o=n.map(i=>`${i.field}: ${i.message}${i.allowed&&i.allowed.length?` (allowed: ${i.allowed.join(", ")})`:""}`);return new p("invalid_kpi",`${t}; fix ${n.length===1?"this":`these ${n.length}`}:
|
|
89
|
+
${o.join(`
|
|
90
|
+
`)}`,g.Failure,`Your saved metrics: closedloop metrics list. ${Jn}`)}async function pi(e,t,n){let o=$(e.runtime),i=await v(e.runtime,o,"GET",`/api/user-goals/${t}/kpis${n?"?include=ended":""}`);if(i.status!==200||!Array.isArray(i.body.kpis))throw $t(i,"The goal's metrics could not be loaded. Try again.");let r=i.body.kpis;await Ce(e,Es,{goal_id:t,ids:r.map(a=>a.id)});let s=i.body.goal;return{kpis:r,time_zone:typeof i.body.time_zone=="string"?i.body.time_zone:null,today:typeof i.body.today=="string"?i.body.today:null,lead_rules:i.body.lead_rules&&typeof i.body.lead_rules=="object"?i.body.lead_rules:{},state:typeof s?.state=="string"?s.state:null}}async function Hc(e,t,n,o){let i=await t(e,n),r={goal_id:i,...await pi(e,i,o)};e.print.result(r,s=>{let a=e.print.style;if(!s.kpis.length)return`This goal has no metrics. Add a key result: closedloop goals metrics add ${s.goal_id} --metric <metric> --period month --target '>=100'`;let l=s.time_zone?`
|
|
91
|
+
|
|
92
|
+
${a.dim(`Days and periods are counted in ${s.time_zone}; a period is settled by the value measured the night after it ends.`)}`:"";return`${di(a,s.kpis,s.lead_rules,s.state==="draft")}${l}`})}var js=e=>/^\d{1,10}$/.test(e)?Number(e):e,Gc=e=>e.trim().replace(/-/g,"_");async function qc(e,t,n,o){let i=await t(e,n);if(o.metric===void 0)throw Bt("missing_metric","give the saved metric with --metric <number|id|name>","Your saved metrics: closedloop metrics list");let r={metric:await se(e,o.metric,Ke,"metrics","closedloop metrics list")??o.metric};o.role!==void 0&&(r.role=Gc(o.role)),o.keyResult!==void 0&&(r.key_result=await mi(e,i,o.keyResult)),o.period!==void 0&&(r.period=o.period),o.days!==void 0&&(r.days=js(o.days)),o.target!==void 0&&(r.target=o.target),o.reason!==void 0&&(r.reason=o.reason),o.dryRun&&(r.dry_run=!0);let s=$(e.runtime),a=await v(e.runtime,s,"POST",`/api/user-goals/${i}/kpis`,r),l=Ds(a,"the metric was not added");if(l)throw l;if(a.status===409&&typeof a.body.kpi_id=="string")throw new p("kpi_exists","this goal already measures that metric this way",g.Failure,`See it: closedloop goals metrics list ${i}`);if(a.status===409)throw new p("goal_archived",String(a.body.error??"this goal is archived; its metrics can no longer change"),g.Failure);let u=a.body.goal_relevance_added??[],c=u.length?`The goal is now about insights bearing on ${u.map(f=>f.replace(/_/g," ")).join(" or ")}.`:null;if(o.dryRun&&a.status===200&&a.body.kpi){let f=a.body.kpi;e.print.result({dry_run:!0,kpi:f,goal_relevance_added:u},y=>[`Checked, nothing saved: ${y.kpi.metric.name} (version ${y.kpi.metric.version}) ${Xe(ai,y.kpi.role)}, ${Yt(y.kpi)}.`,...c?[`Saving it would make the goal about insights bearing on ${u.map(_=>_.replace(/_/g," ")).join(" or ")}.`]:[]].join(`
|
|
93
|
+
`));return}let d=a.body.kpi;if(a.status!==201||!d||typeof d.id!="string")throw $t(a,"The metric could not be added. Try again.");let m=a.body.measured===!0,h=a.body.goal_state==="draft";e.print.result({measured:m,kpi:d,goal_relevance_added:u},f=>{let y=[`Added ${f.kpi.data_series.name} (version ${f.kpi.data_series.version}) ${Xe(ai,f.kpi.role)}, ${Yt(f.kpi)}: target ${ui(f.kpi)}.`,f.measured?`Its ${ci(f.kpi)}.`:h?"The goal is a draft: its baseline is measured when you activate it.":"Its baseline could not be measured now; the nightly run measures it."];return c&&y.push(c),y.join(`
|
|
94
|
+
`)})}async function Jc(e,t,n,o,i){let r=await t(e,n),s=await mi(e,r,o);if(i.target===void 0)throw Bt("missing_target","give the new target with --target",Jn);if(i.reason===void 0)throw Bt("missing_reason","say why the target changes with --reason; it is kept in the goal's history");let a={target:i.target,reason:i.reason};i.period!==void 0&&(a.period=i.period),i.days!==void 0&&(a.days=js(i.days));let l=$(e.runtime),u=await v(e.runtime,l,"POST",`/api/user-goals/${r}/kpis/${s}/target`,a),c=Ds(u,"the target was not set");if(c)throw c;if(u.status===409)throw new p("kpi_archived",String(u.body.error??"this metric or its goal is archived"),g.Failure);let d=u.body.kpi;if(u.status!==200||!d||typeof d.id!="string")throw $t(u,"The target could not be set. Try again.");let m=u.body.measured===!0,h=u.body.goal_state==="draft";e.print.result({measured:m,kpi:d},f=>[`New target for ${f.kpi.data_series.name}, ${Yt(f.kpi)}: ${ui(f.kpi)}.`,f.measured?`Its new ${ci(f.kpi)}.`:h?"The goal is a draft: its baseline is measured when you activate it.":"Its new baseline could not be measured now; the nightly run measures it."].join(`
|
|
95
|
+
`))}async function Vc(e,t,n,o,i){let r=await t(e,n),s=await mi(e,r,o),a=$(e.runtime),l=await v(e.runtime,a,"POST",`/api/user-goals/${r}/kpis/${s}/archive`,i===void 0?{}:{reason:i});if(l.status===404)throw new p("kpi_not_found","this goal has no metric with that id you can see",g.Failure);if(l.status===409)throw new p("kpi_archived",String(l.body.error??"this metric is already archived"),g.Failure);let u=l.body.archived;if(l.status!==200||!u)throw $t(l,"The metric could not be archived. Try again.");let c=Array.isArray(l.body.also_archived)?l.body.also_archived:[];e.print.result({status:"archived",id:u.id,archived_at:u.archived_at,also_archived:c},d=>`Archived the metric${d.also_archived.length?` and its ${d.also_archived.length} indicator${d.also_archived.length===1?"":"s"}`:""}. Its history stays; see it with closedloop goals metrics list ${r} --ended.`)}async function Bc(e,t,n){let o=await t(e,n),i=$(e.runtime),r=await v(e.runtime,i,"POST",`/api/user-goals/${o}/measure`);if(r.status===409)throw new p("measure_refused",String(r.body.error??"the goal cannot be measured now"),g.Failure);if(r.status!==200||!Array.isArray(r.body.measured)||!Array.isArray(r.body.kpis))throw $t(r,"The goal could not be measured. Try again.");let s={measured:r.body.measured,kpis:r.body.kpis};e.print.result(s,a=>{if(!a.kpis.length)return"This goal has no metrics to measure.";let l=e.print.style;return a.kpis.map(u=>{let c=Ls(l,u);return`${l.bold(u.data_series.name)}${l.dim(` \xB7 ${Xe(Is,u.role)}`)}: ${c}`}).join(`
|
|
96
|
+
`)})}var Rs=e=>typeof e=="number"?e:typeof e=="string"&&e.trim()!==""&&Number.isFinite(Number(e))?Number(e):null;function si(e){let t=Rs(e.target_low),n=Rs(e.target_high);return e.target_condition==="between"?`between ${t===null?"?":O(t)} and ${n===null?"?":O(n)}`:e.target_condition==="at_least"?t===null?"at least a change from the baseline":`at least ${O(t)}`:e.target_condition==="at_most"?n===null?"at most a change from the baseline":`at most ${O(n)}`:"no target"}function Yc(e,t){let n=e.before??{},o=e.after;if(e.entity==="value"){let r=typeof o.measured_at=="string"?t?bn(o.measured_at,t):q(o.measured_at):"an unknown day";return e.action==="create"?`set the number to ${o.value} for ${r}`:`replaced ${n.value} with ${o.value} for ${r}`}if(e.entity==="kpi"){let r=e.metric?`${e.metric.name}${e.metric.version===null?"":` (version ${e.metric.version})`}`:"a metric no longer listed",s=typeof o.role=="string"?Xe(Is,o.role):"metric";return e.action==="create"?`added ${r} ${Xe(ai,String(o.role),"as a metric")}${o.target_condition?`, target ${si(o)}`:""}`:e.action==="update"?`set a new target for ${r}: ${si(o)} (was ${si(n)})`:`archived the ${s} ${r}`}if(e.action==="create")return"created the goal";if(e.action==="archive")return"deleted the goal (archived)";if(n.state!==o.state&&o.state==="active")return"activated the goal";if(n.state!==o.state&&o.state==="closed")return"closed the goal";let i=Object.keys(o).filter(r=>r!=="updated_at"&&JSON.stringify(o[r])!==JSON.stringify(n[r])).sort();return`changed the goal${i.length?` (${i.join(", ")})`:""}`}async function Kc(e,t,n,o){let i=await t(e,n),r=[o.limit!==void 0?`limit=${encodeURIComponent(o.limit)}`:"",o.page!==void 0?`page=${encodeURIComponent(o.page)}`:""].filter(Boolean).join("&"),s=$(e.runtime),a=await v(e.runtime,s,"GET",`/api/user-goals/${i}/history${r?`?${r}`:""}`);if(a.status!==200||!Array.isArray(a.body.changes))throw $t(a,"The goal's history could not be loaded. Try again.");let l=a.body.changes.map(d=>({...d,actor:{kind:d.actor?.kind,user_id:d.actor?.user_id??null,is_you:d.actor?.is_you===!0}})),u={page:Number(a.body.page),limit:Number(a.body.limit),total:Number(a.body.total),changes:l},c=typeof a.body.time_zone=="string"?a.body.time_zone:null;e.print.result(u,d=>{if(!d.changes.length)return d.total?`No changes on page ${d.page}; there are ${d.total}.`:"No changes yet.";let m=e.print.style,h=d.changes.map(_=>{let S=_.actor.kind==="system"?"ClosedLoop AI":_.actor.is_you?"you":"a teammate",b=_.reason?` \xB7 "${_.reason}"`:"";return`${m.dim(R(_.at))} ${S} ${Yc(_,c)} ${m.dim(Xe(Fc,_.channel,""))}${b}`}),f=(d.page-1)*d.limit+d.changes.length,y=f<d.total?`
|
|
97
|
+
|
|
98
|
+
${m.dim(`${f} of ${d.total}. More: --page ${d.page+1}`)}`:"";return`${h.join(`
|
|
99
|
+
`)}${y}`})}function Fs(e,t,n){let o=e.command("metrics").alias("metric").alias("kpis").alias("kpi").description("A goal's metrics: each a saved metric (closedloop metrics list) at the version it was added with, as a key result or an indicator of one, with a period and a target.");o.command("list").argument("<goal>","A number from the last goals list, or a goal id.").description("A goal's metrics, each with its target, baseline, newest value, and every finished period with its value and whether it met the target; each indicator with whether its history shows it leads its key result, and each key result with the indicators found to lead it.").option("--ended","Also the metrics that were archived.").action(async(i,r)=>Hc(t,n,i,r.ended===!0)),o.command("add").argument("<goal>","A number from the last goals list, or a goal id.").description("Measure one of your goals on a saved metric. It keeps the metric's current version; its baseline is measured now (a draft's when you activate it). Nothing is saved unless every field is right.").option("--metric <metric>","A number from the last metrics list, a metric id, or its name.").option("--role <role>","key_result (what the goal wants; the default) or indicator (moves sooner; belongs to one key result).").option("--key-result <kpi>","For an indicator: its key result, a number from the last goals metrics list or an id.").option("--period <period>","The span each target is checked over: week, month, quarter or days.").option("--days <n>","The number of days in each period, with --period days.").option("--target <target>",Jn).option("--reason <text>","Why, kept in the goal's history.").option("--dry-run","Check the metric without saving it.").action(async(i,r)=>qc(t,n,i,r)),o.command("target").argument("<goal>","A number from the last goals list, or a goal id.").argument("<kpi>","A number from the last goals metrics list of this goal, or its id.").description("Set a new target (and period) for one of a goal's metrics. Its baseline is measured again; the old target stays in the history.").option("--target <target>",Jn).option("--period <period>","A new period: week, month, quarter or days.").option("--days <n>","The number of days in each period, with --period days.").option("--reason <text>","Why the target changes (required), kept in the goal's history.").action(async(i,r,s)=>Jc(t,n,i,r,s)),o.command("archive").argument("<goal>","A number from the last goals list, or a goal id.").argument("<kpi>","A number from the last goals metrics list of this goal, or its id.").description("Stop measuring one of a goal's metrics; a key result takes its indicators with it. Its history stays.").option("--reason <text>","Why, kept in the goal's history.").action(async(i,r,s)=>Vc(t,n,i,r,s.reason)),e.command("measure").argument("<which>","A number from the last list, or a goal id.").description("Measure one of your goals' metrics now instead of waiting for the night.").action(async i=>Bc(t,n,i)),e.command("history").argument("<which>","A number from the last list, or a goal id.").description("Every change to a goal, its metrics and its values: when, by whom, through what and why, newest first.").option("--limit <n>","Changes per page (default 50, at most 200).").option("--page <n>","The page to show.").action(async(i,r)=>Kc(t,n,i,r))}var gi="working-goal.json",Us=e=>e.team&&typeof e.team.id=="string"?e.team.id:null;function Ws(e,t){let n=pn(e,gi);return!n||typeof n.id!="string"||typeof n.title!="string"||n.api_url!==t.api_url||n.email!==t.email||n.user_id!==(typeof t.user_id=="string"?t.user_id:null)||n.team_id!==null&&n.team_id!==void 0&&typeof n.team_id!="string"?null:{id:n.id,title:n.title,team_id:n.team_id??null}}function Ms(e,t){return Ws(e,t)!==null}function Hs(e,t){let n=Ws(e,t);return n&&n.team_id===Us(t)?{id:n.id,title:n.title}:null}function Gs(e,t,n){let o={id:n.id,title:n.title,api_url:t.api_url,email:t.email,user_id:typeof t.user_id=="string"?t.user_id:null,team_id:Us(t),at:new Date(e.now?.()??Date.now()).toISOString()};gn(e,gi,o)}function Vn(e){Yi(e,gi)}var hi="last-goals.json",Bs=1024*1024,qs=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,Zc={goals:[{key:"eu-onboarding",title:"Raise onboarding completion in the EU",description:"Why it matters and what is in or out.",metric:{name:"onboarding completion",unit:"%",direction:"increase",baseline:42,target:60,due:"2026-12-31"},scope:{country:["DE","AT","FR"],productAreaId:"Onboarding"},visibility:"private"}]},X=(e,t,n)=>new p(e,t,g.Usage,n),Ys=(e,t)=>t?bn(e,t):P(e);async function de(e,t){let n=t.trim();if(qs.test(n))return n.toLowerCase();if(/^\d{1,3}$/.test(n)){let o=await Le(e,hi,Number(n)),i=Array.isArray(o?.ids)?o.ids:[],r=Number(n);if(r>=1&&r<=i.length){if(typeof i[r-1]=="string"&&qs.test(i[r-1]))return i[r-1].toLowerCase();throw X("unknown_number","the last list could not be read","List again: closedloop goals list")}throw X("unknown_number",i.length?`the last list had ${i.length} goals, not ${r}`:"there is no recent list to pick a number from","List first: closedloop goals list")}throw X("invalid_id",`'${t}' is not a goal id or a number from the last list`,"Use a number from `closedloop goals list`, or an id.")}var vt={productId:"product",productAreaId:"area",featureAreaId:"feature"},Ct=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),Bn=e=>e.map(t=>t.replace(/_/g," ")).join(" or ");function Ks(e){let t=e.relevance;return t?t.from==="goal"?`relevance: ${Bn(t.outcomes)}`:t.from==="kpis"?`relevance from its metrics: ${Bn(t.outcomes)}`:t.unknown&&t.unknown.length?`no relevance: ClosedLoop AI does not know what the metric ${t.unknown.join(", ")} bears on; name outcomeCategory in the scope`:"no relevance yet: add a key result whose metric counts customers by stage or paying state, or name outcomeCategory in the scope":Ct(e.scope,"outcomeCategory")?`relevance: ${Bn(e.scope.outcomeCategory)}`:null}function ed(e){let t=e.scope_names??{},n=new Set((e.scope_missing??[]).map(a=>`${a.field}|${a.value}`)),o=[],i=[];for(let[a,l]of Object.entries(e.scope))if(Ct(vt,a))for(let u of l)o.push(Ct(t,u)?`${t[u]} (${vt[a]})`:n.has(`${a}|${u}`)?`a removed ${vt[a]}`:`${vt[a]} ${u}`);else a!=="outcomeCategory"&&i.push(`${a}: ${l.join(", ")}`);let r=Ks(e),s=[...o.length?[o.join(" or ")]:[],...i];return[s.length?s.join("; "):"all insights",...r?[r]:[]].join("; ")}function td(e){let t=e.scope_missing??[];if(!t.length)return null;let n=Ct(vt,t[0].field)?vt[t[0].field]:"item";return`${t.length===1?`${n==="area"?"An":"A"} ${n} in this goal's scope no longer exists`:`${t.length} products, areas or features in this goal's scope no longer exist`}, so its insights cannot be read: save the goal with a new scope.`}function Yn(e,t){if(e===null)return"?";let n=Number.isInteger(e)?k(e):String(e);return t?t==="%"?`${n}%`:`${n} ${t}`:n}var nd=e=>e.split(`
|
|
100
|
+
`).map(t=>t&&` ${t}`).join(`
|
|
101
|
+
`);function od(e){return e.archived_at?`archived ${P(e.archived_at)}`:e.state==="closed"?e.closed_at?`closed ${P(e.closed_at)}`:"closed":e.state??null}function zs(e,t,n,o=0){let i=" ".repeat(o),r=i+(n===null?"":" ".repeat(n.length+2)),s=od(t),a=`${i}${n===null?"":`${n} `}${e.bold(t.title)}${s?e.dim(` \xB7 ${s}`):""}${t.visibility==="team"?e.dim(" \xB7 shared"):""}${t.owner.is_you?"":e.dim(" \xB7 a teammate's")}`,l=t.metric.direction==="increase"?"\u2191":"\u2193",u=`${t.metric.name} ${l} ${Yn(t.metric.target,t.metric.unit)}${t.metric.due?` by ${P(`${t.metric.due}T12:00:00Z`)}`:""}`,c=t.current?`now ${Yn(t.current.value,t.metric.unit)}${t.progress===null?"":` (${Math.round(t.progress*100)}% of the way)`}`:`no value yet${t.metric.baseline===null?"":`, from ${Yn(t.metric.baseline,t.metric.unit)}`}`,d=Array.isArray(t.key_results)?t.key_results:[],m=d.length>1?` \xB7 and ${d.length-1} more key result${d.length===2?"":"s"}`:"",h=d.length?`${Ns(e,d[0])}${m}`:`${u} \xB7 ${c}`,f=t.matching_insights===null?"insights unknown":`${k(t.matching_insights)} insight${t.matching_insights===1?"":"s"}`,y=td(t);return[a,`${r}${h}`,`${r}${e.dim(`${ed(t)} \xB7 ${f}${t.key?` \xB7 key ${t.key}`:""}`)}`,...y?[`${r}${y}`]:[]]}function id(e){let t=new Map(e.map(r=>[r.id,r])),n=new Set,o=[],i=e.filter(r=>!r.parent_id||!t.has(r.parent_id));for(let r of[...i,...e]){let s=[{goal:r,depth:0}];for(;s.length;){let a=s.pop();if(n.has(a.goal.id))continue;n.add(a.goal.id),o.push(a);let l=(a.goal.children??[]).map(u=>t.get(u)).filter(u=>u!==void 0&&u.parent_id===a.goal.id);for(let u of l.reverse())s.push({goal:u,depth:a.depth+1})}}return o}async function rd(e,t,n,o){let i=$(e.runtime),r=[t?"include=team":"",n?"archived=true":"",o!==void 0?`state=${encodeURIComponent(o)}`:""].filter(Boolean).join("&"),s=await v(e.runtime,i,"GET",`/api/user-goals${r?`?${r}`:""}`);if(s.status===400&&Array.isArray(s.body.allowed))throw X("invalid_state",String(s.body.error??"that is not a goal state"),`The states: ${s.body.allowed.join(", ")}`);if(s.status!==200||!Array.isArray(s.body.goals))throw C(s,"api_error","Your goals could not be loaded. Try again.");let a=id(s.body.goals),l=a.map(c=>c.goal),u=s.body.truncated===!0;await Ce(e,hi,{ids:l.map(c=>c.id)}),e.print.result({goals:l,truncated:u},c=>{if(c.goals.length===0)return t?"No goals yet, yours or shared.":"No goals yet. Create some: closedloop goals create --file goals.json";let d=e.print.style,m=String(c.goals.length).length,h=a.map((y,_)=>zs(d,y.goal,String(_+1).padStart(m),y.depth).join(`
|
|
102
|
+
`)),f=c.truncated?`
|
|
103
|
+
|
|
104
|
+
${d.dim(`Showing the first ${c.goals.length}.`)}`:"";return`${h.join(`
|
|
105
|
+
|
|
106
|
+
`)}${f}
|
|
107
|
+
|
|
108
|
+
${d.dim("Read one: closedloop goals show <number>")}`})}async function sd(e,t){let n=await de(e,t),o=$(e.runtime),i=await v(e.runtime,o,"GET",`/api/user-goals/${n}`);if(i.status===404)throw new p("goal_not_found","there is no goal with that id you can see",g.Failure);let r=i.body.goal;if(i.status!==200||!r||typeof r.id!="string")throw C(i,"api_error","The goal could not be loaded. Try again.");let s=typeof i.body.time_zone=="string"?i.body.time_zone:null,{kpis:a,lead_rules:l}=await pi(e,r.id,!1),u=r.parent_id?await ad(e,r.parent_id):null;e.print.result({...r,kpis:a},c=>{let d=e.print.style,m=zs(d,c,null);c.parent_id&&m.push(`Sub-goal of ${u??"a goal you cannot see"}`),c.children&&c.children.length&&m.push(d.dim(`${c.children.length} sub-goal${c.children.length===1?"":"s"}: closedloop goals list --team`)),c.description&&m.push("",c.description),m.push("",c.kpis.length?`Metrics:
|
|
109
|
+
${nd(di(d,c.kpis,l,c.state==="draft"))}`:d.dim(`No metrics yet. Add a key result: closedloop goals metrics add ${c.id} --metric <metric> --period month --target '>=100'`));let h=c.missing_to_activate;return c.state==="draft"&&Array.isArray(h)&&m.push("",h.length?`A draft. To activate it, it still needs:
|
|
110
|
+
${h.map(f=>` - ${f.message}`).join(`
|
|
111
|
+
`)}`:`A draft, ready to activate: closedloop goals activate ${c.id}`),c.values&&c.values.length&&(m.push("","Values:",...c.values.slice(0,10).map(f=>` ${Ys(f.measured_at,s)} ${Yn(f.value,c.metric.unit)}`)),c.values.length>10&&m.push(d.dim(` and ${c.values.length-10} earlier`))),m.push("",d.dim(`id ${c.id}`)),m.join(`
|
|
112
|
+
`)})}async function ad(e,t){let n=await v(e.runtime,$(e.runtime),"GET",`/api/user-goals/${t}`),o=n.body.goal;return n.status===200&&typeof o?.title=="string"?o.title:null}var Js="insights search reads only its insights; add --all for every insight.";async function kt(e,t){if(!Ms(e.runtime,t))return null;let n=await Lt(e);return Hs(e.runtime,{...t,team:{id:n.id,name:n.name}})}async function ld(e,t,n){if(n&&t!==void 0)throw X("invalid_option","give a goal or --clear, not both");let o=$(e.runtime);if(n){Vn(e.runtime),e.print.result({working_goal:null},()=>"No goal picked: insights search reads every insight.");return}if(t===void 0){let u=await kt(e,o);e.print.result({working_goal:u},c=>c.working_goal?`Working on ${c.working_goal.title}: ${Js}`:"No goal picked: insights search reads every insight. Pick one: closedloop goals use <goal>");return}let i=await de(e,t),r=await Lt(e),s=await v(e.runtime,o,"GET",`/api/user-goals/${i}`);if(s.status===404)throw new p("goal_not_found","there is no goal with that id you can see",g.Failure);let a=s.body.goal;if(s.status!==200||!a||typeof a.id!="string")throw C(s,"api_error","The goal could not be loaded. Try again.");Gs(e.runtime,{...o,team:{id:r.id,name:r.name}},{id:a.id,title:a.title});let l=a.relevance&&a.relevance.outcomes.length===0?` It has ${Ks(a)}.`:"";e.print.result({working_goal:{id:a.id,title:a.title}},u=>`Working on ${u.working_goal.title}: ${Js}${l}`)}var Vs={churn_reason:"reason for leaving",general_dislike:"dislike"};async function ud(e,t){let n=$(e.runtime),o=t!==void 0?await de(e,t):(await kt(e,n))?.id;if(!o)throw X("no_goal","name a goal, or pick the one you work on first","closedloop goals use <goal>");let i=await v(e.runtime,n,"GET",`/api/user-goals/${o}/context`);if(i.status===404&&i.body.code==="goal_not_found")throw new p("goal_not_found","there is no goal with that id you can see",g.Failure);if(i.status===404)throw new p("goal_context_unsupported","this ClosedLoop AI API cannot show a goal's context yet",g.Failure);let r=i.body;if(i.status!==200||!r.goal||typeof r.goal.id!="string"||!Array.isArray(r.goal.relevance?.outcomes))throw C(i,"api_error","The goal's context could not be loaded. Try again.");let s={goal:r.goal,deal_blockers:r.deal_blockers??null,churn_risks:r.churn_risks??null};e.print.result(s,a=>{let l=e.print.style,u=[l.bold(a.goal.title)];if(!a.deal_blockers&&!a.churn_risks)return u.push(`It is about ${Bn(a.goal.relevance.outcomes)}: deal blockers go with new sales and churn risks with retention, so it has neither.`),u.join(`
|
|
113
|
+
`);let c=(m,h,f,y,_)=>{f&&(u.push("",`${m}, bearing on ${h}: ${f.total===0?"none in its scope":y(f.total)}`),u.push(...f.items.map(S=>` ${_(S)}`)),f.total>f.items.length&&u.push(l.dim(` and ${k(f.total-f.items.length)} earlier`)))},d=a.deal_blockers?.customers;return c("Deal blockers","new sales",a.deal_blockers,m=>`${k(m)} insight${m===1?"":"s"}${typeof d=="number"?` from ${k(d)} customer${d===1?"":"s"}`:""}`,m=>`${P(m.occurred_at)} ${m.customer_name??"an unknown customer"} \xB7 ${m.title??"an untitled insight"}`),c("Churn risks","retention",a.churn_risks,m=>k(m),m=>`${P(m.occurred_at)} ${m.customer_name??"an unknown customer"} \xB7 ${m.summary??m.quote??"no summary"}`+l.dim([Object.prototype.hasOwnProperty.call(Vs,m.kind)?Vs[m.kind]:null,m.urgency?m.urgency.replace(/_/g," "):null].filter(Boolean).map(h=>` \xB7 ${h}`).join(""))),u.join(`
|
|
114
|
+
`)})}async function cd(e,t){let n=$(e.runtime),o=await v(e.runtime,n,"GET",`/api/user-goals/fields${t?`?field=${encodeURIComponent(t)}`:""}`);if(o.status===400){let r=Array.isArray(o.body.allowed)?o.body.allowed.join(", "):"";throw X("unknown_field","that goal field is not supported",r?`The fields: ${r}`:void 0)}if(o.status!==200||!Array.isArray(o.body.fields))throw C(o,"api_error","The fields could not be loaded. Try again.");let i={fields:o.body.fields};e.print.result(i,r=>{let s=e.print.style;return`${r.fields.map(l=>{let u=`${s.bold(l.field)} ${l.label}${s.dim(` \xB7 ${l.description}`)}`,c=l.values.map(m=>` ${m.label===m.value?m.value:`${m.label} ${s.dim(m.value)}`} ${s.dim(`${k(m.insights)} insight${m.insights===1?"":"s"}`)}`),d=l.more_values>0?[s.dim(` and ${l.more_values} more: closedloop goals fields --field ${l.field}`)]:[];return[u,...c.length?c:[s.dim(" no values in this workspace yet")],...d].join(`
|
|
114
115
|
`)}).join(`
|
|
115
116
|
|
|
116
117
|
`)}
|
|
117
118
|
|
|
118
|
-
${s.dim("Use these in a goal's scope; see closedloop goals example")}`})}function
|
|
119
|
-
`)}function
|
|
120
|
-
${
|
|
121
|
-
${
|
|
119
|
+
${s.dim("Use these in a goal's scope; see closedloop goals example")}`})}function dd(e,t){let n,o;if(e!==void 0){if(!zc(e))throw X("file_not_found",`there is no file ${e}`);if(Qc(e).size>Bs)throw X("file_too_large",`${e} is larger than 1 MB`);n=Xc(e,"utf8"),o=e}else n=t??"",o="standard input";if(!n.trim())throw X("nothing_to_send",`${o} is empty`);try{return JSON.parse(n)}catch{throw X("invalid_file",`${o} is not valid JSON; check its syntax`,"See the format: closedloop goals example")}}function md(e){return e.map(t=>{let n=t.allowed&&t.allowed.length?` (allowed: ${t.allowed.slice(0,12).join(", ")}${t.allowed.length>12?", \u2026":""})`:"";return`goal ${t.goal+1}${t.key?` (${t.key})`:""}, ${t.field}: ${t.message}${n}`}).join(`
|
|
120
|
+
`)}function pd(e,t){let n=o=>o!==null&&typeof o=="object"&&!Array.isArray(o)&&!Ct(o,"parent")?{...o,parent:t}:o;return Array.isArray(e)?e.map(n):e!==null&&typeof e=="object"&&Ct(e,"goals")&&Array.isArray(e.goals)?{...e,goals:e.goals.map(n)}:n(e)}async function gd(e,t){if(t.file===void 0==(t.stdin!==!0))throw X("nothing_to_send","give the goals with --file <path> or --stdin","See the format: closedloop goals example");let n=dd(t.file,t.stdin?await e.runtime.readStdin(Bs):void 0),o=t.parent===void 0?n:pd(n,await de(e,t.parent)),i=$(e.runtime),r=await v(e.runtime,i,"POST",`/api/user-goals${t.dryRun?"?dry_run=true":""}`,o);if(r.status===400&&Array.isArray(r.body.problems)){let l=r.body.problems;throw new p("invalid_goals",`nothing was saved; fix ${l.length===1?"this":`these ${l.length}`}:
|
|
121
|
+
${md(l)}`,g.Failure,"The values a field takes: closedloop goals fields --field <name>")}if(r.status!==200||!Array.isArray(r.body.goals))throw C(r,"api_error","The goals could not be saved. Try again.");let s={dry_run:r.body.dry_run===!0,goals:r.body.goals},a=[...s.goals].sort((l,u)=>l.index-u.index).map(l=>l.id);!s.dry_run&&a.length&&a.every(l=>typeof l=="string")&&await Ce(e,hi,{ids:a}),e.print.result(s,l=>{let u={created:"created",updated:"updated",would_create:"would be created",would_update:"would update the goal with this key"},c=l.goals.map(h=>`goal ${h.index+1}${h.key?` (${h.key})`:""}: ${Object.prototype.hasOwnProperty.call(u,h.result)?u[h.result]:h.result}${h.state&&h.result!=="would_create"?`, ${h.state}`:""} \xB7 ${h.matching_insights===null?"insights unknown":`${k(h.matching_insights)} matching insight${h.matching_insights===1?"":"s"}`}`),m=!l.dry_run&&l.goals.some(h=>h.state==="draft")?`
|
|
122
|
+
${e.print.style.dim("A draft is activated once it has a key result with a target: closedloop goals metrics add <goal> ..., then closedloop goals activate <goal>")}`:"";return`${l.dry_run?`Checked, nothing saved:
|
|
122
123
|
`:""}${c.join(`
|
|
123
|
-
`)}${m}`})}async function
|
|
124
|
-
${n.map(
|
|
125
|
-
`)}`,g.Failure,"Add a key result with a target: closedloop goals metrics add <goal> --metric <metric> --period month --target '>=100'")}return
|
|
126
|
-
`))}async function
|
|
127
|
-
`||a==="\r"?(a==="\r"&&
|
|
128
|
-
`&&s++,n.push(
|
|
129
|
-
`))}var
|
|
130
|
-
`);let
|
|
131
|
-
${
|
|
132
|
-
|
|
133
|
-
${
|
|
124
|
+
`)}${m}`})}async function hd(e,t,n,o){let i=Number(n);if(n.trim()===""||!Number.isFinite(i))throw X("invalid_value",`'${n}' is not a number`);let r=await de(e,t),s=$(e.runtime),a=await v(e.runtime,s,"POST",`/api/user-goals/${r}/values`,o===void 0?{value:i}:{value:i,measured_at:o});if(a.status===404)throw new p("goal_not_found","there is no goal with that id you can see",g.Failure);if(a.status===403)throw new p("not_your_goal","only the goal's owner records its values",g.Failure);if(a.status===409)throw new p("goal_archived",String(a.body.error??"this goal is archived; its values can no longer change"),g.Failure);let l=a.body.value;if(a.status!==200||!l)throw C(a,"api_error","The value could not be recorded. Try again.");let u=typeof a.body.time_zone=="string"?a.body.time_zone:null;e.print.result(l,c=>`${c.result==="replaced"?"Replaced":"Recorded"} ${c.value} for ${Ys(c.measured_at,u)}.`)}async function fd(e,t,n){let o=await de(e,t),i=$(e.runtime);if(!n){if(!e.interactive)throw new p("confirmation_required","deleting a goal needs confirmation",g.Usage,`Run it again with --yes: closedloop goals delete ${t} --yes`);if((await e.runtime.prompt("Delete this goal? It is archived: its values and history stay, and it leaves your list. Type yes to confirm: ")).trim().toLowerCase()!=="yes"){e.print.result({status:"cancelled",id:o},()=>"Nothing changed.");return}}let r=await kt(e,i),s=await v(e.runtime,i,"DELETE",`/api/user-goals/${o}`);if(s.status===404)throw new p("goal_not_found","there is no goal with that id you can see",g.Failure);if(s.status===403)throw new p("not_your_goal","only the goal's owner deletes it",g.Failure);if(s.status===409){let u=s.body.state==="closed";throw new p(u?"goal_closed":"goal_archived",String(s.body.error??(u?"this goal is closed; it stays as it is":"this goal is already archived")),g.Failure)}if(s.status!==200)throw C(s,"api_error","The goal could not be deleted. Try again.");r?.id===o&&Vn(e.runtime);let a=s.body.archived,l=typeof a?.archived_at=="string"?a.archived_at:null;e.print.result({status:"archived",id:o,archived_at:l},()=>"Archived the goal. Its values and history stay; see it with closedloop goals list --archived.")}function Xs(e,t){if(e.status===404)return new p("goal_not_found","there is no goal with that id you can see",g.Failure);if(e.status===403)return new p("not_your_goal",String(e.body.error??"only the goal's owner can do that"),g.Failure);if(e.status===409&&e.body.code==="goal_not_ready"&&Array.isArray(e.body.missing)){let n=e.body.missing;return new p("goal_not_ready",`the goal is not activated; it still needs:
|
|
125
|
+
${n.map(o=>` - ${o.message}`).join(`
|
|
126
|
+
`)}`,g.Failure,"Add a key result with a target: closedloop goals metrics add <goal> --metric <metric> --period month --target '>=100'")}return e.status===409?new p(`goal_${String(e.body.state??"state")}`,String(e.body.error??t),g.Failure):C(e,"api_error",t)}async function yd(e,t,n){let o=await de(e,t),i=$(e.runtime),r=await v(e.runtime,i,"POST",`/api/user-goals/${o}/activate`,n===void 0?{}:{reason:n}),s=r.body.goal;if(r.status!==200||!s||typeof s.id!="string")throw Xs(r,"The goal could not be activated. Try again.");let a=Number(r.body.metrics??0),l=Number(r.body.baselines_measured??0);e.print.result({status:"active",id:s.id,metrics:a,baselines_measured:l},u=>[`Activated ${s.title}. Its statement, scope and parent now stay as they are; it can gain metrics and new targets.`,`The first period of its ${u.metrics} metric${u.metrics===1?"":"s"} starts today; ${u.baselines_measured===u.metrics?"every baseline was measured now":`${u.baselines_measured} baseline${u.baselines_measured===1?" was":"s were"} measured now, the rest tonight`}.`].join(`
|
|
127
|
+
`))}async function wd(e,t,n,o){let i=await de(e,t),r=$(e.runtime);if(!o){if(!e.interactive)throw new p("confirmation_required","closing a goal needs confirmation",g.Usage,`Run it again with --yes: closedloop goals close ${t} --yes`);if((await e.runtime.prompt("Close this goal? It is finished for good: its results and history stay, and it cannot be opened again. Type yes to confirm: ")).trim().toLowerCase()!=="yes"){e.print.result({status:"cancelled",id:i},()=>"Nothing changed.");return}}let s=await kt(e,r),a=await v(e.runtime,r,"POST",`/api/user-goals/${i}/close`,n===void 0?{}:{reason:n}),l=a.body.closed;if(a.status!==200||!l)throw Xs(a,"The goal could not be closed. Try again.");s?.id===i&&Vn(e.runtime);let u=typeof l.closed_at=="string"?l.closed_at:null;e.print.result({status:"closed",id:i,closed_at:u},()=>"Closed the goal. Its results and history stay; see it with closedloop goals list --state closed. Its sub-goals keep their own state.")}var Qs=(e,t)=>{let n=e.command("goals").alias("goal").description("Your goals: a number to move by a date, scoped to the insights it is about.");n.command("list").option("--team","Also the goals teammates share with the team.").option("--archived","Also the goals you deleted, which are kept archived.").option("--state <states>","Only goals in these states, e.g. draft or active,closed (draft, active, closed, archived).").description("Your goals as a tree (each sub-goal under its parent), with their state, current value and how many insights each is about.").action(async o=>rd(t,o.team===!0,o.archived===!0,o.state)),n.command("show").argument("<which>","A number from the last list, or a goal id.").description("One goal in full, with its state, parent goal, metrics (targets, baselines, values and finished periods) and recorded values; for a draft, what it still needs to be activated.").action(async o=>sd(t,o)),n.command("use").argument("[which]","A number from the last list, or a goal id.").description("Pick the goal you work on: insights search then reads only its insights (its scope, bearing on its relevance) until you pick another. With no goal, says which one you work on.").option("--clear","Work on no goal: insights search reads every insight.").action(async(o,i)=>ld(t,o,i.clear===!0)),n.command("context").argument("[which]","A number from the last list, or a goal id; the goal you work on when left out.").description("The deal blockers and churn risks in a goal's scope: deal blockers when it bears on new sales, churn risks when it bears on retention.").action(async o=>ud(t,o)),n.command("fields").description("The fields a goal can be scoped by in this workspace, with the values in use and how many insights each matches.").option("--field <name>","Every value of one field, e.g. country.").action(async o=>cd(t,o.field)),n.command("example").description('An example goals file to copy: { "goals": [ { key, title, description, metric: { name, unit, direction, baseline, target, due }, scope, visibility } ] }.').action(()=>t.print.result(Zc,o=>JSON.stringify(o,null,2))),n.command("create").description('Create goals from a JSON file: one goal, a list, or { "goals": [...] } (see `goals example`). Every goal is checked against your workspace and nothing is saved unless all pass; a goal with a key you used before replaces that goal (a field left out takes its default, so a shared goal sent without visibility becomes private again), as far as its state allows: an active goal keeps its title, scope, parent and metric name, unit and direction, and a new target needs a reason. A new goal is a draft. title, metric.name, metric.direction (increase or decrease) and metric.target are required; scope maps fields from `goals fields` to a value or a list (a product, area or feature by name or id). Products, areas and features together match any of them, at any level; outcomeCategory names the business outcomes the insights must bear on. A goal measures the metrics you add with `goals metrics add`; nothing is added by itself.').option("--file <path>","The JSON file with the goals.").option("--stdin","Read the JSON from standard input.").option("--dry-run","Check the goals and say how many insights each matches, without saving.").option("--parent <goal>","Make every goal in the file a sub-goal of this one (a number from the last list, or an id), unless it names its own parent.").action(async o=>gd(t,o)),n.command("activate").argument("<which>","A number from the last list, or a goal id.").description("Activate one of your draft goals. It needs a key result with a target (and every metric its target); its first period starts today and its baselines are measured now. Then its statement, scope and parent stay as they are.").option("--reason <text>","Why, kept in the goal's history.").action(async(o,i)=>yd(t,o,i.reason)),n.command("close").argument("<which>","A number from the last list, or a goal id.").description("Close one of your active goals: it is finished, its metrics stop and keep their results, and it cannot be opened again. Its sub-goals keep their own state.").option("--reason <text>","Why, kept in the goal's history.").option("--yes","Skip the confirmation.").action(async(o,i)=>wd(t,o,i.reason,i.yes===!0));for(let[o,i]of[["set",!1],["push",!0]])n.command(o,{hidden:i}).argument("<which>","A number from the last list, or a goal id.").argument("<value>","The goal's number as measured, e.g. 47.5 or -3.").description("Set one of your goals' numbers as measured now (or at --at). Earlier values stay as the goal's history; a second value for the same moment replaces the first.").option("--at <date>","When it was measured, e.g. 2026-10-20 (default: now).").action(async(r,s,a)=>hd(t,r,s,a.at));n.command("delete").argument("<which>","A number from the last list, or a goal id.").description("Delete one of your goals. It is archived: its values, metrics and history stay, and it leaves your list.").option("--yes","Skip the confirmation.").action(async(o,i)=>fd(t,o,i.yes===!0)),Fs(n,t,de)};import{existsSync as bd,readFileSync as $d,statSync as vd}from"node:fs";import{extname as Cd}from"node:path";function _d(e){let t=[],n=[],o="",i=!1,r=e.charCodeAt(0)===65279?e.slice(1):e;for(let s=0;s<r.length;s++){let a=r[s];i?a==='"'&&r[s+1]==='"'?(o+='"',s++):a==='"'?i=!1:o+=a:a==='"'&&o===""?i=!0:a===","?(n.push(o),o=""):a===`
|
|
128
|
+
`||a==="\r"?(a==="\r"&&r[s+1]===`
|
|
129
|
+
`&&s++,n.push(o),t.push(n),n=[],o=""):o+=a}if(i)throw new Error("a quoted field is not closed");return(o!==""||n.length>0)&&(n.push(o),t.push(n)),t.filter(s=>s.some(a=>a.trim()!==""))}function Zs(e){let[t,...n]=_d(e);if(!t)return[];let o=t.map(i=>i.trim().toLowerCase());return n.map(i=>Object.fromEntries(o.map((r,s)=>[r,i[s]??""])))}var Kn=["content","title","reporter_name","reporter_email","customer","date","url","id","tags"],kd=100,Sd=4*1024*1024,na=50*1024*1024,ea=900,ta=5,Ad=new Set([".csv",".json",".jsonl",".ndjson"]),Od="They wait until your workspace's company profile is ready: set your company website with `closedloop company --website <url>`, and they are processed once it is researched.",Td="They are analysed once your company's email domains are confirmed; see: closedloop company",D=(e,t,n)=>new p(e,t,g.Usage,n);function Rd(e,t){if(!e||typeof e!="object"||Array.isArray(e))throw D("invalid_record",`record ${t} is not an object with a content field`);let n={};for(let[o,i]of Object.entries(e)){let r=o.trim().toLowerCase();if(!Kn.includes(r))throw D("unknown_field",`record ${t} has a field that ingest does not take`,`The fields: ${Kn.join(", ")}.`);if(!(i==null||i===""))if(r==="tags"){let s=Array.isArray(i)?i:typeof i=="string"?i.split(";"):null;if(!s||s.some(a=>typeof a!="string"))throw D("invalid_record",`record ${t}'s tags must be words (in a CSV, separated by ;)`);n.tags=s.map(a=>a.trim()).filter(Boolean)}else if(typeof i=="string"||typeof i=="number")n[r]=String(i).trim();else throw D("invalid_record",`record ${t}'s ${r} must be text`)}if(typeof n.content!="string"||!n.content)throw D("invalid_record",`record ${t} has no content`);return n}function Ed(e){if(!bd(e))throw D("file_not_found",`there is no file ${e}`);if(vd(e).size>na)throw D("file_too_large",`${e} is larger than 50 MB`,"Split it into smaller files.");return{text:$d(e,"utf8"),ext:Cd(e).toLowerCase()}}function Id(e,t,n){let o;try{if(t===".csv")o=Zs(e);else if(t===".json"){let i=JSON.parse(e);o=Array.isArray(i)?i:[i]}else o=e.split(/\r?\n/).filter(i=>i.trim()).map(i=>JSON.parse(i))}catch{throw D("invalid_file",`${n} could not be read as ${t.slice(1).toUpperCase()}; check its syntax`,`Each record needs content, and may include ${Kn.slice(1).join(", ")}.`)}if(t===".csv"&&o.length>0&&!Object.keys(o[0]).includes("content"))throw D("invalid_file",`${n} has no content column`,`The first row names the columns: content, and any of ${Kn.slice(1).join(", ")}.`);return o.map((i,r)=>Rd(i,r+1))}async function Pd(e,t,n){let o=[t.length>0,n.stdin===!0,n.file!==void 0].filter(Boolean).length;if(o===0)throw D("nothing_to_send","nothing to send","Give some text, --file <path> or --stdin.");if(o>1)throw D("conflicting_options","send text, --file or --stdin, one at a time");let i={title:n.title,reporter_name:n.from,reporter_email:n.email,customer:n.customer,date:n.date,url:n.url,id:n.id},r=Object.values(i).some(l=>l!==void 0)||(n.tag?.length??0)>0,s;if(n.file!==void 0){let{text:l,ext:u}=Ed(n.file);if(Ad.has(u)){if(r)throw D("conflicting_options",`--title, --from and the like describe one record; ${n.file} carries its own fields`);return Id(l,u,n.file)}s=l}else n.stdin?s=await e.runtime.readStdin(na):s=t.join(" ");if(!s.trim())throw D("nothing_to_send","the text to send is empty");let a={content:s.trim()};for(let[l,u]of Object.entries(i))u!==void 0&&u.trim()&&(a[l]=u.trim());return n.tag?.length&&(a.tags=n.tag.map(l=>l.trim()).filter(Boolean)),[a]}function xd(e){let t=[],n=[],o=0,i=0;for(let r of e){let s=Buffer.byteLength(JSON.stringify(r));n.length>0&&(n.length===kd||o+s>Sd)&&(t.push({offset:i,items:n}),i+=n.length,n=[],o=0),n.push(r),o+=s}return n.length>0&&t.push({offset:i,items:n}),t}async function Nd(e,t,n){let o=[],i=[],r=null,s=n.every(a=>typeof a.id=="string"&&a.id)?"records with an id are not sent twice, so sending the file again is safe":"sending the whole file again would send them twice: send only the rest, or give every record an id";for(let a of xd(n)){let l=o.length?`The first ${o.length} record${o.length===1?" was":"s were"} sent; ${s}.`:"",u;try{u=await v(e.runtime,t,"POST","/api/cli/ingest",{items:a.items})}catch(c){if(!(c instanceof p))throw c;let d=c.code==="api_unreachable"||c.code==="api_timeout"?"The last batch may also have been accepted; check with `closedloop ingest list` before resending it.":"";throw new p(c.code,c.message,c.exitCode,[c.hint,l,d].filter(Boolean).join(" ")||void 0)}if(u.status!==201){if(u.body.code==="INVALID_ITEM"&&typeof u.body.index=="number"){let d=String(u.body.error).replace(/^Record \d+ /,"");throw new p("invalid_record",`record ${a.offset+u.body.index+1} ${d.replace(/\.$/,"")}`,g.Failure,l||void 0)}let c=C(u,"ingest_refused","The records could not be sent.");throw new p(c.code,c.message,g.Failure,l||c.hint)}o.push(...u.body.accepted),i.push(...u.body.duplicates),r=typeof u.body.waiting_for=="string"?u.body.waiting_for:null}return{accepted:o,duplicates:i,waitingFor:r}}async function oa(e,t,n){let o=await v(e.runtime,t,"GET",`/api/cli/ingest?limit=${n}`);if(o.status!==200||!Array.isArray(o.body.records))throw C(o,"api_error","The records could not be loaded. Try again.");return o.body.records}async function Ld(e,t,n){let o=new Set(n.map(i=>i.id));for(let i=0;i<=ea;i+=ta){let r=(await oa(e,t,100)).filter(s=>o.has(s.id));if(r.length===o.size&&r.every(s=>s.state==="done"||s.state==="failed"))return r;i<ea&&await e.runtime.sleep(ta*1e3)}throw new p("still_processing","the records are not processed yet",g.Failure,"Check later with: closedloop ingest list")}function ia(e,t){let n=e.state==="done"?`done, ${e.insights} insight${e.insights===1?"":"s"}`:e.state==="waiting"&&e.waiting_for==="company_profile"?"waiting for the company profile":e.state==="waiting"&&e.waiting_for==="email_domains"?"waiting for the email domains":e.state;return t?e.state==="done"?t.green(n):e.state==="failed"?t.red(n):t.orange(n):n}async function Dd(e,t,n){let o=await Pd(e,t,n);if(n.wait&&o.length>100)throw D("wait_too_many","--wait follows up to 100 records","Send without --wait and check with `closedloop ingest list`.");let i=$(e.runtime);await pt(e.runtime,i,"can_manage_integrations","send data in");let{accepted:r,duplicates:s,waitingFor:a}=await Nd(e,i,o),l=s.length?` ${s.length} had an id already sent and ${s.length===1?"was":"were"} skipped.`:"";if(!n.wait||r.length===0||a!==null){e.print.result({status:"sent",sent:r.length,duplicates:s,waiting_for:a,records:r},h=>`Sent ${h.sent} record${h.sent===1?"":"s"}.${l} ${h.waiting_for==="company_profile"?Od:h.waiting_for==="email_domains"?Td:"They are processed in the next few minutes"}; follow them with: closedloop ingest list`);return}let u=e.print.spinner(`Sent ${r.length} record${r.length===1?"":"s"}; waiting for them to be processed\u2026`),c;try{c=await Ld(e,i,r)}finally{u.stop()}let d=c.reduce((h,f)=>h+f.insights,0),m=c.filter(h=>h.state==="failed").length;if(e.print.result({status:"processed",sent:r.length,duplicates:s,insights:d,failed:m,records:c},h=>`Processed ${h.sent} record${h.sent===1?"":"s"}: ${h.insights} insight${h.insights===1?"":"s"}${h.failed?`, ${h.failed} failed`:""}.${l} See them: closedloop insights search --source cli`),m>0)throw new p("ingest_failed",`${m} record${m===1?"":"s"} failed during processing`,g.Failure,"Check the failed records with `closedloop ingest list` or `closedloop ingest status <id>`.")}async function jd(e,t){let n=t===void 0?20:Number(t);if(!Number.isInteger(n)||n<1||n>100)throw D("invalid_option","--limit must be a whole number from 1 to 100");let o=$(e.runtime),i=await oa(e,o,n);e.print.result({records:i},r=>r.records.length===0?"Nothing sent with closedloop ingest yet.":r.records.map(s=>`${e.print.style.dim(P(s.created_at))} ${ia(s,e.print.style)} ${s.title??s.external_id??"(untitled)"} ${e.print.style.dim(`id ${s.id}`)}`).join(`
|
|
130
|
+
`))}var Fd=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;async function Ud(e,t){if(!Fd.test(t))throw D("invalid_id",`'${t}' is not a record id`,"Copy it from `closedloop ingest list`.");let n=$(e.runtime),o=await v(e.runtime,n,"GET",`/api/cli/ingest/${t.toLowerCase()}`);if(o.status===404)throw new p("record_not_found","there is no record with that id in your workspace",g.Failure);if(o.status!==200||typeof o.body.state!="string")throw C(o,"api_error","The record could not be loaded. Try again.");let i=o.body;e.print.result(i,r=>`${r.title??r.external_id??"The record"}: ${ia(r)} (sent ${P(r.created_at)}).`)}var Wd=(e,t)=>[...t??[],e],ra=(e,t)=>{let n=e.command("ingest").argument("[text...]","The text to send as one record.").description("Send your own data in: text, a file (CSV, JSON, JSON Lines, text) or standard input.").option("--file <path>","A file: CSV or JSON (Lines) with a content field per record, or a text file as one record.").option("--stdin","Read one record's text from standard input.").option("--title <title>","A title for the record.").option("--from <name>","Who said it.").option("--email <address>","Their email; the insight is tied to their company when it matches a contact in your CRM.").option("--customer <name>","Their company, kept with the record (the insight's company comes from --email).").option("--date <date>","When it was said, e.g. 2026-10-22.").option("--url <link>","Where it came from.").option("--id <id>","Your own id for it; a record with an id already sent is skipped.").option("--tag <word>","A tag (repeatable).",Wd).option("--wait","Wait until processed, and say how many insights it made.").action(async(o,i)=>Dd(t,o,i));n.command("list").description("The latest records sent with ingest, and their state.").option("--limit <n>","How many, up to 100 (default 20).").action(async o=>jd(t,o.limit)),n.command("status").argument("<id>","The record id, from list or from sending.").description("One record's state, and the insights it made.").action(async o=>Ud(t,o))};var Md=["title","quote","reporter","account"],Hd=["critical","high","medium","low"],sa={week:"1week",month:"1month","3months":"3months"},fi={newest:["source_date","desc"],oldest:["source_date","asc"],severity:["severity","asc"],relevance:["relevance_score","desc"]},aa=240;function zn(e,t,n){if(t===void 0)return;let o=t.trim().toLowerCase();if(!n.includes(o))throw new p("invalid_option",`--${e} must be ${n.join(", ")}`,g.Usage);return o}function la(e,t,n,o){if(t===void 0)return n;let i=Number(t);if(!Number.isInteger(i)||i<1||i>o)throw new p("invalid_option",`--${e} must be a whole number from 1 to ${o}`,g.Usage);return i}async function Gd(e,t,n){let o=await v(e.runtime,t,"GET","/api/cli/integrations");if(o.status!==200||!Array.isArray(o.body.integrations))throw C(o,"api_error","Sources could not be loaded. Try again.");let r=o.body.integrations.find(s=>s.slug===n.toLowerCase()||s.name.toLowerCase()===n.toLowerCase());if(!r)throw new p("unknown_source",`there is no source called "${n}"`,g.Usage,"See the names with `closedloop integrations list`.");return r.name}function qd(e){if(!e)return null;let t=e.replace(/\s+/g," ").trim();return t.length>aa?`${t.slice(0,aa-1)}\u2026`:t}function da(e){let t=e.reporter_name||e.customer_name;return[e.account_name,t&&t!==e.account_name?t:null].filter(n=>!!n)}function Jd(e){return e&&`${e[0].toUpperCase()}${e.slice(1)}`}var Vd=e=>e.map(t=>t.replace(/_/g," ")).join(" or ");async function Bd(e,t,n){if(n.all&&n.goal!==void 0)throw new p("invalid_option","give --goal or --all, not both",g.Usage);if(n.all)return null;if(n.goal!==void 0)return{id:await de(e,n.goal),title:"",picked:!1};let o=await kt(e,t);return o?{...o,picked:!0}:null}async function Yd(e,t,n){let o=zn("in",n.in,Md),i=zn("severity",n.severity,Hd),r=zn("last",n.last,Object.keys(sa)),s=zn("sort",n.sort,Object.keys(fi))??"newest",a=la("limit",n.limit,20,100),l=la("page",n.page,1,1e4);if(o&&!t)throw new p("invalid_option","--in needs search words",g.Usage);let u=$(e.runtime),c=new URLSearchParams({page:String(l),limit:String(a),sortBy:fi[s][0],sortOrder:fi[s][1],filter_options:"false"}),d=t?.trim();d&&(o?c.set("searchIn",o):c.set("searchFragments","true"),c.set("search",d)),n.source&&c.set("source",await Gd(e,u,n.source)),n.category&&c.set("category",n.category.trim().toLowerCase()),i&&c.set("severity",i),r&&c.set("timeRange",sa[r]),n.customer&&c.set("customerEmail",n.customer.trim()),n.account&&c.set("companyDomain",n.account.trim().toLowerCase());let m=await Bd(e,u,n);m&&c.set("goalId",m.id);let h=await v(e.runtime,u,"GET",`/api/feedback?${c}`);if(m&&h.status===404)throw new p("goal_not_found",m.picked?`the goal you work on (${m.title}) is gone or you can no longer see it`:"there is no goal with that id you can see",g.Failure,"Pick another: closedloop goals use <goal>, or search every insight with --all");if(m&&h.status===409&&typeof h.body.error=="string")throw new p(typeof h.body.code=="string"?h.body.code:"goal_unsearchable",h.body.error,g.Failure,"Search every insight with --all");if(h.status!==200||!Array.isArray(h.body.data))throw C(h,"api_error","Insights could not be searched. Try again.");let f=h.body.goal,y=m&&f&&typeof f.id=="string"&&typeof f.title=="string"&&f.relevance&&Array.isArray(f.relevance.outcomes)?f:null;if(m&&(!y||y.id.toLowerCase()!==m.id.toLowerCase()))throw new p("goal_search_unsupported","this ClosedLoop AI API cannot search within a goal yet",g.Failure,"Search every insight with --all");let _=h.body.data,S={query:d||null,goal:y,total:Number(h.body.total)||0,page:l,pages:Number(h.body.totalPages)||0,insights:_.map(b=>({id:b.id,title:b.signal_title,quote:b.content,category:b.category,severity:b.severity,account:b.account_name,customer:b.customer_name,reporter:b.reporter_name,source:b.source_name,date:b.source_date??b.created_at,feature_area:b.feature_area,deal_blocker:b.is_deal_blocker===!0}))};await Ce(e,ma,{ids:S.insights.map(b=>b.id)}),e.print.result(S,b=>{let F=e.print.style,oe=b.goal?` in ${b.goal.title}`:"",G=b.goal?F.dim(`Only ${b.goal.title}'s insights, bearing on ${Vd(b.goal.relevance.outcomes)}; every insight: --all`):null;if(b.insights.length===0)return[b.query?`No insights${oe} match "${b.query}".`:`No insights${oe} match.`,...G?[G]:[]].join(`
|
|
131
|
+
`);let At=`${F.bold(`${b.total} insight${b.total===1?"":"s"}`)}${b.query?` for "${b.query}"`:""}${oe}${F.dim(` \xB7 ${Kd[s]}${b.pages>1?` \xB7 page ${b.page} of ${b.pages}`:""}`)}`,et=String(b.insights.length).length,ao=b.insights.map((J,V)=>zd(F,J,String(V+1).padStart(et),n.ids===!0)),A=[b.page<b.pages?`more: --page ${b.page+1}`:null,`read one: closedloop insights show ${b.insights.length>1?"<number>":"1"}`].filter(Boolean).join(" \xB7 ");return`${At}${G?`
|
|
132
|
+
${G}`:""}
|
|
133
|
+
|
|
134
|
+
${ao.join(`
|
|
134
135
|
|
|
135
136
|
`)}
|
|
136
137
|
|
|
137
|
-
${
|
|
138
|
-
`),l=[`${n} ${
|
|
139
|
-
`)}function
|
|
140
|
-
`)})}var
|
|
138
|
+
${F.dim(Jd(A))}`})}var ma="last-insights.json",Kd={newest:"newest first",oldest:"oldest first",severity:"most severe first",relevance:"most relevant first"},ua={critical:"CRIT",high:"HIGH",medium:"MED",low:"LOW"};function pa(e,t){let n=(t??"").toLowerCase(),o=(Object.prototype.hasOwnProperty.call(ua,n)?ua[n]:"").padEnd(4);return n==="critical"||n==="high"?e.red(o):n==="medium"?e.orange(o):e.dim(o)}function zd(e,t,n,o){let i=" ".repeat(n.length+8),r=e.width-i.length,[s,...a]=Fn(t.title??"(untitled)",r,"").split(`
|
|
139
|
+
`),l=[`${n} ${pa(e,t.severity)} ${e.bold(s)}`,...a.map(m=>`${i}${e.bold(m)}`)],u=[...da({account_name:t.account,customer_name:t.customer,reporter_name:t.reporter}).slice(0,1),t.source,t.date?P(t.date):null].filter(Boolean).join(" \xB7 "),c=t.deal_blocker?e.red(`${u?" \xB7 ":""}deal blocker`):"";(u||c)&&l.push(`${i}${e.dim(u)}${c}`);let d=qd(t.quote);return d&&l.push(`${i}${e.dim(`"${jn(d,r-2)}"`)}`),o&&l.push(`${i}${e.dim(`id ${t.id}`)}`),l.join(`
|
|
140
|
+
`)}function Xd(e){let t=e&&typeof e=="object"?e.company_name:null;return typeof t=="string"&&t?t:null}var ca=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;async function Qd(e,t){let n=t.trim();if(ca.test(n))return n;if(/^\d{1,3}$/.test(n)){let o=await Le(e,ma,Number(n)),i=Array.isArray(o?.ids)?o.ids:[],r=Number(n);if(r>=1&&r<=i.length){if(typeof i[r-1]=="string"&&ca.test(i[r-1]))return i[r-1];throw new p("unknown_number","the last search could not be read",g.Usage,"Search again: closedloop insights search <words>")}throw new p("unknown_number",i.length?`the last search listed ${i.length} insights, not ${r}`:"there is no recent search to pick a number from",g.Usage,"Search first: closedloop insights search <words>")}throw new p("invalid_id",`'${t}' is not an insight id or a number from the last search`,g.Usage,"Use a number from `closedloop insights search`, or an id.")}async function Zd(e,t,n){let o=await Qd(e,t),i=$(e.runtime),r=await v(e.runtime,i,"GET",`/api/feedback/${o.trim().toLowerCase()}`);if(r.status===404)throw new p("insight_not_found","there is no insight with that id in your workspace",g.Failure);let s=r.body.data;if(r.status!==200||!s||typeof s.id!="string")throw C(r,"api_error","The insight could not be loaded. Try again.");let a=c=>typeof s[c]=="string"&&s[c]?s[c]:null,l=a("original_content"),u={id:s.id,title:a("signal_title"),summary:a("summary"),quote:a("content"),original_quote:l&&l!==a("content")?l:null,category:a("category"),severity:a("severity"),deal_blocker:s.is_deal_blocker===!0,account:a("account_name")??Xd(s.crm_account),customer:a("customer_name"),reporter:a("reporter_name"),reporter_role:a("reporter_role"),source:a("source_name"),source_url:a("source_url"),date:a("source_date")??a("created_at"),feature_area:a("feature_area_name")??a("feature_area"),product_area:a("product_area_name"),pain_point:a("pain_point"),workaround:a("workaround"),use_case:a("use_case"),competitor_gap:a("competitor_gap"),tags:Array.isArray(s.tags)?s.tags:[],...n?{transcript:s.transcript??null}:{}};e.print.result(u,c=>{let d=e.print.style,m=[`${c.severity?`${pa(d,c.severity).trimEnd()} `:""}${d.bold(c.title??"(untitled)")}${c.deal_blocker?d.red(" \xB7 deal blocker"):""}`],h=[...da({account_name:c.account,customer_name:c.customer,reporter_name:c.reporter}),c.reporter_role,c.source,c.date?P(c.date):null,c.category].filter(Boolean).join(" \xB7 ");h&&m.push(d.dim(h)),c.summary&&m.push("",c.summary),c.quote&&m.push("",`"${c.quote.trim()}"`),c.original_quote&&m.push(`Original: "${c.original_quote.trim()}"`);let y=[["Feature area",c.feature_area],["Product area",c.product_area],["Pain point",c.pain_point],["Workaround",c.workaround],["Use case",c.use_case],["Competitor gap",c.competitor_gap],["Source link",c.source_url]].filter(([,_])=>_);return y.length&&m.push("",...y.map(([_,S])=>`${d.dim(`${_}:`)} ${S}`)),n&&typeof c.transcript=="string"&&m.push("","Transcript:",c.transcript),m.join(`
|
|
141
|
+
`)})}var ga=(e,t)=>{let n=e.command("insights").alias("insight").description("Search your product insights and read one in full.");n.command("search").argument("[words...]","What to look for; leave out to see the newest.").description("Search insights, as the Insights page does: only the goal you work on (closedloop goals use) when you picked one, else every insight.").option("--in <field>","Search only one field: title, quote, reporter or account.").option("--source <name>","Only from one source, e.g. gong.").option("--category <category>",'Only one category, e.g. "feature request" or bug.').option("--severity <level>","Only one severity: critical, high, medium or low.").option("--last <period>","Only the last week, month or 3months.").option("--customer <email>","Only from one customer email.").option("--account <domain>","Only from one account domain, e.g. acme.com.").option("--sort <order>","newest (default), oldest, severity or relevance.").option("--limit <n>","How many per page, up to 100 (default 20).").option("--page <n>","Which page.").option("--ids","Show each insight's id too.").option("--goal <which>","Only one goal's insights (a number from the last goals list, or a goal id), not the goal you work on.").option("--all","Every insight, not only the goal you work on.").action(async(o,i)=>Yd(t,o.join(" ")||void 0,i)),n.command("show").argument("<which>","A number from the last search, or an insight id.").description("Show one insight in full.").option("--transcript","Include the conversation transcript.").action(async(o,i)=>Zd(t,o,i.transcript===!0))};var ba={1:30,3:90,6:180},em="3";function tm(e){if(e.history===!1)return!1;let t=e.history??em;if(!Object.prototype.hasOwnProperty.call(ba,t))throw new p("invalid_history",`--history takes 1, 3 or 6 months, not '${t}'`,g.Usage,"Or leave the history out with --no-history.");return t}async function nm(e,t,n,o,i){if(n.imports_history!==!0||i===!1||typeof o?.id!="string")return null;let r=ba[i],s=o.config&&typeof o.config=="object"?o.config:{};if((await v(e.runtime,t,"PUT",`/api/user-integrations/${encodeURIComponent(o.id)}`,{config:{...s,backfillPeriod:i,backfill_days:r}})).status!==200)return{months:i,started:!1};let l=await v(e.runtime,t,"POST","/api/trigger-processing",{team_id:await Ge(e.runtime,t),user_integration_id:o.id,integration_id:n.integration_id,backfill_days:r,reason:"manual_trigger"});return{months:i,started:l.status>=200&&l.status<300}}function $a(e,t){if(e.connected){if(!e.connection_id)throw new p("manage_in_app",`${e.name} is set up in its own settings, so it cannot be ${t} here`,g.Failure,`Change it in the app: Integrations, then ${e.name}. ${x}`);if((e.connections??1)>1)throw new p("manage_in_app",`${e.name} has ${e.connections} connections, so it cannot be ${t} as one here`,g.Failure,`Choose the connection in the app: Integrations, then ${e.name}. ${x}`)}}var om=600,im=900,ha=3,rm=3,fa={api_key:"connect with an API key",browser:"connect in the browser",app:"set up in the app",ingest:"send data with closedloop ingest"},sm={browser:"is connected in the browser",app:"is set up in the app",ingest:"receives what you send with closedloop ingest"};async function va(e,t){let n=await v(e.runtime,t,"GET","/api/cli/integrations");if(n.status!==200||!Array.isArray(n.body.integrations))throw C(n,"api_error","Integrations could not be loaded. Try again.");return n.body.integrations}async function zt(e,t,n){let o=n.trim().toLowerCase();if(!o)throw new p("missing_integration","name the integration",g.Usage,"See the names with `closedloop integrations list --available`.");let i=s=>v(e.runtime,t,"GET",`/api/cli/integrations/${encodeURIComponent(s)}`),r=await i(o);if(r.status===404){let s=(await va(e,t)).find(a=>a.name.toLowerCase()===o);s&&(r=await i(s.slug))}if(r.status===404)throw new p("integration_not_found",`there is no integration called "${n.trim()}"`,g.Failure,"See the names with `closedloop integrations list --available`.");if(r.status!==200||typeof r.body.slug!="string")throw C(r,"api_error","The integration could not be loaded. Try again.");return r.body}function ya(e){return e.needs_attention?`needs attention: ${e.problem}`:e.connected?e.status==="paused"?"paused":e.status==="connecting"?"connecting":e.last_sync_at?`connected, last sync ${R(e.last_sync_at)}`:"connected, not synced yet":"not connected"}function am(e){return e.available_on_your_plan?fa[e.connect_method]:`${fa[e.connect_method]}, needs the ${e.plan_label} plan`}function Xn(e){return e.connected&&e.needs_attention?"needs_attention":e.connected?"connected":"available"}var Qn=["needs_attention","connected","available"],lm={needs_attention:"Needs attention",connected:"Connected",available:"Available to connect"};function um(e,t){return t==="needs_attention"?`${e.name} (${e.slug}): ${e.problem}`:t==="available"?`${e.name} (${e.slug}): ${am(e)}`:e.status==="paused"||e.status==="connecting"?`${e.name} (${e.slug}): ${e.status}`:`${e.name} (${e.slug}): ${e.last_sync_at?`last sync ${R(e.last_sync_at)}`:"not synced yet"}`}function cm(e,t,n){let o=e.print.style;return o.enabled?n==="needs_attention"?`${o.red("!")} `:n==="available"?`${o.dim("+")} `:t.status==="paused"||t.status==="connecting"?`${o.orange("\u2016")} `:`${o.green("\u2713")} `:""}async function dm(e,t){if(t.connected&&t.available)throw new p("conflicting_options","use --connected or --available, not both",g.Usage,"Without either, the list shows everything.");let n=$(e.runtime),o=await W(e.runtime,n),i=o.permissions.can_manage_integrations,r=new Set(t.connected?["needs_attention","connected"]:t.available?["available"]:Qn),s=(await va(e,n)).filter(l=>r.has(Xn(l))).sort((l,u)=>Qn.indexOf(Xn(l))-Qn.indexOf(Xn(u))),a=`You cannot connect or change integrations in ${o.name}: ask an owner or admin for the right to manage integrations.`;e.print.result({can_manage_integrations:i,integrations:s},l=>{if(l.integrations.length===0)return t.available?"Every integration is already connected.":"No integrations are connected yet. See what you can connect: closedloop integrations list";let u=Qn.map(c=>({group:c,items:l.integrations.filter(d=>Xn(d)===c)})).filter(({items:c})=>c.length>0).map(({group:c,items:d})=>[e.print.style.bold(lm[c]),...d.map(m=>{let h=` ${cm(e,m,c)}`;return`${h}${Fn(um(m,c),e.print.style.width-h.length," ".repeat(h.length+2))}`})].join(`
|
|
141
142
|
`)).join(`
|
|
142
143
|
`);return i?u:`${u}
|
|
143
144
|
|
|
144
|
-
${a}`})}function
|
|
145
|
-
`)})}function
|
|
146
|
-
${n}`:
|
|
145
|
+
${a}`})}function wa(e){let t=e.choices?` (${e.choices.map(o=>o.value).join(" or ")})`:"",n=e.required===!0?"":e.required===!1?", optional":`, needed when ${e.required.when} is ${e.required.equals}`;return`${e.label}${t}${n}`}async function mm(e,t){let n=$(e.runtime),o=await zt(e,n,t);e.print.result(o,i=>{let r=[`${i.name} (${i.slug})`];if(i.description&&r.push(i.description),r.push(`Status: ${i.connected&&!i.needs_attention&&i.connected_at?`connected since ${R(i.connected_at)}; ${ya(i)}`:ya(i)}`),i.available_on_your_plan||r.push(`Plan: available on the ${i.plan_label} plan, not on yours.`),i.connect.method==="api_key")r.push(`Connect: closedloop integrations connect ${i.slug}`),r.push(` It asks for: ${i.connect.fields.map(wa).join("; ")}.`);else if(i.connect.method==="browser"){r.push(`Connect: closedloop integrations connect ${i.slug} (opens your browser)`);let s=wi(i)?.fields??[];s.length>0&&r.push(` It asks for: ${s.map(wa).join("; ")}.`)}else i.connect.method==="ingest"?r.push('Send data: closedloop ingest "some text", or closedloop ingest --file data.csv'):i.connect.app_url&&(r.push(`Set it up in the app: ${ve(i.connect.app_url,e.runtime.supportsHyperlinks)}`),r.push(` ${x}`));return r.join(`
|
|
146
|
+
`)})}function eo(e,t){return typeof e.required=="boolean"?e.required:t[e.required.when]===e.required.equals}function to(e,t){let n={};for(let o of e.field??[]){let i=o.indexOf("="),r=i>0?o.slice(0,i).trim():"",s=t.find(a=>a.key===r);if(!s){let a=t.filter(l=>!l.secret).map(l=>l.key);throw new p("unknown_field","this integration does not accept that setting; use --field name=value",g.Usage,a.length?`Its fields: ${a.join(", ")}.`:"It takes only the API key, on standard input with --stdin.")}if(s.secret)throw new p("secret_in_argument",`${s.label} cannot be passed as an argument`,g.Usage,"Pipe it in instead, so it stays out of your shell history: --stdin.");n[r]=o.slice(i+1).trim()}return n}function Ca(e,t){return!e.choices||t===""||e.choices.some(n=>n.value===t)?null:`${e.label} must be ${e.choices.map(n=>n.value).join(" or ")}`}async function pm(e,t,n){let o=t.choices?` [${t.choices.map(a=>`${a.value} = ${a.label}`).join(", ")}]`:"",i=t.help?` (${t.help})`:"",r=t.default?` (Enter for ${t.default})`:eo(t,n)?"":" (optional, Enter to skip)",s="";for(let a=1;a<=rm;a++){let l=await e.runtime.prompt(`${s}${t.label}${i}${o}${r}: `)||t.default||"",u=Ca(t,l)??(l===""&&eo(t,n)?`${t.label} is needed`:null);if(!u)return l;s=`${u}. `}throw new p("invalid_field",`no valid ${t.label} was given`,g.Usage)}async function gm(e,t,n,o){let i={};if(t.stdin){let r=(await e.runtime.readStdin()).split(/\r?\n/).map(s=>s.trim()).filter(s=>s!=="");if(r.length!==n.length)throw new p("stdin_mismatch",`expected ${n.length} line${n.length===1?"":"s"} on standard input, got ${r.length}`,g.Usage,`Send ${n.map(s=>s.label).join(", then ")}, one per line.`);return n.forEach((s,a)=>{i[s.key]=r[a]}),i}if(!e.interactive)throw new p("secret_required",`${n.map(r=>r.label).join(" and ")} must be piped in`,g.Usage,`For example: printf '%s\\n' "$KEY" | closedloop integrations connect ${o} --stdin`);for(let r of n){let s=await e.runtime.promptSecret(`${r.label}${r.help?` (${r.help})`:""}: `);if(!s)throw new p("secret_required",`${r.label} is needed`,g.Usage);i[r.key]=s}return i}async function ka(e,t,n,o){let i=n.filter(s=>!s.secret);for(let s of i){o[s.key]===void 0&&s.default&&(o[s.key]=s.default);let a=o[s.key]!==void 0?Ca(s,o[s.key]):null;if(a)throw new p("invalid_field",a,g.Usage)}for(let s of i)o[s.key]!==void 0&&o[s.key]!==""||e.interactive&&(s.required===!1||eo(s,o))&&(o[s.key]=await pm(e,s,o));let r=i.filter(s=>eo(s,o)&&!o[s.key]);if(r.length>0)throw new p("missing_field",`${t} also needs ${r.map(s=>s.label).join(", ")}`,g.Usage,`Add ${r.map(s=>`--field ${s.key}=<${s.label}>`).join(" ")}.`)}async function hm(e,t,n,o){let i=tm(o),r=n.connect.fields,s=to(o,r);await ka(e,n.name,r,s),Object.assign(s,await gm(e,o,r.filter(y=>y.secret),n.slug));let a={};for(let y of r)y.target==="config"&&s[y.key]&&(a[y.key]=s[y.key]);let l=y=>r.find(_=>_.target===y&&s[_.key])?.key,u=l("api_key"),c=l("api_secret"),d=await Ye(e.runtime,t),m=await v(e.runtime,t,"POST","/api/user-integrations",{integration_id:n.integration_id,api_key:u?s[u]:void 0,...c?{api_secret:s[c]}:{},config:a});if(m.status!==200&&m.status!==201)throw C(m,"connect_refused",`${n.name} could not be connected.`);let h=m.body.integration&&typeof m.body.integration=="object"?m.body.integration:void 0,f=await nm(e,t,n,h,i);e.print.result({status:"connected",slug:n.slug,name:n.name,reconnected:n.connected,history:f,...Sa(d)},y=>{let _=`${y.reconnected?"Reconnected":"Connected"} ${y.name}.`;return y.history?.started?Zn(`${_} Importing the last ${y.history.months} month${y.history.months==="1"?"":"s"} of history; follow it with: closedloop backfills show ${y.slug}`,d):y.history?Zn(`${_} The history import did not start: start it in the app (Integrations, ${y.name}). ${x}`,d):Zn(`${_} Check on it with: closedloop integrations show ${y.slug}`,d)})}function Sa(e){return{waiting_for:e?.analysis_waiting?"company_profile":null}}function Zn(e,t){let n=Mt(t);return n?`${e}
|
|
147
|
+
${n}`:e}function fm(e,t){return!t.connected||t.needs_attention?!1:e.connected?t.last_connection_at!==e.last_connection_at||t.connection_id!==e.connection_id:!0}function wi(e){let t=e.connect.browser;return!t||typeof t.start!="string"||!t.start.startsWith("/")||!Array.isArray(t.fields)?null:t}var ym="/api/cli/oauth-handoff",wm="/api/cli/oauth-handoff/go",_m="/closedloop/oauth",Aa=/^[A-Za-z0-9_-]{43}$/;function Oa(e,t,n,o){if(typeof e!="string")return null;let i,r;try{i=new URL(e),r=new URL(t)}catch{return null}let s=i.searchParams.getAll(o);return i.origin!==r.origin||i.pathname!==n||s.length!==1||!Aa.test(s[0])?null:{url:i.href,value:s[0]}}async function Ta(e,t){try{return await e.openUrl(t)===!0}catch{return!1}}var bm={remote_browser:"Your browser is on another computer",no_local_listener:"The CLI could not wait for your browser on this computer",browser_not_opened:"The browser could not be opened"},$m=["SSH_CONNECTION","SSH_TTY","SSH_CLIENT","CODESPACES","REMOTE_CONTAINERS","GITPOD_WORKSPACE_ID","CLOUD_SHELL","container","KUBERNETES_SERVICE_HOST"],vm=["/.dockerenv","/run/.containerenv"];function Cm(e){return $m.some(t=>!!e.env[t])||e.env.AWS_EXECUTION_ENV==="CloudShell"?!0:vm.some(t=>e.fileExists?.(t)===!0)}async function Ra(e,t,n,o){let{print:i,runtime:r}=e,s=o.openOnEnter,a=s?r.onEnter(()=>{Ta(r,s)}):()=>{},l=i.spinner(`Waiting for ${n.name} to connect\u2026`);try{for(let u=0;u<o.seconds;u+=ha){await r.sleep(ha*1e3);let c=o.stopped();if(c)throw c;let d=await zt(e,t,n.slug);if(fm(n,d)){let m=await Ye(r,t);i.result({status:"connected",slug:d.slug,name:d.name,reconnected:n.connected,...Sa(m)},h=>Zn(`${h.reconnected?"Reconnected":"Connected"} ${h.name}.`,m));return}}}finally{l.stop(),a()}throw new p("connect_pending",`${n.name} is not connected yet`,g.Failure,`Finish in the browser, then run \`closedloop integrations connect ${n.slug}\` again.`)}async function Kt(e,t,n,o,i){let{print:r,runtime:s,interactive:a}=e,l=n.connect.url;if(!l)throw new p("no_connect_link",`${n.name} has no link to connect it here`,g.Failure,`Connect it from the Integrations page in the app. ${x}`);let u=i?" (the settings you gave were not used)":"",c=o?`${bm[o]}, so connect ${n.name} in the app${u}:`:null,d={status:"browser_needed",slug:n.slug,name:n.name,url:l,...o?{reason:o}:{},next:`Open the link and connect ${n.name}${u}; then run \`closedloop integrations connect ${n.slug}\` again to check.`,app_sign_in:x};if(!a){r.result(d,m=>c?`${c} ${m.url}
|
|
147
148
|
${m.app_sign_in}
|
|
148
|
-
Then run: closedloop integrations connect ${m.slug}`:`Open ${m.url} and connect ${m.name}${u}. ${m.app_sign_in} Then run: closedloop integrations connect ${m.slug}`);return}
|
|
149
|
-
`)),await
|
|
150
|
-
`));else if(!await
|
|
151
|
-
${l.app_sign_in}`)}async function bd(t,e,n){let r=$(t.runtime),i=await xt(t,r,e);if(Os(i,"disconnected"),!i.connected||!i.connection_id){t.print.result({status:"not_connected",slug:i.slug,name:i.name},s=>`${s.name} is not connected.`);return}if(await Qe(t.runtime,r,"can_manage_integrations",`disconnect ${i.name}`),!n){if(!t.interactive)throw new p("confirmation_required",`disconnecting ${i.name} needs confirmation`,g.Usage,`Run it again with --yes: closedloop integrations disconnect ${i.slug} --yes`);if((await t.runtime.prompt(`Disconnect ${i.name}? Syncing stops; what was already imported stays. Type yes to confirm: `)).toLowerCase()!=="yes"){t.print.result({status:"cancelled",slug:i.slug,name:i.name},()=>"Nothing changed.");return}}let o=await v(t.runtime,r,"DELETE",`/api/user-integrations/${encodeURIComponent(i.connection_id)}`);if(o.status!==200&&o.status!==204)throw C(o,"disconnect_refused",`${i.name} could not be disconnected.`);t.print.result({status:"disconnected",slug:i.slug,name:i.name},s=>`Disconnected ${s.name}. What was already imported stays.`)}async function Ss(t,e,n){let r=$(t.runtime),i=await xt(t,r,e),o={slug:i.slug,name:i.name};if(Os(i,n?"paused":"resumed"),!i.connected||!i.connection_id){t.print.result({status:"not_connected",...o},a=>`${a.name} is not connected.`);return}if(n===(i.status==="paused")){t.print.result({status:n?"already_paused":"not_paused",...o},a=>n?`${a.name} is already paused.`:`${a.name} is not paused.`);return}await Qe(t.runtime,r,"can_manage_integrations",`${n?"pause":"resume"} ${i.name}`);let s=await v(t.runtime,r,"PUT",`/api/user-integrations/${encodeURIComponent(i.connection_id)}`,{status:n?"inactive":"active"});if(s.status!==200)throw C(s,n?"pause_refused":"resume_refused",`${i.name} could not be ${n?"paused":"resumed"}.`);t.print.result({status:n?"paused":"resumed",...o},a=>n?`Paused ${a.name}. Nothing new syncs until you resume it: closedloop integrations resume ${a.slug}`:`Resumed ${a.name}. It syncs again on its usual schedule.`)}var $d=(t,e)=>[...e??[],t],js=(t,e)=>{let n=t.command("integrations").alias("integration").description("See, connect and disconnect your workspace's integrations.");n.command("list").description("List integrations: those needing attention, those connected, and those you can connect.").option("--connected","Only the connected ones.").option("--available","Only the ones you can connect.").action(async r=>td(e,{connected:r.connected===!0,available:r.available===!0})),n.command("show").argument("<name>","The integration, e.g. hubspot.").description("Show one integration: its status and how to connect it.").action(async r=>nd(e,r)),n.command("connect").argument("<name>","The integration, e.g. hubspot.").description("Connect an integration: with an API key, in the browser, or with a link to the app.").option("--field <key=value>","A setting the integration needs, e.g. --field subdomain=acme (repeatable).",$d).option("--stdin","Read the API key (and secret, on a second line) from standard input.").option("--reconnect","Connect again even if it is already connected, e.g. with a new key.").option("--history <months>","With an API key: months of history to import, 1, 3 or 6 (3 if left out).").option("--no-history","With an API key: import no older history, only what comes from now on.").option("--in-app","In the browser: connect on the app's connect page instead (for example when the browser cannot reach this computer).").action(async(r,i)=>_d(e,r,i)),n.command("pause").argument("<name>","The integration, e.g. hubspot.").description("Stop syncing an integration without disconnecting it.").action(async r=>Ss(e,r,!0)),n.command("resume").argument("<name>","The integration, e.g. hubspot.").description("Start syncing a paused integration again.").action(async r=>Ss(e,r,!1)),n.command("disconnect").argument("<name>","The integration, e.g. hubspot.").description("Disconnect an integration. Syncing stops; imported data stays.").option("--yes","Skip the confirmation.").action(async(r,i)=>bd(e,r,i.yes===!0))};import{randomUUID as vd}from"node:crypto";var K="pending-login.json",ce="pending-browser-login.json",Cd=60,ct={us:"https://api.closedloop.sh",eu:"https://eu.api.closedloop.sh"},Nn="Choose --region us|eu: --region us stores new account data in the United States; --region eu stores it in the European Union.",kd="Choose your data region: United States (US) or European Union (EU) [US]: ",Nt="Run `closedloop login --email <email>` to get a new code.",Sd="Run `closedloop login --sso` to sign in through your browser.",Ad="Run `closedloop login --sso` to sign in with your password in your browser.",Od="Your company website, e.g. https://acme.example: ",Td=3,Ed=`Next, set your company website: ${X}. ${Ct} Nothing is analysed until it is done.`,Fs={INVALID_EMAIL:{code:"invalid_email"},DISPOSABLE_EMAIL:{code:"disposable_email"},EMAIL_SEND_FAILED:{code:"email_send_failed",hint:"Run the same command again in a minute."},RATE_LIMIT_EXCEEDED:{code:"rate_limited",hint:"Wait a while, then try again.",keepPending:!0},INVALID_CODE:{code:"invalid_code",hint:"Use the code from the newest ClosedLoop AI email and run the command again.",keepPending:!0},CODE_EXPIRED:{code:"code_expired",hint:Nt},CODE_ALREADY_USED:{code:"code_already_used",hint:Nt},INVALID_CHALLENGE:{code:"code_request_invalid",hint:Nt},TWO_FACTOR_REQUIRED:{code:"two_factor_required",hint:"Run it again with --totp <code from your authenticator app> or --backup-code <code>.",keepPending:!0},INVALID_2FA_CODE:{code:"invalid_two_factor_code",hint:`${Nt} Then use the current code from your authenticator app.`},TWO_FACTOR_SETUP_REQUIRED:{code:"two_factor_setup_required"},SSO_REQUIRED:{code:"sso_required",hint:Sd},PASSWORD_SIGN_IN:{code:"password_sign_in",hint:Ad},ACCOUNT_LOCKED:{code:"account_locked"},SIGNUP_LIMIT:{code:"signup_limit"},ACCOUNT_IN_OTHER_REGION:{code:"account_in_other_region",hint:"Set CLOSEDLOOP_API_URL to the API of your account's region and run `closedloop login` again."}},Us={"api.closedloop.sh":"eu.api.closedloop.sh","eu.api.closedloop.sh":"api.closedloop.sh"};function Ln(t,e){let n=typeof t.body.code=="string"?t.body.code:"",r=Object.prototype.hasOwnProperty.call(Fs,n)?Fs[n]:{code:"api_error"},i=typeof t.body.error=="string"&&t.body.error?t.body.error:`ClosedLoop AI could not complete the request (HTTP ${t.status})`;return Object.assign(new p(r.code,i,g.Failure,e??r.hint),{keepPending:r.keepPending===!0})}function Nr(){return new p("api_error","ClosedLoop AI sent an unexpected reply",g.Failure)}function Gs(t,e){return ne(t.runtime,n=>{let r=n.read(e),i={request_id:vd()};return n.write(e,i),{intent:i,previous:r}})}function $e(t,e,n,r,i=!0){ne(t.runtime,o=>{if(JSON.stringify(o.read(e))!==JSON.stringify(n)){if(i)throw Xe();return}r===null?o.remove(e):o.write(e,r)})}function be(t,e,n){ne(t.runtime,r=>{if(JSON.stringify(r.read(e))!==JSON.stringify(n))throw Xe()})}function qs(t,e){return/^[a-z0-9@._+-]+$/i.test(e)?e:Q(e,t.runtime.platform)}var Rd=/^[^\s\p{C}@<>,;:"()\\[\]]+@(?:[^\s\p{C}@<>,;:"()\\[\].]+\.)+[^\s\p{C}@<>,;:"()\\[\].]+$/u;function Id(t){let e=t?.trim().toLowerCase();return e&&e.length<=320&&Rd.test(e)?e:null}function Pd(t){try{return Jt(t.runtime)}catch(e){throw new p("invalid_api_url",e.message,g.Usage)}}function xd(t,e){if(e!==void 0){if(e!=="us"&&e!=="eu")throw new p("invalid_region","the region must be us or eu",g.Usage,Nn);if(t.runtime.env.CLOSEDLOOP_API_URL?.trim())throw new p("conflicting_options","--region cannot be combined with CLOSEDLOOP_API_URL",g.Usage,"Unset CLOSEDLOOP_API_URL to choose a production region, or omit --region to use the configured API.");return e}}var We={company:null,website_saved:!1,website_error:null,next:null,note:null},Nd=`Your workspace has no company website yet, so nothing is analysed; an owner or admin sets it with: ${X}`;async function Ws(t,e){try{return{...We,company:await sr(t.runtime,$(t.runtime),e),website_saved:!0}}catch(n){if(!(n instanceof p))throw n;return n.message}}async function Ld(t,e,n){let{runtime:r,print:i,interactive:o}=t;if(e!==void 0){let u=await Ws(t,e);return typeof u=="string"?{...We,website_error:u,next:X}:u}if(!n)return We;let s=$(r),a=await De(r,s);if(a?.research.state!=="no_website")return We;let l=!1;try{l=(await F(r,s)).permissions.can_manage_team_settings}catch(u){if(!(u instanceof p))throw u}if(!l)return{...We,company:a,note:Nd};if(!o)return{...We,company:a,next:X};i.note(Ct);for(let u=0;u<Td;u++){let c=(await r.prompt(Od)).trim();if(!c)break;let d=await Ws(t,c);if(typeof d!="string")return d;i.note(`That website could not be saved: ${d}`)}return{...We,company:a,next:X}}async function Dd(t,e,n){let r={domains_saved:!1,domains_instructions:null,company:e.company};if(!n||e.note)return r;let i=$(t.runtime),o=e.company;if(!o||!cn(o))return{...r,company:o};if(!t.interactive)return{domains_saved:!1,domains_instructions:St(o),company:o};try{let s=await to(t,i,o);if(s)return{domains_saved:!0,domains_instructions:null,company:s}}catch(s){if(!(s instanceof p))throw s;t.print.note(`The email domains were not saved: ${s.message}`)}return{...r,company:o}}async function Js(t,e,n,r,i,o){let s=n.body;if(typeof s.accessToken!="string"||typeof s.refreshToken!="string"||typeof s.user?.email!="string")throw Nr();let a=s.team&&typeof s.team.name=="string"?{id:String(s.team.id),name:s.team.name}:null;ai(t.runtime,{api_url:e,email:s.user.email,user_id:s.user.id,team:a,region:s.region,access_token:s.accessToken,refresh_token:s.refreshToken},r,Kt(s.sign_in_method),{file:r==="email_code"?K:ce,value:o});let l=s.new_account===!0,u=r==="email_code"&&l,c=await Ld(t,i.website,u),d=await Dd(t,c,u),m={...c,company:d.company},f={status:"signed_in",new_account:l,email:s.user.email,team:a,region:s.region??null,...m,domains_saved:d.domains_saved,domains_instructions:d.domains_instructions};t.print.result(f,h=>{let y=[h.new_account?`Account created for ${h.email} on the Free plan. You're signed in.`:`Signed in as ${h.email}${h.team?` (${h.team.name})`:""}.`];return h.website_error&&y.push(`The website was not saved: ${h.website_error}`),h.website_saved&&y.push(i.website===void 0?"Website saved.":`Website saved. ${Ct}`),h.domains_saved&&y.push("Email domains saved."),(h.website_saved||h.domains_saved)&&h.company?y.push(ar(h.company,Date.now())):h.note?y.push(h.note):h.next&&y.push(Ed),y.join(`
|
|
152
|
-
`)})}function
|
|
153
|
-
`)),
|
|
154
|
-
`)}
|
|
155
|
-
`)}async function
|
|
156
|
-
|
|
157
|
-
`
|
|
158
|
-
`))}async function
|
|
159
|
-
`)
|
|
160
|
-
`)})}async function
|
|
149
|
+
Then run: closedloop integrations connect ${m.slug}`:`Open ${m.url} and connect ${m.name}${u}. ${m.app_sign_in} Then run: closedloop integrations connect ${m.slug}`);return}r.result(d,m=>[c??`Connect ${m.name} in your browser${u}:`,` ${ve(m.url,s.supportsHyperlinks)}`,m.app_sign_in,"Press Enter to open it. Waiting for the connection\u2026"].join(`
|
|
150
|
+
`)),await Ra(e,t,n,{seconds:om,openOnEnter:l,stopped:()=>null})}var yi="ClosedLoop AI could not finish connecting here. Go back to your terminal.",Ea={status:404,body:"Not found."};function km(e,t,n,o,i){if(i.method!=="GET"||i.path!==_m||o.token===null)return Promise.resolve(Ea);let r=i.query.getAll("n");if(r.length!==1||!Aa.test(r[0]))return Promise.resolve({status:400,body:"This link is not complete. Go back to your terminal."});let s=o.queue.then(()=>Sm(e,t,n,o,r[0]));return o.queue=s.catch(()=>{}),s}async function Sm(e,t,n,o,i){if(o.success)return o.success.n===i?o.success.reply:Ea;try{let r=await v(e.runtime,t,"POST","/api/cli/oauth-handoff/confirm",{t:o.token,n:i});if(r.status===409)return{status:409,body:"Press Continue on the page the link opened, in this browser."};if(r.status!==200)return o.failure=C(r,"connect_refused",`${n.name} could not be connected.`),{status:400,body:yi,last:!0};let s=Oa(r.body.go_url,t.api_url,wm,"g");if(!s)return o.failure=new p("api_error",`${n.name} could not be connected from here`,g.Failure,`Try again, or connect it from the Integrations page in the app. ${x}`),{status:502,body:yi,last:!0};let a={status:302,location:s.url,last:!0};return o.success={n:i,reply:a},a}catch(r){return o.failure=r instanceof p?r:new p("api_error",`${n.name} could not be connected from here`,g.Failure),{status:500,body:yi,last:!0}}}async function Am(e,t,n,o){let i=e.runtime.listenLoopback;if(!i)return null;try{return await i(r=>km(e,t,n,o,r))}catch{return null}}async function Om(e,t,n,o,i,r,s){let a=n.connected&&typeof n.connection_id=="string"&&(n.connections??1)<=1?{existing_connection_id:n.connection_id,is_reconnection:!0}:{},l=await v(e.runtime,t,"POST",o.start,{team_id:r,...i,...a},{"X-ClosedLoop-Browser-Handoff":"1","X-ClosedLoop-Loopback-Port":String(s)});if(l.status<200||l.status>=300)throw C(l,"connect_refused",`${n.name} could not be connected.`);let u=Oa(l.body.handoff_url,t.api_url,ym,"t");if(!u)throw new p("api_error",`${n.name} could not be started from here`,g.Failure,`Try again, or connect it from the Integrations page in the app. ${x}`);return u}async function Tm(e,t,n,o,i){let r=wi(n),s=(o.field??[]).length>0;if(!r)return Kt(e,t,n,null,!1);if(o.inApp)return Kt(e,t,n,null,s);let{print:a,runtime:l,interactive:u}=e;if(Cm(l))return Kt(e,t,n,"remote_browser",s);let c=to(o,r.fields);await ka(e,n.name,r.fields,c);let d=Object.fromEntries(r.fields.filter(y=>c[y.key]).map(y=>[y.key,c[y.key]])),m=Object.keys(d).length>0,h={token:null,failure:null,success:null,queue:Promise.resolve()},f=await Am(e,t,n,h);if(!f)return Kt(e,t,n,"no_local_listener",m);try{let y=await Om(e,t,n,r,d,i,f.port);if(h.token=y.value,u)a.result({status:"browser_needed",slug:n.slug,name:n.name,url:y.url},_=>[`Connect ${_.name} in your browser:`,` ${ve(_.url,l.supportsHyperlinks)}`,"Press Enter to open it. Waiting for the connection\u2026"].join(`
|
|
151
|
+
`));else if(!await Ta(l,y.url))return f.close(),Kt(e,t,n,"browser_not_opened",m);await Ra(e,t,n,{seconds:im,openOnEnter:u?y.url:null,stopped:()=>h.failure})}finally{f.close()}}async function Rm(e,t,n){let o=$(e.runtime),i=await zt(e,o,t);if(n.inApp&&i.connect.method!=="browser")throw new p("not_a_browser_integration",`${i.name} does not connect in the browser, so --in-app does not apply`,g.Usage,`Run it without --in-app: closedloop integrations connect ${i.slug}`);let r=i.connect.method==="browser"?wi(i)?.fields??[]:[];if(i.connect.method==="api_key")to(n,i.connect.fields);else if(r.length>0&&!n.stdin)to(n,r);else if((n.field??[]).length>0||n.stdin)throw new p("not_an_api_key_integration",`${i.name} ${sm[i.connect.method]}, not with a key`,g.Usage,`Run it without --field and --stdin: closedloop integrations connect ${i.slug}`);if(i.connected&&!i.needs_attention&&!n.reconnect){e.print.result({status:"already_connected",slug:i.slug,name:i.name},l=>`${l.name} is already connected. To connect it again (for example with a new key), add --reconnect.`);return}let s=i.connect.method!=="ingest"?await pt(e.runtime,o,"can_manage_integrations",`connect ${i.name}`):null;if(!i.available_on_your_plan)throw new p("plan_required",`${i.name} is available on the ${i.plan_label} plan`,g.Failure,"Change your plan with: closedloop plan upgrade");if(i.connect.method==="api_key")return hm(e,o,i,n);if(i.connect.method==="browser"&&s)return Tm(e,o,i,n,s.id);if(i.connect.method==="ingest"){e.print.result({status:"use_ingest",slug:i.slug,name:i.name},()=>"Send your own data with closedloop ingest: some text, or --file with a CSV, JSON or text file. The CLI source is created on the first send.");return}let a=i.connect.app_url;e.print.result({status:"set_up_in_app",slug:i.slug,name:i.name,url:a,app_sign_in:x},l=>`${l.name} is set up in the app${l.url?`: ${ve(l.url,e.runtime.supportsHyperlinks)}`:"."}
|
|
152
|
+
${l.app_sign_in}`)}async function Em(e,t,n){let o=$(e.runtime),i=await zt(e,o,t);if($a(i,"disconnected"),!i.connected||!i.connection_id){e.print.result({status:"not_connected",slug:i.slug,name:i.name},s=>`${s.name} is not connected.`);return}if(await pt(e.runtime,o,"can_manage_integrations",`disconnect ${i.name}`),!n){if(!e.interactive)throw new p("confirmation_required",`disconnecting ${i.name} needs confirmation`,g.Usage,`Run it again with --yes: closedloop integrations disconnect ${i.slug} --yes`);if((await e.runtime.prompt(`Disconnect ${i.name}? Syncing stops; what was already imported stays. Type yes to confirm: `)).toLowerCase()!=="yes"){e.print.result({status:"cancelled",slug:i.slug,name:i.name},()=>"Nothing changed.");return}}let r=await v(e.runtime,o,"DELETE",`/api/user-integrations/${encodeURIComponent(i.connection_id)}`);if(r.status!==200&&r.status!==204)throw C(r,"disconnect_refused",`${i.name} could not be disconnected.`);e.print.result({status:"disconnected",slug:i.slug,name:i.name},s=>`Disconnected ${s.name}. What was already imported stays.`)}async function _a(e,t,n){let o=$(e.runtime),i=await zt(e,o,t),r={slug:i.slug,name:i.name};if($a(i,n?"paused":"resumed"),!i.connected||!i.connection_id){e.print.result({status:"not_connected",...r},a=>`${a.name} is not connected.`);return}if(n===(i.status==="paused")){e.print.result({status:n?"already_paused":"not_paused",...r},a=>n?`${a.name} is already paused.`:`${a.name} is not paused.`);return}await pt(e.runtime,o,"can_manage_integrations",`${n?"pause":"resume"} ${i.name}`);let s=await v(e.runtime,o,"PUT",`/api/user-integrations/${encodeURIComponent(i.connection_id)}`,{status:n?"inactive":"active"});if(s.status!==200)throw C(s,n?"pause_refused":"resume_refused",`${i.name} could not be ${n?"paused":"resumed"}.`);e.print.result({status:n?"paused":"resumed",...r},a=>n?`Paused ${a.name}. Nothing new syncs until you resume it: closedloop integrations resume ${a.slug}`:`Resumed ${a.name}. It syncs again on its usual schedule.`)}var Im=(e,t)=>[...t??[],e],Ia=(e,t)=>{let n=e.command("integrations").alias("integration").description("See, connect and disconnect your workspace's integrations.");n.command("list").description("List integrations: those needing attention, those connected, and those you can connect.").option("--connected","Only the connected ones.").option("--available","Only the ones you can connect.").action(async o=>dm(t,{connected:o.connected===!0,available:o.available===!0})),n.command("show").argument("<name>","The integration, e.g. hubspot.").description("Show one integration: its status and how to connect it.").action(async o=>mm(t,o)),n.command("connect").argument("<name>","The integration, e.g. hubspot.").description("Connect an integration: with an API key, in the browser, or with a link to the app.").option("--field <key=value>","A setting the integration needs, e.g. --field subdomain=acme (repeatable).",Im).option("--stdin","Read the API key (and secret, on a second line) from standard input.").option("--reconnect","Connect again even if it is already connected, e.g. with a new key.").option("--history <months>","With an API key: months of history to import, 1, 3 or 6 (3 if left out).").option("--no-history","With an API key: import no older history, only what comes from now on.").option("--in-app","In the browser: connect on the app's connect page instead (for example when the browser cannot reach this computer).").action(async(o,i)=>Rm(t,o,i)),n.command("pause").argument("<name>","The integration, e.g. hubspot.").description("Stop syncing an integration without disconnecting it.").action(async o=>_a(t,o,!0)),n.command("resume").argument("<name>","The integration, e.g. hubspot.").description("Start syncing a paused integration again.").action(async o=>_a(t,o,!1)),n.command("disconnect").argument("<name>","The integration, e.g. hubspot.").description("Disconnect an integration. Syncing stops; imported data stays.").option("--yes","Skip the confirmation.").action(async(o,i)=>Em(t,o,i.yes===!0))};import{randomUUID as Pm}from"node:crypto";var j=$e,me=ie,xm=60,no="Choose --region us|eu: --region us stores new account data in the United States; --region eu stores it in the European Union.",Nm="Choose your data region: United States (US) or European Union (EU) [US]: ",Xt="Run `closedloop login --email <email>` to get a new code.",Lm="Run `closedloop login --browser` to sign in through your browser.",Dm="Run `closedloop login --browser` to sign in with your password in your browser.",jm="Your company website, e.g. https://acme.example: ",Fm=3,Um=`Next, set your company website: ${te}. ${Wt} Nothing is analysed until it is done.`,Wm=["sso_required","password_sign_in"],Pa={INVALID_EMAIL:{code:"invalid_email"},DISPOSABLE_EMAIL:{code:"disposable_email"},EMAIL_SEND_FAILED:{code:"email_send_failed",hint:"Run the same command again in a minute."},RATE_LIMIT_EXCEEDED:{code:"rate_limited",hint:"Wait a while, then try again.",keepPending:!0},INVALID_CODE:{code:"invalid_code",hint:"Use the code from the newest ClosedLoop AI email and run the command again.",keepPending:!0},CODE_EXPIRED:{code:"code_expired",hint:Xt},CODE_ALREADY_USED:{code:"code_already_used",hint:Xt},INVALID_CHALLENGE:{code:"code_request_invalid",hint:Xt},TWO_FACTOR_REQUIRED:{code:"two_factor_required",hint:"Run it again with --totp <code from your authenticator app> or --backup-code <code>.",keepPending:!0},INVALID_2FA_CODE:{code:"invalid_two_factor_code",hint:`${Xt} Then use the current code from your authenticator app.`},TWO_FACTOR_SETUP_REQUIRED:{code:"two_factor_setup_required"},SSO_REQUIRED:{code:"sso_required",hint:Lm},PASSWORD_SIGN_IN:{code:"password_sign_in",hint:Dm},ACCOUNT_LOCKED:{code:"account_locked"},SIGNUP_LIMIT:{code:"signup_limit"},ACCOUNT_IN_OTHER_REGION:{code:"account_in_other_region",hint:"Set CLOSEDLOOP_API_URL to the API of your account's region and run `closedloop login` again."}},xa={"api.closedloop.sh":"eu.api.closedloop.sh","eu.api.closedloop.sh":"api.closedloop.sh"};function io(e,t){let n=typeof e.body.code=="string"?e.body.code:"",o=Object.prototype.hasOwnProperty.call(Pa,n)?Pa[n]:{code:"api_error"},i=typeof e.body.error=="string"&&e.body.error?e.body.error:`ClosedLoop AI could not complete the request (HTTP ${e.status})`;return Object.assign(new p(o.code,i,g.Failure,t??o.hint),{keepPending:o.keepPending===!0})}function bi(){return new p("api_error","ClosedLoop AI sent an unexpected reply",g.Failure)}function $i(e,t,n){return dn(e.runtime,t,o=>{let i=o.read(n),r={request_id:Pm()};return o.write(n,r),{intent:r,previous:i}})}function pe(e,t,n,o,i,r=!0){dn(e.runtime,t,s=>{if(JSON.stringify(s.read(n))!==JSON.stringify(o)){if(r)throw ct();return}i===null?s.remove(n):s.write(n,i)})}function Oe(e,t,n,o){dn(e.runtime,t,i=>{if(JSON.stringify(i.read(n))!==JSON.stringify(o))throw ct()})}function Da(e,t){return So(t,e.runtime.platform)}function ja(e,t,n){return`${Ie(t,e.runtime.platform)} --browser${n!==void 0?` --region ${n}`:""}`}function _i(){return new p("missing_email","a valid email address is required",g.Usage,"Run `closedloop login you@example.com` with your email address.")}function Mm(e){try{return sn(e.runtime)}catch(t){throw new p("invalid_api_url",t.message,g.Usage)}}function Hm(e,t){if(t!==void 0){if(t!=="us"&&t!=="eu")throw new p("invalid_region","the region must be us or eu",g.Usage,no);if(e.runtime.env.CLOSEDLOOP_API_URL?.trim())throw new p("conflicting_options","--region cannot be combined with CLOSEDLOOP_API_URL",g.Usage,"Unset CLOSEDLOOP_API_URL to choose a production region, or omit --region to use the configured API.");return t}}var Qe={company:null,website_saved:!1,website_error:null,next:null,note:null},Gm=`Your workspace has no company website yet, so nothing is analysed; an owner or admin sets it with: ${te}`;async function Na(e,t){try{return{...Qe,company:await Jo(e.runtime,$(e.runtime),t),website_saved:!0}}catch(n){if(!(n instanceof p))throw n;return n.message}}async function qm(e,t,n){let{runtime:o,print:i,interactive:r}=e;if(t!==void 0){let u=await Na(e,t);return typeof u=="string"?{...Qe,website_error:u,next:te}:u}if(!n)return Qe;let s=$(o),a=await Ye(o,s);if(a?.research.state!=="no_website")return Qe;let l=!1;try{l=(await W(o,s)).permissions.can_manage_team_settings}catch(u){if(!(u instanceof p))throw u}if(!l)return{...Qe,company:a,note:Gm};if(!r)return{...Qe,company:a,next:te};i.note(Wt);for(let u=0;u<Fm;u++){let c=(await o.prompt(jm)).trim();if(!c)break;let d=await Na(e,c);if(typeof d!="string")return d;i.note(`That website could not be saved: ${d}`)}return{...Qe,company:a,next:te}}async function Jm(e,t,n){let o={domains_saved:!1,domains_instructions:null,company:t.company};if(!n||t.note)return o;let i=$(e.runtime),r=t.company;if(!r||!xn(r))return{...o,company:r};if(!e.interactive)return{domains_saved:!1,domains_instructions:Ht(r),company:r};try{let s=await zr(e,i,r);if(s)return{domains_saved:!0,domains_instructions:null,company:s}}catch(s){if(!(s instanceof p))throw s;e.print.note(`The email domains were not saved: ${s.message}`)}return{...o,company:r}}async function Fa(e,t,n,o,i,r){let s=n.body;if(typeof s.accessToken!="string"||typeof s.refreshToken!="string"||typeof s.user?.email!="string")throw bi();let a=s.team&&typeof s.team.name=="string"?{id:String(s.team.id),name:s.team.name}:null,l=r.account,u=l!==null&&s.user.email.trim().toLowerCase()!==l;er(e.runtime,{api_url:t,email:s.user.email,user_id:s.user.id,team:a,region:s.region,access_token:s.accessToken,refresh_token:s.refreshToken},o,_n(s.sign_in_method),r);let c=s.new_account===!0,d=o==="email_code"&&c,m=await qm(e,i.website,d),h=await Jm(e,m,d),f={...m,company:h.company},y={status:"signed_in",new_account:c,email:s.user.email,team:a,region:s.region??null,requested_email:l,different_account:u,...f,domains_saved:h.domains_saved,domains_instructions:h.domains_instructions};e.print.result(y,_=>{let S=[_.different_account?`You asked to sign in as ${_.requested_email} but approved as ${_.email}; signed in as ${_.email}${_.team?` (${_.team.name})`:""}.`:_.new_account?`Account created for ${_.email} on the Free plan. You're signed in.`:`Signed in as ${_.email}${_.team?` (${_.team.name})`:""}.`];return _.website_error&&S.push(`The website was not saved: ${_.website_error}`),_.website_saved&&S.push(i.website===void 0?"Website saved.":`Website saved. ${Wt}`),_.domains_saved&&S.push("Email domains saved."),(_.website_saved||_.domains_saved)&&_.company?S.push(Vo(_.company,Date.now())):_.note?S.push(_.note):_.next&&S.push(Um),S.join(`
|
|
153
|
+
`)})}function Ua(e,t){if(e!==Q.us&&e!==Q.eu||typeof t!="string")return null;try{let n=new URL(e).hostname,o=Object.prototype.hasOwnProperty.call(xa,n)?xa[n]:null,i=new URL(t);return i.protocol!=="https:"||i.hostname!==o||i.port!==""||i.username!==""||i.password!==""?null:i.origin}catch{return null}}function Wa(e,t){return e.status!==409||e.body.code!=="ACCOUNT_IN_OTHER_REGION"?null:Ua(t,e.body.api_url)}async function Vm(e,t,n){let o=Me(e.runtime),i=o.last_email===t&&o.last_region==="eu"?["eu","us"]:["us","eu"],r=null;for(let s of i){let a=await He(e.runtime,Q[s],"/api/check-email",{email:t});if(n(),a.status!==200||a.body.success!==!0||typeof a.body.exists!="boolean")throw new p("region_check_failed","ClosedLoop AI could not check where this account belongs",g.Failure,`Try again. ${no}`);let l=a.body.ssoRequired===!0;if(a.body.exists){let u=Array.isArray(a.body.authMethods)?a.body.authMethods:[];return{kind:"account",region:s,browser:l||u.includes("password")}}l&&r===null&&(r=s)}return r!==null?{kind:"company_sign_in",region:r}:{kind:"none"}}async function Bm(e,t){if(!e.interactive)throw new p("region_required","choose where the new account's data should live before signing up",g.Usage,no);let n=(await e.runtime.prompt(Nm)).trim().toLowerCase();t();let o=n===""?"us":n;if(o!=="us"&&o!=="eu")throw new p("invalid_region","the region must be us or eu",g.Usage,no);return o}async function Ym(e,t,n,o,i){let r=i??$i(e,n,j);try{Oe(e,n,j,r.intent);let s=t,a=await He(e.runtime,s,"/api/cli/auth/code",{email:n});Oe(e,n,j,r.intent);let l=Wa(a,t);if(l!==null){let m=typeof a.body.region=="string"?a.body.region.toUpperCase():"other";e.print.note(`Your ClosedLoop AI account is in the ${m} region; signing in there.`),s=l,mn(e.runtime,n,Ue(s)),a=await He(e.runtime,s,"/api/cli/auth/code",{email:n}),Oe(e,n,j,r.intent)}if(a.status!==200)throw Object.assign(io(a),{api:s});let{challenge:u,expires_in:c}=a.body;if(typeof u!="string"||typeof c!="number")throw bi();let d={api_url:s,configured_api_url:t,email:n,challenge:u,...o!==void 0&&{requested_region:o},expires_at:new Date((e.runtime.now?.()??Date.now())+c*1e3).toISOString()};return pe(e,n,j,r.intent,d),d}catch(s){throw pe(e,n,j,r.intent,r.previous,!1),s}}function Km(e,t,n){let o=ko(e.runtime,t,j);if(o===null||typeof o!="object"||o.email!==t||!n.includes(o.configured_api_url??o.api_url)||typeof o.challenge!="string"||!o.challenge.trim())throw new p("no_code_requested",`no code was requested for ${t} on this computer`,g.Failure,`Run \`closedloop login --email ${Da(e,t)}\` first, then use the code from the email.`);let i=typeof o.expires_at=="string"?Date.parse(o.expires_at):NaN;if(!Number.isFinite(i)||i<=(e.runtime.now?.()??Date.now()))throw pe(e,t,j,o,null,!1),new p("code_expired","the saved sign-in code request has expired",g.Failure,Xt);return o}async function La(e,t,n,o){return He(e.runtime,t.api_url,"/api/cli/auth/verify",{challenge:t.challenge,code:n.trim(),...o.totp&&{totpCode:o.totp.trim()},...o.backupCode&&{backupCode:o.backupCode.trim()}})}async function Ma(e,t,n,o){let{runtime:i,interactive:r}=e,s=await La(e,t,n,o);if(Oe(e,t.email,j,t),r&&s.status===401&&s.body.code==="TWO_FACTOR_REQUIRED"){let a=await i.promptSecret("Code from your authenticator app: ");s=await La(e,t,n,{...o,totp:a}),Oe(e,t.email,j,t)}if(s.status!==200){let a=Object.assign(io(s),{api:t.api_url});throw a.keepPending||pe(e,t.email,j,t,null,!1),a}await Fa(e,t.api_url,s,"email_code",o,{account:t.email,file:j,value:t})}async function Ha(e,t,n,o,i){try{await i()}catch(r){if(!(r instanceof p)||!Wm.includes(r.code))throw r;let s=r.code==="sso_required";if(!e.interactive)throw new p(r.code,r.message,r.exitCode,`Run \`${ja(e,t,n.region)}\` ${s?"to sign in through your browser":"to sign in with your password in your browser"}.`);e.print.note(`${s?"Your workspace signs in with SSO":"This account signs in with its password"}, so you sign in through your browser.`),await oo(e,r.api??o,n,t)}}async function Ga(e,t,n,o,i,r){let{runtime:s,print:a,interactive:l}=e;await Ha(e,n,o,t,async()=>{let u=await Ym(e,t,n,i,r),c=o.website!==void 0?` --website ${ee(o.website.trim(),s.platform)}`:"",d=u.requested_region!==void 0?` --region ${u.requested_region}`:"",m={status:"code_sent",email:n,region:Ue(u.api_url),expires_at:u.expires_at,next:`closedloop login --email ${Da(e,n)} --code <code>${d}${c}`};if(!l){a.result(m,h=>`We sent a sign-in code to ${h.email}. Finish with: ${h.next}`);return}a.result(m,h=>`We sent a sign-in code to ${h.email}.`),await Ma(e,u,await s.prompt("Code from the email: "),o)})}function zm(e,t,n){let o=ko(e.runtime,t,me);if(o===null||typeof o!="object"||(o.configured_api_url??o.api_url)!==n||typeof o.challenge!="string"||!o.challenge.trim()||typeof o.device_secret!="string"||!o.device_secret.trim()||typeof o.user_code!="string"||!o.user_code.trim()||typeof o.url!="string"||typeof o.interval!="number"||!Number.isFinite(o.interval)||o.interval<1)return null;let i=o.regional_hops??0;if(i!==0&&i!==1||i===0&&o.api_url!==n||i===1&&(o.api_url===n||Ua(n,o.api_url)!==o.api_url))return null;let r=typeof o.expires_at=="string"?Date.parse(o.expires_at):NaN;return!Number.isFinite(r)||r<=(e.runtime.now?.()??Date.now())?null:o}async function Xm(e,t,n){let o=$i(e,t,me);try{let i=await He(e.runtime,n,"/api/cli/auth/device/start",t!==null?{email:t}:{});if(Oe(e,t,me,o.intent),i.status!==200)throw io(i);let r=i.body;if(typeof r.challenge!="string"||typeof r.device_secret!="string"||typeof r.user_code!="string"||typeof r.verification_url!="string"||typeof r.expires_in!="number")throw bi();let s={api_url:n,configured_api_url:n,regional_hops:0,challenge:r.challenge,device_secret:r.device_secret,user_code:r.user_code,url:r.verification_url,interval:typeof r.interval=="number"&&r.interval>=1?r.interval:3,expires_at:new Date((e.runtime.now?.()??Date.now())+r.expires_in*1e3).toISOString()};return pe(e,t,me,o.intent,s),s}catch(i){throw pe(e,t,me,o.intent,o.previous,!1),i}}function qa(e,t,n,o){throw pe(e,t,me,n,null,!1),new p("invalid_region_handoff","ClosedLoop AI returned an invalid regional sign-in response",g.Failure,o)}function Qm(e,t,n,o,i){let r=Wa(o,n.api_url),s=o.body,a=e.runtime.now?.()??Date.now(),l=Date.parse(n.expires_at);(r===null||(n.regional_hops??0)!==0||s.region!==(r===Q.eu?"eu":"us")||typeof s.challenge!="string"||!s.challenge.trim()||s.challenge===n.challenge||typeof s.expires_in!="number"||!Number.isFinite(s.expires_in)||s.expires_in<=0||typeof s.interval!="number"||!Number.isFinite(s.interval)||s.interval<1||!Number.isFinite(l)||l<=a)&&qa(e,t,n,i);let u={...n,configured_api_url:n.configured_api_url??n.api_url,api_url:r,challenge:s.challenge,regional_hops:1,interval:s.interval,expires_at:new Date(Math.min(l,a+s.expires_in*1e3)).toISOString()};return pe(e,t,me,n,u),t!==null&&mn(e.runtime,t,Ue(r)),u}async function Zm(e,t,n,o,i){let r=Math.max(1,Math.ceil(o/n.interval)),s=n,a=()=>He(e.runtime,s.api_url,"/api/cli/auth/device/token",{challenge:s.challenge,deviceSecret:s.device_secret});for(let l=0;l<r;l++){let u=await a();if(Oe(e,t,me,s),u.status===409&&u.body.code==="ACCOUNT_IN_OTHER_REGION"&&(s=Qm(e,t,s,u,i),u=await a(),Oe(e,t,me,s),u.status===409&&u.body.code==="ACCOUNT_IN_OTHER_REGION"&&qa(e,t,s,i)),u.status===200&&u.body.status==="signed_in")return{reply:u,pending:s};if(u.status!==200||u.body.status!=="pending"){let c=io(u,i);throw c.keepPending||pe(e,t,me,s,null,!1),c}if(l<r-1){let c=Math.max(0,Date.parse(s.expires_at)-(e.runtime.now?.()??Date.now()));await e.runtime.sleep(Math.min(s.interval*1e3,c))}}return{reply:null,pending:s}}async function oo(e,t,n,o){let{print:i,interactive:r}=e,s=ja(e,o,n.region),a=`${s}${n.website!==void 0?` --website ${ee(n.website.trim(),e.runtime.platform)}`:""}`,l=r?null:zm(e,o,t),u=l??await Xm(e,o,t),c={status:"approval_needed",email:o,region:Ue(u.api_url),url:u.url,user_code:u.user_code,expires_at:u.expires_at,next:`Open the link, enter the code, approve; then run \`${a}\` again.`};if(!r&&l===null){i.result(c,y=>`Open ${y.url} , enter the code ${y.user_code}, and approve. Then run: ${a}`);return}let d=()=>{};if(r){let{runtime:y}=e,_=await y.copyToClipboard(u.user_code);i.result(c,S=>["Open this link, sign in, and enter this code:",` ${ve(S.url,y.supportsHyperlinks)}`,` ${S.user_code}${_?" (copied to your clipboard)":""}`,"Press Enter to open the link in your browser. Waiting for your approval\u2026"].join(`
|
|
154
|
+
`)),d=y.onEnter(()=>{y.openUrl(u.url)})}let m=Math.max(0,(Date.parse(u.expires_at)-(e.runtime.now?.()??Date.now()))/1e3),h,f=r?e.print.spinner("Waiting for your approval in the browser\u2026"):null;try{let y=await Zm(e,o,u,r?m:Math.min(m,xm),`Run \`${s}\` to start again.`);h=y.reply,u=y.pending}finally{f?.stop(),d()}if(h===null)throw new p("approval_pending","the sign-in has not been approved in the browser yet",g.Failure,`Open ${u.url} , enter the code ${u.user_code} and approve, then run \`${s}\` again.`);await Fa(e,u.api_url,h,"browser",n,{account:o,file:me,value:u})}async function ep(e,t,n,o){if(t!==void 0&&n.email!==void 0&&ut(t)!==ut(n.email))throw new p("conflicting_options","give the email once: as the argument or with --email",g.Usage,"Run `closedloop login you@example.com`.");let i=t??n.email;if(i!==void 0){let a=ut(i);if(a===null)throw _i();return a}let r=ut(Me(e.runtime).last_email);if(r!==null)return r;if(o)return null;if(!e.interactive)throw _i();let s=ut(await e.runtime.prompt("Email: "));if(s===null)throw _i();return s}async function tp(e,t,n,o,i){let r=$i(e,t,j),s=()=>Oe(e,t,j,r.intent),a,l;try{a=await Vm(e,t,s),a.kind==="none"&&!o&&(l=await Bm(e,s))}catch(d){throw pe(e,t,j,r.intent,r.previous,!1),d}a.kind==="account"&&(mn(e.runtime,t,a.region),e.print.note(`Signing in as ${t} (${a.region.toUpperCase()})`));let u=a.kind!=="none"?Q[a.region]:l!==void 0?Q[l]:i;if(!(o||a.kind==="company_sign_in"||a.kind==="account"&&a.browser)){await Ga(e,u,t,n,l,r);return}pe(e,t,j,r.intent,r.previous),await oo(e,u,n,t)}async function np(e,t,n){if(n.website!==void 0&&!n.website.trim())throw new p("invalid_website","--website needs the company website, e.g. https://acme.example",g.Usage);let o=Hm(e,n.region),i=n.browser===!0||n.sso===!0;if(i&&n.code!==void 0)throw new p("conflicting_options","--browser signs in through the browser and takes no --code",g.Usage,"Run `closedloop login <email> --browser` without --code.");let r=o!==void 0?Q[o]:Mm(e),s=o!==void 0||!!e.runtime.env.CLOSEDLOOP_API_URL?.trim(),a=await ep(e,t,n,i);if(a===null){await oo(e,r,n,null);return}if(n.code!==void 0){let l=n.code,u=s?[r]:[Q.us,Q.eu];await Ha(e,a,n,r,()=>Ma(e,Km(e,a,u),l,n));return}if(!s){await tp(e,a,n,i,r);return}i?await oo(e,r,n,a):await Ga(e,r,a,n,o)}var Ja=(e,t)=>{e.command("login").alias("signup").description("Sign in, or create an account, with your email. The CLI finds your account and picks how you sign in.").argument("[email]","Your email address. Left out, the CLI uses the one it signed in with last.").option("--email <email>","Your email address, instead of the argument.").option("--code <code>","The code from the email, like BCDFG-HJKLM (finishes a sign-in started without it).").option("--totp <code>","The code from your authenticator app, if your account uses two-factor authentication.").option("--backup-code <code>","A two-factor backup code, instead of --totp.").option("--browser","Sign in through your browser (company sign-in or password).").option("--sso","The same as --browser.").option("--region <region>","New account region, or where a browser sign-in starts: us or eu. Existing accounts sign in in their home region.").option("--website <url>","Your company website, set right after you sign in (ClosedLoop AI researches it before analysing anything).").action(async(n,o)=>{await np(t,n,o)})};function ro(e){try{return e()}catch(t){if(!(t instanceof p)||t.code!=="config_invalid")throw t;return null}}function Va(e,t){let n=({active_account:o,...i})=>JSON.stringify({...i,active:o??null});n(t)!==n(e.settings())&&e.setSettings(t)}function op(e){return Y(e,{accounts:It(e),settings:!0},t=>{let n=[];for(let r of t.emails())dt(e,ro(()=>t.read(r,N)),r)!==null&&n.push(r),t.forget(r);let{pending_browser_login:o,...i}=t.settings();return Va(t,{...i,active_account:null}),t.removeLeftovers(),n})}function ip(e,t){let n=[...new Set([...It(e),...t!==null?[t]:[]])];return Y(e,{accounts:n,settings:!0},o=>{let i=null,r=[];for(let l of o.emails()){let u=dt(e,ro(()=>o.read(l,N)),l);l===t?(i=u,o.forget(l)):u!==null?r.push(l):(ro(()=>o.read(l,$e))!==null||ro(()=>o.read(l,ie))!==null)&&(o.remove(l,$e),o.remove(l,ie))}let{pending_browser_login:s,...a}=o.settings();return Va(o,{...a,...a.active_account===t&&{active_account:null}}),o.removeLeftovers(),{session:i,others:r}})}var Ba=(e,{print:t,runtime:n})=>{e.command("logout").description("Sign out the active account on this computer. Your other sessions stay signed in.").option("--all","Sign out every account on this computer.").action(o=>{if(o.all){let s=op(n);t.result({status:s.length>0?"signed_out":"not_signed_in",emails:s},a=>a.emails.length>0?`Signed out ${a.emails.join(", ")} on this computer.`:"You were not signed in on this computer.");return}let{session:i,others:r}=ip(n,Ee(n).email);if(i===null){t.result({status:"not_signed_in"},()=>"You were not signed in on this computer.");return}t.result({status:"signed_out",email:i.email,other_accounts:r},s=>[`Signed out ${s.email} on this computer.`,...s.other_accounts.length>0?[`Still signed in here: ${s.other_accounts.join(", ")}. Switch with: closedloop use <email>`]:[]].join(`
|
|
155
|
+
`))})};var Ya=(e,t)=>{let n=e.command("metrics").alias("metric").description("Metrics: one calculation over a segment, measured every night; a goal measures a saved one with goals metrics add.");vs(n,t),Os(n,t)};var rp="closedloop company --domains <domain,...>",sp=7200,Ka=30,ap=new Set(["waiting","researching","retrying"]);async function Ze(e){try{return await e()}catch(t){if(t instanceof p&&!["not_signed_in","session_expired","upgrade_required"].includes(t.code))return null;throw t}}async function Qt(e,t,n,o){let i=await v(e,t,"GET",n),r=i.body[o];if(i.status!==200||!Array.isArray(r))throw new p("api_error",`${n} could not be read`,g.Failure);return r}async function za(e,t){let[n,o,i,r,s,a,l]=await Promise.all([Ze(()=>Be(e,t)),Ze(()=>Qt(e,t,"/api/cli/backfills?limit=100","backfills")),Ze(()=>Qt(e,t,"/api/cli/integrations","integrations")),Ze(()=>Qt(e,t,"/api/cli/ingest?limit=100","records")),Ze(()=>gt(e,t)),Ze(()=>Qt(e,t,"/api/user-goals?include=team","goals")),Ze(()=>Qt(e,t,"/api/team-members/all","members"))]),u={company:n,backfills:o,integrations:i,records:r,structure:s,goals:a,members:l};if(Object.values(u).every(c=>c===null))throw new p("status_unavailable","your workspace status could not be checked",g.Failure,"Check your connection and try `closedloop status` again.");return u}var Zt=(e,t)=>`${k(e)} ${t}${e===1?"":"s"}`;function lp(e,t){let n=`${Wo} ${Fr}`,o=e.suggested_products.length;return o>0?`${ke} (${k(o)} suggested by your company research). ${n}`:t&&t.research.state!=="ready"?`Suggestions appear once your company research is done: closedloop company --watch. ${n}`:`${Pn}. ${n}`}function Xa(e,t){let{company:n,backfills:o,integrations:i,records:r,structure:s,goals:a,members:l}=e,u=s?s.structure.products:null,c=(o??[]).filter(b=>Pe.has(b.state)),d=r?{waiting:r.filter(b=>b.state==="waiting").length,processing:r.filter(b=>b.state==="processing").length}:null,m=i?i.filter(b=>b.connected):null,h=r?r.length>0:null,f=n?.research.state??null,y=n?.email_domains,_=m===null&&h===null?null:(m?.length??0)>0||h===!0,S=[{step:"website",title:"Company website",optional:!1,done:n?n.website!==null:null,detail:n?.website??null,next:n&&n.website===null?te:null},{step:"research",title:"Company research",optional:!1,done:n?f==="ready":null,detail:n?ft(n,t):null,next:n&&f!=="ready"?f==="no_website"?te:"closedloop company --watch":null},{step:"email_domains",title:"Email domains",optional:!1,done:n?y?y.confirmed:!0:null,detail:n?qo(n):null,next:y&&!y.confirmed?rp:null},{step:"product_structure",title:"Product structure",optional:!1,done:u?u.length>0:null,detail:u?`${Zt(u.length,"product")} confirmed`:null,next:s&&u&&u.length===0?lp(s,n):null},{step:"source",title:"A source of customer conversations",optional:!1,done:_,detail:m&&m.length>0?m.map(b=>b.name??b.slug).join(", "):h?"records sent with closedloop ingest":null,next:_===!1?"closedloop integrations list --available, or send records with: closedloop ingest":null},{step:"history",title:"History imported",optional:!1,done:o===null||_===null?null:_===!0&&c.length===0,detail:o===null?null:c.length>0?`${Zt(c.length,"import")} still running`:null,next:c.length>0?"closedloop backfills":_===!1?"Connect a source first; its history is imported when you connect it":null},{step:"goal",title:"A goal",optional:!0,done:a?a.length>0:null,detail:a?Zt(a.length,"goal"):null,next:a&&a.length===0?"closedloop goals create --file goals.json":null},{step:"team",title:"Your team invited",optional:!0,done:l?l.length>1:null,detail:l?Zt(l.length,"member"):null,next:l&&l.length<=1?`In the app: Settings, then Team. ${x}`:null}];return{running:{research:f&&ap.has(f)?f:null,backfills:c,own_data:d},onboarding:S,instructions:n?Ht(n):null}}function up(e){let t=e.running.own_data;return e.running.research!==null||e.running.backfills.length>0||!!(t&&t.processing>0)}function cp(e,t,n){let o=t.print.style,i=[],r=e.running.own_data,s=[...e.running.backfills.map(c=>` ${$n(c,n,o)}`),...r&&r.waiting+r.processing>0?[` Your own data: ${[r.processing?`${k(r.processing)} processing`:"",r.waiting?`${k(r.waiting)} waiting`:""].filter(Boolean).join(", ")}`]:[]],a=e.onboarding.find(c=>c.step==="research");e.running.research&&a?.detail&&s.unshift(` ${a.detail}`),i.push("Running now"),i.push(...s.length>0?s:[" Nothing."]),i.push("","Onboarding");for(let c of e.onboarding){let d=c.done===!0?o.green("\u2713"):c.done===!1?c.optional?o.dim("\u25CB"):o.orange("\u25CB"):o.dim("?"),m=c.optional?`${c.title} (optional)`:c.title,h=c.done===!0&&c.detail?o.dim(` \xB7 ${c.detail}`):"";i.push(` ${d} ${m}${h}`),c.done!==!0&&c.next&&i.push(` ${o.dim("Next:")} ${c.next}`)}let l=e.onboarding.filter(c=>!c.optional&&c.done===!1).length,u=e.onboarding.filter(c=>!c.optional&&c.done===null).length;return i.push("",l===0&&u===0?"Onboarding is complete.":[l>0?`${Zt(l,"step")} left before your data is fully analysed.`:"",u>0?`${u} required step${u===1?"":"s"} could not be checked; try status again.`:""].filter(Boolean).join(" ")),e.instructions&&!t.interactive&&i.push("",e.instructions),i.join(`
|
|
156
|
+
`)}async function dp(e,t){let n=$(e.runtime),o=Xa(await za(e.runtime,n),Date.now());if(t.watch){let i=e.print.spinner("Checking every 30 seconds\u2026");try{for(let r=0;r<sp&&up(o);r+=Ka)await e.runtime.sleep(Ka*1e3),o=Xa(await za(e.runtime,n),Date.now())}finally{i.stop()}}e.print.result(o,i=>cp(i,e,Date.now()))}var Qa=(e,t)=>{e.command("status").description("What is running now (research, history imports, your own data) and what onboarding still needs.").option("--watch","Keep checking every 30 seconds until nothing is running (up to 2 hours).").action(async n=>dp(t,n))};import{existsSync as mp,writeFileSync as pp}from"node:fs";import{resolve as gp}from"node:path";var hp=/^\d{4}-(0[1-9]|1[0-2])$/;async function vi(e,t){let n=await Ge(e.runtime,t),o=await v(e.runtime,t,"GET",`/api/teams-billing/${n}/billing`);if(o.status!==200||typeof o.body.credits_used!="number")throw C(o,"api_error","Your usage could not be loaded. Try again.");return o.body}function fp(){return new Date().toISOString().slice(0,7)}function Za(e){let t=e??fp();if(!hp.test(t))throw new p("invalid_month",`'${t}' is not a month`,g.Usage,"Write it like 2026-10.");return t}function yp(e,t){let n=k(e.credits_used),o=e.current_period_end?R(e.current_period_end):null;if(e.credits_limit===null){let c=e.current_period_cost?` Cost so far: ${`${e.current_period_cost} ${e.currency??""}`.trim()}.`:"";return`${n} credits used this period (${e.plan_label} plan).${c}${o?` The period ends ${o}.`:""}`}let i=k(e.credits_limit),r=e.credits_used/e.credits_limit,s=Math.floor(r*100),a=e.credits_used>0&&s===0?"<1":String(s),u=[`${(e.limit_reached||r>=1?t.red:r>=.8?t.orange:t.green)(t.bar(r,30))} ${t.bold(`${a}%`)}`,`${n} of ${i} credits used this period (${a}%, ${e.plan_label} plan).`];return e.limit_reached?u.push(`The limit is reached: new conversations wait${o?` until ${o}`:" until the next period"}, or until you upgrade (closedloop plan upgrade).`):e.limit_enforced?(o&&u.push(`It resets ${o}.`),e.limit_reached_on&&u.push(`At this pace you reach the limit around ${P(e.limit_reached_on)}; after that, new conversations wait until it resets.`)):(r>=1&&u.push("That is more than the credits your plan includes this period."),o&&u.push(`The period ends ${o}.`)),u.join(`
|
|
157
|
+
`)}async function wp(e){let t=$(e.runtime),n=await vi(e,t),o={plan:n.subscription_tier,plan_label:n.plan_label,credits_used:n.credits_used,credits_limit:n.credits_limit,limit_enforced:n.limit_enforced,limit_reached:n.limit_reached,limit_reached_on:n.limit_reached_on,period_start:n.current_period_start,period_end:n.current_period_end,cost_so_far:n.current_period_cost??null,currency:n.currency??null};e.print.result(o,()=>yp(n,e.print.style))}async function _p(e,t){let n=t===void 0?12:Number(t);if(!Number.isInteger(n)||n<1||n>12)throw new p("invalid_months","--months must be a whole number from 1 to 12",g.Usage);let o=$(e.runtime),i=await Ge(e.runtime,o),r=await v(e.runtime,o,"GET",`/api/teams-billing/${i}/usage-history`);if(r.status!==200||!Array.isArray(r.body.months))throw C(r,"api_error","Your usage history could not be loaded. Try again.");let s=r.body.months.map(a=>({month:a.month,credits:a.billableCredits})).sort((a,l)=>a.month<l.month?1:-1).slice(0,n);e.print.result({months:s},a=>{if(a.months.length===0)return"No credits used in the last 12 months.";let l=e.print.style,u=Math.max(...a.months.map(y=>y.credits)),c=a.months.map(y=>xt(y.month)),d=a.months.map(y=>k(y.credits)),m=Math.max(...c.map(y=>y.length)),h=Math.max(...d.map(y=>y.length)),f=a.months.length>1?`
|
|
158
|
+
${l.dim(`Trend, oldest to newest: ${l.sparkline([...a.months].reverse().map(y=>y.credits))}`)}`:"";return a.months.map((y,_)=>`${c[_].padEnd(m)} ${d[_].padStart(h)} credits ${l.green(l.bar(u>0?y.credits/u:0,20))}`).join(`
|
|
159
|
+
`)+f})}async function bp(e,t){let n=Za(t),o=$(e.runtime),i=await Ge(e.runtime,o),r=await v(e.runtime,o,"GET",`/api/teams-billing/${i}/usage-by-source/${n}`);if(r.status!==200||!Array.isArray(r.body.sources))throw C(r,"api_error","Your usage could not be loaded. Try again.");let s={month:n,total:Number(r.body.total)||0,sources:r.body.sources};e.print.result(s,a=>a.sources.length===0?`No credits used in ${xt(a.month)}.`:[`${xt(a.month)}: ${k(a.total)} credits`,...a.sources.map(l=>` ${l.name}: ${k(l.credits)}`)].join(`
|
|
160
|
+
`))}async function $p(e,t){let n=Za(t.month),o=gp(t.output??`closedloop-ai-usage-${n}.csv`);if(mp(o)&&!t.force)throw new p("file_exists",`${o} already exists`,g.Usage,"Add --force to replace it, or pick another file with --output.");let i=$(e.runtime),r=await Ge(e.runtime,i),s=await v(e.runtime,i,"GET",`/api/teams-billing/${r}/usage-history/${n}/export`);if(s.status!==200||!s.text.startsWith("date,integration,credits"))throw C(s,"api_error","Your usage could not be exported. Try again.");pp(o,s.text,{mode:384});let a=s.text.trimEnd().split(`
|
|
161
|
+
`).length-1;e.print.result({status:"saved",month:n,path:o,rows:a},l=>`Saved ${xt(l.month)}'s usage to ${l.path} (${l.rows} rows: date, integration, credits).`)}async function vp(e){let t=$(e.runtime),n=await vi(e,t),o={plan:n.subscription_tier,plan_label:n.plan_label,credits_included:n.credits_limit,limit_enforced:n.limit_enforced,period_start:n.current_period_start,period_end:n.current_period_end,plan_url:n.plan_url};e.print.result(o,i=>{let r=i.credits_included===null?"no monthly limit; you pay for the credits you use":`${k(i.credits_included)} credits a month included${i.limit_enforced?"; new conversations wait once they are used":""}`;return[`${i.plan_label} plan: ${r}.`,"See usage: closedloop usage. Change plan: closedloop plan upgrade"].join(`
|
|
162
|
+
`)})}async function Cp(e){let t=$(e.runtime),n=await W(e.runtime,t),o=await vi(e,t),i=o.plan_url;if(!i)throw new p("no_plan_link","the plan page link is not available",g.Failure,`Open Settings, Plan & billing in the app. ${x}`);let r=n.permissions.can_manage_billing,s=r&&e.interactive?await e.runtime.openUrl(i):!1;e.print.result({status:s?"opened":"link",url:i,plan_label:o.plan_label,can_change_plan:r},a=>{let l=ve(a.url,e.runtime.supportsHyperlinks);return a.can_change_plan?`${s?"Opened":"Change your plan at"} ${l}${s?" in your browser.":"."} You are on the ${a.plan_label} plan.
|
|
161
163
|
${x}`:`You are on the ${a.plan_label} plan. Only someone who can manage billing in ${n.name} can change it: ask an owner or admin. The plan page: ${l}
|
|
162
|
-
${x}`})}var
|
|
163
|
-
`))})};var
|
|
164
|
-
`}function
|
|
165
|
-
`,
|
|
166
|
-
${
|
|
167
|
-
${
|
|
164
|
+
${x}`})}var el=(e,t)=>{let n=e.command("usage").description("Credits used this period; history, by source and export below.").action(async()=>wp(t));n.command("history").description("Credits used per month, newest first.").option("--months <n>","How many months, up to 12.").action(async o=>_p(t,o.months)),n.command("by-source").description("Credits used in a month per integration or feature, largest first.").option("--month <yyyy-mm>","The month; this month if left out.").action(async o=>bp(t,o.month)),n.command("export").description("Save a month's usage per day and integration as a CSV file.").option("--month <yyyy-mm>","The month; this month if left out.").option("--output <file>","Where to save it; closedloop-ai-usage-<month>.csv if left out.").option("--force","Replace the file if it exists.").action(async o=>$p(t,o))},tl=(e,t)=>{e.command("plan").description("Your plan and what it includes.").action(async()=>vp(t)).command("upgrade").description("Open Plan & billing to change your plan (payment happens in the browser).").action(async()=>Cp(t))};function St(e){return typeof e=="string"&&e!==""?e:null}function nl(e){let t=e;return!t||typeof t!="object"||!St(t.team_id)||!St(t.team_name)?null:{id:t.team_id,name:t.team_name,role:St(t.role),plan:St(t.plan)}}var sl=[["can_manage_integrations","manage integrations"],["can_manage_billing","manage billing"],["can_manage_members","manage members"],["can_manage_team_settings","manage settings"],["can_view_analytics","view analytics"]];function kp(e){let t=e?.permissions??{};return Object.fromEntries(sl.map(([n])=>[n,t[n]===!0]))}var ol={password:"a password",email_code:"an email code",google:"Google SSO",microsoft:"Microsoft SSO",okta:"Okta SSO",saml:"SAML SSO"};function il(e,t){return e==="email_code"?ol.email_code:e!=="browser"?null:t?`${ol[t]} in the browser`:"the browser"}function Sp(e){return typeof e=="string"&&!Number.isNaN(Date.parse(e))?e:null}function Ap(e){let t=[e.role,e.plan&&`${e.plan[0].toUpperCase()}${e.plan.slice(1).replace(/_/g," ")} plan`].filter(Boolean).join(", ");return t?`${e.name} (${t})`:e.name}function rl(e,t,n){if(e.status===403){let o=C(e,"forbidden","you cannot access this account or workspace"),i=o.code==="sso_required"?`Run \`${n}\` to sign in with your company account.`:["two_fa_verification_required","two_fa_setup_session_expired"].includes(o.code)?"Run `closedloop login` to sign in again and complete authentication.":"Ask a workspace owner or admin to check your access.";return new p(o.code,o.message,g.Failure,i)}if(e.status===404&&e.body.code==="USER_NOT_FOUND"){let o=C(e,"user_not_found","your saved account could not be found");return new p(o.code,o.message,g.Failure,"Run `closedloop login` to sign in again.")}return t==="workspace"&&e.status===404&&e.body.error==="No team found for user"?new p("no_workspace","you are not in a workspace yet",g.Failure,`Create or join one in the app. ${x}`):new p("api_error","ClosedLoop AI could not load your account",g.Failure,"Try again in a moment.")}var al=(e,{print:t,runtime:n})=>{e.command("whoami").description("Show who you are signed in as (the active account), and your workspace.").action(async()=>{let o=$(n),i=`${Ie(o.email,n.platform)} --browser`,r=await No(n,o,"/api/auth/profile");if(r.status!==200)throw rl(r,"profile",i);let s=await No(n,o,"/api/team-info");if(s.status!==200)throw rl(s,"workspace",i);let a=r.body.profile??{},l=nl(s.body.teamInfo),u=Array.isArray(s.body.allTeams)?s.body.allTeams.map(nl).filter(m=>m!==null):[],c=l?await Ye(n,o):null,d={status:"signed_in",email:St(a.email)??o.email,name:St(a.full_name),two_factor:a.totp_enabled===!0,workspace:l,other_workspaces:u.filter(m=>m.id!==l?.id),permissions:kp(s.body.teamInfo),company_research:c?c.research.state:null,analysis_waiting:c?c.analysis_waiting:null,region:typeof o.region=="string"?o.region:null,api_url:o.api_url,signed_in_at:Sp(o.signed_in_at),sign_in_method:o.sign_in_method==="email_code"||o.sign_in_method==="browser"?o.sign_in_method:null,signed_in_with:_n(o.signed_in_with),session_expires_at:tr(o),cli:{version:re,latest_version:n.release?.latest??null,update_available:To(n)!==null}};t.result(d,m=>[`Signed in as ${t.style.bold(m.name?`${m.name} <${m.email}>`:m.email)}${m.two_factor?" (two-factor on)":""}`,`Workspace: ${m.workspace?Ap(m.workspace):"none"}`,...m.other_workspaces.length?[`Also in: ${m.other_workspaces.map(h=>h.name).join(", ")}`]:[],(()=>{let h=sl.filter(([f])=>m.permissions[f]).map(([,f])=>f);return h.length?`You can: ${h.join(", ")}.`:"You can read this workspace; to change integrations, billing or members, ask an owner or admin."})(),...c&&Mt(c)?[`Your data: ${Mt(c)}`]:[],`Region: ${(m.region??"unknown").toUpperCase()} \xB7 API: ${m.api_url}`,m.signed_in_at?`Signed in here: ${R(m.signed_in_at)}${il(m.sign_in_method,m.signed_in_with)?` with ${il(m.sign_in_method,m.signed_in_with)}`:""}`:"Signed in here: time not recorded",...m.session_expires_at?[`Stays signed in while you use the CLI; ends ${R(m.session_expires_at)} if unused.`]:[]].join(`
|
|
165
|
+
`))})};var ll=[Ja,al,Ba,or,Qa,Xr,Gr,ns,ra,ga,Qs,as,hs,Ya,Ia,dr,el,tl,ls];function ki(e){let t=e.indexOf("--");return(t===-1?e:e.slice(0,t)).includes("--json")}var dl={update:()=>{},stop:()=>{}},ul="\u280B\u2819\u2839\u2838\u283C\u2834\u2826\u2827\u2807\u280F";function Op(e,t){let n=t,o=0,i=()=>e.stderr(`\r\x1B[2K\x1B[2m${ul[o++%ul.length]} ${n}\x1B[22m`);i();let r=setInterval(i,80);return r.unref?.(),{update:s=>{n=s},stop:()=>{clearInterval(r),e.stderr("\r\x1B[2K")}}}function ml(e,t){return ki(e)||!t.stdoutIsTTY?Lp(t):Dp(t)}function Tp(e){return e.hint===void 0?{code:e.code,message:e.message}:{code:e.code,message:e.message,hint:e.hint}}function Ci(e){let t=JSON.stringify(e);if(t===void 0)throw new Error("a result must be JSON data, not undefined or a function");return`${t}
|
|
166
|
+
`}function pl(e){return e.options.filter(t=>!t.hidden&&t.long!=="--help")}function gl(e){let t=e.aliases()[0],n=e.registeredArguments.map(i=>{let r=`${i.name()}${i.variadic?"...":""}`;return i.required?`<${r}>`:`[${r}]`}),o=pl(e).map(i=>`[${i.long&&i.short?i.flags.replace(`${i.short}, `,""):i.flags}]`);return[`${e.name()}${t?`|${t}`:""}`,...n,...o].join(" ")}var Rp=`Add --help after a command for details.
|
|
167
|
+
`,Ep=30,cl=6;function Ip(e,t){return[...t.visibleOptions(e).map(n=>t.optionTerm(n)),...t.visibleGlobalOptions(e).map(n=>t.optionTerm(n)),...t.visibleArguments(e).map(n=>t.argumentTerm(n)),...t.visibleCommands(e).map(n=>t.subcommandTerm(n))]}function Pp(e,t){return Math.max(0,...Ip(e,t).map(n=>t.displayWidth(n)).filter(n=>n<=Ep))}function xp(e,t,n,o){if(o.displayWidth(e)<=t)return Te.prototype.formatItem.call(o,e,t,n,o);if(!n)return` ${e}`;let i=(o.helpWidth??80)-cl,r=" ".repeat(cl);return` ${e}
|
|
168
|
+
${r}${o.boxWrap(n,i).replace(/\n/g,`
|
|
169
|
+
${r}`)}`}function Np(e,t){let n=[];for(let i=e;i!==null;i=i.parent)n.unshift(i.name());let o=i=>({flags:i.flags,description:t.optionDescription(i)});return{command:n.join(" "),description:t.commandDescription(e),usage:t.commandUsage(e),arguments:t.visibleArguments(e).map(i=>({name:i.name(),required:i.required,variadic:i.variadic,description:t.argumentDescription(i)})),options:t.visibleOptions(e).map(o),globalOptions:t.visibleGlobalOptions(e).map(o),commands:t.visibleCommands(e).map(i=>({name:i.name(),usage:t.subcommandTerm(i),description:t.subcommandDescription(i),options:pl(i).map(o)}))}}function Lp(e){return{result:t=>e.stdout(Ci(t)),error:t=>e.stderr(Ci({error:Tp(t)})),note:()=>{},style:ei({}),spinner:()=>dl,help:{showGlobalOptions:!0,subcommandTerm:gl,formatHelp:(t,n)=>Ci(Np(t,n))},writeHelp:t=>e.stdout(t),writeHelpOnError:()=>{}}}function Dp(e){let t=ei({color:e.color,columns:e.columns});return{style:t,spinner:n=>e.animate?Op(e,n):dl,result:(n,o)=>{let i=o(n);e.stdout(i.endsWith(`
|
|
168
170
|
`)?i:`${i}
|
|
169
|
-
`)},error:n=>
|
|
170
|
-
${n.hint===void 0?"":`${
|
|
171
|
-
`}`),note:n=>
|
|
172
|
-
`),help:{showGlobalOptions:!0,subcommandTerm:
|
|
173
|
-
${
|
|
174
|
-
`)}},readStdin:async(
|
|
175
|
-
${
|
|
176
|
-
`),
|
|
171
|
+
`)},error:n=>e.stderr(`${t.red(t.bold("error:"))} ${n.message}
|
|
172
|
+
${n.hint===void 0?"":`${t.dim(n.hint)}
|
|
173
|
+
`}`),note:n=>e.stderr(`${n}
|
|
174
|
+
`),help:{showGlobalOptions:!0,subcommandTerm:gl,padWidth:Pp,formatItem:xp,formatHelp:(n,o)=>{let i=Te.prototype.formatHelp.call(o,n,o);return o.visibleCommands(n).length>0?`${i}
|
|
175
|
+
${Rp}`:i}},writeHelp:n=>e.stdout(n),writeHelpOnError:n=>e.stderr(n)}}var Si="Run `closedloop --help` to see the commands and options.";function hl(e,t){let{print:n}=e,o=re,i=new it("closedloop").description("ClosedLoop AI on the command line, for people and AI coding agents.").usage("<command> [options]").option("--json","Print JSON. This is the default when the output is not a terminal.").option("--as <email>","Run the command as one of the accounts signed in on this computer (or set CLOSEDLOOP_ACCOUNT).").option("-V, --version","Print the version.").helpOption("-h, --help","Show help.").helpCommand("help [command]","Show help for a command.").configureHelp(n.help).exitOverride().configureOutput({writeOut:r=>n.writeHelp(r),writeErr:r=>n.writeHelpOnError(r),outputError:()=>{}}).argument("[command...]").action(r=>{if(r.length===0){i.outputHelp();return}throw new p("unknown_command",`unknown command '${r[0]}'`,g.Usage,Si)});i.on("option:version",()=>{throw n.result({version:o},r=>r.version),new B(0,"commander.version",o)}),i.hook("preAction",()=>{let r=i.opts().as;typeof r=="string"&&(e.runtime.accountOption=r)});for(let r of t)r(i,e);return i}import{spawn as yl}from"node:child_process";import{existsSync as jp}from"node:fs";import{createServer as Fp}from"node:http";import{homedir as Up}from"node:os";import{createInterface as Ai}from"node:readline/promises";import{Writable as Wp}from"node:stream";function Mp(e){if(e.FORCE_HYPERLINK==="1")return!0;if(e.FORCE_HYPERLINK==="0")return!1;let t=e.TERM_PROGRAM??"";return["iTerm.app","WezTerm","vscode","ghostty","Hyper"].includes(t)||e.WT_SESSION||e.KITTY_WINDOW_ID||e.KONSOLE_VERSION?!0:Number(e.VTE_VERSION)>=5e3}function so(e,t,n){return new Promise(o=>{try{let i=yl(e,t,{stdio:["pipe","ignore","ignore"],windowsHide:!0}),r=!1,s=u=>{r||(r=!0,clearTimeout(l),o(u))},a=()=>{if(!r){s(!1);try{i.kill("SIGKILL")}catch{}}},l=setTimeout(a,5e3);i.on("error",()=>s(!1)),i.on("close",u=>s(u===0)),i.stdin.on("error",a),i.stdin.end(n)}catch{o(!1)}})}function Oi(e,t){return new Promise(n=>{try{let o=yl(e,t,{stdio:"ignore",detached:!0});o.on("error",()=>n(!1)),o.on("spawn",()=>{o.unref(),n(!0)})}catch{n(!1)}})}async function Hp(e){return process.platform==="darwin"?so("pbcopy",[],e):process.platform==="win32"?so("powershell.exe",["-NoProfile","-NonInteractive","-Command","[Console]::InputEncoding = [System.Text.UTF8Encoding]::new($false); Set-Clipboard -Value ([Console]::In.ReadToEnd())"],e):process.env.WAYLAND_DISPLAY&&await so("wl-copy",[],e)?!0:so("xclip",["-selection","clipboard"],e)}async function Gp(e){let t;try{t=new URL(e)}catch{return!1}return t.protocol!=="https:"&&t.protocol!=="http:"?!1:process.platform==="darwin"?Oi("open",[t.href]):process.platform==="win32"?Oi("rundll32",["url.dll,FileProtocolHandler",t.href]):Oi("xdg-open",[t.href])}var qp=10*1024*1024,fl={"Content-Type":"text/plain; charset=utf-8","Cache-Control":"no-store","Referrer-Policy":"no-referrer"};function Jp(e){return new Promise((t,n)=>{let o=0,i=!1,r=Fp((a,l)=>{let u=d=>{l.writeHead(d.status,d.location?{...fl,Location:d.location}:fl),l.end(d.body??"",()=>{d.last&&s()})},c;try{c=new URL(a.url??"/",`http://127.0.0.1:${o}`)}catch{u({status:400,body:"Bad request."});return}if(a.headers.host!==`127.0.0.1:${o}`){u({status:400,body:"Bad request."});return}e({method:a.method??"GET",path:c.pathname,query:c.searchParams}).then(u,()=>u({status:500,body:"Something went wrong. Go back to your terminal."}))}),s=()=>{i||(i=!0,r.close(),r.closeIdleConnections())};r.once("error",n),r.listen(0,"127.0.0.1",()=>{let a=r.address();if(a===null||typeof a=="string"){s(),n(new Error("the loopback server has no port"));return}o=a.port,r.unref(),t({port:o,close:s})})})}function wl(){return{env:process.env,fetch:globalThis.fetch,homeDir:Up(),platform:process.platform,stdinIsTTY:process.stdin.isTTY===!0,supportsHyperlinks:Mp(process.env),sleep:e=>new Promise(t=>setTimeout(t,e)),prompt:async e=>{let t=Ai({input:process.stdin,output:process.stderr});try{return(await t.question(e)).trim()}finally{t.close()}},promptSecret:async e=>{process.stderr.write(e);let t=new Wp({write:(o,i,r)=>r()}),n=Ai({input:process.stdin,output:t,terminal:!0});try{return(await n.question("")).trim()}finally{n.close(),process.stderr.write(`
|
|
176
|
+
`)}},readStdin:async(e=qp)=>{let t=[],n=0;for await(let o of process.stdin){if(n+=o.length,n>e)throw new p("stdin_too_large",`standard input exceeds this command's ${e}-byte input limit`,g.Usage,"Split the input into smaller records; --file uses this command's same input limit.");t.push(o)}return Buffer.concat(t).toString("utf8")},copyToClipboard:Hp,openUrl:Gp,now:()=>Date.now(),release:{latest:null},onEnter:e=>{let t=Ai({input:process.stdin});return t.once("line",()=>e()),()=>t.close()},listenLoopback:Jp,fileExists:e=>jp(e)}}var Vp="Run it again; if it keeps failing, report it with the output of `closedloop --version`.",Bp="Add --help to the same command to see its commands.";function Yp(e,t,n){if(e instanceof p)return e;if(e instanceof B){let o=e.code==="commander.excessArguments"?Ir(t):null;if(o!==null)return Cn(o.kind,n,o.option);if(e.code==="commander.help")return new p("missing_command","this command needs one of its commands",g.Usage,Bp);let i=e.code==="commander.unknownOption"?"unknown_option":"usage_error";return new p(i,e.message.replace(/^error: /,""),g.Usage,Si)}return new p("internal_error","something went wrong inside the CLI",g.Failure,Vp)}async function _l(e,t,n=ll,o=wl()){let i=ml(e,t),r=t.stdoutIsTTY&&o.stdinIsTTY&&!ki(e),s={...o,account:void 0,accountOption:void 0},a=hl({print:i,runtime:s,interactive:r},n),l=await Kp(a,e,i,o.platform),u=Qi(s,r);return u!==null&&t.stderr(`
|
|
177
|
+
${u}
|
|
178
|
+
`),l}async function Kp(e,t,n,o){try{return await e.parseAsync([...t],{from:"user"}),g.Success}catch(i){if(i instanceof B&&i.exitCode===0)return g.Success;let r=Yp(i,t,o);return n.error(r),r.exitCode}}function bl(e=process.env){let n=e.FORCE_COLOR==="1"||process.stdout.isTTY===!0&&!e.NO_COLOR&&e.TERM!=="dumb";return{stdout:o=>{process.stdout.write(o)},stderr:o=>{process.stderr.write(o)},stdoutIsTTY:process.stdout.isTTY===!0,color:n,animate:n&&process.stderr.isTTY===!0&&!e.CI,columns:process.stdout.columns||void 0}}process.exitCode=await _l(process.argv.slice(2),bl());
|