@closedloop-ai/cli 0.3.4 → 0.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -28
- package/dist/closedloop.js +149 -142
- package/package.json +1 -1
package/dist/closedloop.js
CHANGED
|
@@ -1,178 +1,185 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
var B=class extends Error{constructor(t,n,
|
|
3
|
-
`)}displayWidth(t){return
|
|
4
|
-
`+" ".repeat(n+l)),s+a+" ".repeat(l)+
|
|
5
|
-
${s}`)}boxWrap(t,n){if(n<this.minWidthToWrap)return t;let
|
|
6
|
-
`)}};var
|
|
2
|
+
var B=class extends Error{constructor(t,n,i){super(i),Error.captureStackTrace(this,this.constructor),this.name=this.constructor.name,this.code=n,this.exitCode=t,this.nestedError=void 0}},Ue=class extends B{constructor(t){super(1,"commander.invalidArgument",t),Error.captureStackTrace(this,this.constructor),this.name=this.constructor.name}};var It=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,i)=>{if(!this.argChoices.includes(n))throw new Ue(`Allowed choices are ${this.argChoices.join(", ")}.`);return this.variadic?this._collectValue(n,i):n},this}argRequired(){return this.required=!0,this}argOptional(){return this.required=!1,this}};function tn(e){let t=e.name()+(e.variadic===!0?"...":"");return e.required?"<"+t+">":"["+t+"]"}import{EventEmitter as Al}from"node:events";import si from"node:child_process";import $e from"node:path";import on from"node:fs";import A from"node:process";import{stripVTControlCharacters as Rl}from"node:util";import{stripVTControlCharacters as Sl}from"node:util";var Ie=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(o=>!o._hidden),i=t._getHelpCommand();return i&&!i._hidden&&n.push(i),this.sortSubcommands&&n.sort((o,r)=>o.name().localeCompare(r.name())),n}compareOptions(t,n){let i=o=>o.short?o.short.replace(/^-/,""):o.long.replace(/^--/,"");return i(t).localeCompare(i(n))}visibleOptions(t){let n=t.options.filter(o=>!o.hidden),i=t._getHelpOption();if(i&&!i.hidden){let o=i.short&&t._findOption(i.short),r=i.long&&t._findOption(i.long);!o&&!r?n.push(i):i.long&&!r?n.push(t.createOption(i.long,i.description)):i.short&&!o&&n.push(t.createOption(i.short,i.description))}return this.sortOptions&&n.sort(this.compareOptions),n}visibleGlobalOptions(t){if(!this.showGlobalOptions)return[];let n=[];for(let i=t.parent;i;i=i.parent){let o=i.options.filter(r=>!r.hidden);n.push(...o)}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(i=>tn(i)).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((i,o)=>Math.max(i,this.displayWidth(n.styleSubcommandTerm(n.subcommandTerm(o)))),0)}longestOptionTermLength(t,n){return n.visibleOptions(t).reduce((i,o)=>Math.max(i,this.displayWidth(n.styleOptionTerm(n.optionTerm(o)))),0)}longestGlobalOptionTermLength(t,n){return n.visibleGlobalOptions(t).reduce((i,o)=>Math.max(i,this.displayWidth(n.styleOptionTerm(n.optionTerm(o)))),0)}longestArgumentTermLength(t,n){return n.visibleArguments(t).reduce((i,o)=>Math.max(i,this.displayWidth(n.styleArgumentTerm(n.argumentTerm(o)))),0)}commandUsage(t){let n=t._name;t._aliases[0]&&(n=n+"|"+t._aliases[0]);let i="";for(let o=t.parent;o;o=o.parent)i=o.name()+" "+i;return i+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(i=>JSON.stringify(i)).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 i=`(${n.join(", ")})`;return t.description?`${t.description} ${i}`:i}return t.description}argumentDescription(t){let n=[];if(t.argChoices&&n.push(`choices: ${t.argChoices.map(i=>JSON.stringify(i)).join(", ")}`),t.defaultValue!==void 0&&n.push(`default: ${t.defaultValueDescription||JSON.stringify(t.defaultValue)}`),n.length>0){let i=`(${n.join(", ")})`;return t.description?`${t.description} ${i}`:i}return t.description}formatItemList(t,n,i){return n.length===0?[]:[i.styleTitle(t),...n,""]}groupItems(t,n,i){let o=new Map;return t.forEach(r=>{let s=i(r);o.has(s)||o.set(s,[])}),n.forEach(r=>{let s=i(r);o.has(s)||o.set(s,[]),o.get(s).push(r)}),o}formatHelp(t,n){let i=n.padWidth(t,n),o=n.helpWidth??80;function r(m,d){return n.formatItem(m,i,d,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),o),""]));let l=n.visibleArguments(t).map(m=>r(n.styleArgumentTerm(n.argumentTerm(m)),n.styleArgumentDescription(n.argumentDescription(m))));if(s=s.concat(this.formatItemList("Arguments:",l,n)),this.groupItems(t.options,n.visibleOptions(t),m=>m.helpGroupHeading??"Options:").forEach((m,d)=>{let h=m.map(f=>r(n.styleOptionTerm(n.optionTerm(f)),n.styleOptionDescription(n.optionDescription(f))));s=s.concat(this.formatItemList(d,h,n))}),n.showGlobalOptions){let m=n.visibleGlobalOptions(t).map(d=>r(n.styleOptionTerm(n.optionTerm(d)),n.styleOptionDescription(n.optionDescription(d))));s=s.concat(this.formatItemList("Global Options:",m,n))}return this.groupItems(t.commands,n.visibleCommands(t),m=>m.helpGroup()||"Commands:").forEach((m,d)=>{let h=m.map(f=>r(n.styleSubcommandTerm(n.subcommandTerm(f)),n.styleSubcommandDescription(n.subcommandDescription(f))));s=s.concat(this.formatItemList(d,h,n))}),s.join(`
|
|
3
|
+
`)}displayWidth(t){return Sl(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,i,o){let s=" ".repeat(2);if(!i)return s+t;let a=t.padEnd(n+t.length-o.displayWidth(t)),l=2,c=(this.helpWidth??80)-n-l-2,m;return c<this.minWidthToWrap||o.preformatted(i)?m=i:m=o.boxWrap(i,c).replace(/\n/g,`
|
|
4
|
+
`+" ".repeat(n+l)),s+a+" ".repeat(l)+m.replace(/\n/g,`
|
|
5
|
+
${s}`)}boxWrap(t,n){if(n<this.minWidthToWrap)return t;let i=t.split(/\r\n|\n/),o=/[\s]*[^\s]+/g,r=[];return i.forEach(s=>{let a=s.match(o);if(a===null){r.push("");return}let l=[a.shift()],u=this.displayWidth(l[0]);a.forEach(c=>{let m=this.displayWidth(c);if(u+m<=n){l.push(c),u+=m;return}r.push(l.join(""));let d=c.trimStart();l=[d],u=this.displayWidth(d)}),r.push(l.join(""))}),r.join(`
|
|
6
|
+
`)}};var xe=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 i=Ol(t);this.short=i.shortFlag,this.long=i.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,i)=>{if(!this.argChoices.includes(n))throw new Ue(`Allowed choices are ${this.argChoices.join(", ")}.`);return this.variadic?this._collectValue(n,i):n},this}name(){return this.long?this.long.replace(/^--/,""):this.short.replace(/^-/,"")}attributeName(){return this.negate?Ro(this.name().replace(/^no-/,"")):Ro(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}},nn=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,i)=>{this.positiveOptions.has(i)&&this.dualOptions.add(i)})}valueFromOption(t,n){let i=n.attributeName();if(!this.dualOptions.has(i))return!0;let o=this.negativeOptions.get(i).presetArg,r=o!==void 0?o:!1;return n.negate===(r===t)}};function Ro(e){return e.split("-").reduce((t,n)=>t+n[0].toUpperCase()+n.slice(1))}function Ol(e){let t,n,i=/^-[^-]$/,o=/^--[^-]/,r=e.split(/[ |,]+/).concat("guard");if(i.test(r[0])&&(t=r.shift()),o.test(r[0])&&(n=r.shift()),!t&&i.test(r[0])&&(t=r.shift()),!t&&o.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')`):
|
|
10
|
-
- too many short flags`):
|
|
9
|
+
- or use a double dash for a long option (and can have two, like '--ws, --workspace')`):i.test(s)?new Error(`${a}
|
|
10
|
+
- too many short flags`):o.test(s)?new Error(`${a}
|
|
11
11
|
- too many long flags`):new Error(`${a}
|
|
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
|
|
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=t._name),(n.noHelp||n.hidden)&&(t._hidden=!0),this._registerCommand(t),t.parent=this,t._checkForBrokenPassThrough(),this}createArgument(t,n){return new
|
|
16
|
-
Expecting one of '${
|
|
17
|
-
- already used by option '${n.flags}'`)}this._initOptionGroup(t),this.options.push(t)}_registerCommand(t){let n=
|
|
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,
|
|
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 Tl(e,t){if(Math.abs(e.length-t.length)>3)return Math.max(e.length,t.length);let n=[];for(let i=0;i<=e.length;i++)n[i]=[i];for(let i=0;i<=t.length;i++)n[0][i]=i;for(let i=1;i<=t.length;i++)for(let o=1;o<=e.length;o++){let r;e[o-1]===t[i-1]?r=0:r=1,n[o][i]=Math.min(n[o-1][i]+1,n[o][i-1]+1,n[o-1][i-1]+r),o>1&&i>1&&e[o-1]===t[i-2]&&e[o-2]===t[i-1]&&(n[o][i]=Math.min(n[o][i],n[o-2][i-2]+1))}return n[e.length][t.length]}function ri(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 i=[],o=3,r=.4;return t.forEach(s=>{if(s.length<=1)return;let a=Tl(e,s),l=Math.max(e.length,s.length);(l-a)/l>r&&(a<o?(o=a,i=[s]):a===o&&i.push(s))}),i.sort((s,a)=>s.localeCompare(a)),n&&(i=i.map(s=>`--${s}`)),i.length>1?`
|
|
13
|
+
(Did you mean one of ${i.join(", ")}?)`:i.length===1?`
|
|
14
|
+
(Did you mean ${i[0]}?)`:""}var ot=class e extends Al{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=>A.stdout.write(n),writeErr:n=>A.stderr.write(n),outputError:(n,i)=>i(n),getOutHelpWidth:()=>A.stdout.isTTY?A.stdout.columns:void 0,getErrHelpWidth:()=>A.stderr.isTTY?A.stderr.columns:void 0,getOutHasColors:()=>Io()??(A.stdout.isTTY&&A.stdout.hasColors?.()),getErrHasColors:()=>Io()??(A.stderr.isTTY&&A.stderr.hasColors?.()),stripColor:n=>Rl(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,i){let o=n,r=i;typeof o=="object"&&o!==null&&(r=o,o=null),r=r||{};let[,s,a]=t.match(/([^ ]+) *(.*)/),l=this.createCommand(s);return o&&(l.description(o),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),o?this:l}createCommand(t){return new e(t)}createHelp(){return Object.assign(new Ie,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 It(t,n)}argument(t,n,i,o){let r=this.createArgument(t,n);return typeof i=="function"?r.default(o).argParser(i):r.default(i),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 i=t??"help [command]",[,o,r]=i.match(/([^ ]+) *(.*)/),s=n??"display help for command",a=this.createCommand(o);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 i=["preSubcommand","preAction","postAction"];if(!i.includes(t))throw new Error(`Unexpected value for event passed to hook : '${t}'.
|
|
16
|
+
Expecting one of '${i.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,i){this._exitCallback&&this._exitCallback(new B(t,n,i)),A.exit(t)}action(t){let n=i=>{let o=this.registeredArguments.length,r=i.slice(0,o);return this._storeOptionsAsProperties?r[o]=this:r[o]=this.opts(),r.push(this),t.apply(this,r)};return this._actionHandler=n,this}createOption(t,n){return new xe(t,n)}_callParseArg(t,n,i,o){try{return t.parseArg(n,i)}catch(r){if(r.code==="commander.invalidArgument"){let s=`${o} ${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 i=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 '${i}'
|
|
17
|
+
- already used by option '${n.flags}'`)}this._initOptionGroup(t),this.options.push(t)}_registerCommand(t){let n=o=>[o.name()].concat(o.aliases()),i=n(t).find(o=>this._findCommand(o));if(i){let o=n(this._findCommand(i)).join("|"),r=n(t).join("|");throw new Error(`cannot add command '${r}' as already have command '${o}'`)}this._initCommandGroup(t),this.commands.push(t)}addOption(t){this._registerOption(t);let n=t.name(),i=t.attributeName();t.defaultValue!==void 0&&this.setOptionValueWithSource(i,t.defaultValue,"default");let o=(r,s,a)=>{r==null&&t.presetArg!==void 0&&(r=t.presetArg);let l=this.getOptionValue(i);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(i,r,a)};return this.on("option:"+n,r=>{let s=`error: option '${t.flags}' argument '${r}' is invalid.`;o(r,s,"cli")}),t.envVar&&this.on("optionEnv:"+n,r=>{let s=`error: option '${t.flags}' value '${r}' from env '${t.envVar}' is invalid.`;o(r,s,"env")}),this}_optionEx(t,n,i,o,r){if(typeof n=="object"&&n instanceof xe)throw new Error("To add an Option object use addOption() instead of option() or requiredOption()");let s=this.createOption(n,i);if(s.makeOptionMandatory(!!t.mandatory),typeof o=="function")s.default(r).argParser(o);else if(o instanceof RegExp){let a=o;o=(l,u)=>{let c=a.exec(l);return c?c[0]:u},s.default(r).argParser(o)}else s.default(o);return this.addOption(s)}option(t,n,i,o){return this._optionEx({},t,n,i,o)}requiredOption(t,n,i,o){return this._optionEx({mandatory:!0},t,n,i,o)}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,i){return this._storeOptionsAsProperties?this[t]=n:this._optionValues[t]=n,this._optionValueSources[t]=i,this}getOptionValueSource(t){return this._optionValueSources[t]}getOptionValueSourceWithGlobals(t){let n;return this._getCommandAndAncestors().forEach(i=>{i.getOptionValueSource(t)!==void 0&&(n=i.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){A.versions?.electron&&(n.from="electron");let o=A.execArgv??[];(o.includes("-e")||o.includes("--eval")||o.includes("-p")||o.includes("--print"))&&(n.from="eval")}t===void 0&&(t=A.argv),this.rawArgs=t.slice();let i;switch(n.from){case void 0:case"node":this._scriptPath=t[1],i=t.slice(2);break;case"electron":A.defaultApp?(this._scriptPath=t[1],i=t.slice(2)):i=t.slice(1);break;case"user":i=t.slice(0);break;case"eval":i=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",i}parse(t,n){this._prepareForParse();let i=this._prepareUserArgs(t,n);return this._parseCommand([],i),this}async parseAsync(t,n){this._prepareForParse();let i=this._prepareUserArgs(t,n);return await this._parseCommand([],i),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,i){if(on.existsSync(t))return;let o=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 '${i}' 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
|
-
- ${
|
|
21
|
+
- ${o}`;throw new Error(r)}_executeSubCommand(t,n){n=n.slice();let i=[".js",".ts",".tsx",".mjs",".cjs"];function o(c,m){let d=$e.resolve(c,m);if(on.existsSync(d))return d;if(i.includes($e.extname(m)))return;let h=i.find(f=>on.existsSync(`${d}${f}`));if(h)return`${d}${h}`}this._checkForMissingMandatoryOptions(),this._checkForConflictingOptions();let r=t._executableFile||`${this._name}-${t._name}`,s=this._executableDir||"";if(this._scriptPath){let c;try{c=on.realpathSync(this._scriptPath)}catch{c=this._scriptPath}s=$e.resolve($e.dirname(c),s)}if(s){let c=o(s,r);if(!c&&!t._executableFile&&this._scriptPath){let m=$e.basename(this._scriptPath,$e.extname(this._scriptPath));m!==this._name&&(c=o(s,`${m}-${t._name}`))}r=c||r}let a=i.includes($e.extname(r)),l;A.platform!=="win32"?a?(n.unshift(r),n=Eo(A.execArgv).concat(n),l=si.spawn(A.argv[0],n,{stdio:"inherit"})):l=si.spawn(r,n,{stdio:"inherit"}):(this._checkForMissingExecutable(r,s,t._name),n.unshift(r),n=Eo(A.execArgv).concat(n),l=si.spawn(A.execPath,n,{stdio:"inherit"})),l.killed||["SIGUSR1","SIGUSR2","SIGTERM","SIGINT","SIGHUP"].forEach(m=>{A.on(m,()=>{l.killed===!1&&l.exitCode===null&&l.kill(m)})});let u=this._exitCallback;l.on("close",c=>{c=c??1,u?u(new B(c,"commander.executeSubCommandAsync","(close)")):A.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)A.exit(1);else{let m=new B(1,"commander.executeSubCommandAsync","(error)");m.nestedError=c,u(m)}}),this.runningCommand=l}_dispatchSubcommand(t,n,i){let o=this._findCommand(t);o||this.help({error:!0}),o._prepareForParse();let r;return r=this._chainOrCallSubCommandHook(r,o,"preSubcommand"),r=this._chainOrCall(r,()=>{if(o._executableHandler)this._executeSubCommand(o,n.concat(i));else return o._parseCommand(n,i)}),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=(i,o,r)=>{let s=o;if(o!==null&&i.parseArg){let a=`error: command-argument value '${o}' is invalid for argument '${i.name()}'.`;s=this._callParseArg(i,o,r,a)}return s};this._checkNumberOfArguments();let n=[];this.registeredArguments.forEach((i,o)=>{let r=i.defaultValue;i.variadic?o<this.args.length?(r=this.args.slice(o),i.parseArg&&(r=r.reduce((s,a)=>t(i,a,s),i.defaultValue))):r===void 0&&(r=[]):o<this.args.length&&(r=this.args[o],i.parseArg&&(r=t(i,r,i.defaultValue))),n[o]=r}),this.processedArgs=n}_chainOrCall(t,n){return t?.then&&typeof t.then=="function"?t.then(()=>n()):n()}_chainOrCallHooks(t,n){let i=t,o=[];return this._getCommandAndAncestors().reverse().filter(r=>r._lifeCycleHooks[n]!==void 0).forEach(r=>{r._lifeCycleHooks[n].forEach(s=>{o.push({hookedCommand:r,callback:s})})}),n==="postAction"&&o.reverse(),o.forEach(r=>{i=this._chainOrCall(i,()=>r.callback(r.hookedCommand,this))}),i}_chainOrCallSubCommandHook(t,n,i){let o=t;return this._lifeCycleHooks[i]!==void 0&&this._lifeCycleHooks[i].forEach(r=>{o=this._chainOrCall(o,()=>r(this,n))}),o}_parseCommand(t,n){let i=this.parseOptions(n);if(this._parseOptionsEnv(),this._parseOptionsImplied(),t=t.concat(i.operands),n=i.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(i.unknown),this._checkForMissingMandatoryOptions(),this._checkForConflictingOptions();let o=()=>{i.unknown.length>0&&this.unknownOption(i.unknown[0])},r=`command:${this.name()}`;if(this._actionHandler){o(),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))o(),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():(o(),this._processArguments())}else this.commands.length?(o(),this.help({error:!0})):(o(),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(i=>{let o=i.attributeName();return this.getOptionValue(o)===void 0?!1:this.getOptionValueSource(o)!=="default"});t.filter(i=>i.conflictsWith.length>0).forEach(i=>{let o=t.find(r=>i.conflictsWith.includes(r.attributeName()));o&&this._conflictingOption(i,o)})}_checkForConflictingOptions(){this._getCommandAndAncestors().forEach(t=>{t._checkForConflictingLocalOptions()})}parseOptions(t){let n=[],i=[],o=n;function r(c){return c.length>1&&c[0]==="-"}let s=c=>/^-(\d+|\d*\.\d+)(e[+-]?\d+)?$/.test(c)?!this._getCommandAndAncestors().some(m=>m.options.map(d=>d.short).some(d=>/^-\d$/.test(d))):!1,a=null,l=null,u=0;for(;u<t.length||l;){let c=l??t[u++];if(l=null,c==="--"){o===i&&o.push(c),o.push(...t.slice(u));break}if(a&&(!r(c)||s(c))){this.emit(`option:${a.name()}`,c);continue}if(a=null,r(c)){let m=this._findOption(c);if(m){if(m.required){let d=t[u++];d===void 0&&this.optionMissingArgument(m),this.emit(`option:${m.name()}`,d)}else if(m.optional){let d=null;u<t.length&&(!r(t[u])||s(t[u]))&&(d=t[u++]),this.emit(`option:${m.name()}`,d)}else this.emit(`option:${m.name()}`);a=m.variadic?m:null;continue}}if(c.length>2&&c[0]==="-"&&c[1]!=="-"){let m=this._findOption(`-${c[1]}`);if(m){m.required||m.optional&&this._combineFlagAndOptionalValue?this.emit(`option:${m.name()}`,c.slice(2)):(this.emit(`option:${m.name()}`),l=`-${c.slice(2)}`);continue}}if(/^--[^=]+=/.test(c)){let m=c.indexOf("="),d=this._findOption(c.slice(0,m));if(d&&(d.required||d.optional)){this.emit(`option:${d.name()}`,c.slice(m+1));continue}}if(o===n&&r(c)&&!(this.commands.length===0&&s(c))&&(o=i),(this._enablePositionalOptions||this._passThroughOptions)&&n.length===0&&i.length===0){if(this._findCommand(c)){n.push(c),i.push(...t.slice(u));break}else if(this._getHelpCommand()&&c===this._getHelpCommand().name()){n.push(c,...t.slice(u));break}else if(this._defaultCommandName){i.push(c,...t.slice(u));break}}if(this._passThroughOptions){o.push(c,...t.slice(u));break}o.push(c)}return{operands:n,unknown:i}}opts(){if(this._storeOptionsAsProperties){let t={},n=this.options.length;for(let i=0;i<n;i++){let o=this.options[i].attributeName();t[o]=o===this._versionOptionName?this._version:this[o]}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",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
|
|
26
|
-
Expecting one of '${
|
|
27
|
-
`)}),this}_outputHelpIfRequested(t){let n=this._getHelpOption();n&&t.find(
|
|
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(
|
|
30
|
-
Progress covers importing;
|
|
31
|
-
`)}${
|
|
32
|
-
`)}async function
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
${
|
|
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
|
-
`)
|
|
24
|
+
`),this.outputHelp({error:!0}));let i=n||{},o=i.exitCode||1,r=i.code||"commander.error";this._exit(o,r,t)}_parseOptionsEnv(){this.options.forEach(t=>{if(t.envVar&&t.envVar in A.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()}`,A.env[t.envVar]):this.emit(`optionEnv:${t.name()}`))}})}_parseOptionsImplied(){let t=new nn(this.options),n=i=>this.getOptionValue(i)!==void 0&&!["default","implied"].includes(this.getOptionValueSource(i));this.options.filter(i=>i.implied!==void 0&&n(i.attributeName())&&t.valueFromOption(this.getOptionValue(i.attributeName()),i)).forEach(i=>{Object.keys(i.implied).filter(o=>!n(o)).forEach(o=>{this.setOptionValueWithSource(o,i.implied[o],"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 i=s=>{let a=s.attributeName(),l=this.getOptionValue(a),u=this.options.find(m=>m.negate&&a===m.attributeName()),c=this.options.find(m=>!m.negate&&a===m.attributeName());return u&&(u.presetArg===void 0&&l===!1||u.presetArg!==void 0&&l===u.presetArg)?u:c||s},o=s=>{let a=i(s),l=a.attributeName();return this.getOptionValueSource(l)==="env"?`environment variable '${a.envVar}'`:`option '${a.flags}'`},r=`error: ${o(t)} cannot be used with ${o(n)}`;this.error(r,{code:"commander.conflictingOption"})}unknownOption(t){if(this._allowUnknownOption)return;let n="";if(t.startsWith("--")&&this._showSuggestionAfterError){let o=[],r=this;do{let s=r.createHelp().visibleOptions(r).filter(a=>a.long).map(a=>a.long);o=o.concat(s),r=r.parent}while(r&&!r._enablePositionalOptions);n=ri(t,o)}let i=`error: unknown option '${t}'${n}`;this.error(i,{code:"commander.unknownOption"})}_excessArguments(t){if(this._allowExcessArguments)return;let n=this.registeredArguments.length,i=n===1?"":"s",o=t.length,r=this.parent?` for '${this.name()}'`:"",s=t.join(", "),a=`error: too many arguments${r}. Expected ${n} argument${i} but got ${o}: ${s}.`;this.error(a,{code:"commander.excessArguments"})}unknownCommand(){let t=this.args[0],n="";if(this._showSuggestionAfterError){let o=[];this.createHelp().visibleCommands(this).forEach(r=>{o.push(r.name()),r.alias()&&o.push(r.alias())}),n=ri(t,o)}let i=`error: unknown command '${t}'${n}`;this.error(i,{code:"commander.unknownCommand"})}version(t,n,i){if(t===void 0)return this._version;this._version=t,n=n||"-V, --version",i=i||"output the version number";let o=this.createOption(n,i);return this._versionOptionName=o.attributeName(),this._registerOption(o),this.on("option:"+o.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 i=this.parent?._findCommand(t);if(i){let o=[i.name()].concat(i.aliases()).join("|");throw new Error(`cannot add alias '${t}' to command '${this.name()}' as already have command '${o}'`)}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(i=>tn(i));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=$e.basename(t,$e.extname(t)),this}executableDir(t){return t===void 0?this._executableDir:(this._executableDir=t,this)}helpInformation(t){let n=this.createHelp(),i=this._getOutputContext(t);n.prepareContext({error:i.error,helpWidth:i.helpWidth,outputHasColors:i.hasColors});let o=n.formatHelp(this,n);return i.hasColors?o:this._outputConfiguration.stripColor(o)}_getOutputContext(t){t=t||{};let n=!!t.error,i,o,r;return n?(i=a=>this._outputConfiguration.writeErr(a),o=this._outputConfiguration.getErrHasColors(),r=this._outputConfiguration.getErrHelpWidth()):(i=a=>this._outputConfiguration.writeOut(a),o=this._outputConfiguration.getOutHasColors(),r=this._outputConfiguration.getOutHelpWidth()),{error:n,write:a=>(o||(a=this._outputConfiguration.stripColor(a)),i(a)),hasColors:o,helpWidth:r}}outputHelp(t){let n;typeof t=="function"&&(n=t,t=void 0);let i=this._getOutputContext(t),o={error:i.error,write:i.write,command:this};this._getCommandAndAncestors().reverse().forEach(s=>s.emit("beforeAllHelp",o)),this.emit("beforeHelp",o);let r=this.helpInformation({error:i.error});if(n&&(r=n(r),typeof r!="string"&&!Buffer.isBuffer(r)))throw new Error("outputHelp callback must return a string or a Buffer");i.write(r),this._getHelpOption()?.long&&this.emit(this._getHelpOption().long),this.emit("afterHelp",o),this._getCommandAndAncestors().forEach(s=>s.emit("afterAllHelp",o))}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(A.exitCode??0);n===0&&t&&typeof t!="function"&&t.error&&(n=1),this._exit(n,"commander.help","(outputHelp)")}addHelpText(t,n){let i=["beforeAll","before","after","afterAll"];if(!i.includes(t))throw new Error(`Unexpected value for position to addHelpText.
|
|
26
|
+
Expecting one of '${i.join("', '")}'`);let o=`${t}Help`;return this.on(o,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(o=>n.is(o))&&(this.outputHelp(),this._exit(0,"commander.helpDisplayed","(outputHelp)"))}};function Eo(e){return e.map(t=>{if(!t.startsWith("--inspect"))return t;let n,i="127.0.0.1",o="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])?o=r[3]:i=r[3]):(r=t.match(/^(--inspect(-brk|-port)?)=([^:]+):(\d+)$/))!==null&&(n=r[1],i=r[3],o=r[4]),n&&o!=="0"?`${n}=${i}:${parseInt(o)+1}`:t})}function Io(){if(A.env.NO_COLOR||A.env.FORCE_COLOR==="0"||A.env.FORCE_COLOR==="false")return!1;if(A.env.FORCE_COLOR||A.env.CLICOLOR_FORCE!==void 0)return!0}var Ng=new ot;import{createHash as ql}from"node:crypto";import{readdirSync as qo}from"node:fs";import{join as Jl}from"node:path";import{createHash as Il,randomUUID as xo}from"node:crypto";import{chmodSync as xl,closeSync as ai,cpSync as Pl,existsSync as We,fstatSync as Nl,fsyncSync as Ll,lstatSync as li,mkdirSync as mi,mkdtempSync as Dl,openSync as Po,readFileSync as No,readdirSync as jl,renameSync as Lo,rmSync as sn,writeFileSync as Do}from"node:fs";import{basename as pi,dirname as rt,join as ve,win32 as ui}from"node:path";var g={Success:0,Failure:1,Usage:2},El=/^[a-z][a-z0-9_]*$/,p=class extends Error{code;exitCode;hint;httpStatus;constructor(t,n,i,o,r){if(!El.test(t))throw new Error(`CliError code must be snake_case: '${t}'`);if(i===g.Success)throw new Error(`CliError '${t}' cannot use the success exit code`);super(n),this.name="CliError",this.code=t,this.exitCode=i,this.hint=o,this.httpStatus=r}};var Fl="https://api.closedloop.sh",X={us:"https://api.closedloop.sh",eu:"https://eu.api.closedloop.sh"};function He(e){return e===X.us?"us":e===X.eu?"eu":null}function an(e){let t=e.env.CLOSEDLOOP_API_URL?.trim(),n=new URL(t||Fl),i=["localhost","127.0.0.1","[::1]"].includes(n.hostname);if(n.protocol!=="https:"&&!(n.protocol==="http:"&&i))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 Q(e){let t=e.env.CLOSEDLOOP_CONFIG_DIR?.trim();if(t)return t;let n=e.env.XDG_CONFIG_HOME?.trim();if(n)return ve(n,"closedloop");let i=e.platform??process.platform;return i==="darwin"?ve(e.homeDir,"Library","Application Support","closedloop"):i==="win32"?ui.join(e.env.LOCALAPPDATA?.trim()||ui.join(e.homeDir,"AppData","Local"),"closedloop"):ve(e.homeDir,".config","closedloop")}function rn(e,t,n){(e.platform??process.platform)!=="win32"&&xl(t,n)}function ci(e,t){let n=Q(e);mi(t,{recursive:!0,mode:448}),rn(e,n,448),t!==n&&rn(e,t,448)}function jo(e){return e.env.CLOSEDLOOP_CONFIG_DIR?.trim()||e.env.XDG_CONFIG_HOME?.trim()?null:(e.platform??process.platform)==="win32"?ui.join(e.homeDir,".config","closedloop"):ve(e.homeDir,".config","closedloop")}function Ul(e){let t=Q(e),n=jo(e);if(n===null||t===n||We(t)||!We(n))return;let i;try{mi(rt(t),{recursive:!0}),i=Dl(ve(rt(t),`.${pi(t)}-migration-`)),Pl(n,i,{recursive:!0,filter:o=>{let r=li(o);if(!r.isDirectory()&&!r.isFile())throw new Error("legacy config contains a non-regular entry");return!0}}),rn(e,i,448);for(let o of jl(i,{recursive:!0})){let r=ve(i,String(o));rn(e,r,li(r).isDirectory()?448:384)}We(t)||Lo(i,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{i&&sn(i,{recursive:!0,force:!0})}}function Fo(e){let t,n=new Int32Array(new SharedArrayBuffer(4));for(let s=0;s<5;s++)try{t=Po(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 i=t,o,r=()=>{try{if(o&&We(e)){let s=li(e);s.dev===o.dev&&s.ino===o.ino&&sn(e)}}finally{ai(i)}};try{o=Nl(i),Do(i,JSON.stringify({pid:process.pid,owner:xo()}))}catch(s){throw r(),s}return r}function Wl(e,t){let n=Fo(e);try{return t()}finally{n()}}function ln(e,t){let n=Q(e);try{return mi(rt(n),{recursive:!0}),Wl(ve(rt(n),`.${pi(n)}.lock`),()=>(Ul(e),t()))}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 ${n}, then repeat the command.`)}}function xt(e,t){return ve(Q(e),...t.split("/"))}function Uo(e,t){let n=xt(e,t);try{return JSON.parse(No(n,"utf8"))}catch(i){if(i.code==="ENOENT")return null;throw i 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 gi(e){let t=jo(e);t!==null&&t!==Q(e)&&!We(Q(e))&&We(t)&&ln(e,()=>{})}function Pe(e,t){return gi(e),Uo(e,t)}function hi(e,t){let n=xt(e,t);try{return Il("sha256").update(No(n)).digest("hex")}catch(i){if(i.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 Wo(e,t,n){let i=xt(e,t),o=rt(i),r=ve(o,`.${pi(i)}.${xo()}.tmp`),s;try{let a=`${JSON.stringify(n,null,2)}
|
|
28
|
+
`;ci(e,o),s=Po(r,"wx",384),Do(s,a),Ll(s),ai(s),s=void 0,Lo(r,i)}catch{throw new p("config_write_failed",`the saved ${t} file could not be updated`,g.Failure,`Check the permissions and free space for ${o}, then repeat the command. The previous file was kept.`)}finally{s!==void 0&&ai(s),sn(r,{force:!0})}}function Hl(e,t){try{sn(xt(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 Ml=["credentials.json","pending-login.json","pending-browser-login.json"];function di(e,t,n){ci(e,Q(e));let i=[];try{for(let o of t){let r=xt(e,o);ci(e,rt(r)),i.push(Fo(r))}return n({read:o=>Uo(e,o),write:(o,r)=>Wo(e,o,r),remove:o=>Hl(e,o)})}finally{for(let o of i.reverse())o()}}function Gl(e,t){return ln(e,()=>di(e,[".credentials.json.lock"],t))}function fi(e,t,n,i=!1){if(i||!We(Q(e)))return ln(e,()=>di(e,t,n));try{return di(e,t,n)}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 ${Q(e)}, then repeat the command.`)}}function Ho(e,t,n){Ml.includes(t)?Gl(e,i=>i.write(t,n)):ln(e,()=>Wo(e,t,n))}var at="general.json",j="credentials.json",ke="pending-login.json",se="pending-browser-login.json",Vl="working-goal.json",Pt="accounts",Jo=".general.json.lock",Bl=".credentials.json.lock",Yl=/^[^\s\p{C}@<>,;:"()\\[\]]+@(?:[^\s\p{C}@<>,;:"()\\[\].]+\.)+[^\s\p{C}@<>,;:"()\\[\].]+$/u;function ut(e){let t=e?.trim().toLowerCase();return t&&t.length<=320&&Yl.test(t)?t:null}function Ce(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 ql("sha256").update(e.trim().toLowerCase()).digest("hex").slice(0,16)}function ge(e){return`${Pt}/${lt(e)}.json`}function Vo(e){return`${Pt}/.${lt(e)}.lock`}function st(e){return e.replace(/\.json$/,"").replace(/-/g,"_")}function Kl(e){return[j,ke,se,Vl].includes(e)||/^last-[a-z0-9-]+\.json$/.test(e)}function Me(e){return e!==null&&typeof e=="object"&&!Array.isArray(e)}function Bo(e){return e==="us"||e==="eu"?e:null}function un(e){return Me(e)?e:{}}function _i(e,t){return Me(e)&&typeof e.email=="string"&&e.email===t?e:null}function Yo(e){let t;try{t=qo(Jl(Q(e),Pt))}catch(i){if(i.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 i of t.filter(o=>/^[0-9a-f]{16}\.json$/.test(o)).sort()){let o;try{o=Pe(e,`${Pt}/${i}`)}catch(s){if(s instanceof p&&s.code==="config_invalid")continue;throw s}let r=Me(o)?Ce(o.email):null;r!==null&&r===o?.email&&ge(r)===`${Pt}/${i}`&&n.push(r)}return n.sort()}function Mo(e,t){let n=hi(e,t);if(n===null)return null;try{return{value:Pe(e,t),fingerprint:n}}catch(i){if(i instanceof p&&i.code==="config_invalid")return null;throw i}}function Go(e,t,n){let i=Me(n.migrated)?n.migrated:{},o={moves:[],signedIn:null,region:null},r=t.includes(j)?Mo(e,j):null;if(r!==null){let a=Me(r.value)?Ce(r.value.email):null;i[j]===r.fingerprint?o.moves.push({file:j,fingerprint:r.fingerprint,to:null}):a!==null&&(o.signedIn=a,o.region=Bo(r.value.region),o.moves.push({file:j,fingerprint:r.fingerprint,to:a,value:r.value}))}let s=o.signedIn??Ce(n.active_account);for(let a of t.filter(l=>l!==j)){let l=Mo(e,a);if(l===null)continue;if(i[a]===l.fingerprint){o.moves.push({file:a,fingerprint:l.fingerprint,to:null});continue}let u=null;a===ke?u=Me(l.value)?Ce(l.value.email):null:a===se?u=s??"settings":u=s,u!==null&&o.moves.push({file:a,fingerprint:l.fingerprint,to:u,value:l.value})}return o}function wi(e){try{return qo(Q(e)).filter(Kl).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 yi(e){return[...new Set(e.moves.map(t=>t.to).filter(t=>t!==null&&t!=="settings"))].sort()}function cn(e){gi(e);let t=wi(e);if(t.length===0)return;let n=Go(e,t,un(Pe(e,at)));if(n.moves.length===0)return;let i=yi(n),o=[Bl,Jo,...[...i].sort((r,s)=>lt(r).localeCompare(lt(s))).map(Vo)];fi(e,o,r=>{let s=un(r.read(at)),a=Go(e,wi(e),s);if(a.moves.length===0)return;if(yi(a).some(c=>!i.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 yi(a)){let m=_i(r.read(ge(c)),c)??{email:c};for(let d of a.moves.filter(h=>h.to===c))m[st(d.file)]=d.value;r.write(ge(c),m)}let l={...Me(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 zl(e,t){for(let n of wi(e))t.remove(n)}function Ge(e){return cn(e),un(Pe(e,at))}function dn(e,t,n){cn(e);let o=_i(Pe(e,ge(t)),t)?.[st(n)];return o===void 0?null:o}function Nt(e){return cn(e),Yo(e)}function Ne(e){if(e.account)return e.account;let t=e.accountOption??(e.env.CLOSEDLOOP_ACCOUNT?.trim()||void 0),n;if(t!==void 0){let i=Ce(t);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.");n={email:i,explicit:!0}}else n={email:Ce(Ge(e).active_account),explicit:!1};return e.account=n,n}function Y(e,t,n){cn(e);let i=[...new Set((t.accounts==="all"?Yo(e):t.accounts).map(r=>r.trim().toLowerCase()))].sort((r,s)=>lt(r).localeCompare(lt(s))),o=[...t.settings?[Jo]:[],...i.map(Vo)];return fi(e,o,r=>{let s=l=>{let u=l.trim().toLowerCase();if(!i.includes(u))throw new Error("an account file was changed without its lock");return u},a=l=>_i(r.read(ge(l)),l);return n({settings:()=>un(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 m=s(l);r.write(ge(m),{...a(m)??{email:m},[st(u)]:c})},remove:(l,u)=>{let c=s(l),m=a(c);if(m===null||!(st(u)in m))return;let{[st(u)]:d,...h}=m;Object.keys(h).length===1?r.remove(ge(c)):r.write(ge(c),h)},forget:l=>r.remove(ge(s(l))),emails:()=>i.filter(l=>hi(e,ge(l))!==null).sort(),removeLeftovers:()=>zl(e,r)})})}function mn(e,t,n){return t!==null?Y(e,{accounts:[t]},i=>n({read:o=>i.read(t,o),write:(o,r)=>i.write(t,o,r),remove:o=>i.remove(t,o)})):Y(e,{accounts:[],settings:!0},i=>{let o=r=>{if(r!==se)throw new Error("only a browser sign-in is kept without an account")};return n({read:r=>{o(r);let s=i.settings().pending_browser_login;return s===void 0?null:s},write:(r,s)=>{o(r),i.setSettings({...i.settings(),pending_browser_login:s})},remove:r=>{o(r);let{pending_browser_login:s,...a}=i.settings();i.setSettings(a)}})})}function bi(e,t,n){if(t!==null)return dn(e,t,n);if(n!==se)return null;let i=Ge(e).pending_browser_login;return i===void 0?null:i}function pn(e,t,n){Y(e,{accounts:[],settings:!0},i=>{let o=i.settings();i.setSettings({...o,last_email:t,...n!==null&&{last_region:n}})})}function Ko(e,t){let{email:n}=Ne(e);return n===null?null:dn(e,n,t)}function zo(e,t,n){let{email:i}=Ne(e);i!==null&&Y(e,{accounts:[i]},o=>{o.read(i,j)!==null&&o.write(i,t,n)})}function Xo(e,t){let{email:n}=Ne(e);n!==null&&Y(e,{accounts:[n]},i=>i.remove(n,t))}function gn(e){return Bo(e.region)??He(e.api_url)}function he(e,t=process.platform){return t==="win32"?`'${e.replace(/['‘-‛]/g,"$&$&")}'`:`'${e.replace(/'/g,"'\\''")}'`}function $i(e,t=process.platform){return/^[a-z0-9@._+-]+$/i.test(e)?e:he(e,t)}function Le(e,t=process.platform){if(e===null)return"closedloop login";let n=$i(e,t);return n.startsWith("-")?`closedloop login --email ${n}`:`closedloop login ${n}`}function hn(e,t,n=process.platform){let i=JSON.stringify(e);if(n!=="win32")return t(he(i,n));let o=i.replace(/[^\x20-\x7e]/g,r=>`\\u${r.charCodeAt(0).toString(16).padStart(4,"0")}`);return`${he(o,n)} | ${t("-")}`}var ae="0.3.6";var fn="npm install -g @closedloop-ai/cli@latest",Ci=/^(\d{1,6})\.(\d{1,6})\.(\d{1,6})$/,Qo="update-notice.json",Xl=1440*60*1e3;function Lt(e){return typeof e=="string"&&Ci.test(e)}function Zo(e,t){let n=Ci.exec(e),i=Ci.exec(t);if(!n||!i)throw new Error("compareVersions needs two x.y.z versions");for(let o=1;o<=3;o++)if(Number(n[o])!==Number(i[o]))return Number(n[o])-Number(i[o]);return 0}function er(e,t){e.release&&Lt(t)&&(e.release.latest===null||Zo(t,e.release.latest)>0)&&(e.release.latest=t)}function ki(e){let t=e.release?.latest??null;return t===null||!Lt(ae)?null:Zo(t,ae)>0?t:null}function vi(e){return e!==void 0&&e!==""&&e!=="0"&&e!=="false"}function tr(e,t){let n=ki(e);if(n===null||!t)return null;let{env:i}=e;if(vi(i.CI)||vi(i.NO_UPDATE_NOTIFIER)||vi(i.CLOSEDLOOP_NO_UPDATE_NOTIFIER))return null;let o=e.now?.()??Date.now(),r=null;try{r=Pe(e,Qo)}catch{}if(r&&r.version===n&&typeof r.shown_at=="number"&&o>=r.shown_at&&o-r.shown_at<Xl)return null;try{Ho(e,Qo,{version:n,shown_at:o})}catch{}return`ClosedLoop AI CLI ${n} is out (you have ${ae}). Update: ${fn}`}var Ql=7e4,Si=/[\u0000-\u0008\u000B-\u001F\u007F-\u009F\u061C\u200E\u200F\u202A-\u202E\u2066-\u2069]/g,Zl=(e,t)=>{if(typeof t=="string")return t.replace(Si,"");if(t===null||typeof t!="object"||Array.isArray(t))return t;let n=Object.keys(t);return n.some(i=>i!==i.replace(Si,""))?Object.fromEntries(n.map(i=>[i.replace(Si,""),t[i]])):t};async function yn(e,t,n,i,o={}){if(!i.startsWith("/"))throw new Error("an API path starts with /");let r,s,a=AbortSignal.timeout(Ql);try{r=await e.fetch(`${t}${i}`,{method:n,headers:{...o.headers,Accept:"application/json","X-ClosedLoop-Client":"cli","X-ClosedLoop-CLI-Version":ae,...o.payload!==void 0&&{"Content-Type":"application/json"},...o.token&&{Authorization:`Bearer ${o.token}`}},...o.payload!==void 0&&{body:JSON.stringify(o.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,Zl);u!==null&&typeof u=="object"&&!Array.isArray(u)&&(l=u)}catch{}if(r.status===426&&l.code==="upgrade_required")throw tu(l);if(er(e,r.headers.get("X-ClosedLoop-CLI-Latest")),r.status===429)throw eu(e,r.headers.get("Retry-After"),l);return{status:r.status,body:l,text:s}}function eu(e,t,n){let i=typeof n.code=="string"?n.code.toLowerCase():"",o=i==="rate_limit_exceeded"?"rate_limited":i,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(o)?o:"rate_limited",typeof n.error=="string"&&n.error?n.error:"too many requests to ClosedLoop AI",g.Failure,a,429)}function tu(e){let t=Lt(e.minimum_version)?e.minimum_version:"a newer version";return new p("upgrade_required",`this ClosedLoop AI CLI (${ae}) is too old for the API; ${t} or later is needed`,g.Failure,Lt(e.latest_version)?`Update to ${e.latest_version}: ${fn}`:`Update: ${fn}`)}function qe(e,t,n,i){return yn(e,t,"POST",n,{payload:i})}var nu=["password","email_code","google","microsoft","okta","saml"];function wn(e){return nu.includes(e)?e:null}var Oi="Run `closedloop login` to sign in.",D='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 i=t,o=r=>typeof r=="string"&&r.trim()!==""&&r.trim()===r;if(!o(i.api_url)||!o(i.email)||!o(i.access_token)||!o(i.refresh_token)||i.email.toLowerCase()!==n)return null;try{if(an({...e,env:{CLOSEDLOOP_API_URL:i.api_url}})!==i.api_url)return null}catch{return null}return i}function Ai(e,t){let n=t.trim().toLowerCase();return dt(e,dn(e,n,j),n)}function iu(e){let{email:t}=Ne(e);return t===null?null:Ai(e,t)}function Ri(e){let t=[];for(let n of Nt(e)){let i=Ai(e,n);i!==null&&t.push({email:n,session:i})}return t}function v(e){let t=iu(e);if(t!==null)return t;let{email:n,explicit:i}=Ne(e);if(i&&n!==null)throw new p("account_not_signed_in",`${n} is not signed in on this computer`,g.Failure,`Run \`${Le(n,e.platform)}\` to sign it in, or \`closedloop accounts\` to see the accounts signed in here.`);let o=Ri(e).map(r=>r.email);throw new p("not_signed_in","you are not signed in on this computer",g.Failure,o.length===0?Oi:`Run \`closedloop use <email>\` to use one of the accounts signed in here (${o.join(", ")}), or \`closedloop login\` to sign in.`)}function ir(e,t,n,i,o){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=o?.account??null,l=gn(t);Y(e,{accounts:a!==null&&a!==r?[r,a]:[r],settings:!0},u=>{if(o){let d=a===null?u.settings().pending_browser_login??null:u.read(a,o.file);if(JSON.stringify(d)!==JSON.stringify(o.value))throw ct()}u.write(r,j,{...t,signed_in_at:s,sign_in_method:n,signed_in_with:i,saved_at:s}),u.remove(r,ke),u.remove(r,se),a!==null&&a!==r&&o&&u.remove(a,o.file);let{pending_browser_login:c,...m}=u.settings();u.setSettings({...m,...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 Ti(e){let t=e.split(".");if(t.length!==3)return null;try{let n=JSON.parse(Buffer.from(t[1],"base64url").toString("utf8")),{iat:i,exp:o}=n??{};return typeof i!="number"||typeof o!="number"||!Number.isFinite(i)||!Number.isFinite(o)||i<0||o<=i||o*1e3>864e13?null:{issuedAt:i,expiresAt:o}}catch{return null}}function or(e){let t=Ti(e.refresh_token)??Ti(e.access_token);return t?new Date(t.expiresAt*1e3).toISOString():null}function ou(e,t){let n=Ti(e);return n!==null&&t/1e3>=n.issuedAt+(n.expiresAt-n.issuedAt)/2}async function ru(e,t){let n=t.email.toLowerCase(),i=Ai(e,n);if(!i||i.api_url!==t.api_url||i.email!==t.email||i.refresh_token!==t.refresh_token)throw ct();let o=JSON.stringify(i),r=await yn(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,j))!==o)throw ct();u.write(n,j,{...t,...l})}),l}async function nr(e,t){let n=e.pendingSessionRefreshes??=new Map,i=JSON.stringify([t.api_url,t.email,t.refresh_token]),o=n.get(i);o||(o=ru(e,t),n.set(i,o));try{let r=await o;return r?(Object.assign(t,r),!0):!1}finally{n.get(i)===o&&n.delete(i)}}async function $(e,t,n,i,o,r){let s=()=>yn(e,t.api_url,n,i,{token:t.access_token,payload:o,headers:r}),a=null;if(ou(t.access_token,e.now?.()??Date.now()))try{a=await nr(e,t)}catch(c){if(!(c instanceof p))throw c;let m=["api_unreachable","api_timeout"].includes(c.code),d=c.httpStatus===429;if(!m&&!d)throw c;a=!1}let l=await s();if(l.status!==401)return l;if(a!==null||!await nr(e,t))throw new p("session_expired","your saved sign-in has expired or was signed out",g.Failure,Oi);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,Oi);return u}function Ei(e,t,n){return $(e,t,"GET",n)}function rr(e,t,n){let i=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:gn(t),workspace:i,api_url:t.api_url,active:e===n}}function su(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 sr=(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 i=Ge(n),o=Ce(i.active_account),r=Ri(n).map(a=>rr(a.email,a.session,o)),s={accounts:r,active_account:r.some(a=>a.active)?o: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(su);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(i=>{let o=Ce(i);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.");let r=Y(n,{accounts:[o],settings:!0},a=>{let l=dt(n,a.read(o,j),o);return l!==null&&a.setSettings({...a.settings(),active_account:o}),l});if(r===null)throw new p("account_not_signed_in",`${o} is not signed in on this computer`,g.Failure,`Run \`${Le(o,n.platform)}\` to sign it in, or \`closedloop accounts\` to see the accounts signed in here.`);let s={status:"active",...rr(o,r,o)};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 Se(e,t){return t?`\x1B]8;;${e}\x07${e}\x1B]8;;\x07`:e}function N(e){return new Intl.DateTimeFormat(void 0,{dateStyle:"medium"}).format(new Date(e))}function _n(e,t){try{return new Intl.DateTimeFormat(void 0,{dateStyle:"medium",timeZone:t}).format(new Date(e))}catch{return N(e)}}function Dt(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 i=t%60;return i>=5?`${n} h ${i} min`:`${n} h`}return`${Math.round(n/24)} days`}function V(e){return new Intl.DateTimeFormat(void 0,{dateStyle:"medium",timeZone:"UTC"}).format(new Date(e))}var jt=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,ar=e=>jt.test(e.trim()),L=e=>`See the ids with: ${e}`,au=e=>/^[aeiou]/i.test(e)?"an":"a";function I(e,t,n){let i=e.trim();if(jt.test(i))return i.toLowerCase();throw new p("invalid_id",`'${e}' is not ${au(t)} ${t} id`,g.Usage,L(n))}var E=(e,t,n)=>`${t}${e.dim(`id ${String(n)}`)}`;function C(e,t,n){let i=typeof e.body.code=="string"?e.body.code.toLowerCase():"",o=typeof e.body.error=="string"&&e.body.error?e.body.error:n;return new p(/^[a-z][a-z0-9_]*$/.test(i)?i:t,o,g.Failure)}var lu=["can_manage_integrations","can_manage_billing","can_manage_members","can_manage_team_settings","can_view_analytics"];async function M(e,t){let n=await $(e,t,"GET","/api/team-info"),i=n.body.teamInfo;if(n.status!==200)throw C(n,"api_error","Your workspace could not be loaded. Try again.");if(typeof i?.team_id!="string"||!i.team_id)throw new p("no_workspace","you are not in a workspace yet",g.Failure,`Create or join one in the app. ${D}`);let o=Object.fromEntries(lu.map(r=>[r,i.permissions?.[r]===!0]));return{id:i.team_id,name:typeof i.team_name=="string"?i.team_name:"your workspace",role:typeof i.role=="string"?i.role:null,permissions:o}}async function Je(e,t){return(await M(e,t)).id}var uu={can_manage_integrations:"manage integrations",can_manage_billing:"manage billing"};async function Ve(e,t,n,i){let o=await M(e,t);if(!o.permissions[n])throw new p(n==="can_manage_integrations"?"integration_manager_required":"billing_manager_required",`you cannot ${i}: it needs the right to ${uu[n]} in ${o.name}`,g.Failure,`Ask an owner or admin of ${o.name} to give you that right, or to do it for you.`);return o}var Ii=new Set(["queued","importing","stalled"]),pt=new Set([...Ii,"analysing"]),fe=e=>e.overall_state??e.state,cu=e=>e.overall_state?e.overall_finished_at??null:e.finished_at,du=7200,lr=30,ur="closedloop backfills",dr=e=>`${e[0].toUpperCase()}${e.slice(1)}`,mr=(e,t)=>`${k(e)} ${e===1?t.replace(/s$/,""):t}`;function pr(e){if(!e.progress)return null;let{unit:t,done:n,total:i,failed:o}=e.progress;return`${k(n)} of ${k(i)} ${t}${e.percent!==null?` (${e.percent}%)`:""}${o?`, ${k(o)} failed`:""}`}function gr(e){return e.from&&e.to?`${V(e.from)} to ${V(e.to)}${e.window?` (${e.window})`:""}`:e.window}function hr(e,t){let n=fe(e);if(n==="stalled")return e.last_activity_at?`no progress since ${R(e.last_activity_at)}`:"no progress";if(n==="queued")return e.created_at?`waiting to start since ${R(e.created_at)}`:"waiting to start";if(n==="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";let i=cu(e);if(i){let o=e.started_at??e.created_at,r=o?Date.parse(i)-Date.parse(o):NaN,s=!Number.isFinite(r)||r<0?"":e.started_at?` (took ${mt(r)})`:` (${mt(r)} after it was requested)`;return`${n==="done"?"finished":"ended"} ${R(i)}${s}`}return null}function mu(e,t){let{imported:n,analysed:i,insights:o}=t,r=pt.has(fe(e)),s=l=>l?`, finished ${R(l)}`:"",a=o.complete?`done${s(o.finished_at)}`:o.to_go>0?`${k(o.to_go)} to go`:r?"waiting for the analysis":"none to go";return[`Imported: ${mr(n.total,t.unit)}${!n.complete&&r?" so far":""} (${k(n.new)} new)${s(n.finished_at)}`,`Analysed: ${k(i.done)} of ${k(i.total)}${s(i.finished_at)}`,`Insights: ${a}`]}function pu(e){let t=e.stages;if(!t||!t.imported.complete)return null;let{imported:n,analysed:i,insights:o}=t,r=[`imported ${mr(n.total,t.unit)} (${k(n.new)} new)`,`analysed ${k(i.done)} of ${k(i.total)}`];return i.complete&&!o.complete&&r.push(`insights ${k(o.to_go)} to go`),r.join(" \xB7 ")}function gu(e,t){let n=fe(e),i=dr(n);return n==="failed"||n==="stalled"?t.red(i):n==="importing"||n==="queued"||n==="analysing"?t.orange(i):n==="done"?t.green(i):t.dim(i)}function bn(e,t,n){let i=fe(e),o=e.stages?.analysed,r=i==="analysing"&&o&&o.total>0?o.done/o.total:Ii.has(i)&&e.percent!==null?e.percent/100:null,s=n.enabled&&r!==null?`${n.orange(n.bar(r,12))} `:"",a=pu(e);return[gu(e,n),`${s}${[e.source,gr(e),a??pr(e),hr(e,t)].filter(Boolean).join(n.dim(" \xB7 "))}`].join(" ")}var fr="insights appear as the imported records are analysed.",yr=e=>pt.has(fe(e))&&!e.stages;async function hu(e,t,n){let i=await $(e.runtime,t,"GET",`/api/cli/backfills?limit=${n}`);if(i.status!==200||!Array.isArray(i.body.backfills))throw C(i,"api_error","Backfills could not be loaded. Try again.");return i.body.backfills}async function fu(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 i=v(e.runtime),o=await hu(e,i,n),r=Date.now();e.print.result({backfills:o},s=>{if(s.backfills.length===0)return"No backfills in this workspace yet.";let a=e.print.style,l=s.backfills.flatMap(c=>[bn(c,r,a),E(a," ",c.id)]),u=s.backfills.some(yr)?`
|
|
30
|
+
Progress covers importing; ${fr}`:"";return`${l.join(`
|
|
31
|
+
`)}${u}
|
|
32
|
+
${a.dim("Read one by its id: closedloop backfills show <id>")}`})}async function cr(e,t,n){let i=I(n,"backfill",ur),o=await $(e.runtime,t,"GET",`/api/cli/backfills/${i}`);if(o.status===404)throw new p("backfill_not_found","there is no backfill with that id in your workspace",g.Failure,L(ur));if(o.status!==200||typeof o.body.id!="string")throw C(o,"api_error","The backfill could not be loaded. Try again.");return o.body}function yu(e,t){let n=fe(e),i=[`${e.source} backfill: ${n}`],o=gr(e);o&&i.push(`Covers: ${o}`);let r=pr(e),s=!!e.stages?.imported.complete&&!e.progress?.failed;r&&!s&&i.push(`Progress: ${r}`),e.stages&&i.push(...mu(e,e.stages)),e.created_at&&i.push(`Requested: ${R(e.created_at)}`),e.started_at&&i.push(`Started: ${R(e.started_at)}`),e.last_activity_at&&Ii.has(n)&&i.push(`Last activity: ${R(e.last_activity_at)}`);let a=hr(e,t);return a&&i.push(dr(a)+"."),yr(e)&&i.push(`This covers importing; ${fr}`),i.push(`id ${e.id}`),i.join(`
|
|
33
|
+
`)}async function wu(e,t,n){let i=v(e.runtime),o=await cr(e,i,t);if(n&&pt.has(fe(o))){let r="",s=e.print.spinner("Checking every 30 seconds\u2026");try{for(let a=0;a<du&&pt.has(fe(o));a+=lr){let l=bn(o,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(lr*1e3),o=await cr(e,i,o.id)}}finally{s.stop()}}e.print.result(o,r=>yu(r,Date.now()))}var wr=(e,t)=>{e.command("backfills").alias("backfill").description("History imports, each with its id: their state, progress, analysis and insights, and an estimate of when the import finishes.").option("--limit <n>","How many, up to 100 (default 20).").action(async i=>fu(t,i.limit)).command("show").argument("<id>","The backfill's id, from closedloop backfills.").description("One backfill in detail: what it imported, how much is analysed, the insights still to go, and when each finished.").option("--watch","Keep checking every 30 seconds until it finishes (up to 2 hours).").action(async(i,o)=>wu(t,i,o.watch===!0))};import{existsSync as Er,readFileSync as _u,statSync as Ir}from"node:fs";var w=e=>e!==null&&typeof e=="object"&&!Array.isArray(e),xi=e=>Array.isArray(e)?e.filter(w):[];function De(e){let{success:t,...n}=e;return n}function Ft(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 i=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:"",m=typeof u=="string"?u:JSON.stringify(s);return c?`${c}: ${m}`:m});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 ${i.length===1?"this":`these ${i.length}`}:
|
|
34
|
+
${i.join(`
|
|
35
|
+
`)}`,g.Failure);let r=`the ${t} was not accepted; fix ${i.length===1?"this":`these ${i.length}`}:`;return new p(t==="rule"?"invalid_rule":"invalid_metric",`${r}
|
|
36
|
+
${i.join(`
|
|
37
|
+
`)}`,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,i){let o=v(e.runtime),r=await $(e.runtime,o,t,n,i);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 ye(e){return w(e)&&w(e.current_version)?e.current_version:{}}function je(e){return w(e)&&typeof e.version=="number"?e.version:null}async function _r(e,t){let n=new URLSearchParams;t.q&&n.set("q",t.q),t.entity&&n.set("entity",t.entity);let i=n.toString(),o=await K(e,"GET",`/api/fields${i?`?${i}`:""}`);if(!z(o)||!Array.isArray(o.body.fields))throw C(o,"api_error","The fields could not be loaded. Try again.");return xi(o.body.fields)}async function br(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 $r(e,t){let n=await K(e,"POST","/api/segments/preview",{rule:t}),i=Ft(n,"rule");if(i)throw i;if(!z(n)||typeof n.body.count!="number")throw C(n,"api_error","The preview could not be made. Try again.");return De(n.body)}async function vr(e,t,n,i){let o=await K(e,"POST","/api/segments",{name:t,rule:n,...i!==void 0&&{description:i}}),r=Ft(o,"rule");if(r)throw r;if(o.status===409&&o.body.code==="NAME_TAKEN")throw new p("segment_exists",typeof o.body.error=="string"&&o.body.error?o.body.error:`a segment named "${t}" already exists`,g.Failure,"Pick another name, or see yours: closedloop segments list");if(!z(o)||!w(o.body.segment)||typeof o.body.segment.id!="string")throw C(o,"api_error","The segment could not be saved. Try again.");return o.body.segment}async function Cr(e,t){try{let n=await K(e,"GET",`/api/segments/${encodeURIComponent(t)}`),i=z(n)?ye(n.body.segment).rule:null;return w(i)?i:null}catch(n){if(n instanceof p)return null;throw n}}async function kr(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 xi(t.body.segments)}async function Pi(e,t,n){let i=await K(e,"POST","/api/metrics/preview",{segment:t,calc:n}),o=Ft(i,"metric");if(o)throw o;if(!z(i)||!("value"in i.body))throw C(i,"api_error","The preview could not be made. Try again.");return De(i.body)}async function Sr(e,t,n,i,o){let r=await K(e,"POST","/api/metrics",{name:t,segment:n,calc:i,...o!==void 0&&{description:o}}),s=Ft(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 Or(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 xi(t.body.metrics)}async function Tr(e,t,n){let i=await K(e,"DELETE",`/api/metrics/${encodeURIComponent(t)}`,n===void 0?{}:{reason:n}),o=typeof i.body.error=="string"&&i.body.error?i.body.error:null;if(i.status===404)throw new p("not_found",o??"There is no such metric.",g.Failure,"See them: closedloop metrics list");if(i.status===409){let s=i.body.code==="ARCHIVED";throw new p(s?"metric_deleted":"metric_in_use",o??(s?"this metric is already deleted":"this metric is in use"),g.Failure)}let r=Ft(i,"metric");if(r)throw r;if(!z(i)||!w(i.body.metric)||!Array.isArray(i.body.removed))throw C(i,"api_error","The metric could not be deleted. Try again.");return De(i.body)}async function Ar(e,t,n){try{let i=await K(e,"GET",`/api/metrics/${encodeURIComponent(t)}?runs_limit=1`);if(!z(i)||!Array.isArray(i.body.versions))return null;let o=i.body.versions.find(r=>w(r)&&r.version===n);return w(o)&&w(o.calc)?o.calc:null}catch(i){if(i instanceof p)return null;throw i}}async function Rr(e,t){let n=await K(e,"GET",`/api/metrics/${encodeURIComponent(t)}?runs_limit=1000`);if(n.status===404){let i=typeof n.body.error=="string"&&n.body.error?n.body.error:"There is no such metric.";throw new p("not_found",i,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 De(n.body)}async function Ni(e,t,n,i){let o=new URLSearchParams({as_of:n});i!==void 0&&o.set("version",String(i));let r=await K(e,"GET",`/api/metrics/${encodeURIComponent(t)}/value?${o.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 De(r.body)}var bu=1024*1024,gt=(e,t,n)=>new p(e,t,g.Usage,n);function Ut(e){let t=e.trim().replace(/^'/,"");return/^[{[]/.test(t)&&!t.includes('"')&&/[{,]\s*[A-Za-z_][\w.]*\s*:/.test(t)}function $n(e,t=process.platform,n=e){let i=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 gt(`invalid_${e}`,`the ${e} given is not valid JSON: the shell removed its double quotes`,i)}var $u=[{option:"rule",kind:"rule"},{option:"segment",kind:"segment"},{option:"calc",kind:"calc"},{option:"file",kind:"structure",command:["products","save"]}];function vu(e,t,n){if(n===void 0)return!0;let i=e.slice(0,t).filter(o=>!o.startsWith("-"));return n.every((o,r)=>i[r]===o)}function jr(e){for(let t=0;t<e.length;t++)for(let{option:n,kind:i,command:o}of $u){let r=e[t]===`--${n}`?e[t+1]:e[t].startsWith(`--${n}=`)?e[t].slice(n.length+3):void 0;if(r!==void 0&&Ut(r)&&vu(e,t,o))return{option:n,kind:i}}return null}async function Fe(e,t,n,i,o=bu,r=n){let s,a,l=t.trim(),u=l.startsWith("{")||l.startsWith("[");if(l==="-")s=await e.runtime.readStdin(o),a="standard input";else if(u)s=l,a=`the ${n} given`;else{if(!Er(l)&&Ut(l))throw $n(n,e.runtime.platform,r);if(!Er(l)||!Ir(l).isFile())throw gt("file_not_found",`there is no file ${l}`,i);if(Ir(l).size>o)throw gt("file_too_large",`${l} is larger than ${Math.round(o/(1024*1024))} MB`);s=_u(l,"utf8").replace(/^\uFEFF/,""),a=l}if(!s.trim())throw gt(`invalid_${n}`,`${a} is empty`,i);let c;try{c=JSON.parse(s)}catch{throw u&&Ut(s)?$n(n,e.runtime.platform,r):gt(`invalid_${n}`,`${a} is not valid JSON; check its syntax`,i)}if(!w(c))throw gt(`invalid_${n}`,`${a} is not a JSON object`,i);return c}var xr={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"]},Cn=(e,t)=>Object.prototype.hasOwnProperty.call(e,t);function G(e,t=2){return typeof e!="string"||!Cn(xr,e)?t===1?"member":"members":xr[e][t===1?0:1]}function T(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 F(e){return typeof e=="string"?JSON.stringify(e):Array.isArray(e)?e.map(F).join(", "):e==null?"empty":typeof e=="number"?T(e):typeof e=="object"?JSON.stringify(e):String(e)}function Be(e,t){switch(e){case"exists":return"has a value";case"is_empty":return"is empty";case"within_last":return`within the last ${F(t)} days`;case"within_period":return`within this ${typeof t=="string"?t:F(t)} so far`;case"between":return Array.isArray(t)&&t.length===2?`between ${F(t[0])} and ${F(t[1])}`:`between ${F(t)}`;case"not_in":return`not in ${F(t)}`;default:return`${String(e)} ${F(t)}`}}var Pr={">=":"at least",">":"more than","<=":"at most","<":"fewer than","=":"exactly"},Nr={count_distinct:"distinct values of",sum:"sum of",avg:"average of",min:"smallest",max:"largest",median:"median of"};function Fr(e){if(typeof e!="string")return String(e);let t=/^p(\d{1,2})$/.exec(e);return t?`${t[1]}th percentile of`:Cn(Nr,e)?Nr[e]:e}function Ur(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 Wt(e,t=new Map){if(typeof e=="string")return e;if(!w(e))return"";let n=o=>Wt(o,t),i=(o,r)=>{let s=n(o),a=r==="and"?"or":"and";return w(o)&&Array.isArray(o[a])?`(${s})`:s};if(Array.isArray(e.and))return e.and.map(o=>i(o,"and")).join(" and ");if(Array.isArray(e.or))return e.or.map(o=>i(o,"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} ${Be(e.op,e.value)}`;if(typeof e.has=="string"){let o=w(e.count)?e.count:null;return`has ${typeof e.min=="number"&&e.min!==1?`at least ${F(e.min)} ${G(e.has)}`:o&&typeof o.op=="string"&&Cn(Pr,o.op)?`${Pr[o.op]} ${F(o.value)} ${G(e.has)}`:`${/^[aeiou]/.test(G(e.has,1))?"an":"a"} ${G(e.has,1)}`}${e.where?` where ${n(e.where)}`:""}`}if(w(e.subtotal)){let o=e.subtotal,r=o.fn==="count"?`count of ${G(o.of)}`:`${Fr(o.fn)} ${String(o.field??G(o.of))}`,s=o.where?` over ${G(o.of)} where ${n(o.where)}`:"";return`${r}${s} ${Be(e.op,e.value)}`}if(typeof e.moved=="string"){let o=e.from!==void 0?` from ${F(e.from)}`:"";return`${e.moved} moved into ${F(e.into)}${o} ${Lr(e)}`}if(w(e.change)){let o=e.change,r=w(o.start)?` (start ${Be(o.start.op,o.start.value)})`:"";return`change of ${String(o.field)} ${Lr(o)} ${Be(e.op,e.value)}${r}`}if(e.usage==="event"){let o=e.within_last!==void 0?` in the last ${F(e.within_last)} days`:"";return`usage event ${F(e.event)} ${String(e.measure??"count")}${o} ${Be(e.op,e.value)}`}return JSON.stringify(e)}function Lr(e){return e.within_period!==void 0?Be("within_period",e.within_period):Be("within_last",e.within_last)}function Wr(e,t=new Map){return w(e)?e.where===void 0?`all ${G(e.of)}`:Wt(e.where,t):"no rule"}function vn(e){if(!w(e))return"no calculation";if(e.fn==="ratio")return`share: ${vn(e.numerator)} / ${vn(e.denominator)}`;let t=e.fn==="count"?"count":`${Fr(e.fn)} ${String(e.field)}`;return e.where?`${t} where ${Wt(e.where)}`:t}function Ht(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 Ye(e,t,n,i){if(typeof t!="number"||!Number.isFinite(t))return null;if(w(e)&&e.fn==="ratio"){let o=typeof n=="number"&&typeof i=="number"?` (${T(n)} / ${T(i)})`:"";return Ht(e)?`${(t*100).toFixed(1)}%${o}`:`${T(t)}${o}`}return T(t)}var Dr={crm:"CRM",insights:"insights",usage:"product usage"},kn=e=>typeof e=="string"&&Cn(Dr,e)?Dr[e]:String(e);function Mt(e){let t=Array.isArray(e)?e.filter(n=>typeof n=="string"):[];return t.length?`the data is late (${t.map(kn).join(", ")}), so this number may be out of date`:null}function Sn(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),i=(Array.isArray(e.suggestions)?e.suggestions:[]).map(o=>w(o)?`${F(o.value)}${typeof o.count=="number"?` (${k(o.count)})`:""}`:F(o));return i.length?`${t} Closest: ${i.join(", ")}.`:t}function Hr(e){return Array.isArray(e)?e.filter(w).filter(t=>typeof t.field=="string"&&typeof t.instead_of=="string"):[]}var Li=e=>typeof e=="number"?k(e):"no number";function On(e,t){let n=Hr(e);return n.length?`If the request did not say which field it means, ask the user to choose before saving: ${n.map(o=>`${String(o.instead_of)} gives ${Li(t)}, ${String(o.field)} gives ${Li(o.value)}${typeof o.reason=="string"&&o.reason?` (${o.reason})`:""}`).join("; ")}.`:null}function Tn(e,t){return Hr(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})`:""}`:Li(n.value)}. They are often confused; say which one you mean.`))}var ji="Insights are filed against the products, areas and features you confirm here.",qr="An area is a part of a product; a feature is a part of the product that exists, never planned work.",Oe="closedloop products confirm",Mr="closedloop products",En="closedloop products save --file <structure.json>",Cu=10*1024*1024,An="Start from the current structure: closedloop products --json > structure.json";function ku(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 ht(e,t){let n=await $(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 ku(n.body)}var Jr=e=>typeof e=="number"?` \xB7 ${k(e)} insight${e===1?"":"s"}`:"",Rn=(e,t,n,i)=>typeof n=="string"&&n?[t,E(e,`${i} `,n)]:[t],Di=(e,t,n)=>(t??[]).flatMap(i=>Rn(e,`${n}Feature: ${i.name}${Jr(i.insight_count)}`,i.id,n)),Gr=(e,t,n)=>(t??[]).flatMap(i=>[...Rn(e,`${n}Area: ${i.name}${Jr(i.insight_count)}`,i.id,n),...Di(e,i.features,`${n} `)]);function Su(e,t){let{structure:n,suggested_products:i}=e,o=[ji];if(n.products.length+n.areas.length+n.features.length===0&&i.length===0)return o.push("",`No products yet. Suggestions appear once your company research is done; see: closedloop company. Or add your own: ${En}`),o.join(`
|
|
38
|
+
`);for(let s of n.products)o.push("",...Rn(t,`Product: ${s.name}`,s.id,""),...Gr(t,s.areas," "),...Di(t,s.features," "));return n.areas.length+n.features.length>0&&o.push("","Under no product",...Gr(t,n.areas," "),...Di(t,n.features," ")),n.products.length===0&&o.push("","No products confirmed yet."),i.length>0&&o.push("","Suggested by your company research, not confirmed yet:",...i.flatMap(s=>Rn(t,` ${s.name}`,s.id," ")),`Confirm them all with: ${Oe}; or only some, by their ids: ${Oe} <id>...`),e.can_change||o.push("","Only an owner or admin of your workspace can change it."),o.join(`
|
|
39
|
+
`)}async function Ou(e){let t=v(e.runtime);e.print.result(await ht(e.runtime,t),n=>Su(n,e.print.style))}var Tu={product_area:["area","product"],area_feature:["feature","area"],product_feature:["feature","product"]};function Au(e,t){if(e.action==="link"||e.action==="unlink"){let[i,o]=Tu[e.level]??["item","item"];return e.action==="link"?`Put ${i} "${e.child}" under ${o} "${e.parent}"`:`Take ${i} "${e.child}" out of ${o} "${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(i=>i.replace(/_/g," ")).join(", ")})`:""}`;case"archive":return`Archive ${n}`;default:return`${e.action} ${n}`}}function Vr(e){if(e.length===0)return new p("invalid_structure","the structure has problems; nothing was saved",g.Failure,An);let t=e.map(n=>`${n.path}: ${n.message}`).join(`
|
|
39
40
|
`);return new p("invalid_structure",`nothing was saved; fix ${e.length===1?"this":`these ${e.length}`}:
|
|
40
|
-
${t}`,g.Failure,
|
|
41
|
-
`),
|
|
42
|
-
${c}`);return}let
|
|
43
|
-
${c}`),h=!0,
|
|
41
|
+
${t}`,g.Failure,An)}async function Ru(e,t,n){let i=await $(e,t,"POST","/api/product-structure/check",{structure:n});if(i.status!==200||typeof i.body.valid!="boolean")throw C(i,"api_error","The product structure could not be checked. Try again.");let o=i.body;if(!o.valid||typeof o.check_token!="string")throw Vr(Array.isArray(o.problems)?o.problems:[]);return{...o,changes:Array.isArray(o.changes)?o.changes:[],warnings:Array.isArray(o.warnings)?o.warnings:[]}}var Eu={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 Iu(e,t,n,i){let o=await $(e,t,"POST","/api/product-structure/save",{structure:n,check_token:i});if(o.status===400&&Array.isArray(o.body.problems))throw Vr(o.body.problems);if(o.status!==200||typeof o.body.status!="string"){let s=C(o,"api_error","The product structure could not be saved. Try again."),a=Eu[s.code];throw a?new p(s.code,s.message,s.exitCode,a):s}let r=o.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 xu=e=>/^(y|yes)$/i.test(e.trim());async function Br(e,t,n,i){let{runtime:o,interactive:r,print:s}=e,a=await Ru(o,t,n),l=typeof a.estimated_credits=="number"?a.estimated_credits:null,u=(y,_)=>[...y.map(S=>` ${Au(S,i.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(`
|
|
42
|
+
`),m={...i.extra,changes:a.changes,warnings:a.warnings,estimated_credits:l};if(a.changes.length===0){s.result({...m,status:"unchanged",recalculation:null,instructions:null},()=>"Nothing to change: the product structure is already like this. Nothing was saved.");return}if(i.checkOnly){s.result({...m,status:"checked",recalculation:null,instructions:null},()=>`Checked, nothing saved. Saving it would:
|
|
43
|
+
${c}`);return}let d=i.yes,h=!1;if(!d&&r&&(s.note(`Saving it would:
|
|
44
|
+
${c}`),h=!0,d=xu(await o.prompt('Save these changes? Type "yes" to save: '))),!d){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: ${i.again} --yes`;s.result({...m,status:"not_saved",recalculation:null,instructions:y},_=>h?_.instructions:`Saving it would:
|
|
44
45
|
${c}
|
|
45
|
-
${_.instructions}`);return}let f=await
|
|
46
|
-
`)})}var
|
|
47
|
-
`)}async function
|
|
48
|
-
The research text appears here once it is done.`;if(typeof n.text!="string"||!n.text.trim())return`${
|
|
49
|
-
ClosedLoop AI did not send the research text. Try again later.`;let
|
|
50
|
-
|
|
51
|
-
${n.text.trim()}`}async function
|
|
52
|
-
`)})}var
|
|
53
|
-
`)}async function
|
|
46
|
+
${_.instructions}`);return}let f=await Iu(o,t,n,a.check_token);s.result({...m,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(`
|
|
47
|
+
`)})}var Pu=e=>/^[\w.@-]+$/.test(e)?e:`"${e.replace(/(["\\$`])/g,"\\$1")}"`;async function Nu(e,t,n){let i=t.map(c=>I(c,"product",Mr)),o=v(e.runtime),r=await ht(e.runtime,o),s=r.suggested_products;if(s.length===0){let c=r.structure.products.length===0?`Nothing is suggested yet. Suggestions appear once your company research is done; see: closedloop company. Or add your own: ${En}`:"Nothing is suggested right now. See your products with: closedloop products";e.print.result({status:"nothing_suggested",confirmed:[],confirmed_ids:[],changes:[],instructions:null},()=>c);return}let a=[];for(let c of i){let m=s.find(d=>d.id.toLowerCase()===c);if(!m)throw new p("unknown_product",`${c} is not a suggested product`,g.Usage,`See the suggested products and their ids with: ${Mr}`);a.includes(m)||a.push(m)}let l=i.length===0?s:a,u={...r.structure,products:[...r.structure.products,...l.map(({id:c,name:m,description:d})=>({id:c,name:m,description:d}))]};await Br(e,o,u,{yes:n,checkOnly:!1,again:i.length===0?Oe:`${Oe} ${l.map(c=>c.id).join(" ")}`,suggestedIds:new Set(l.map(c=>c.id)),extra:{confirmed:l.map(c=>c.name),confirmed_ids:l.map(c=>c.id)}})}async function Lu(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,An);let n=await Fe(e,t.file,"structure",An,Cu,"file"),i=w(n.structure)?n.structure:n,o=Array.isArray(n.suggested_products)?n.suggested_products.filter(w):[],r=v(e.runtime);await Br(e,r,i,{yes:t.yes===!0,checkOnly:t.check===!0,again:`closedloop products save --file ${Pu(t.file.trim())}`,suggestedIds:new Set(o.map(s=>String(s.id))),extra:{}})}var Yr=(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, each with its id; confirm the products your company research suggested, or save your own. See also: closedloop glossary").action(async()=>Ou(t));n.command("confirm").argument("[id...]","The ids of the suggested products to confirm, from closedloop products; 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(i,o)=>Nu(t,i,o.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: [...] }. Keep each id from that output: the id says which product, area or feature an entry is, and a new one has none. 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 i=>Lu(t,i))};var Z="closedloop company --website <url>",Gt="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.",Kr="It reads your website to learn your company and usually takes a few minutes.",Du="closedloop company --research";var Qr="closedloop company --no-domains --confirmed-by-user",Ui="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.",ju=3600,zr=30,Fu=new Set(["waiting","researching","retrying"]);function Wi(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 Ke(e,t,n=!1){let i=await $(e,t,"GET",n?"/api/cli/company?include=research":"/api/cli/company");if(i.status!==200)throw C(i,"api_error","The company profile could not be loaded. Try again.");return Wi(i.body)}async function ze(e,t){try{return await Ke(e,t)}catch(n){if(n instanceof p)return null;throw n}}function In(e){return!!(e?.email_domains&&!e.email_domains.confirmed)}function qt(e){return!e||!e.analysis_waiting?null:e.research.state==="no_website"?`Nothing is analysed until your company website is set: ${Z}`:e.research.state!=="ready"?"It is analysed once your company research is done; see: closedloop company":In(e)?"Nothing is analysed until your company's email domains are confirmed; see: closedloop company":"It is analysed soon; see: closedloop company"}var ft=e=>e.join(", ");function Jt(e){if(!In(e))return null;let t=e.email_domains.suggested,n=t.length>0?`Ask your user whether their colleagues' work email addresses end in ${ft(t.map(o=>`@${o}`))}, and whether there are others.`:"Ask your user which domains their colleagues' work email addresses end in (the part after the @).",i=t.length>0?t.join(","):"<domain,...>";return[`ClosedLoop AI needs your user to confirm their company's email domains before it analyses anything. ${Ui}`,n,`When they have answered, run: closedloop company --domains ${i} --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: ${Qr}`,"Do not run either command, and do not add --confirmed-by-user, without your user's answer."].join(" ")}function Hi(e){let t=e.email_domains;return t?t.confirmed?t.domains.length>0?`Email domains: ${ft(t.domains)}.`:"Email domains: none (confirmed); colleagues count as customers.":`Email domains: not confirmed yet, so nothing is analysed.${t.suggested.length>0?` Suggested: ${ft(t.suggested)}.`:""} Confirm with: closedloop company --domains ${t.suggested.length>0?t.suggested.join(","):"<domain,...>"}`:null}async function Fi(e,t,n){let o=await $(e,t,"PUT","/api/cli/company/domains",n===null?{no_domains:!0,confirmed_by_user:!0}:{domains:n,confirmed_by_user:!0});if(o.status!==200)throw C(o,"api_error","The email domains could not be saved. Try again.");return Wi(o.body)}function Zr(e){return e.split(/[\s,]+/).map(t=>t.trim().replace(/^@/,"").toLowerCase()).filter(Boolean)}var es=e=>/^(y|yes)$/i.test(e.trim());async function ts(e,t,n){let{runtime:i,print:o}=e,r=n.email_domains?.suggested??[];o.note(`Your colleagues' email domains tell ClosedLoop AI who is a colleague and who is a customer. ${Ui}`);let s=r.length>0?`Your company's email domains, separated by commas [${ft(r)}]: `:`Your company's email domains, separated by commas (e.g. acme.example), or "none" if it has none: `,a=(await i.prompt(s)).trim();if(/^none$/i.test(a)||!a&&r.length===0){if(!a)return null;let u=await i.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 es(u)?Fi(i,t,null):null}let l=a?Zr(a):r;return Fi(i,t,l)}async function Mi(e,t,n){let i=await $(e,t,"PUT","/api/cli/company/website",{website:n.trim()});if(i.status!==200)throw C(i,"api_error","The website could not be saved. Try again.");return Wi(i.body)}function yt(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: ${Z}`;case"waiting":return`Company research: waiting to start. ${Kr} 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`:""}. ${Kr} 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: ${Z}. 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 Uu(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: ${Du}`),n}function Wu(e){return e===null||e<=0?null:e===1?`Next: confirm the product it suggested: ${Oe}`:`Next: confirm the ${e} products it suggested: ${Oe}`}function Gi(e,t,n=null){let i=[];e.website&&i.push(`Website: ${e.website}`),i.push(yt(e,t));let o=Hi(e);o&&i.push(o),e.research.state==="ready"&&e.research.researched_url&&e.website&&e.research.researched_url!==e.website&&i.push(`The profile was researched from ${e.research.researched_url}.`),i.push(...Uu(e));let r=e.research.state==="ready"?Wu(n):null;return r&&i.push(r),i.join(`
|
|
48
|
+
`)}async function Hu(e,t){try{return(await ht(e,t)).suggested_products.length}catch(n){if(n instanceof p)return null;throw n}}function Mu(e,t){let n=e.research;if(n.state!=="ready")return`${yt(e,t)}
|
|
49
|
+
The research text appears here once it is done.`;if(typeof n.text!="string"||!n.text.trim())return`${yt(e,t)}
|
|
50
|
+
ClosedLoop AI did not send the research text. Try again later.`;let i=n.researched_url??e.website,o=n.researched_at?`, ${R(n.researched_at)}`:"";return`What the company research found${i?` (from ${i}${o})`:""}:
|
|
51
|
+
|
|
52
|
+
${n.text.trim()}`}async function Xr(e,t,n,i){let o=n,r=e.print.spinner("Checking every 30 seconds\u2026");try{for(let s=0;s<ju&&Fu.has(o.research.state);s+=zr)r.update(`${yt(o,Date.now())} Checking every 30 seconds\u2026`),await e.runtime.sleep(zr*1e3),o=await Ke(e.runtime,t,i)}finally{r.stop()}return o}async function Gu(e,t,n){let{runtime:i,interactive:o}=e,r=n.domains===!1?null:Zr(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&&o){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: ${ft(r)}? Type "yes" to save them: `;s=es(await i.prompt(a))}if(!s){let a=await Ke(i,t),l=r===null?"that the company has no email domain of its own":`that their colleagues' email domains are ${ft(r)}`,u=r===null?Qr:`closedloop company --domains ${r.join(",")} --confirmed-by-user`,c=o?"Nothing was saved.":`Nothing was saved: only the person who uses this workspace can confirm the email domains. Has your user confirmed ${l}? ${Ui} If they have not, ask them first. Once they say yes, run: ${u}`;return{profile:a,saved:!1,instructions:c}}return{profile:await Fi(i,t,r),saved:!0,instructions:null}}async function qu(e,t){let n=v(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 Ke(e.runtime,n,!0);t.watch&&(l=await Xr(e,n,l,!0)),e.print.result(l,u=>Mu(u,Date.now()));return}let i=t.website!==void 0?await Mi(e.runtime,n,t.website):await Ke(e.runtime,n),o=!1,r=null;if(t.domains!==void 0){let l=await Gu(e,n,t);({profile:i}=l),o=l.saved,r=l.instructions}t.watch&&(i=await Xr(e,n,i,!1)),!r&&!e.interactive&&(r=Jt(i));let s=i.research.state==="ready"?await Hu(e.runtime,n):null,a={...i,email_domains_saved:o,instructions:r,products_suggested:s};e.print.result(a,l=>{let u=[];return t.website!==void 0&&u.push(`Website saved. ${Gt}`),l.email_domains_saved?u.push("Email domains saved."):t.domains!==void 0&&l.instructions&&u.push(l.instructions),u.push(Gi(l,Date.now(),l.products_suggested)),u.join(`
|
|
53
|
+
`)})}var ns=(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=>qu(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.",is="closedloop competitors add <name> [--website <url>]",qi="closedloop competitors",Vu="Changing competitors needs the right to change the workspace's settings (an owner or admin).",Bu="It is then marked as not a competitor; the customer mentions behind it are kept, and adding it again restores it.",Yu={researched:"AI research",researched_and_mentioned:"AI research, mentioned by customers",mentioned:"mentioned by customers",edited:"manual"},os=e=>e.trim().toLowerCase(),Ku=e=>/^(y|yes)$/i.test(e.trim()),zu=e=>/^[\w.@-]+$/.test(e)?e:`"${e.replace(/(["\\$`])/g,"\\$1")}"`;function xn(e,t,n){let i=e.status===403,o=C(e,i?"access_denied":"api_error",t);return i&&n?new p(o.code,o.message,g.Failure,Vu):o}var Pn=(e,t)=>`/api/competitors/${encodeURIComponent(e)}${t?`/${encodeURIComponent(t)}`:""}`;function Ji(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 Vi(e,t,n){let i=await $(e,t,"GET",Pn(n));if(i.status!==200)throw xn(i,"The competitors could not be loaded. Try again.",!1);if(!Array.isArray(i.body.competitors))throw new p("api_error","ClosedLoop AI sent an unexpected reply",g.Failure);return i.body.competitors.map(Ji)}function rs(e,t){let n=e.find(i=>i.id.toLowerCase()===t.toLowerCase());if(n)return n;throw new p("unknown_competitor",`there is no competitor with the id ${t} on your list`,g.Usage,L(qi))}function ss(e){let t=[e.competitor_product&&os(e.competitor_product)!==os(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?Yu[e.provenance]:void 0;return n&&t.push(n),t.join(" \xB7 ")}function Xu(e,t){let n=[Ju,""];return e.competitors.length===0?n.push(`No competitors yet. Your company research adds the ones it finds; add your own with: ${is}`):(n.push(...e.competitors.flatMap(i=>[` ${ss(i)}`,E(t," ",i.id)]),""),n.push(`${k(e.competitors.length)} competitor${e.competitors.length===1?"":"s"}. Add one: ${is}. Change one by its id: closedloop competitors edit <id> or remove <id>.`)),e.can_change||n.push("Only someone who can change the workspace's settings (an owner or admin) can change this list."),n.join(`
|
|
54
|
+
`)}async function Qu(e){let t=v(e.runtime),n=await M(e.runtime,t),i=await Vi(e.runtime,t,n.id);e.print.result({competitors:i,can_change:n.permissions.can_manage_team_settings},o=>Xu(o,e.print.style))}async function Zu(e,t,n){let i=t.trim();if(!i)throw new p("missing_name",`give the competitor's name, e.g. closedloop competitors add "Acme Rival"`,g.Usage);let o=v(e.runtime),{id:r}=await M(e.runtime,o),s={competitor_company:i,is_direct_competitor:!0};n.website?.trim()&&(s.product_website=n.website.trim()),n.description?.trim()&&(s.description=n.description.trim());let a=await $(e.runtime,o,"POST",Pn(r),s);if(a.status!==200)throw xn(a,"The competitor could not be added. Try again.",!0);let l=Ji(a.body.competitor);e.print.result({competitor:l},u=>`Added ${u.competitor.competitor_company} to your competitors.`)}async function ec(e,t,n){let i=I(t,"competitor",qi);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=v(e.runtime),{id:r}=await M(e.runtime,o),s=rs(await Vi(e.runtime,o,r),i),a={};n.name!==void 0&&(a.competitor_company=n.name.trim()),n.website!==void 0&&(a.product_website=n.website.trim()||null),n.description!==void 0&&(a.description=n.description.trim()||null);let l=await $(e.runtime,o,"PUT",Pn(r,s.id),a);if(l.status!==200)throw xn(l,"The competitor could not be saved. Try again.",!0);let u=Ji(l.body.competitor);e.print.result({competitor:u},c=>`Saved: ${ss(c.competitor)}`)}async function tc(e,t,n){let{runtime:i,interactive:o,print:r}=e,s=I(t,"competitor",qi),a=v(i),{id:l}=await M(i,a),u=rs(await Vi(i,a,l),s),c=u.competitor_company,m=n;if(!m&&o&&(r.note(`${c} 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.`),m=Ku(await i.prompt(`Type "yes" to remove ${c}: `))),!m){let h=o?"Nothing was removed.":`Nothing was removed: only the person who uses this workspace can remove a competitor. Has your user agreed to remove ${c} from the competitor list? ${Bu} If they have not, ask them first. Once they say yes, run: closedloop competitors remove ${u.id} --yes`;r.result({status:"not_removed",competitor:u,instructions:h},f=>f.instructions);return}let d=await $(i,a,"DELETE",Pn(l,u.id));if(d.status!==200)throw xn(d,"The competitor could not be removed. Try again.",!0);r.result({status:"removed",competitor:u,instructions:null},h=>`Removed ${h.competitor.competitor_company} from your competitors. The customer mentions behind it are kept; to restore it, run: closedloop competitors add ${zu(h.competitor.competitor_company)}`)}var as=(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, each with its id; add one, or edit or remove one by its id (someone who can change the workspace's settings).").action(async()=>Qu(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(i,o)=>Zu(t,i,o)),n.command("edit").argument("<id>","The competitor's id, from closedloop competitors. A name is not used: two competitors can share one.").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(i,o)=>ec(t,i,o)),n.command("remove").argument("<id>","The competitor's id, from closedloop competitors.").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(i,o)=>tc(t,i,o.yes===!0))};var ls={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"},nc=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),ee=e=>typeof e=="string"?e:"",Bi=e=>typeof e!="string"?String(e):/^\d{4}-\d{2}-\d{2}$/.test(e)?V(`${e}T00:00:00Z`):N(e);function ic(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 oc(e,t){return typeof e.distinct_count=="number"?Math.max(0,e.distinct_count-t):0}var le=e=>e!=null,rc=e=>e.hidden!==!0&&(e.kind==="number"&&![e.min,e.max,e.p10,e.p90].some(le)||e.kind==="date"&&![e.first,e.last,e.p10,e.p90].some(le)),us=e=>String(e).replace(/\s+/g," ").trim(),Yi=["customer","deal","person","insight","context","conversation","usage"],Ki="When two fields fit a request, ask the user which one they mean before writing the rule; never pick one silently.";function sc(e){let t=i=>typeof i=="number"?T(i):Bi(i),n=typeof e.zero_share=="number"&&e.zero_share>0?[`${Math.round(e.zero_share*100)}% are 0`]:[];return le(e.median)?[...n,`${n.length?"other values: ":""}10th percentile ${t(e.p10)}`,`median ${t(e.median)}`,`90th percentile ${t(e.p90)}`]:n}function ds(e,t){let n=ee(t.label)&&t.label!==t.key?` \xB7 ${t.label}`:"";return`${e.bold(ee(t.key))} ${e.dim(`${ee(t.type)}${n}`)}`}function ac(e){return w(e.guide)&&w(e.guide.preview)&&e.guide.preview.kind==="empty"}function lc(e,t,n,i){if(!t.length)return n?`No fields match "${n}".`:i?"No fields hold values yet.":"No fields yet.";let o=new Map;for(let a of t){let l=ee(a.entity)||"other";o.set(l,[...o.get(l)??[],a])}let r=a=>[` ${ds(e,a)}`,...ee(a.id)&&a.id!==a.key?[E(e," ",a.id)]:[]];return`${[...o.entries()].map(([a,l])=>[e.bold(a),...l.flatMap(r)].join(`
|
|
54
55
|
`)).join(`
|
|
55
56
|
|
|
56
57
|
`)}
|
|
57
58
|
|
|
58
|
-
${e.dim(
|
|
59
|
-
${e.dim("Read one in full: closedloop fields show <key>")}`}function
|
|
60
|
-
`)}async function
|
|
61
|
-
`)))};import{existsSync as
|
|
62
|
-
`)
|
|
63
|
-
`)}
|
|
64
|
-
|
|
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))??""))?`
|
|
67
|
-
${i.dim("A segment keeps the version it included; save its rule again to use the newer one.")}`:"";return`${l.join(`
|
|
68
|
-
|
|
69
|
-
`)}${c}
|
|
70
|
-
|
|
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(`
|
|
75
|
-
`)}).join(`
|
|
76
|
-
|
|
77
|
-
`)}
|
|
78
|
-
|
|
79
|
-
${i.dim("Measure a goal on one: closedloop goals metrics add <goal> --metric <number> --period month")}
|
|
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(`
|
|
59
|
+
${e.dim(Ki)}
|
|
60
|
+
${e.dim("Read one in full: closedloop fields show <key>")}`}function uc(e,t,n){let i=[`${ds(e,t)}${e.dim(` \xB7 ${ee(t.entity)}`)}`];ee(t.id)&&t.id!==t.key&&i.push(E(e,"",t.id)),ee(t.description)&&i.push(ee(t.description)),t.example!==void 0&&t.example!==null&&i.push(e.dim(`e.g. ${JSON.stringify(t.example)}`)),typeof t.history=="string"&&i.push(e.dim(`history: ${nc(ls,t.history)?ls[t.history]:t.history}`));let o=Array.isArray(t.confused_with)?t.confused_with.filter(l=>typeof l=="string"):[];o.length&&i.push(`Often confused with ${o.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)&&i.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")i.push("",`Values look like ${r.pattern}.`);else if(r.kind==="text")i.push("",`Free text${typeof r.distinct_count=="number"?` with ${T(r.distinct_count)} different values`:""}; no values are kept.`);else if(l.length){let c=Math.max(...l.map(d=>us(d.value).length)),m=Math.max(...l.map(d=>T(d.count).length));if(i.push("","Values:",...l.map(d=>` ${us(d.value).padEnd(c)} ${T(d.count).padStart(m)}`)),typeof r.hidden_values!="number"){let d=oc(r,l.length);d>0&&i.push(e.dim(` and ${T(d)} more`))}}else if(r.hidden===!0)i.push("",`Not shown: fewer than ${T(r.hidden_below)} customers hold the values at one end of the range.`);else if(le(r.p10)||le(r.p90)||typeof r.zero_share=="number"&&r.zero_share>0)i.push("",sc(r).join(" \xB7 "));else if(rc(r))i.push("","no values yet: no record has one.");else if(le(r.min)||le(r.max)){let c=[`smallest ${T(r.min)}`,...le(r.median)?[`median ${T(r.median)}`]:[],...le(r.p90)?[`90th percentile ${T(r.p90)}`]:[],`largest ${T(r.max)}`];i.push("",c.join(" \xB7 "))}else(le(r.first)||le(r.last))&&i.push("",`first ${Bi(r.first)} \xB7 last ${Bi(r.last)}`);typeof r.hidden_values=="number"&&r.hidden_values>0&&i.push(e.dim(`${T(r.hidden_values)} values held by fewer than ${T(r.hidden_below)} customers are not shown.`));let u=ic(r);u!==null&&i.push(`${k(r.empty_count)} of ${k(r.record_count)} records have no value (${u}).`)}let a={of:!ee(t.entity)||t.entity==="usage"?"customer":ee(t.entity),where:{field:ee(t.key),op:"exists"}};return i.push("",e.dim(`Use it in a rule: ${hn(a,l=>`closedloop segments preview --rule ${l}`,n)}`)),i.join(`
|
|
61
|
+
`)}async function cs(e,t,n){if(t!==void 0&&!Yi.includes(t))throw new p("invalid_entity",`--entity must be one of ${Yi.join(", ")}`,g.Usage);let i=await _r(e,{entity:t,q:n}),o=n===void 0?i.filter(s=>!ac(s)):i,r={filter:{entity:t??null,q:n??null},fields:o,ask:Ki,next:"closedloop fields show <key>"};e.print.result(r,s=>lc(e.print.style,s.fields,n,i.length-o.length))}async function cc(e,t){let i={field:await br(e,t),next:"closedloop segments preview --rule <file|json|->"};e.print.result(i,o=>uc(e.print.style,o.field,e.runtime.platform))}var ms=(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. ${Ki}`),i=`Only one kind of record: ${Yi.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>",i).action(async o=>cs(t,o.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>",i).action(async(o,r)=>cs(t,r.entity,o.join(" ").trim())),n.command("show").argument("<key>","A field key, e.g. customer.country or customer.crm.Property_Type__c, or the field's id when the list shows one.").description("One field in full: what it means, every value with how many records have it, and how many have none.").action(async o=>cc(t,o))};var dc=[{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."}],ps=(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:dc},n=>n.terms.map(i=>`${t.print.style.bold(i.term)}: ${i.definition}`).join(`
|
|
62
|
+
`)))};import{existsSync as Hc,readFileSync as Mc,statSync as Gc}from"node:fs";var fs="closedloop metrics list",Vt='A formula combines saved metrics by their ids, e.g. "<id> / <id>", "<id> * 12" or "(<id> - <id>@90d) / <id>@90d" (@90d: its value 90 days earlier); write each id alone or in brackets, [<id>], as closedloop metrics list shows it.',Te=e=>new p("invalid_formula",e,g.Usage,Vt),gs=e=>new p("invalid_formula",`${e} is not a metric id: a formula names each metric by its id, alone or in brackets`,g.Usage,L(fs)),ys={"+":"add","-":"subtract","*":"multiply","/":"divide"},hs={add:"+",subtract:"-",multiply:"*",divide:"/"};function mc(e){let t=[],n=0,i=()=>!t.length||t[t.length-1].kind==="op"||t[t.length-1].kind==="open";for(;n<e.length;){let o=e[n];if(/\s/.test(o)){n+=1;continue}let r=null;if(o==="["){let a=e.indexOf("]",n+1);if(a<0)throw Te(`a metric opened with [ at ${n+1} is not closed with ]`);let l=e.slice(n+1,a).trim();if(!l)throw new p("invalid_formula",`an empty [] at ${n+1}: put a metric's id in it`,g.Usage,L(fs));if(!jt.test(l))throw gs(`[${l}]`);r={kind:"id",text:l.toLowerCase()},n=a+1}else{if(o==="#")throw gs(/^#\d*/.exec(e.slice(n))?.[0]||"#");jt.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 Te(`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 Te("@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))||o==="-"&&i()&&/^-\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(o in ys){t.push({kind:"op",value:o}),n+=1;continue}if(o==="("){t.push({kind:"open"}),n+=1;continue}if(o===")"){t.push({kind:"close"}),n+=1;continue}throw Te(`'${o}' at ${n+1} is not part of a formula`)}return t}function pc(e){let t=0,n=()=>{let a=e[t];if(!a)throw Te("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 Te("a ( is not closed with )");return t+=1,l}throw Te(`a metric or a number is expected where ${a.kind==="op"?a.value:")"} is`)},i=(a,l)=>{let u=a();for(;e[t]?.kind==="op"&&l.includes(e[t].value);){let c=ys[e[t].value];t+=1,u={fn:"formula",op:c,args:[u,a()]}}return u},o=()=>i(n,"*/"),r=()=>i(o,"+-"),s=r();if(t<e.length)throw Te("the formula goes on after it is complete; join the parts with + - * or /");return s}function ws(e){let t=pc(mc(e));if(typeof t=="number"||!("fn"in t))throw Te("a formula combines at least two things with + - * or /");return t}function zi(e,t){let n=i=>typeof i=="number"?i:"metric"in i?{metric:t(i.metric),...i.days_ago!==void 0&&{days_ago:i.days_ago}}:zi(i,t);return{fn:"formula",op:e.op,args:[n(e.args[0]),n(e.args[1])]}}var we=e=>w(e)&&e.fn==="formula";function Nn(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,s=typeof e.days_ago=="number"?`@${e.days_ago}d`:"";return`[${r}]${s}`}let n=Array.isArray(e.args)?e.args:[],i=r=>we(r)?`(${Nn(r,t)})`:Nn(r,t),o=typeof e.op=="string"&&e.op in hs?hs[e.op]:String(e.op);return`${i(n[0])} ${o} ${i(n[1])}`}function Ae(e,t,n,i){if(typeof t!="number"||!Number.isFinite(t))return null;let o=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 i=="number"?` (${r(n)} / ${r(i)})`:"";return`${o}${s}`}var Xi="closedloop metrics list",_s=(e,t,n)=>new p(e,t,g.Usage,n),gc=/^\d{4}-\d{2}-\d{2}$/,ue=e=>typeof e=="string"&&gc.test(e)?V(`${e}T00:00:00Z`):String(e),P=e=>typeof e=="number"&&Number.isFinite(e)?e:null;function vs(e){return e===7?"week":e===1?"day":`${e} days`}function wt(e){return e===7?"weekly":e===1?"daily":`${e}-day`}function hc(e,t){if(t===7)return`about ${e} week${e===1?"":"s"}`;let n=e*t;return`about ${n} day${n===1?"":"s"}`}var bs=e=>`${e>0?"+":""}${T(e)}`;function _t(e,t){let n=P(e.every_days)??7,i=vs(n),o=P(e.lag_points),r=P(e.correlation),s=P(e.moves)??0,a=P(e.followed)??0,l=o!==null?hc(o,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 ${wt(n)} changes ${r===null?"?":r.toFixed(2)}).`;if(e.verdict==="not_enough_history"){let m=P(e.points)??0,d=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 ${i} 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: ${m} ${wt(n)} point${m===1?"":"s"}${d}; at least ${String(e.needed_points)} are needed.${y}`}let c=P(t.min_correlation);switch(e.reason){case"moves_together":return`No lead: it changes in the same ${i} 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 fc(e){let t=Array.isArray(e.learnings)?e.learnings.filter(w):[];if(!t.length)return[];let n=vs(P(e.every_days)??7);return["When the key result did not follow:",...t.map(i=>{let o=P(i.indicator_from)??0,r=P(i.indicator_to)??0,s=(P(i.key_result_to)??0)-(P(i.key_result_from)??0),a=P(i.key_result_usual_change)??0;return` ${ue(i.from_day)} to ${ue(i.day)}: the indicator ${r>=o?"rose":"fell"} from ${T(o)} to ${T(r)}; from ${ue(i.key_result_from_day)} to ${ue(i.key_result_day)} the key result changed by ${bs(s)} (usually ${bs(a)} a ${n}).`})]}function yc(e){let t=Array.isArray(e)?e.filter(w):[],n=t.filter(o=>o.basis==="measured"&&P(o.value)!==null).length,i=t.filter(o=>o.basis==="rebuilt"&&P(o.value)!==null).length;return[n?`${n} measured`:"",i?`${i} rebuilt from the history`:""].filter(Boolean).join(", ")}function wc(e){let t=Array.isArray(e)?e.filter(w):[];if(!t.length||!t.every(i=>i.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 jn(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 _c(e){return e.status==="failed"?`Could not be finished: ${jn(e.error_code)}. Ask again tomorrow.`:e.status==="running"?"Being computed now.":"Queued: ClosedLoop AI computes it in the background, usually within minutes."}var Qi=e=>Array.isArray(e.checks)?e.checks.filter(w):[],Ln=e=>w(e.counts)?(P(e.counts.queued)??0)+(P(e.counts.running)??0):0,Dn=e=>w(e.indicator)&&typeof e.indicator.name=="string"?e.indicator.name:"the indicator";function bc(e,t,n){let i=e.print.style,o=w(t.rules)?t.rules:{},r=w(t.key_result)&&typeof t.key_result.name=="string"?t.key_result.name:"the metric",s=Qi(t).find(y=>w(y.indicator)&&y.indicator.id===n)??Qi(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=[i.bold(`Does "${Dn(s)}" lead "${r}"?`)],l=w(s.check)?s.check:null;if(s.status!=="done"||!l)return[...a,_c(s)].join(`
|
|
63
|
+
`);a.push(_t(l,o));let u=w(s.series)?s.series:{},c=wc(u.indicator);c&&a.push(c);let m=y=>Array.isArray(y)?y.filter(w).map(_=>P(_.value)).filter(_=>_!==null):[],d=P(l.points)??0;if(d&&l.history_from){let y=yc(u.key_result);a.push(i.dim(`${d} ${wt(P(l.every_days)??7)} points from ${ue(l.history_from)}${y?` (key result: ${y})`:""}; checked for ${ue(s.as_of)}.`))}let h=m(u.indicator),f=m(u.key_result);return h.length>1&&f.length>1&&a.push(i.dim(`indicator ${i.sparkline(h)}`),i.dim(`key result ${i.sparkline(f)}`)),a.push(...fc(l)),a.join(`
|
|
64
|
+
`)}function $c(e,t){let n=t.map(o=>w(o.check)?o.check:{});if(!n.length)return null;if(n.every(o=>o.verdict==="not_enough_history"&&w(o.history_ends)&&o.history_ends.series==="key_result")){let o=n[0],r=w(o.history_ends)?o.history_ends:{},s=typeof r.reason=="string"?` (${r.reason})`:"";return`Not enough history of "${e}" to find what leads it: ${String(o.points)} ${wt(P(o.every_days)??7)} points${o.history_from?` from ${ue(o.history_from)}`:""}${s}; at least ${String(o.needed_points)} are needed.`}return n.every(o=>o.reason==="key_result_flat")?`"${e}" did not change over the last ${String(n[0].points)} ${wt(P(n[0].every_days)??7)} points, so nothing can be shown to lead it.`:null}function vc(e,t,n){let i=e.print.style;if(typeof t.not_supported=="string")return t.not_supported;let o=w(t.rules)?t.rules:{},r=w(t.key_result)&&typeof t.key_result.name=="string"?t.key_result.name:"this metric",s=Qi(t).filter(f=>w(f.indicator)&&f.indicator.kind==="candidate"),a=s.filter(f=>f.status==="done"&&w(f.check)),l=$c(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)?i.dim(` (${String(y.check.points)} ${wt(P(y.check.every_days)??7)} points from ${ue(y.check.history_from)})`):"";u.push(i.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,W)=>{let ie=w(b.check)?b.check:{},J=w(b.indicator)?b.indicator:{};u.push(`${W+1} ${i.bold(String(J.name))}`,` ${_t(ie,o)}`,i.dim(` Save it: ${hn(J.rule,Rt=>`closedloop metrics define ${he(`${r}: ${String(J.name)}`,e.runtime.platform)} --segment ${Rt} --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(` ${Dn(b)}: ${_t(b.check,o)}`)}}let c=s.filter(f=>f.status==="failed");if(c.length){u.push("",i.orange("Could not be finished:"));for(let f of c)u.push(i.orange(` ${Dn(f)}: ${jn(f.error_code)}`))}let m=s.filter(f=>f.status==="queued"||f.status==="running");m.length&&u.push("",`Still being checked (${m.length}): ${m.map(Dn).join("; ")}.`,i.dim(`ClosedLoop AI checks them in the background. See them: closedloop metrics leads ${n} --wait`));let d=Array.isArray(t.skipped)?t.skipped.filter(w):[];if(d.length){u.push("",i.dim("Not checked, no data for them:"));for(let f of d)u.push(i.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("",i.orange(`Unknown candidates: ${h.join(", ")}.`)),u.join(`
|
|
65
|
+
`)}var $s=15e3,Zi=30*6e4;async function Cc(e,t,n,i){let o=i,r=e.print.spinner(`Waiting for ClosedLoop AI to finish ${Ln(o)} lead check${Ln(o)===1?"":"s"} (up to ${Zi/6e4} minutes)`);try{for(let s=0;s<Zi/$s&&Ln(o)>0;s+=1){await e.runtime.sleep($s);let a=await $(e.runtime,t,"GET",n);if(a.status!==200)throw C(a,"api_error","The lead checks could not be read. Try again.");o={...De(a.body),...kc(i,["queued","skipped","unknown","not_supported"])}}}finally{r.stop()}return o}var kc=(e,t)=>Object.fromEntries(t.filter(n=>e[n]!==void 0).map(n=>[n,e[n]]));async function Sc(e,t,n){let i=I(t,"metric",Xi);if(n.indicator!==void 0&&n.candidates!==void 0)throw _s("indicator_and_candidates","give --indicator or --candidates, not both");let o=n.indicator===void 0?void 0:I(n.indicator,"metric",Xi),r=n.metricVersion;if(r!==void 0&&!/^[1-9][0-9]*$/.test(r))throw _s("invalid_version","--metric-version must be a version number");let s={...o&&{indicator:o},...n.candidates&&{candidates:n.candidates},...r&&{version:Number(r)}},a=v(e.runtime),l=await $(e.runtime,a,"POST",`/api/metrics/${i}/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=De(l.body),c=new URLSearchParams({...o&&{indicator:o},...r&&{version:r}}).toString();n.wait&&(u=await Cc(e,a,`/api/metrics/${i}/leads${c?`?${c}`:""}`,u));let m=`${o?` --indicator ${o}`:""}${r?` --metric-version ${r}`:""}`,d=Ln(u)>0?`closedloop metrics leads ${i}${m} --wait`:o?Xi:`closedloop metrics leads ${i} --indicator <id>`;e.print.result({...u,next:d},h=>o?bc(e,h,o):vc(e,h,i))}function Cs(e,t){e.command("leads").argument("<metric>","The key result: its metric id, from closedloop metrics list.").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 <id>","The indicator to check: its metric id, from closedloop metrics list.").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 ${Zi/6e4} minutes).`).action(async(n,i)=>Sc(t,n,i))}var Oc=/^\d{4}-\d{2}-\d{2}$/,Fn="A target is one line: >=1400, <=0.3, between 40 and 60, +500, +10% or, for a share, +5pts.",to=(e,t,n)=>new p(e,t,g.Usage,n),Tc=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),Qe=(e,t,n=t)=>Tc(e,t)?e[t]:n,Ac={week:"weekly",month:"monthly",quarter:"quarterly"},Rc={cli:"through the CLI",app:"in the app",mcp:"through an AI assistant",api:"through the API",job:"automatically"},Ss={key_result:"key result",indicator:"indicator"},no={key_result:"as a key result",indicator:"as an indicator"},ce=e=>typeof e=="string"&&Oc.test(e)?V(`${e}T00:00:00Z`):String(e??"an unknown day"),Os=e=>new Date(Date.parse(`${e}T00:00:00Z`)-864e5).toISOString().slice(0,10),Bt=e=>e.period==="days"?`every ${e.period_days} days`:Qe(Ac,e.period),Xe=(e,t)=>t===null?"no value":Ht(e)?`${(t*100).toFixed(1)}%`:we(e)?Ae({},t,null,null):T(t);function io(e){let t=e.written,n=t.value<0?"-":"+",i=Math.abs(t.value)>0&&Math.abs(t.value)<1?String(Number(Math.abs(t.value).toPrecision(3))):T(Math.abs(t.value));return t.as==="percent"?`${n}${i}%`:t.as==="points"?`${n}${i} points`:`${n}${i}`}function Ts(e,t){return t.condition==="between"?t.low===null||t.high===null?null:`between ${Xe(e,t.low)} and ${Xe(e,t.high)}`:t.condition==="at_least"?t.low===null?null:`at least ${Xe(e,t.low)}`:t.high===null?null:`at most ${Xe(e,t.high)}`}function oo(e){let t=e.target;if(!t)return"no target yet";let n=e.data_series.calc,i=Ts(n,t);if(t.written.as==="number")return i??"a target without a bound";let o=e.baseline&&e.baseline.value!==null?`from ${Xe(n,e.baseline.value)} on ${ce(e.baseline.as_of)}`:"from the baseline";return i?`${i} (${io(t)} ${o})`:`${io(t)} from the baseline, once it is measured`}function As(e,t){let n=t.target,i=n?Ts(t.calc,n)??`${io(n)} from the baseline, once it is measured`:"no target yet",o=t.current,r=o?(we(t.calc)?Ae(t.calc,o.value,o.numerator,o.denominator):Ye(t.calc,o.value,o.numerator,o.denominator))??"no value":null,s=o?`now ${r} on ${ce(o.as_of)}${o.status==="stale"?` ${e.orange("(stale)")}`:""}`:"not measured yet";return`${t.name}: ${i}, ${Bt(t)} \xB7 ${s}`}function ro(e,t=!1){return!e.baseline&&t?"baseline measured when the goal is activated":e.baseline?`baseline ${Xe(e.data_series.calc,e.baseline.value)} on ${ce(e.baseline.as_of)}`:"baseline not measured yet (it is measured tonight)"}function Rs(e,t,n=!1){if(!t.current)return"not measured yet";let i=t.current;return`now ${(we(t.data_series.calc)?Ae(t.data_series.calc,i.value,i.numerator,i.denominator):Ye(t.data_series.calc,i.value,i.numerator,i.denominator))??"no value"} on ${ce(i.as_of)}${i.status==="stale"?` ${e.orange("(stale: a source it reads is late)")}`:""}`+(n?e.dim(" (a preview: periods start when the goal is activated)"):"")}function Ec(e,t,n){let i=`${ce(n.start)} to ${ce(Os(n.end))}`;if(n.value===null||n.value===void 0)return`${i}: ${n.status==="failed"||n.status==="not_measured"?"no value":`not measured yet (the run of ${ce(n.end)} settles it)`}`;let o=n.result==="met"?e.green("met"):n.result==="missed"?e.red("missed"):"not judged yet";return`${i}: ${Xe(t.data_series.calc,n.value)}, ${o}${n.status==="stale"?e.dim(" (stale)"):""}`}function Ic(e,t,n){return t?t.status==="done"&&t.check?`lead check of ${ce(t.as_of)}: ${_t(t.check,n)}`:t.status==="failed"?e.orange(`lead check of ${ce(t.as_of)} could not be finished: ${jn(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 xc(e,t,n={},i=!1){let r=t.role==="indicator"?`indicator of ${t.key_result_id?`key result ${t.key_result_id}`:"a key result"}`:"key result",s=t.data_series,a=`version ${s.version}${s.current_version!==null&&s.current_version!==s.version?` (the metric is at version ${s.current_version})`:""}`,u=[`${e.bold(s.name)}${e.dim(` \xB7 ${r} \xB7 ${Bt(t)} \xB7 ${a}`)}`+(t.archived_at?e.dim(` \xB7 archived ${V(t.archived_at)}`):""),` target ${oo(t)} \xB7 ${ro(t,i)}`,` ${Rs(e,t,i)}`];for(let c of t.periods)u.push(` ${Ec(e,t,c)}`);if(t.current_period&&u.push(` ${e.dim(`this period: ${ce(t.current_period.start)} to ${ce(Os(t.current_period.end))}`)}`),t.role==="indicator"&&t.lead_check!==void 0&&u.push(` ${Ic(e,t.lead_check,n)}`),t.role==="key_result"&&t.suggested_indicators?.length){u.push(" suggested indicators (they led it on this workspace's history):");for(let c of t.suggested_indicators)u.push(` ${e.bold(typeof c.indicator.name=="string"?c.indicator.name:"an indicator")}: ${c.check?_t(c.check,n):""}`);u.push(` ${e.dim(`save one as a metric: closedloop metrics leads ${s.id}${s.current_version!==null&&s.current_version!==s.version?` --metric-version ${s.version}`:""}`)}`)}return u.push(E(e," ",t.id)),u}function so(e,t,n={},i=!1){return t.map(o=>xc(e,o,n,i).join(`
|
|
86
66
|
`)).join(`
|
|
87
67
|
|
|
88
|
-
`)}
|
|
89
|
-
${
|
|
90
|
-
`)}`,g.Failure,`Your saved metrics: closedloop metrics list. ${
|
|
68
|
+
`)}function ao(e,t){return I(t,"goal metric",`closedloop goals metrics list ${e}`)}function bt(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,L("closedloop goals list")):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 Es(e,t){if(e.status!==400||!Array.isArray(e.body.problems))return null;let n=e.body.problems,i=n.map(o=>`${o.field}: ${o.message}${o.allowed&&o.allowed.length?` (allowed: ${o.allowed.join(", ")})`:""}`);return new p("invalid_kpi",`${t}; fix ${n.length===1?"this":`these ${n.length}`}:
|
|
69
|
+
${i.join(`
|
|
70
|
+
`)}`,g.Failure,`Your saved metrics: closedloop metrics list. ${Fn}`)}async function lo(e,t,n){let i=v(e.runtime),o=await $(e.runtime,i,"GET",`/api/user-goals/${t}/kpis${n?"?include=ended":""}`);if(o.status!==200||!Array.isArray(o.body.kpis))throw bt(o,"The goal's metrics could not be loaded. Try again.");let r=o.body.kpis,s=o.body.goal;return{kpis:r,time_zone:typeof o.body.time_zone=="string"?o.body.time_zone:null,today:typeof o.body.today=="string"?o.body.today:null,lead_rules:o.body.lead_rules&&typeof o.body.lead_rules=="object"?o.body.lead_rules:{},state:typeof s?.state=="string"?s.state:null}}async function Pc(e,t,n,i){let o=await t(e,n),r={goal_id:o,...await lo(e,o,i)};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
71
|
|
|
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`${
|
|
93
|
-
`));return}let
|
|
94
|
-
`)})}async function
|
|
95
|
-
`))}async function
|
|
96
|
-
`)})}var
|
|
72
|
+
${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`${so(a,s.kpis,s.lead_rules,s.state==="draft")}${l}`})}var Is=e=>/^\d{1,10}$/.test(e)?Number(e):e,Nc=e=>e.trim().replace(/-/g,"_");async function Lc(e,t,n,i){let o=await t(e,n);if(i.metric===void 0)throw to("missing_metric","give the saved metric with --metric <id>",L("closedloop metrics list"));let r={metric:I(i.metric,"metric","closedloop metrics list")};i.role!==void 0&&(r.role=Nc(i.role)),i.keyResult!==void 0&&(r.key_result=ao(o,i.keyResult)),i.period!==void 0&&(r.period=i.period),i.days!==void 0&&(r.days=Is(i.days)),i.target!==void 0&&(r.target=i.target),i.reason!==void 0&&(r.reason=i.reason),i.dryRun&&(r.dry_run=!0);let s=v(e.runtime),a=await $(e.runtime,s,"POST",`/api/user-goals/${o}/kpis`,r),l=Es(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 ${o}`);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(i.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}) ${Qe(no,y.kpi.role)}, ${Bt(y.kpi)}.`,...c?[`Saving it would make the goal about insights bearing on ${u.map(_=>_.replace(/_/g," ")).join(" or ")}.`]:[]].join(`
|
|
73
|
+
`));return}let m=a.body.kpi;if(a.status!==201||!m||typeof m.id!="string")throw bt(a,"The metric could not be added. Try again.");let d=a.body.measured===!0,h=a.body.goal_state==="draft";e.print.result({measured:d,kpi:m,goal_relevance_added:u},f=>{let y=[`Added ${f.kpi.data_series.name} (version ${f.kpi.data_series.version}) ${Qe(no,f.kpi.role)}, ${Bt(f.kpi)}: target ${oo(f.kpi)}.`,f.measured?`Its ${ro(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(`
|
|
74
|
+
`)})}async function Dc(e,t,n,i,o){let r=await t(e,n),s=ao(r,i);if(o.target===void 0)throw to("missing_target","give the new target with --target",Fn);if(o.reason===void 0)throw to("missing_reason","say why the target changes with --reason; it is kept in the goal's history");let a={target:o.target,reason:o.reason};o.period!==void 0&&(a.period=o.period),o.days!==void 0&&(a.days=Is(o.days));let l=v(e.runtime),u=await $(e.runtime,l,"POST",`/api/user-goals/${r}/kpis/${s}/target`,a),c=Es(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 m=u.body.kpi;if(u.status!==200||!m||typeof m.id!="string")throw bt(u,"The target could not be set. Try again.");let d=u.body.measured===!0,h=u.body.goal_state==="draft";e.print.result({measured:d,kpi:m},f=>[`New target for ${f.kpi.data_series.name}, ${Bt(f.kpi)}: ${oo(f.kpi)}.`,f.measured?`Its new ${ro(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(`
|
|
75
|
+
`))}async function jc(e,t,n,i,o){let r=await t(e,n),s=ao(r,i),a=v(e.runtime),l=await $(e.runtime,a,"POST",`/api/user-goals/${r}/kpis/${s}/archive`,o===void 0?{}:{reason:o});if(l.status===404)throw new p("kpi_not_found","this goal has no metric with that id you can see",g.Failure,L(`closedloop goals metrics list ${r}`));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 bt(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},m=>`Archived the metric${m.also_archived.length?` and its ${m.also_archived.length} indicator${m.also_archived.length===1?"":"s"}`:""}. Its history stays; see it with closedloop goals metrics list ${r} --ended.`)}async function Fc(e,t,n){let i=await t(e,n),o=v(e.runtime),r=await $(e.runtime,o,"POST",`/api/user-goals/${i}/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 bt(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=Rs(l,u);return`${l.bold(u.data_series.name)}${l.dim(` \xB7 ${Qe(Ss,u.role)}`)}: ${c}`}).join(`
|
|
76
|
+
`)})}var ks=e=>typeof e=="number"?e:typeof e=="string"&&e.trim()!==""&&Number.isFinite(Number(e))?Number(e):null;function eo(e){let t=ks(e.target_low),n=ks(e.target_high);return e.target_condition==="between"?`between ${t===null?"?":T(t)} and ${n===null?"?":T(n)}`:e.target_condition==="at_least"?t===null?"at least a change from the baseline":`at least ${T(t)}`:e.target_condition==="at_most"?n===null?"at most a change from the baseline":`at most ${T(n)}`:"no target"}function Uc(e,t){let n=e.before??{},i=e.after;if(e.entity==="value"){let r=typeof i.measured_at=="string"?t?_n(i.measured_at,t):V(i.measured_at):"an unknown day";return e.action==="create"?`set the number to ${i.value} for ${r}`:`replaced ${n.value} with ${i.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 i.role=="string"?Qe(Ss,i.role):"metric";return e.action==="create"?`added ${r} ${Qe(no,String(i.role),"as a metric")}${i.target_condition?`, target ${eo(i)}`:""}`:e.action==="update"?`set a new target for ${r}: ${eo(i)} (was ${eo(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!==i.state&&i.state==="active")return"activated the goal";if(n.state!==i.state&&i.state==="closed")return"closed the goal";let o=Object.keys(i).filter(r=>r!=="updated_at"&&JSON.stringify(i[r])!==JSON.stringify(n[r])).sort();return`changed the goal${o.length?` (${o.join(", ")})`:""}`}async function Wc(e,t,n,i){let o=await t(e,n),r=[i.limit!==void 0?`limit=${encodeURIComponent(i.limit)}`:"",i.page!==void 0?`page=${encodeURIComponent(i.page)}`:""].filter(Boolean).join("&"),s=v(e.runtime),a=await $(e.runtime,s,"GET",`/api/user-goals/${o}/history${r?`?${r}`:""}`);if(a.status!==200||!Array.isArray(a.body.changes))throw bt(a,"The goal's history could not be loaded. Try again.");let l=a.body.changes.map(m=>({...m,actor:{kind:m.actor?.kind,user_id:m.actor?.user_id??null,is_you:m.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,m=>{if(!m.changes.length)return m.total?`No changes on page ${m.page}; there are ${m.total}.`:"No changes yet.";let d=e.print.style,h=m.changes.map(_=>{let S=_.actor.kind==="system"?"ClosedLoop AI":_.actor.is_you?"you":"a teammate",b=_.reason?` \xB7 "${_.reason}"`:"";return`${d.dim(R(_.at))} ${S} ${Uc(_,c)} ${d.dim(Qe(Rc,_.channel,""))}${b}`}),f=(m.page-1)*m.limit+m.changes.length,y=f<m.total?`
|
|
97
77
|
|
|
98
|
-
${
|
|
99
|
-
`)}${y}`})}function
|
|
78
|
+
${d.dim(`${f} of ${m.total}. More: --page ${m.page+1}`)}`:"";return`${h.join(`
|
|
79
|
+
`)}${y}`})}function xs(e,t,n){let i=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.");i.command("list").argument("<goal>","The goal's id, from closedloop goals list.").description("A goal's metrics, each with its id, 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(o,r)=>Pc(t,n,o,r.ended===!0)),i.command("add").argument("<goal>","The goal's id, from closedloop goals list.").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 <id>","The saved metric's id, from closedloop metrics list.").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's id, from closedloop goals metrics list <goal>.").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>",Fn).option("--reason <text>","Why, kept in the goal's history.").option("--dry-run","Check the metric without saving it.").action(async(o,r)=>Lc(t,n,o,r)),i.command("target").argument("<goal>","The goal's id, from closedloop goals list.").argument("<kpi>","The goal metric's id, from closedloop goals metrics list <goal>.").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>",Fn).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(o,r,s)=>Dc(t,n,o,r,s)),i.command("archive").argument("<goal>","The goal's id, from closedloop goals list.").argument("<kpi>","The goal metric's id, from closedloop goals metrics list <goal>.").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(o,r,s)=>jc(t,n,o,r,s.reason)),e.command("measure").argument("<goal>","The goal's id, from closedloop goals list.").description("Measure one of your goals' metrics now instead of waiting for the night.").action(async o=>Fc(t,n,o)),e.command("history").argument("<goal>","The goal's id, from closedloop goals list.").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(o,r)=>Wc(t,n,o,r))}var uo="working-goal.json",Ps=e=>e.team&&typeof e.team.id=="string"?e.team.id:null;function Ns(e,t){let n=Ko(e,uo);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 Ls(e,t){return Ns(e,t)!==null}function Ds(e,t){let n=Ns(e,t);return n&&n.team_id===Ps(t)?{id:n.id,title:n.title}:null}function js(e,t,n){let i={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:Ps(t),at:new Date(e.now?.()??Date.now()).toISOString()};zo(e,uo,i)}function Un(e){Xo(e,uo)}function co(e){return e.workspace??=M(e.runtime,v(e.runtime)),e.workspace}var Ws="closedloop goals list",Hs=1024*1024,$t="The goal's id, from closedloop goals list.",qc={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"}]},_e=(e,t,n)=>new p(e,t,g.Usage,n),Ms=(e,t)=>t?_n(e,t):N(e);async function de(e,t){return I(t,"goal",Ws)}var kt=()=>new p("goal_not_found","there is no goal with that id you can see",g.Failure,L(Ws)),vt={productId:"product",productAreaId:"area",featureAreaId:"feature"},Ct=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),Wn=e=>e.map(t=>t.replace(/_/g," ")).join(" or ");function Gs(e){let t=e.relevance;return t?t.from==="goal"?`relevance: ${Wn(t.outcomes)}`:t.from==="kpis"?`relevance from its metrics: ${Wn(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: ${Wn(e.scope.outcomeCategory)}`:null}function Jc(e){let t=e.scope_names??{},n=new Set((e.scope_missing??[]).map(a=>`${a.field}|${a.value}`)),i=[],o=[];for(let[a,l]of Object.entries(e.scope))if(Ct(vt,a))for(let u of l)i.push(Ct(t,u)?`${t[u]} (${vt[a]})`:n.has(`${a}|${u}`)?`a removed ${vt[a]}`:`${vt[a]} ${u}`);else a!=="outcomeCategory"&&o.push(`${a}: ${l.join(", ")}`);let r=Gs(e),s=[...i.length?[i.join(" or ")]:[],...o];return[s.length?s.join("; "):"all insights",...r?[r]:[]].join("; ")}function Vc(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 Hn(e,t){if(e===null)return"?";let n=Number.isInteger(e)?k(e):String(e);return t?t==="%"?`${n}%`:`${n} ${t}`:n}var Bc=e=>e.split(`
|
|
100
80
|
`).map(t=>t&&` ${t}`).join(`
|
|
101
|
-
`);function
|
|
102
|
-
`)),
|
|
81
|
+
`);function Yc(e){return e.archived_at?`archived ${N(e.archived_at)}`:e.state==="closed"?e.closed_at?`closed ${N(e.closed_at)}`:"closed":e.state??null}function qs(e,t,n,i=0){let o=" ".repeat(i),r=n?`${o} `:o,s=Yc(t),a=`${o}${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} ${Hn(t.metric.target,t.metric.unit)}${t.metric.due?` by ${N(`${t.metric.due}T12:00:00Z`)}`:""}`,c=t.current?`now ${Hn(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 ${Hn(t.metric.baseline,t.metric.unit)}`}`,m=Array.isArray(t.key_results)?t.key_results:[],d=m.length>1?` \xB7 and ${m.length-1} more key result${m.length===2?"":"s"}`:"",h=m.length?`${As(e,m[0])}${d}`:`${u} \xB7 ${c}`,f=t.matching_insights===null?"insights unknown":`${k(t.matching_insights)} insight${t.matching_insights===1?"":"s"}`,y=Vc(t);return[a,`${r}${h}`,`${r}${e.dim(`${Jc(t)} \xB7 ${f}${t.key?` \xB7 key ${t.key}`:""}`)}`,...y?[`${r}${y}`]:[],...n?[E(e,r,t.id)]:[]]}function Kc(e){let t=new Map(e.map(r=>[r.id,r])),n=new Set,i=[],o=e.filter(r=>!r.parent_id||!t.has(r.parent_id));for(let r of[...o,...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),i.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 i}async function zc(e,t,n,i){let o=v(e.runtime),r=[t?"include=team":"",n?"archived=true":"",i!==void 0?`state=${encodeURIComponent(i)}`:""].filter(Boolean).join("&"),s=await $(e.runtime,o,"GET",`/api/user-goals${r?`?${r}`:""}`);if(s.status===400&&Array.isArray(s.body.allowed))throw _e("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=Kc(s.body.goals),l=a.map(c=>c.goal),u=s.body.truncated===!0;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 m=e.print.style,d=a.map(f=>qs(m,f.goal,!0,f.depth).join(`
|
|
82
|
+
`)),h=c.truncated?`
|
|
103
83
|
|
|
104
|
-
${
|
|
84
|
+
${m.dim(`Showing the first ${c.goals.length}.`)}`:"";return`${d.join(`
|
|
105
85
|
|
|
106
|
-
`)}${
|
|
86
|
+
`)}${h}
|
|
107
87
|
|
|
108
|
-
${
|
|
109
|
-
${
|
|
88
|
+
${m.dim("Read one by its id: closedloop goals show <id>")}`})}async function Xc(e,t){let n=await de(e,t),i=v(e.runtime),o=await $(e.runtime,i,"GET",`/api/user-goals/${n}`);if(o.status===404)throw kt();let r=o.body.goal;if(o.status!==200||!r||typeof r.id!="string")throw C(o,"api_error","The goal could not be loaded. Try again.");let s=typeof o.body.time_zone=="string"?o.body.time_zone:null,{kpis:a,lead_rules:l}=await lo(e,r.id,!1),u=r.parent_id?await Qc(e,r.parent_id):null;e.print.result({...r,kpis:a},c=>{let m=e.print.style,d=qs(m,c,!1);c.parent_id&&d.push(`Sub-goal of ${u??"a goal you cannot see"}`),c.children&&c.children.length&&d.push(m.dim(`${c.children.length} sub-goal${c.children.length===1?"":"s"}: closedloop goals list --team`)),c.description&&d.push("",c.description),d.push("",c.kpis.length?`Metrics:
|
|
89
|
+
${Bc(so(m,c.kpis,l,c.state==="draft"))}`:m.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)&&d.push("",h.length?`A draft. To activate it, it still needs:
|
|
110
90
|
${h.map(f=>` - ${f.message}`).join(`
|
|
111
|
-
`)}`:`A draft, ready to activate: closedloop goals activate ${c.id}`),c.values&&c.values.length&&(
|
|
112
|
-
`)})}async function
|
|
113
|
-
`);let c=(
|
|
114
|
-
`)})}async function
|
|
91
|
+
`)}`:`A draft, ready to activate: closedloop goals activate ${c.id}`),c.values&&c.values.length&&(d.push("","Values:",...c.values.slice(0,10).map(f=>` ${Ms(f.measured_at,s)} ${Hn(f.value,c.metric.unit)}`)),c.values.length>10&&d.push(m.dim(` and ${c.values.length-10} earlier`))),d.push("",m.dim(`id ${c.id}`)),d.join(`
|
|
92
|
+
`)})}async function Qc(e,t){let n=await $(e.runtime,v(e.runtime),"GET",`/api/user-goals/${t}`),i=n.body.goal;return n.status===200&&typeof i?.title=="string"?i.title:null}var Fs="insights search reads only its insights; add --all for every insight.";async function St(e,t){if(!Ls(e.runtime,t))return null;let n=await co(e);return Ds(e.runtime,{...t,team:{id:n.id,name:n.name}})}async function Zc(e,t,n){if(n&&t!==void 0)throw _e("invalid_option","give a goal or --clear, not both");let i=v(e.runtime);if(n){Un(e.runtime),e.print.result({working_goal:null},()=>"No goal picked: insights search reads every insight.");return}if(t===void 0){let u=await St(e,i);e.print.result({working_goal:u},c=>c.working_goal?`Working on ${c.working_goal.title}: ${Fs}`:"No goal picked: insights search reads every insight. Pick one: closedloop goals use <goal>");return}let o=await de(e,t),r=await co(e),s=await $(e.runtime,i,"GET",`/api/user-goals/${o}`);if(s.status===404)throw kt();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.");js(e.runtime,{...i,team:{id:r.id,name:r.name}},{id:a.id,title:a.title});let l=a.relevance&&a.relevance.outcomes.length===0?` It has ${Gs(a)}.`:"";e.print.result({working_goal:{id:a.id,title:a.title}},u=>`Working on ${u.working_goal.title}: ${Fs}${l}`)}var Us={churn_reason:"reason for leaving",general_dislike:"dislike"};async function ed(e,t){let n=v(e.runtime),i=t!==void 0?await de(e,t):(await St(e,n))?.id;if(!i)throw _e("no_goal","name a goal, or pick the one you work on first","closedloop goals use <goal>");let o=await $(e.runtime,n,"GET",`/api/user-goals/${i}/context`);if(o.status===404&&o.body.code==="goal_not_found")throw kt();if(o.status===404)throw new p("goal_context_unsupported","this ClosedLoop AI API cannot show a goal's context yet",g.Failure);let r=o.body;if(o.status!==200||!r.goal||typeof r.goal.id!="string"||!Array.isArray(r.goal.relevance?.outcomes))throw C(o,"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 ${Wn(a.goal.relevance.outcomes)}: deal blockers go with new sales and churn risks with retention, so it has neither.`),u.join(`
|
|
93
|
+
`);let c=(d,h,f,y,_,S)=>{f&&(u.push("",`${d}, bearing on ${h}: ${f.total===0?"none in its scope":y(f.total)}`),u.push(...f.items.flatMap(b=>[` ${_(b)}`,E(l," ",S(b))])),f.total>f.items.length&&u.push(l.dim(` and ${k(f.total-f.items.length)} earlier`)))},m=a.deal_blockers?.customers;return c("Deal blockers","new sales",a.deal_blockers,d=>`${k(d)} insight${d===1?"":"s"}${typeof m=="number"?` from ${k(m)} customer${m===1?"":"s"}`:""}`,d=>`${N(d.occurred_at)} ${d.customer_name??"an unknown customer"} \xB7 ${d.title??"an untitled insight"}`,d=>d.insight_id),a.deal_blockers?.items.length&&u.push(l.dim("Read a deal blocker by its id: closedloop insights show <id>")),c("Churn risks","retention",a.churn_risks,d=>k(d),d=>`${N(d.occurred_at)} ${d.customer_name??"an unknown customer"} \xB7 ${d.summary??d.quote??"no summary"}`+l.dim([Object.prototype.hasOwnProperty.call(Us,d.kind)?Us[d.kind]:null,d.urgency?d.urgency.replace(/_/g," "):null].filter(Boolean).map(h=>` \xB7 ${h}`).join("")),d=>d.context_id),u.join(`
|
|
94
|
+
`)})}async function td(e,t){let n=v(e.runtime),i=await $(e.runtime,n,"GET",`/api/user-goals/fields${t?`?field=${encodeURIComponent(t)}`:""}`);if(i.status===400){let r=Array.isArray(i.body.allowed)?i.body.allowed.join(", "):"";throw _e("unknown_field","that goal field is not supported",r?`The fields: ${r}`:void 0)}if(i.status!==200||!Array.isArray(i.body.fields))throw C(i,"api_error","The fields could not be loaded. Try again.");let o={fields:i.body.fields};e.print.result(o,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(d=>` ${d.label===d.value?d.value:`${d.label} ${s.dim(d.value)}`} ${s.dim(`${k(d.insights)} insight${d.insights===1?"":"s"}`)}`),m=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")],...m].join(`
|
|
115
95
|
`)}).join(`
|
|
116
96
|
|
|
117
97
|
`)}
|
|
118
98
|
|
|
119
|
-
${s.dim("Use these in a goal's scope; see closedloop goals example")}`})}function
|
|
120
|
-
`)}function
|
|
121
|
-
${
|
|
122
|
-
${e.print.style
|
|
123
|
-
`:""
|
|
124
|
-
|
|
125
|
-
${n.
|
|
126
|
-
|
|
127
|
-
`)
|
|
99
|
+
${s.dim("Use these in a goal's scope; see closedloop goals example")}`})}function nd(e,t){let n,i;if(e!==void 0){if(!Hc(e))throw _e("file_not_found",`there is no file ${e}`);if(Gc(e).size>Hs)throw _e("file_too_large",`${e} is larger than 1 MB`);n=Mc(e,"utf8"),i=e}else n=t??"",i="standard input";if(!n.trim())throw _e("nothing_to_send",`${i} is empty`);try{return JSON.parse(n)}catch{throw _e("invalid_file",`${i} is not valid JSON; check its syntax`,"See the format: closedloop goals example")}}function id(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(`
|
|
100
|
+
`)}function od(e,t){let n=i=>i!==null&&typeof i=="object"&&!Array.isArray(i)&&!Ct(i,"parent")?{...i,parent:t}:i;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 rd(e,t){if(t.file===void 0==(t.stdin!==!0))throw _e("nothing_to_send","give the goals with --file <path> or --stdin","See the format: closedloop goals example");let n=nd(t.file,t.stdin?await e.runtime.readStdin(Hs):void 0),i=t.parent===void 0?n:od(n,await de(e,t.parent)),o=v(e.runtime),r=await $(e.runtime,o,"POST",`/api/user-goals${t.dryRun?"?dry_run=true":""}`,i);if(r.status===400&&Array.isArray(r.body.problems)){let a=r.body.problems;throw new p("invalid_goals",`nothing was saved; fix ${a.length===1?"this":`these ${a.length}`}:
|
|
101
|
+
${id(a)}`,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};e.print.result(s,a=>{let l={created:"created",updated:"updated",would_create:"would be created",would_update:"would update the goal with this key"},u=a.goals.map(d=>`goal ${d.index+1}${d.key?` (${d.key})`:""}: ${Object.prototype.hasOwnProperty.call(l,d.result)?l[d.result]:d.result}${d.state&&d.result!=="would_create"?`, ${d.state}`:""} \xB7 ${d.matching_insights===null?"insights unknown":`${k(d.matching_insights)} matching insight${d.matching_insights===1?"":"s"}`}`+(typeof d.id=="string"&&d.id?`
|
|
102
|
+
${E(e.print.style," ",d.id)}`:"")),m=!a.dry_run&&a.goals.some(d=>d.state==="draft")?`
|
|
103
|
+
${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`${a.dry_run?`Checked, nothing saved:
|
|
104
|
+
`:""}${u.join(`
|
|
105
|
+
`)}${m}`})}async function sd(e,t,n,i){let o=Number(n);if(n.trim()===""||!Number.isFinite(o))throw _e("invalid_value",`'${n}' is not a number`);let r=await de(e,t),s=v(e.runtime),a=await $(e.runtime,s,"POST",`/api/user-goals/${r}/values`,i===void 0?{value:o}:{value:o,measured_at:i});if(a.status===404)throw kt();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 ${Ms(c.measured_at,u)}.`)}async function ad(e,t,n){let i=await de(e,t),o=v(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 ${i} --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:i},()=>"Nothing changed.");return}}let r=await St(e,o),s=await $(e.runtime,o,"DELETE",`/api/user-goals/${i}`);if(s.status===404)throw kt();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===i&&Un(e.runtime);let a=s.body.archived,l=typeof a?.archived_at=="string"?a.archived_at:null;e.print.result({status:"archived",id:i,archived_at:l},()=>"Archived the goal. Its values and history stay; see it with closedloop goals list --archived.")}function Js(e,t){if(e.status===404)return kt();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:
|
|
106
|
+
${n.map(i=>` - ${i.message}`).join(`
|
|
107
|
+
`)}`,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 ld(e,t,n){let i=await de(e,t),o=v(e.runtime),r=await $(e.runtime,o,"POST",`/api/user-goals/${i}/activate`,n===void 0?{}:{reason:n}),s=r.body.goal;if(r.status!==200||!s||typeof s.id!="string")throw Js(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(`
|
|
108
|
+
`))}async function ud(e,t,n,i){let o=await de(e,t),r=v(e.runtime);if(!i){if(!e.interactive)throw new p("confirmation_required","closing a goal needs confirmation",g.Usage,`Run it again with --yes: closedloop goals close ${o} --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:o},()=>"Nothing changed.");return}}let s=await St(e,r),a=await $(e.runtime,r,"POST",`/api/user-goals/${o}/close`,n===void 0?{}:{reason:n}),l=a.body.closed;if(a.status!==200||!l)throw Js(a,"The goal could not be closed. Try again.");s?.id===o&&Un(e.runtime);let u=typeof l.closed_at=="string"?l.closed_at:null;e.print.result({status:"closed",id:o,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 Vs=(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, how many insights each is about, and each goal's id.").action(async i=>zc(t,i.team===!0,i.archived===!0,i.state)),n.command("show").argument("<goal>",$t).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 i=>Xc(t,i)),n.command("use").argument("[goal]",$t).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(i,o)=>Zc(t,i,o.clear===!0)),n.command("context").argument("[goal]","The goal's id, from closedloop goals list; 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 i=>ed(t,i)),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 i=>td(t,i.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(qc,i=>JSON.stringify(i,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 the goal with this id (from closedloop goals list), unless it names its own parent.").action(async i=>rd(t,i)),n.command("activate").argument("<goal>",$t).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(i,o)=>ld(t,i,o.reason)),n.command("close").argument("<goal>",$t).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(i,o)=>ud(t,i,o.reason,o.yes===!0));for(let[i,o]of[["set",!1],["push",!0]])n.command(i,{hidden:o}).argument("<goal>",$t).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)=>sd(t,r,s,a.at));n.command("delete").argument("<goal>",$t).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(i,o)=>ad(t,i,o.yes===!0)),xs(n,t,de)};import{existsSync as dd,readFileSync as md,statSync as pd}from"node:fs";import{extname as gd}from"node:path";function cd(e){let t=[],n=[],i="",o=!1,r=e.charCodeAt(0)===65279?e.slice(1):e;for(let s=0;s<r.length;s++){let a=r[s];o?a==='"'&&r[s+1]==='"'?(i+='"',s++):a==='"'?o=!1:i+=a:a==='"'&&i===""?o=!0:a===","?(n.push(i),i=""):a===`
|
|
128
109
|
`||a==="\r"?(a==="\r"&&r[s+1]===`
|
|
129
|
-
`&&s++,n.push(
|
|
130
|
-
|
|
131
|
-
`)
|
|
132
|
-
${
|
|
110
|
+
`&&s++,n.push(i),t.push(n),n=[],i=""):i+=a}if(o)throw new Error("a quoted field is not closed");return(i!==""||n.length>0)&&(n.push(i),t.push(n)),t.filter(s=>s.some(a=>a.trim()!==""))}function Bs(e){let[t,...n]=cd(e);if(!t)return[];let i=t.map(o=>o.trim().toLowerCase());return n.map(o=>Object.fromEntries(i.map((r,s)=>[r,o[s]??""])))}var Mn=["content","title","reporter_name","reporter_email","customer","date","url","id","tags"],hd=100,fd=4*1024*1024,zs=50*1024*1024,Ys=900,Ks=5,yd=new Set([".csv",".json",".jsonl",".ndjson"]),wd="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.",_d="They are analysed once your company's email domains are confirmed; see: closedloop company",q=(e,t,n)=>new p(e,t,g.Usage,n);function bd(e,t){if(!e||typeof e!="object"||Array.isArray(e))throw q("invalid_record",`record ${t} is not an object with a content field`);let n={};for(let[i,o]of Object.entries(e)){let r=i.trim().toLowerCase();if(!Mn.includes(r))throw q("unknown_field",`record ${t} has a field that ingest does not take`,`The fields: ${Mn.join(", ")}.`);if(!(o==null||o===""))if(r==="tags"){let s=Array.isArray(o)?o:typeof o=="string"?o.split(";"):null;if(!s||s.some(a=>typeof a!="string"))throw q("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 o=="string"||typeof o=="number")n[r]=String(o).trim();else throw q("invalid_record",`record ${t}'s ${r} must be text`)}if(typeof n.content!="string"||!n.content)throw q("invalid_record",`record ${t} has no content`);return n}function $d(e){if(!dd(e))throw q("file_not_found",`there is no file ${e}`);if(pd(e).size>zs)throw q("file_too_large",`${e} is larger than 50 MB`,"Split it into smaller files.");return{text:md(e,"utf8"),ext:gd(e).toLowerCase()}}function vd(e,t,n){let i;try{if(t===".csv")i=Bs(e);else if(t===".json"){let o=JSON.parse(e);i=Array.isArray(o)?o:[o]}else i=e.split(/\r?\n/).filter(o=>o.trim()).map(o=>JSON.parse(o))}catch{throw q("invalid_file",`${n} could not be read as ${t.slice(1).toUpperCase()}; check its syntax`,`Each record needs content, and may include ${Mn.slice(1).join(", ")}.`)}if(t===".csv"&&i.length>0&&!Object.keys(i[0]).includes("content"))throw q("invalid_file",`${n} has no content column`,`The first row names the columns: content, and any of ${Mn.slice(1).join(", ")}.`);return i.map((o,r)=>bd(o,r+1))}async function Cd(e,t,n){let i=[t.length>0,n.stdin===!0,n.file!==void 0].filter(Boolean).length;if(i===0)throw q("nothing_to_send","nothing to send","Give some text, --file <path> or --stdin.");if(i>1)throw q("conflicting_options","send text, --file or --stdin, one at a time");let o={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(o).some(l=>l!==void 0)||(n.tag?.length??0)>0,s;if(n.file!==void 0){let{text:l,ext:u}=$d(n.file);if(yd.has(u)){if(r)throw q("conflicting_options",`--title, --from and the like describe one record; ${n.file} carries its own fields`);return vd(l,u,n.file)}s=l}else n.stdin?s=await e.runtime.readStdin(zs):s=t.join(" ");if(!s.trim())throw q("nothing_to_send","the text to send is empty");let a={content:s.trim()};for(let[l,u]of Object.entries(o))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 kd(e){let t=[],n=[],i=0,o=0;for(let r of e){let s=Buffer.byteLength(JSON.stringify(r));n.length>0&&(n.length===hd||i+s>fd)&&(t.push({offset:o,items:n}),o+=n.length,n=[],i=0),n.push(r),i+=s}return n.length>0&&t.push({offset:o,items:n}),t}async function Sd(e,t,n){let i=[],o=[],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 kd(n)){let l=i.length?`The first ${i.length} record${i.length===1?" was":"s were"} sent; ${s}.`:"",u;try{u=await $(e.runtime,t,"POST","/api/cli/ingest",{items:a.items})}catch(c){if(!(c instanceof p))throw c;let m=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,m].filter(Boolean).join(" ")||void 0)}if(u.status!==201){if(u.body.code==="INVALID_ITEM"&&typeof u.body.index=="number"){let m=String(u.body.error).replace(/^Record \d+ /,"");throw new p("invalid_record",`record ${a.offset+u.body.index+1} ${m.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)}i.push(...u.body.accepted),o.push(...u.body.duplicates),r=typeof u.body.waiting_for=="string"?u.body.waiting_for:null}return{accepted:i,duplicates:o,waitingFor:r}}async function Xs(e,t,n){let i=await $(e.runtime,t,"GET",`/api/cli/ingest?limit=${n}`);if(i.status!==200||!Array.isArray(i.body.records))throw C(i,"api_error","The records could not be loaded. Try again.");return i.body.records}async function Od(e,t,n){let i=new Set(n.map(o=>o.id));for(let o=0;o<=Ys;o+=Ks){let r=(await Xs(e,t,100)).filter(s=>i.has(s.id));if(r.length===i.size&&r.every(s=>s.state==="done"||s.state==="failed"))return r;o<Ys&&await e.runtime.sleep(Ks*1e3)}throw new p("still_processing","the records are not processed yet",g.Failure,"Check later with: closedloop ingest list")}function Qs(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 Td(e,t,n){let i=await Cd(e,t,n);if(n.wait&&i.length>100)throw q("wait_too_many","--wait follows up to 100 records","Send without --wait and check with `closedloop ingest list`.");let o=v(e.runtime);await Ve(e.runtime,o,"can_manage_integrations","send data in");let{accepted:r,duplicates:s,waitingFor:a}=await Sd(e,o,i),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"?wd:h.waiting_for==="email_domains"?_d:"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 Od(e,o,r)}finally{u.stop()}let m=c.reduce((h,f)=>h+f.insights,0),d=c.filter(h=>h.state==="failed").length;if(e.print.result({status:"processed",sent:r.length,duplicates:s,insights:m,failed:d,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`),d>0)throw new p("ingest_failed",`${d} record${d===1?"":"s"} failed during processing`,g.Failure,"Check the failed records with `closedloop ingest list` or `closedloop ingest status <id>`.")}async function Ad(e,t){let n=t===void 0?20:Number(t);if(!Number.isInteger(n)||n<1||n>100)throw q("invalid_option","--limit must be a whole number from 1 to 100");let i=v(e.runtime),o=await Xs(e,i,n),r=e.print.style;e.print.result({records:o},s=>s.records.length===0?"Nothing sent with closedloop ingest yet.":s.records.map(a=>`${r.dim(N(a.created_at))} ${Qs(a,r)} ${a.title??a.external_id??"(untitled)"}
|
|
111
|
+
${E(r," ",a.id)}`).join(`
|
|
112
|
+
`))}async function Rd(e,t){let n=I(t,"record","closedloop ingest list"),i=v(e.runtime),o=await $(e.runtime,i,"GET",`/api/cli/ingest/${n}`);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 r=o.body;e.print.result(r,s=>`${s.title??s.external_id??"The record"}: ${Qs(s)} (sent ${N(s.created_at)}).`)}var Ed=(e,t)=>[...t??[],e],Zs=(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).",Ed).option("--wait","Wait until processed, and say how many insights it made.").action(async(i,o)=>Td(t,i,o));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 i=>Ad(t,i.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 i=>Rd(t,i))};var mo="\u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588";function Yt(e,t,n){return i=>n&&i?`\x1B[${e}m${i}\x1B[${t}m`:i}function po(e){let t=e.color===!0;return{enabled:t,width:Math.max(60,Math.min(100,e.columns??100)),bold:Yt(1,22,t),dim:Yt(2,22,t),red:Yt(31,39,t),orange:Yt(33,39,t),green:Yt(32,39,t),bar:(n,i)=>{let o=Number.isFinite(n)?Math.min(1,Math.max(0,n)):0,r=Math.round(o*i);return`${"\u2588".repeat(r)}${"\u2591".repeat(i-r)}`},sparkline:n=>{let i=Math.max(0,...n);return n.map(o=>mo[i>0?Math.min(mo.length-1,Math.round(o/i*(mo.length-1))):0]).join("")}}}function Gn(e,t){return t<=1?e.slice(0,Math.max(0,t)):e.length<=t?e:`${e.slice(0,t-1).trimEnd()}\u2026`}function qn(e,t,n){let i=e.split(/\s+/).filter(Boolean),o=[],r="";for(let s of i){let a=t-(o.length===0?0:n.length);r&&r.length+1+s.length>a?(o.push(r),r=s):r=r?`${r} ${s}`:s}return r&&o.push(r),o.map((s,a)=>a===0?s:`${n}${s}`).join(`
|
|
113
|
+
`)}var Id=["title","quote","reporter","account"],xd=["critical","high","medium","low"],ea={week:"1week",month:"1month","3months":"3months"},go={newest:["source_date","desc"],oldest:["source_date","asc"],severity:["severity","asc"],relevance:["relevance_score","desc"]},ta=240;function Jn(e,t,n){if(t===void 0)return;let i=t.trim().toLowerCase();if(!n.includes(i))throw new p("invalid_option",`--${e} must be ${n.join(", ")}`,g.Usage);return i}function na(e,t,n,i){if(t===void 0)return n;let o=Number(t);if(!Number.isInteger(o)||o<1||o>i)throw new p("invalid_option",`--${e} must be a whole number from 1 to ${i}`,g.Usage);return o}async function Pd(e,t,n){let i=await $(e.runtime,t,"GET","/api/cli/integrations");if(i.status!==200||!Array.isArray(i.body.integrations))throw C(i,"api_error","Sources could not be loaded. Try again.");let r=i.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 Nd(e){if(!e)return null;let t=e.replace(/\s+/g," ").trim();return t.length>ta?`${t.slice(0,ta-1)}\u2026`:t}function ra(e){let t=e.reporter_name||e.customer_name;return[e.account_name,t&&t!==e.account_name?t:null].filter(n=>!!n)}function Ld(e){return e&&`${e[0].toUpperCase()}${e.slice(1)}`}var Dd=e=>e.map(t=>t.replace(/_/g," ")).join(" or ");async function jd(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 i=await St(e,t);return i?{...i,picked:!0}:null}async function Fd(e,t,n){let i=Jn("in",n.in,Id),o=Jn("severity",n.severity,xd),r=Jn("last",n.last,Object.keys(ea)),s=Jn("sort",n.sort,Object.keys(go))??"newest",a=na("limit",n.limit,20,100),l=na("page",n.page,1,1e4);if(i&&!t)throw new p("invalid_option","--in needs search words",g.Usage);let u=v(e.runtime),c=new URLSearchParams({page:String(l),limit:String(a),sortBy:go[s][0],sortOrder:go[s][1],filter_options:"false"}),m=t?.trim();m&&(i?c.set("searchIn",i):c.set("searchFragments","true"),c.set("search",m)),n.source&&c.set("source",await Pd(e,u,n.source)),n.category&&c.set("category",n.category.trim().toLowerCase()),o&&c.set("severity",o),r&&c.set("timeRange",ea[r]),n.customer&&c.set("customerEmail",n.customer.trim()),n.account&&c.set("companyDomain",n.account.trim().toLowerCase());let d=await jd(e,u,n);d&&c.set("goalId",d.id);let h=await $(e.runtime,u,"GET",`/api/feedback?${c}`);if(d&&h.status===404)throw new p("goal_not_found",d.picked?`the goal you work on (${d.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(d&&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=d&&f&&typeof f.id=="string"&&typeof f.title=="string"&&f.relevance&&Array.isArray(f.relevance.outcomes)?f:null;if(d&&(!y||y.id.toLowerCase()!==d.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:m||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}))};e.print.result(S,b=>{let W=e.print.style,ie=b.goal?` in ${b.goal.title}`:"",J=b.goal?W.dim(`Only ${b.goal.title}'s insights, bearing on ${Dd(b.goal.relevance.outcomes)}; every insight: --all`):null;if(b.insights.length===0)return[b.query?`No insights${ie} match "${b.query}".`:`No insights${ie} match.`,...J?[J]:[]].join(`
|
|
114
|
+
`);let Rt=`${W.bold(`${b.total} insight${b.total===1?"":"s"}`)}${b.query?` for "${b.query}"`:""}${ie}${W.dim(` \xB7 ${Ud[s]}${b.pages>1?` \xB7 page ${b.page} of ${b.pages}`:""}`)}`,tt=b.insights.map(O=>Wd(W,O)),oi=[b.page<b.pages?`more: --page ${b.page+1}`:null,"read one: closedloop insights show <id>"].filter(Boolean).join(" \xB7 ");return`${Rt}${J?`
|
|
115
|
+
${J}`:""}
|
|
133
116
|
|
|
134
|
-
${
|
|
117
|
+
${tt.join(`
|
|
135
118
|
|
|
136
119
|
`)}
|
|
137
120
|
|
|
138
|
-
${
|
|
139
|
-
`),
|
|
140
|
-
`)}function
|
|
141
|
-
`)})}var
|
|
121
|
+
${W.dim(Ld(oi))}`})}var ia="closedloop insights search",Ud={newest:"newest first",oldest:"oldest first",severity:"most severe first",relevance:"most relevant first"},oa={critical:"CRIT",high:"HIGH",medium:"MED",low:"LOW"};function sa(e,t){let n=(t??"").toLowerCase(),i=(Object.prototype.hasOwnProperty.call(oa,n)?oa[n]:"").padEnd(4);return n==="critical"||n==="high"?e.red(i):n==="medium"?e.orange(i):e.dim(i)}function Wd(e,t){let n=" ".repeat(6),i=e.width-n.length,[o,...r]=qn(t.title??"(untitled)",i,"").split(`
|
|
122
|
+
`),s=[`${sa(e,t.severity)} ${e.bold(o)}`,...r.map(c=>`${n}${e.bold(c)}`)],a=[...ra({account_name:t.account,customer_name:t.customer,reporter_name:t.reporter}).slice(0,1),t.source,t.date?N(t.date):null].filter(Boolean).join(" \xB7 "),l=t.deal_blocker?e.red(`${a?" \xB7 ":""}deal blocker`):"";(a||l)&&s.push(`${n}${e.dim(a)}${l}`);let u=Nd(t.quote);return u&&s.push(`${n}${e.dim(`"${Gn(u,i-2)}"`)}`),s.push(E(e,n,t.id)),s.join(`
|
|
123
|
+
`)}function Hd(e){let t=e&&typeof e=="object"?e.company_name:null;return typeof t=="string"&&t?t:null}async function Md(e,t,n){let i=I(t,"insight",ia),o=v(e.runtime),r=await $(e.runtime,o,"GET",`/api/feedback/${i}`);if(r.status===404)throw new p("insight_not_found","there is no insight with that id in your workspace",g.Failure,L(ia));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")??Hd(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 m=e.print.style,d=[`${c.severity?`${sa(m,c.severity).trimEnd()} `:""}${m.bold(c.title??"(untitled)")}${c.deal_blocker?m.red(" \xB7 deal blocker"):""}`],h=[...ra({account_name:c.account,customer_name:c.customer,reporter_name:c.reporter}),c.reporter_role,c.source,c.date?N(c.date):null,c.category].filter(Boolean).join(" \xB7 ");h&&d.push(m.dim(h)),c.summary&&d.push("",c.summary),c.quote&&d.push("",`"${c.quote.trim()}"`),c.original_quote&&d.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&&d.push("",...y.map(([_,S])=>`${m.dim(`${_}:`)} ${S}`)),n&&typeof c.transcript=="string"&&d.push("","Transcript:",c.transcript),d.join(`
|
|
124
|
+
`)})}var aa=(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. Each insight shows its id.").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.").addOption(new xe("--ids").hideHelp()).option("--goal <goal>","Only one goal's insights (the goal's id, from closedloop goals list), not the goal you work on.").option("--all","Every insight, not only the goal you work on.").action(async(i,o)=>Fd(t,i.join(" ")||void 0,o)),n.command("show").argument("<id>","The insight's id, from closedloop insights search.").description("Show one insight in full.").option("--transcript","Include the conversation transcript.").action(async(i,o)=>Md(t,i,o.transcript===!0))};var ha={1:30,3:90,6:180},Gd="3";function qd(e){if(e.history===!1)return!1;let t=e.history??Gd;if(!Object.prototype.hasOwnProperty.call(ha,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 Jd(e,t,n,i,o){if(n.imports_history!==!0||o===!1||typeof i?.id!="string")return null;let r=ha[o],s=i.config&&typeof i.config=="object"?i.config:{};if((await $(e.runtime,t,"PUT",`/api/user-integrations/${encodeURIComponent(i.id)}`,{config:{...s,backfillPeriod:o,backfill_days:r}})).status!==200)return{months:o,started:!1};let l=await $(e.runtime,t,"POST","/api/trigger-processing",{team_id:await Je(e.runtime,t),user_integration_id:i.id,integration_id:n.integration_id,backfill_days:r,reason:"manual_trigger"}),u=l.status>=200&&l.status<300,c=u&&typeof l.body.backfill_operation_id=="string"?l.body.backfill_operation_id:null;return{months:o,started:u,...u?{backfill_id:c}:{}}}function fo(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}. ${D}`);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}. ${D}`)}}var la="closedloop integrations list",Vd=600,Bd=900,ua=3,Yd=3,ca={api_key:"connect with an API key",browser:"connect in the browser",app:"set up in the app",ingest:"send data with closedloop ingest"},Kd={browser:"is connected in the browser",app:"is set up in the app",ingest:"receives what you send with closedloop ingest"};async function zd(e,t){let n=await $(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 Ot(e,t,n){let i=I(n,"integration",la),o=await $(e.runtime,t,"GET",`/api/cli/integrations/${encodeURIComponent(i)}`);if(o.status===404)throw new p("integration_not_found",`there is no integration with the id ${i}`,g.Failure,L(la));if(o.status!==200||typeof o.body.slug!="string")throw C(o,"api_error","The integration could not be loaded. Try again.");return o.body}function da(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 Xd(e){return e.available_on_your_plan?ca[e.connect_method]:`${ca[e.connect_method]}, needs the ${e.plan_label} plan`}function Vn(e){return e.connected&&e.needs_attention?"needs_attention":e.connected?"connected":"available"}var Bn=["needs_attention","connected","available"],Qd={needs_attention:"Needs attention",connected:"Connected",available:"Available to connect"};function Zd(e,t){return t==="needs_attention"?`${e.name}: ${e.problem}`:t==="available"?`${e.name}: ${Xd(e)}`:e.status==="paused"||e.status==="connecting"?`${e.name}: ${e.status}`:`${e.name}: ${e.last_sync_at?`last sync ${R(e.last_sync_at)}`:"not synced yet"}`}function em(e,t,n){let i=e.print.style;return i.enabled?n==="needs_attention"?`${i.red("!")} `:n==="available"?`${i.dim("+")} `:t.status==="paused"||t.status==="connecting"?`${i.orange("\u2016")} `:`${i.green("\u2713")} `:""}async function tm(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=v(e.runtime),i=await M(e.runtime,n),o=i.permissions.can_manage_integrations,r=new Set(t.connected?["needs_attention","connected"]:t.available?["available"]:Bn),s=(await zd(e,n)).filter(l=>r.has(Vn(l))).sort((l,u)=>Bn.indexOf(Vn(l))-Bn.indexOf(Vn(u))),a=`You cannot connect or change integrations in ${i.name}: ask an owner or admin for the right to manage integrations.`;e.print.result({can_manage_integrations:o,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 c=`${Bn.map(m=>({group:m,items:l.integrations.filter(d=>Vn(d)===m)})).filter(({items:m})=>m.length>0).map(({group:m,items:d})=>[e.print.style.bold(Qd[m]),...d.map(h=>{let f=` ${em(e,h,m)}`,y=" ".repeat(f.length+2),_=`${f}${qn(Zd(h,m),e.print.style.width-f.length,y)}`;return typeof h.integration_id=="string"?`${_}
|
|
125
|
+
${E(e.print.style,y,h.integration_id)}`:_})].join(`
|
|
142
126
|
`)).join(`
|
|
143
|
-
`)
|
|
144
|
-
|
|
145
|
-
${
|
|
146
|
-
|
|
147
|
-
${
|
|
148
|
-
${m.
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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
|
-
`)
|
|
154
|
-
|
|
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
|
|
157
|
-
`)
|
|
158
|
-
|
|
159
|
-
`)
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
`)})
|
|
163
|
-
${
|
|
164
|
-
|
|
165
|
-
`)
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
`)?i
|
|
127
|
+
`)}
|
|
128
|
+
|
|
129
|
+
${e.print.style.dim("Show or connect one by its id: closedloop integrations show <id>")}`;return o?c:`${c}
|
|
130
|
+
|
|
131
|
+
${a}`})}function ma(e){let t=e.choices?` (${e.choices.map(i=>i.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 nm(e,t){let n=v(e.runtime),i=await Ot(e,n,t);e.print.result(i,o=>{let r=[o.name];if(o.description&&r.push(o.description),r.push(`Status: ${o.connected&&!o.needs_attention&&o.connected_at?`connected since ${R(o.connected_at)}; ${da(o)}`:da(o)}`),o.available_on_your_plan||r.push(`Plan: available on the ${o.plan_label} plan, not on yours.`),o.connect.method==="api_key")r.push(`Connect: closedloop integrations connect ${o.integration_id}`),r.push(` It asks for: ${o.connect.fields.map(ma).join("; ")}.`);else if(o.connect.method==="browser"){r.push(`Connect: closedloop integrations connect ${o.integration_id} (opens your browser)`);let s=yo(o)?.fields??[];s.length>0&&r.push(` It asks for: ${s.map(ma).join("; ")}.`)}else o.connect.method==="ingest"?r.push('Send data: closedloop ingest "some text", or closedloop ingest --file data.csv'):o.connect.app_url&&(r.push(`Set it up in the app: ${Se(o.connect.app_url,e.runtime.supportsHyperlinks)}`),r.push(` ${D}`));return r.push(E(e.print.style,"",o.integration_id)),r.join(`
|
|
132
|
+
`)})}function Kn(e,t){return typeof e.required=="boolean"?e.required:t[e.required.when]===e.required.equals}function zn(e,t){let n={};for(let i of e.field??[]){let o=i.indexOf("="),r=o>0?i.slice(0,o).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]=i.slice(o+1).trim()}return n}function fa(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 im(e,t,n){let i=t.choices?` [${t.choices.map(a=>`${a.value} = ${a.label}`).join(", ")}]`:"",o=t.help?` (${t.help})`:"",r=t.default?` (Enter for ${t.default})`:Kn(t,n)?"":" (optional, Enter to skip)",s="";for(let a=1;a<=Yd;a++){let l=await e.runtime.prompt(`${s}${t.label}${o}${i}${r}: `)||t.default||"",u=fa(t,l)??(l===""&&Kn(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 om(e,t,n,i){let o={};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)=>{o[s.key]=r[a]}),o}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 ${i} --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);o[r.key]=s}return o}async function ya(e,t,n,i){let o=n.filter(s=>!s.secret);for(let s of o){i[s.key]===void 0&&s.default&&(i[s.key]=s.default);let a=i[s.key]!==void 0?fa(s,i[s.key]):null;if(a)throw new p("invalid_field",a,g.Usage)}for(let s of o)i[s.key]!==void 0&&i[s.key]!==""||e.interactive&&(s.required===!1||Kn(s,i))&&(i[s.key]=await im(e,s,i));let r=o.filter(s=>Kn(s,i)&&!i[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 rm(e,t,n,i){let o=qd(i),r=n.connect.fields,s=zn(i,r);await ya(e,n.name,r,s),Object.assign(s,await om(e,i,r.filter(y=>y.secret),n.integration_id));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"),m=await ze(e.runtime,t),d=await $(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(d.status!==200&&d.status!==201)throw C(d,"connect_refused",`${n.name} could not be connected.`);let h=d.body.integration&&typeof d.body.integration=="object"?d.body.integration:void 0,f=await Jd(e,t,n,h,o);e.print.result({status:"connected",...te(n),reconnected:n.connected,history:f,...wa(m)},y=>{let _=`${y.reconnected?"Reconnected":"Connected"} ${y.name}.`;if(y.history?.started){let S=y.history.backfill_id?`closedloop backfills show ${y.history.backfill_id}`:"closedloop backfills";return Yn(`${_} Importing the last ${y.history.months} month${y.history.months==="1"?"":"s"} of history; follow it with: ${S}`,m)}return y.history?Yn(`${_} The history import did not start: start it in the app (Integrations, ${y.name}). ${D}`,m):Yn(`${_} Check on it with: closedloop integrations show ${y.integration_id}`,m)})}var te=e=>({integration_id:e.integration_id,slug:e.slug,name:e.name});function wa(e){return{waiting_for:e?.analysis_waiting?"company_profile":null}}function Yn(e,t){let n=qt(t);return n?`${e}
|
|
133
|
+
${n}`:e}function sm(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 yo(e){let t=e.connect.browser;return!t||typeof t.start!="string"||!t.start.startsWith("/")||!Array.isArray(t.fields)?null:t}var am="/api/cli/oauth-handoff",lm="/api/cli/oauth-handoff/go",um="/closedloop/oauth",_a=/^[A-Za-z0-9_-]{43}$/;function ba(e,t,n,i){if(typeof e!="string")return null;let o,r;try{o=new URL(e),r=new URL(t)}catch{return null}let s=o.searchParams.getAll(i);return o.origin!==r.origin||o.pathname!==n||s.length!==1||!_a.test(s[0])?null:{url:o.href,value:s[0]}}async function $a(e,t){try{return await e.openUrl(t)===!0}catch{return!1}}var cm={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"},dm=["SSH_CONNECTION","SSH_TTY","SSH_CLIENT","CODESPACES","REMOTE_CONTAINERS","GITPOD_WORKSPACE_ID","CLOUD_SHELL","container","KUBERNETES_SERVICE_HOST"],mm=["/.dockerenv","/run/.containerenv"];function pm(e){return dm.some(t=>!!e.env[t])||e.env.AWS_EXECUTION_ENV==="CloudShell"?!0:mm.some(t=>e.fileExists?.(t)===!0)}async function va(e,t,n,i){let{print:o,runtime:r}=e,s=i.openOnEnter,a=s?r.onEnter(()=>{$a(r,s)}):()=>{},l=o.spinner(`Waiting for ${n.name} to connect\u2026`);try{for(let u=0;u<i.seconds;u+=ua){await r.sleep(ua*1e3);let c=i.stopped();if(c)throw c;let m=await Ot(e,t,n.integration_id);if(sm(n,m)){let d=await ze(r,t);o.result({status:"connected",...te(m),reconnected:n.connected,...wa(d)},h=>Yn(`${h.reconnected?"Reconnected":"Connected"} ${h.name}.`,d));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.integration_id}\` again.`)}async function Kt(e,t,n,i,o){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. ${D}`);let u=o?" (the settings you gave were not used)":"",c=i?`${cm[i]}, so connect ${n.name} in the app${u}:`:null,m={status:"browser_needed",...te(n),url:l,...i?{reason:i}:{},next:`Open the link and connect ${n.name}${u}; then run \`closedloop integrations connect ${n.integration_id}\` again to check.`,app_sign_in:D};if(!a){r.result(m,d=>c?`${c} ${d.url}
|
|
134
|
+
${d.app_sign_in}
|
|
135
|
+
Then run: closedloop integrations connect ${d.integration_id}`:`Open ${d.url} and connect ${d.name}${u}. ${d.app_sign_in} Then run: closedloop integrations connect ${d.integration_id}`);return}r.result(m,d=>[c??`Connect ${d.name} in your browser${u}:`,` ${Se(d.url,s.supportsHyperlinks)}`,d.app_sign_in,"Press Enter to open it. Waiting for the connection\u2026"].join(`
|
|
136
|
+
`)),await va(e,t,n,{seconds:Vd,openOnEnter:l,stopped:()=>null})}var ho="ClosedLoop AI could not finish connecting here. Go back to your terminal.",Ca={status:404,body:"Not found."};function gm(e,t,n,i,o){if(o.method!=="GET"||o.path!==um||i.token===null)return Promise.resolve(Ca);let r=o.query.getAll("n");if(r.length!==1||!_a.test(r[0]))return Promise.resolve({status:400,body:"This link is not complete. Go back to your terminal."});let s=i.queue.then(()=>hm(e,t,n,i,r[0]));return i.queue=s.catch(()=>{}),s}async function hm(e,t,n,i,o){if(i.success)return i.success.n===o?i.success.reply:Ca;try{let r=await $(e.runtime,t,"POST","/api/cli/oauth-handoff/confirm",{t:i.token,n:o});if(r.status===409)return{status:409,body:"Press Continue on the page the link opened, in this browser."};if(r.status!==200)return i.failure=C(r,"connect_refused",`${n.name} could not be connected.`),{status:400,body:ho,last:!0};let s=ba(r.body.go_url,t.api_url,lm,"g");if(!s)return i.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. ${D}`),{status:502,body:ho,last:!0};let a={status:302,location:s.url,last:!0};return i.success={n:o,reply:a},a}catch(r){return i.failure=r instanceof p?r:new p("api_error",`${n.name} could not be connected from here`,g.Failure),{status:500,body:ho,last:!0}}}async function fm(e,t,n,i){let o=e.runtime.listenLoopback;if(!o)return null;try{return await o(r=>gm(e,t,n,i,r))}catch{return null}}async function ym(e,t,n,i,o,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 $(e.runtime,t,"POST",i.start,{team_id:r,...o,...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=ba(l.body.handoff_url,t.api_url,am,"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. ${D}`);return u}async function wm(e,t,n,i,o){let r=yo(n),s=(i.field??[]).length>0;if(!r)return Kt(e,t,n,null,!1);if(i.inApp)return Kt(e,t,n,null,s);let{print:a,runtime:l,interactive:u}=e;if(pm(l))return Kt(e,t,n,"remote_browser",s);let c=zn(i,r.fields);await ya(e,n.name,r.fields,c);let m=Object.fromEntries(r.fields.filter(y=>c[y.key]).map(y=>[y.key,c[y.key]])),d=Object.keys(m).length>0,h={token:null,failure:null,success:null,queue:Promise.resolve()},f=await fm(e,t,n,h);if(!f)return Kt(e,t,n,"no_local_listener",d);try{let y=await ym(e,t,n,r,m,o,f.port);if(h.token=y.value,u)a.result({status:"browser_needed",...te(n),url:y.url},_=>[`Connect ${_.name} in your browser:`,` ${Se(_.url,l.supportsHyperlinks)}`,"Press Enter to open it. Waiting for the connection\u2026"].join(`
|
|
137
|
+
`));else if(!await $a(l,y.url))return f.close(),Kt(e,t,n,"browser_not_opened",d);await va(e,t,n,{seconds:Bd,openOnEnter:u?y.url:null,stopped:()=>h.failure})}finally{f.close()}}async function _m(e,t,n){let i=v(e.runtime),o=await Ot(e,i,t);if(n.inApp&&o.connect.method!=="browser")throw new p("not_a_browser_integration",`${o.name} does not connect in the browser, so --in-app does not apply`,g.Usage,`Run it without --in-app: closedloop integrations connect ${o.integration_id}`);let r=o.connect.method==="browser"?yo(o)?.fields??[]:[];if(o.connect.method==="api_key")zn(n,o.connect.fields);else if(r.length>0&&!n.stdin)zn(n,r);else if((n.field??[]).length>0||n.stdin)throw new p("not_an_api_key_integration",`${o.name} ${Kd[o.connect.method]}, not with a key`,g.Usage,`Run it without --field and --stdin: closedloop integrations connect ${o.integration_id}`);if(o.connected&&!o.needs_attention&&!n.reconnect){e.print.result({status:"already_connected",...te(o)},l=>`${l.name} is already connected. To connect it again (for example with a new key), add --reconnect.`);return}let s=o.connect.method!=="ingest"?await Ve(e.runtime,i,"can_manage_integrations",`connect ${o.name}`):null;if(!o.available_on_your_plan)throw new p("plan_required",`${o.name} is available on the ${o.plan_label} plan`,g.Failure,"Change your plan with: closedloop plan upgrade");if(o.connect.method==="api_key")return rm(e,i,o,n);if(o.connect.method==="browser"&&s)return wm(e,i,o,n,s.id);if(o.connect.method==="ingest"){e.print.result({status:"use_ingest",...te(o)},()=>"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=o.connect.app_url;e.print.result({status:"set_up_in_app",...te(o),url:a,app_sign_in:D},l=>`${l.name} is set up in the app${l.url?`: ${Se(l.url,e.runtime.supportsHyperlinks)}`:"."}
|
|
138
|
+
${l.app_sign_in}`)}async function bm(e,t,n){let i=v(e.runtime),o=await Ot(e,i,t);if(fo(o,"disconnected"),!o.connected||!o.connection_id){e.print.result({status:"not_connected",...te(o)},s=>`${s.name} is not connected.`);return}if(await Ve(e.runtime,i,"can_manage_integrations",`disconnect ${o.name}`),!n){if(!e.interactive)throw new p("confirmation_required",`disconnecting ${o.name} needs confirmation`,g.Usage,`Run it again with --yes: closedloop integrations disconnect ${o.integration_id} --yes`);if((await e.runtime.prompt(`Disconnect ${o.name}? Syncing stops; what was already imported stays. Type yes to confirm: `)).toLowerCase()!=="yes"){e.print.result({status:"cancelled",...te(o)},()=>"Nothing changed.");return}}let r=await $(e.runtime,i,"DELETE",`/api/user-integrations/${encodeURIComponent(o.connection_id)}`);if(r.status!==200&&r.status!==204)throw C(r,"disconnect_refused",`${o.name} could not be disconnected.`);e.print.result({status:"disconnected",...te(o)},s=>`Disconnected ${s.name}. What was already imported stays.`)}async function pa(e,t,n){let i=v(e.runtime),o=await Ot(e,i,t),r=te(o);if(fo(o,n?"paused":"resumed"),!o.connected||!o.connection_id){e.print.result({status:"not_connected",...r},a=>`${a.name} is not connected.`);return}if(n===(o.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 Ve(e.runtime,i,"can_manage_integrations",`${n?"pause":"resume"} ${o.name}`);let s=await $(e.runtime,i,"PUT",`/api/user-integrations/${encodeURIComponent(o.connection_id)}`,{status:n?"inactive":"active"});if(s.status!==200)throw C(s,n?"pause_refused":"resume_refused",`${o.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.integration_id}`:`Resumed ${a.name}. It syncs again on its usual schedule.`)}var $m=2147483647,ka="Daily credit limits are for sources of conversations and feedback, such as Gong, Intercom or Slack.";function ga(e){let t=i=>typeof i=="number"&&Number.isFinite(i)?i:null,n=i=>typeof i=="string"&&i?i:null;return{supported:e.supported===!0,daily_credit_limit:t(e.daily_credit_limit),spent_today:t(e.spent_today),resets_at:n(e.resets_at),paused:e.paused===!0,paused_until:n(e.paused_until)}}function vm(e){let t=e.replace(/[,_]/g,""),n=/^[1-9][0-9]*$/.test(t)?Number(t):Number.NaN;if(!Number.isSafeInteger(n)||n>$m)throw new p("invalid_limit","--set takes a whole number of credits of at least 1, for example --set 1000000",g.Usage);return n}function Cm(e,t){if(!t.supported)return`${e} takes no daily credit limit. ${ka}`;let n=[t.daily_credit_limit===null?`${e} has no daily credit limit.`:`${e}: at most ${k(t.daily_credit_limit)} credits a day.`];return t.spent_today!==null&&n.push(`Used today: ${k(t.spent_today)} credits${t.resets_at?` (the day resets ${R(t.resets_at)})`:""}.`),t.paused&&t.paused_until&&n.push(`Analysis is paused until ${R(t.paused_until)} because today's limit was reached; new data still syncs and waits.`),n.join(`
|
|
139
|
+
`)}async function km(e,t,n){if(n.set!==void 0&&n.remove)throw new p("invalid_options","use --set or --remove, not both",g.Usage);let i=n.remove?null:n.set!==void 0?vm(n.set):void 0,o=v(e.runtime),r=await Ot(e,o,t),s=te(r);if(fo(r,"given a daily credit limit"),!r.connected||!r.connection_id){e.print.result({status:"not_connected",...s},d=>`${d.name} is not connected, so it has no daily credit limit.`);return}let a=`/api/user-integrations/${encodeURIComponent(r.connection_id)}/daily-credit-limit`,l=await $(e.runtime,o,"GET",a);if(l.status!==200)throw C(l,"api_error",`The daily credit limit of ${r.name} could not be loaded. Try again.`);let u=ga(l.body);if(i===void 0){e.print.result({status:u.supported?"limit":"not_supported",...s,...u},()=>Cm(r.name,u));return}if(!u.supported)throw new p("daily_limit_not_supported",`${r.name} takes no daily credit limit`,g.Failure,ka);await Ve(e.runtime,o,"can_manage_integrations",`change the daily credit limit of ${r.name}`);let c=await $(e.runtime,o,"PUT",a,{daily_credit_limit:i});if(c.status!==200)throw C(c,"limit_refused",`The daily credit limit of ${r.name} could not be saved.`);let m=ga(c.body);e.print.result({status:i===null?"limit_removed":"limit_set",...s,...m},()=>i===null?`Removed the daily credit limit of ${r.name}. If today's limit had paused its analysis, it continues now.`:`${r.name} now stops at ${k(i)} credits a day: once a day reaches it, its analysis pauses until 00:00 UTC.`)}var Sm=(e,t)=>[...t??[],e],Sa=(e,t)=>{let n=e.command("integrations").alias("integration").description("See, connect and disconnect your workspace's integrations, each by its id from the list.");n.command("list").description("List integrations, each with its id: 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 i=>tm(t,{connected:i.connected===!0,available:i.available===!0})),n.command("show").argument("<id>","The integration's id, from closedloop integrations list.").description("Show one integration: its status and how to connect it.").action(async i=>nm(t,i)),n.command("connect").argument("<id>","The integration's id, from closedloop integrations list.").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).",Sm).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(i,o)=>_m(t,i,o)),n.command("pause").argument("<id>","The integration's id, from closedloop integrations list.").description("Stop syncing an integration without disconnecting it.").action(async i=>pa(t,i,!0)),n.command("resume").argument("<id>","The integration's id, from closedloop integrations list.").description("Start syncing a paused integration again.").action(async i=>pa(t,i,!1)),n.command("limit").argument("<id>","The integration's id, from closedloop integrations list.").description("Show or change the most credits a day an integration may use; over it, its analysis pauses until 00:00 UTC.").option("--set <credits>","Set the limit, a whole number of credits a day, e.g. --set 1000000.").option("--remove","Remove the limit.").action(async(i,o)=>km(t,i,o)),n.command("disconnect").argument("<id>","The integration's id, from closedloop integrations list.").description("Disconnect an integration. Syncing stops; imported data stays.").option("--yes","Skip the confirmation.").action(async(i,o)=>bm(t,i,o.yes===!0))};import{randomUUID as Om}from"node:crypto";var U=ke,me=se,Tm=60,Xn="Choose --region us|eu: --region us stores new account data in the United States; --region eu stores it in the European Union.",Am="Choose your data region: United States (US) or European Union (EU) [US]: ",zt="Run `closedloop login --email <email>` to get a new code.",Rm="Run `closedloop login --browser` to sign in through your browser.",Em="Run `closedloop login --browser` to sign in with your password in your browser.",Im="Your company website, e.g. https://acme.example: ",xm=3,Pm=`Next, set your company website: ${Z}. ${Gt} Nothing is analysed until it is done.`,Nm=["sso_required","password_sign_in"],Oa={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:zt},CODE_ALREADY_USED:{code:"code_already_used",hint:zt},INVALID_CHALLENGE:{code:"code_request_invalid",hint:zt},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:`${zt} Then use the current code from your authenticator app.`},TWO_FACTOR_SETUP_REQUIRED:{code:"two_factor_setup_required"},SSO_REQUIRED:{code:"sso_required",hint:Rm},PASSWORD_SIGN_IN:{code:"password_sign_in",hint:Em},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."}},Ta={"api.closedloop.sh":"eu.api.closedloop.sh","eu.api.closedloop.sh":"api.closedloop.sh"};function Zn(e,t){let n=typeof e.body.code=="string"?e.body.code:"",i=Object.prototype.hasOwnProperty.call(Oa,n)?Oa[n]:{code:"api_error"},o=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(i.code,o,g.Failure,t??i.hint),{keepPending:i.keepPending===!0})}function _o(){return new p("api_error","ClosedLoop AI sent an unexpected reply",g.Failure)}function bo(e,t,n){return mn(e.runtime,t,i=>{let o=i.read(n),r={request_id:Om()};return i.write(n,r),{intent:r,previous:o}})}function pe(e,t,n,i,o,r=!0){mn(e.runtime,t,s=>{if(JSON.stringify(s.read(n))!==JSON.stringify(i)){if(r)throw ct();return}o===null?s.remove(n):s.write(n,o)})}function Re(e,t,n,i){mn(e.runtime,t,o=>{if(JSON.stringify(o.read(n))!==JSON.stringify(i))throw ct()})}function Ea(e,t){return $i(t,e.runtime.platform)}function Ia(e,t,n){return`${Le(t,e.runtime.platform)} --browser${n!==void 0?` --region ${n}`:""}`}function wo(){return new p("missing_email","a valid email address is required",g.Usage,"Run `closedloop login you@example.com` with your email address.")}function Lm(e){try{return an(e.runtime)}catch(t){throw new p("invalid_api_url",t.message,g.Usage)}}function Dm(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,Xn);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 Ze={company:null,website_saved:!1,website_error:null,next:null,note:null},jm=`Your workspace has no company website yet, so nothing is analysed; an owner or admin sets it with: ${Z}`;async function Aa(e,t){try{return{...Ze,company:await Mi(e.runtime,v(e.runtime),t),website_saved:!0}}catch(n){if(!(n instanceof p))throw n;return n.message}}async function Fm(e,t,n){let{runtime:i,print:o,interactive:r}=e;if(t!==void 0){let u=await Aa(e,t);return typeof u=="string"?{...Ze,website_error:u,next:Z}:u}if(!n)return Ze;let s=v(i),a=await ze(i,s);if(a?.research.state!=="no_website")return Ze;let l=!1;try{l=(await M(i,s)).permissions.can_manage_team_settings}catch(u){if(!(u instanceof p))throw u}if(!l)return{...Ze,company:a,note:jm};if(!r)return{...Ze,company:a,next:Z};o.note(Gt);for(let u=0;u<xm;u++){let c=(await i.prompt(Im)).trim();if(!c)break;let m=await Aa(e,c);if(typeof m!="string")return m;o.note(`That website could not be saved: ${m}`)}return{...Ze,company:a,next:Z}}async function Um(e,t,n){let i={domains_saved:!1,domains_instructions:null,company:t.company};if(!n||t.note)return i;let o=v(e.runtime),r=t.company;if(!r||!In(r))return{...i,company:r};if(!e.interactive)return{domains_saved:!1,domains_instructions:Jt(r),company:r};try{let s=await ts(e,o,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{...i,company:r}}async function xa(e,t,n,i,o,r){let s=n.body;if(typeof s.accessToken!="string"||typeof s.refreshToken!="string"||typeof s.user?.email!="string")throw _o();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;ir(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},i,wn(s.sign_in_method),r);let c=s.new_account===!0,m=i==="email_code"&&c,d=await Fm(e,o.website,m),h=await Um(e,d,m),f={...d,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(o.website===void 0?"Website saved.":`Website saved. ${Gt}`),_.domains_saved&&S.push("Email domains saved."),(_.website_saved||_.domains_saved)&&_.company?S.push(Gi(_.company,Date.now())):_.note?S.push(_.note):_.next&&S.push(Pm),S.join(`
|
|
140
|
+
`)})}function Pa(e,t){if(e!==X.us&&e!==X.eu||typeof t!="string")return null;try{let n=new URL(e).hostname,i=Object.prototype.hasOwnProperty.call(Ta,n)?Ta[n]:null,o=new URL(t);return o.protocol!=="https:"||o.hostname!==i||o.port!==""||o.username!==""||o.password!==""?null:o.origin}catch{return null}}function Na(e,t){return e.status!==409||e.body.code!=="ACCOUNT_IN_OTHER_REGION"?null:Pa(t,e.body.api_url)}async function Wm(e,t,n){let i=Ge(e.runtime),o=i.last_email===t&&i.last_region==="eu"?["eu","us"]:["us","eu"],r=null;for(let s of o){let a=await qe(e.runtime,X[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. ${Xn}`);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 Hm(e,t){if(!e.interactive)throw new p("region_required","choose where the new account's data should live before signing up",g.Usage,Xn);let n=(await e.runtime.prompt(Am)).trim().toLowerCase();t();let i=n===""?"us":n;if(i!=="us"&&i!=="eu")throw new p("invalid_region","the region must be us or eu",g.Usage,Xn);return i}async function Mm(e,t,n,i,o){let r=o??bo(e,n,U);try{Re(e,n,U,r.intent);let s=t,a=await qe(e.runtime,s,"/api/cli/auth/code",{email:n});Re(e,n,U,r.intent);let l=Na(a,t);if(l!==null){let d=typeof a.body.region=="string"?a.body.region.toUpperCase():"other";e.print.note(`Your ClosedLoop AI account is in the ${d} region; signing in there.`),s=l,pn(e.runtime,n,He(s)),a=await qe(e.runtime,s,"/api/cli/auth/code",{email:n}),Re(e,n,U,r.intent)}if(a.status!==200)throw Object.assign(Zn(a),{api:s});let{challenge:u,expires_in:c}=a.body;if(typeof u!="string"||typeof c!="number")throw _o();let m={api_url:s,configured_api_url:t,email:n,challenge:u,...i!==void 0&&{requested_region:i},expires_at:new Date((e.runtime.now?.()??Date.now())+c*1e3).toISOString()};return pe(e,n,U,r.intent,m),m}catch(s){throw pe(e,n,U,r.intent,r.previous,!1),s}}function Gm(e,t,n){let i=bi(e.runtime,t,U);if(i===null||typeof i!="object"||i.email!==t||!n.includes(i.configured_api_url??i.api_url)||typeof i.challenge!="string"||!i.challenge.trim())throw new p("no_code_requested",`no code was requested for ${t} on this computer`,g.Failure,`Run \`closedloop login --email ${Ea(e,t)}\` first, then use the code from the email.`);let o=typeof i.expires_at=="string"?Date.parse(i.expires_at):NaN;if(!Number.isFinite(o)||o<=(e.runtime.now?.()??Date.now()))throw pe(e,t,U,i,null,!1),new p("code_expired","the saved sign-in code request has expired",g.Failure,zt);return i}async function Ra(e,t,n,i){return qe(e.runtime,t.api_url,"/api/cli/auth/verify",{challenge:t.challenge,code:n.trim(),...i.totp&&{totpCode:i.totp.trim()},...i.backupCode&&{backupCode:i.backupCode.trim()}})}async function La(e,t,n,i){let{runtime:o,interactive:r}=e,s=await Ra(e,t,n,i);if(Re(e,t.email,U,t),r&&s.status===401&&s.body.code==="TWO_FACTOR_REQUIRED"){let a=await o.promptSecret("Code from your authenticator app: ");s=await Ra(e,t,n,{...i,totp:a}),Re(e,t.email,U,t)}if(s.status!==200){let a=Object.assign(Zn(s),{api:t.api_url});throw a.keepPending||pe(e,t.email,U,t,null,!1),a}await xa(e,t.api_url,s,"email_code",i,{account:t.email,file:U,value:t})}async function Da(e,t,n,i,o){try{await o()}catch(r){if(!(r instanceof p)||!Nm.includes(r.code))throw r;let s=r.code==="sso_required";if(!e.interactive)throw new p(r.code,r.message,r.exitCode,`Run \`${Ia(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 Qn(e,r.api??i,n,t)}}async function ja(e,t,n,i,o,r){let{runtime:s,print:a,interactive:l}=e;await Da(e,n,i,t,async()=>{let u=await Mm(e,t,n,o,r),c=i.website!==void 0?` --website ${he(i.website.trim(),s.platform)}`:"",m=u.requested_region!==void 0?` --region ${u.requested_region}`:"",d={status:"code_sent",email:n,region:He(u.api_url),expires_at:u.expires_at,next:`closedloop login --email ${Ea(e,n)} --code <code>${m}${c}`};if(!l){a.result(d,h=>`We sent a sign-in code to ${h.email}. Finish with: ${h.next}`);return}a.result(d,h=>`We sent a sign-in code to ${h.email}.`),await La(e,u,await s.prompt("Code from the email: "),i)})}function qm(e,t,n){let i=bi(e.runtime,t,me);if(i===null||typeof i!="object"||(i.configured_api_url??i.api_url)!==n||typeof i.challenge!="string"||!i.challenge.trim()||typeof i.device_secret!="string"||!i.device_secret.trim()||typeof i.user_code!="string"||!i.user_code.trim()||typeof i.url!="string"||typeof i.interval!="number"||!Number.isFinite(i.interval)||i.interval<1)return null;let o=i.regional_hops??0;if(o!==0&&o!==1||o===0&&i.api_url!==n||o===1&&(i.api_url===n||Pa(n,i.api_url)!==i.api_url))return null;let r=typeof i.expires_at=="string"?Date.parse(i.expires_at):NaN;return!Number.isFinite(r)||r<=(e.runtime.now?.()??Date.now())?null:i}async function Jm(e,t,n){let i=bo(e,t,me);try{let o=await qe(e.runtime,n,"/api/cli/auth/device/start",t!==null?{email:t}:{});if(Re(e,t,me,i.intent),o.status!==200)throw Zn(o);let r=o.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 _o();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,i.intent,s),s}catch(o){throw pe(e,t,me,i.intent,i.previous,!1),o}}function Fa(e,t,n,i){throw pe(e,t,me,n,null,!1),new p("invalid_region_handoff","ClosedLoop AI returned an invalid regional sign-in response",g.Failure,i)}function Vm(e,t,n,i,o){let r=Na(i,n.api_url),s=i.body,a=e.runtime.now?.()??Date.now(),l=Date.parse(n.expires_at);(r===null||(n.regional_hops??0)!==0||s.region!==(r===X.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)&&Fa(e,t,n,o);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&&pn(e.runtime,t,He(r)),u}async function Bm(e,t,n,i,o){let r=Math.max(1,Math.ceil(i/n.interval)),s=n,a=()=>qe(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(Re(e,t,me,s),u.status===409&&u.body.code==="ACCOUNT_IN_OTHER_REGION"&&(s=Vm(e,t,s,u,o),u=await a(),Re(e,t,me,s),u.status===409&&u.body.code==="ACCOUNT_IN_OTHER_REGION"&&Fa(e,t,s,o)),u.status===200&&u.body.status==="signed_in")return{reply:u,pending:s};if(u.status!==200||u.body.status!=="pending"){let c=Zn(u,o);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 Qn(e,t,n,i){let{print:o,interactive:r}=e,s=Ia(e,i,n.region),a=`${s}${n.website!==void 0?` --website ${he(n.website.trim(),e.runtime.platform)}`:""}`,l=r?null:qm(e,i,t),u=l??await Jm(e,i,t),c={status:"approval_needed",email:i,region:He(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){o.result(c,y=>`Open ${y.url} , enter the code ${y.user_code}, and approve. Then run: ${a}`);return}let m=()=>{};if(r){let{runtime:y}=e,_=await y.copyToClipboard(u.user_code);o.result(c,S=>["Open this link, sign in, and enter this code:",` ${Se(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(`
|
|
141
|
+
`)),m=y.onEnter(()=>{y.openUrl(u.url)})}let d=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 Bm(e,i,u,r?d:Math.min(d,Tm),`Run \`${s}\` to start again.`);h=y.reply,u=y.pending}finally{f?.stop(),m()}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 xa(e,u.api_url,h,"browser",n,{account:i,file:me,value:u})}async function Ym(e,t,n,i){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 o=t??n.email;if(o!==void 0){let a=ut(o);if(a===null)throw wo();return a}let r=ut(Ge(e.runtime).last_email);if(r!==null)return r;if(i)return null;if(!e.interactive)throw wo();let s=ut(await e.runtime.prompt("Email: "));if(s===null)throw wo();return s}async function Km(e,t,n,i,o){let r=bo(e,t,U),s=()=>Re(e,t,U,r.intent),a,l;try{a=await Wm(e,t,s),a.kind==="none"&&!i&&(l=await Hm(e,s))}catch(m){throw pe(e,t,U,r.intent,r.previous,!1),m}a.kind==="account"&&(pn(e.runtime,t,a.region),e.print.note(`Signing in as ${t} (${a.region.toUpperCase()})`));let u=a.kind!=="none"?X[a.region]:l!==void 0?X[l]:o;if(!(i||a.kind==="company_sign_in"||a.kind==="account"&&a.browser)){await ja(e,u,t,n,l,r);return}pe(e,t,U,r.intent,r.previous),await Qn(e,u,n,t)}async function zm(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 i=Dm(e,n.region),o=n.browser===!0||n.sso===!0;if(o&&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=i!==void 0?X[i]:Lm(e),s=i!==void 0||!!e.runtime.env.CLOSEDLOOP_API_URL?.trim(),a=await Ym(e,t,n,o);if(a===null){await Qn(e,r,n,null);return}if(n.code!==void 0){let l=n.code,u=s?[r]:[X.us,X.eu];await Da(e,a,n,r,()=>La(e,Gm(e,a,u),l,n));return}if(!s){await Km(e,a,n,o,r);return}o?await Qn(e,r,n,a):await ja(e,r,a,n,i)}var Ua=(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,i)=>{await zm(t,n,i)})};function ei(e){try{return e()}catch(t){if(!(t instanceof p)||t.code!=="config_invalid")throw t;return null}}function Wa(e,t){let n=({active_account:i,...o})=>JSON.stringify({...o,active:i??null});n(t)!==n(e.settings())&&e.setSettings(t)}function Xm(e){return Y(e,{accounts:Nt(e),settings:!0},t=>{let n=[];for(let r of t.emails())dt(e,ei(()=>t.read(r,j)),r)!==null&&n.push(r),t.forget(r);let{pending_browser_login:i,...o}=t.settings();return Wa(t,{...o,active_account:null}),t.removeLeftovers(),n})}function Qm(e,t){let n=[...new Set([...Nt(e),...t!==null?[t]:[]])];return Y(e,{accounts:n,settings:!0},i=>{let o=null,r=[];for(let l of i.emails()){let u=dt(e,ei(()=>i.read(l,j)),l);l===t?(o=u,i.forget(l)):u!==null?r.push(l):(ei(()=>i.read(l,ke))!==null||ei(()=>i.read(l,se))!==null)&&(i.remove(l,ke),i.remove(l,se))}let{pending_browser_login:s,...a}=i.settings();return Wa(i,{...a,...a.active_account===t&&{active_account:null}}),i.removeLeftovers(),{session:o,others:r}})}var Ha=(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(i=>{if(i.all){let s=Xm(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:o,others:r}=Qm(n,Ne(n).email);if(o===null){t.result({status:"not_signed_in"},()=>"You were not signed in on this computer.");return}t.result({status:"signed_out",email:o.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(`
|
|
142
|
+
`))})};import{existsSync as ip}from"node:fs";var Xt='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',Ma="<file|json|->";async function Ga(e,t){if(t===void 0)throw new p("missing_rule","give the rule with --rule <file|json|->",g.Usage,Xt);return Fe(e,t,"rule",Xt)}function Zm(e,t,n,i){let o=typeof t.entity=="string"?t.entity:n.of,r=typeof t.count=="number"?t.count:0,s=[`${k(r)} ${G(o,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 ${G(o)}`:Wt(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,W)=>` ${Gn(b,_).padEnd(_)} ${y[W].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 m=Array.isArray(t.warnings)?t.warnings:[],d=Mt(t.stale);(m.length||d)&&s.push("",...d?[e.orange(`Warning: ${d}.`)]:[],...m.map(f=>e.orange(`Warning: ${Sn(f)}`)));let h=Tn(e,t.alternatives);return h.length&&s.push("",...h),s.push("",e.dim(i)),s.join(`
|
|
143
|
+
`)}async function ep(e,t){let n=await Ga(e,t),i=await $r(e,n),o=On(i.alternatives,i.count),r={...i,rule:n,...o?{ask:o}:{},next:`closedloop segments define <name> --rule ${Ma}`};e.print.result(r,s=>Zm(e.print.style,s,n,"Save it: closedloop segments define <name> --rule <the same rule>"))}function $o(e){let t=w(e)&&Array.isArray(e.includes)?e.includes.filter(w):[];return t.length?t.map(n=>{let i=n.newer_version===!0&&typeof n.current_version=="number"?`; version ${n.current_version} is newer`:"",o=n.archived===!0?"; archived":"";return`${String(n.name)} (version ${String(n.version)}${i}${o})`}).join(", "):null}async function tp(e,t,n){let i=await Ga(e,n.rule),o=await vr(e,t,i,n.description),r=String(o.id),s=ye(o),a={segment:o,rule:w(s.rule)?s.rule:i,next:`closedloop metrics preview --segment ${r} --calc count`};e.print.result(a,l=>{let u=typeof o.entity=="string"?o.entity:l.rule.of,c=je(s),m=c===null?"":`version ${c}, `,d=$o(s);return`Saved the segment "${String(o.name??t)}" (${m}${G(u)}).${d?`
|
|
144
|
+
It includes ${d}.`:""}
|
|
145
|
+
${e.print.style.dim(`Measure it: ${l.next}`)}`})}async function np(e){let n={segments:await kr(e),next:"closedloop metrics define <name> --segment <id> --calc <calc>"};e.print.result(n,i=>{let o=e.print.style;if(!i.segments.length)return`No segments yet. Define one: closedloop segments define <name> --rule ${Ma}`;let r=" ",s=new Map;for(let c of i.segments){let m=ye(c).includes;for(let d of Array.isArray(m)?m.filter(w):[]){let h=String(d.segment_id);s.set(h,[...s.get(h)??[],String(c.name)])}}let a=i.segments.map(c=>{let m=ye(c),d=je(m),h=d===null?"":` \xB7 version ${d}`,f=$o(m),y=s.get(String(c.id))??[];return[`${o.bold(String(c.name))}${o.dim(` \xB7 ${G(c.entity)}${h}`)}`,`${r}${Wr(m.rule,Ur(m))}`,...f?[`${r}includes ${f}`]:[],...y.length?[`${r}included in ${y.join(", ")}`]:[],E(o,r,c.id)].join(`
|
|
146
|
+
`)}),u=i.segments.some(c=>/is newer/.test($o(ye(c))??""))?`
|
|
147
|
+
${o.dim("A segment keeps the version it included; save its rule again to use the newer one.")}`:"";return`${a.join(`
|
|
148
|
+
|
|
149
|
+
`)}${u}
|
|
150
|
+
|
|
151
|
+
${o.dim("Measure one: closedloop metrics preview --segment <id> --calc count")}`})}var qa=(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."),i=`The rule: a JSON file, inline JSON, or - for standard input. ${Xt}.`;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>",i).option("--description <text>","What the segment is for.").action(async(o,r)=>tp(t,o,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>",i).action(async o=>ep(t,o.rule)),n.command("list").description("Your workspace's segments, each with its rule in words, the segments it includes, the ones that include it, and its id.").action(async()=>np(t))};var vo="closedloop metrics list",Ba="closedloop segments list",Qt='A calculation is count, or JSON such as {"fn":"sum","field":"customer.open_pipeline"} or {"fn":"ratio","numerator":{"fn":"count","where":{...}},"denominator":{"fn":"count"}}',ne=(e,t,n)=>new p(e,t,g.Usage,n),op=e=>/[\\/]/.test(e)||/\.[a-z0-9]{1,8}$/i.test(e),rp=e=>zi(ws(e),t=>t.text);async function sp(e,t){let n=t.trim();if(ar(n))return n.toLowerCase();if(n==="-"||/^[{[]/.test(n)||Ut(n)||ip(n)||op(n))return Fe(e,t,"segment",Xt);throw new p("invalid_id",`'${t}' is not a segment id, and not a rule (a JSON file, inline JSON or -)`,g.Usage,L(Ba))}async function Ya(e,t){if(t.formula!==void 0){if(t.calc!==void 0)throw ne("formula_and_calc","give --formula or --calc, not both",Vt);if(t.segment!==void 0)throw ne("formula_has_no_segment","a formula has no segment: it combines saved metrics, each over its own",Vt);return{segment:void 0,calc:rp(t.formula)}}if(t.segment===void 0&&t.calc!==void 0&&!/^[a-z_0-9]+$/i.test(t.calc.trim())){let r=await Fe(e,t.calc,"calc",Qt);if(we(r))return{segment:void 0,calc:r}}if(t.segment===void 0)throw ne("missing_segment","give the segment with --segment <id|file|json|->",L(Ba));if(t.calc===void 0)throw ne("missing_calc","give the calculation with --calc <calc>",Qt);if(t.segment.trim()==="-"&&t.calc.trim()==="-")throw ne("stdin_twice","only one of --segment and --calc can come from standard input");let n=await sp(e,t.segment),i=t.calc.trim(),o;if(i==="count")o={fn:"count"};else{if(/^[a-z_0-9]+$/i.test(i))throw ne("invalid_calc",`'${i}' is not a calculation`,Qt);o=await Fe(e,i,"calc",Qt)}return{segment:n,calc:o}}var Ka=async(e,t)=>t===void 0?null:typeof t!="string"?t:Cr(e,t),ti=e=>String(e).replace(/\s*\(?\b(failed|not_measured):\s*[a-z_]+\)?/g,"").trim();function za(e){let t={measured:"measured",rebuilt:"rebuilt from the history",not_available:"no value"};return(Array.isArray(e)?e.filter(w):[]).map(n=>{let i=typeof n.name=="string"?n.name:String(n.metric),o=typeof n.days_ago=="number"&&n.days_ago>0?` ${n.days_ago} days earlier (${Ee(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"?T(n.value):`no value${typeof n.reason=="string"&&ti(n.reason)?` (${ti(n.reason)})`:""}`;return` ${i}${o}: ${s}${typeof n.value=="number"?`, ${r}`:""}`})}async function ap(e,t,n){let i=await Pi(e,void 0,t),o=n.formula!==void 0?`--formula ${he(n.formula,e.runtime.platform)}`:"--calc <the same formula>",r={...i,calc:w(i.calc)?i.calc:t,next:`closedloop metrics define <name> ${o}`};e.print.result(r,s=>{let a=e.print.style,l=Ae(s.calc,s.value,s.numerator,s.denominator),u=[l!==null?`${l} on ${Ee(s.as_of)}.`:`No value: ${s.reason===void 0||s.reason===null?"an input has no value":ti(s.reason)}.`,...za(s.inputs)],c=Mt(s.stale);return c&&u.push(a.orange(`Warning: ${c}.`)),u.push("",a.dim(`Save it: ${s.next}`)),u.join(`
|
|
152
|
+
`)})}async function lp(e,t){let{segment:n,calc:i}=await Ya(e,t);if(n===void 0)return ap(e,i,t);let o=await Pi(e,n,i),r=await Ka(e,n),s=typeof n=="string"?n:"<the same segment>",a=On(o.alternatives,o.value),l={...o,segment:n,rule:r,calc:i,...a?{ask:a}:{},next:`closedloop metrics define <name> --segment ${s} --calc <calc>`};e.print.result(l,u=>{let c=e.print.style,m=typeof u.member_count=="number"?u.member_count:null,d=G(typeof u.entity=="string"?u.entity:u.rule?.of,m??2),h=m===null?"":u.calc.fn==="count"&&u.value===m?` ${d}`:` over ${k(m)} ${d}`,f=Ye(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&&m!==null&&typeof u.calc.field=="string"&&y.push(`${k(_)} of ${k(m)} have no value for ${u.calc.field}, so they are left out.`);let S=Array.isArray(u.warnings)?u.warnings:[],b=Mt(u.stale);return b&&y.push(c.orange(`Warning: ${b}.`)),y.push(...S.map(W=>c.orange(`Warning: ${Sn(W)}`))),y.push(...Tn(c,u.alternatives)),y.push("",c.dim(`Save it: closedloop metrics define <name> --segment ${s} --calc <the same calc>`)),y.join(`
|
|
153
|
+
`)})}async function up(e,t,n){let{segment:i,calc:o}=await Ya(e,n),r=await Sr(e,t,i,o,n.description),s=String(r.id),a=ye(r),l={metric:r,rule:await Ka(e,i),calc:w(a.calc)?a.calc:o,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,m=i===void 0?", each metric it uses pinned at its current version":typeof i=="string"?"":c?` with its own segment "${c}"`:" with its own segment, named after it",d=je(a),h=d===null?"":` (version ${d})`;return`Saved the metric "${String(r.name??t)}"${h}${m}.
|
|
154
|
+
`+e.print.style.dim(`It is measured every night. Measure a goal on it: ${u.next}`)})}function cp(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(o=>typeof t[o]=="string"&&!Number.isNaN(Date.parse(t[o]))?`${kn(o)} last updated ${N(t[o])}`:`${kn(o)} late`).join(", ")}function dp(e,t,n,i={}){let o=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)?V(`${n.as_of}T00:00:00Z`):N(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=je(i),c=l!==null&&u!==null&&l!==u,d=`${(c?typeof n.value=="number"?T(n.value):null:we(t)?Ae(t,n.value,n.numerator,n.denominator):Ye(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`${d} on ${r}${h}`;let f=cp(n);return`${d} on ${r}${h} \xB7 ${o.orange(`stale${f?`: ${f}`:a}`)}`}async function mp(e){let n={metrics:await Or(e),next:"closedloop goals metrics add <goal> --metric <id> --period month"};e.print.result(n,i=>{let o=e.print.style;if(!i.metrics.length)return"No metrics defined yet. Define one: closedloop metrics define <name> --segment <segment> --calc <calc>";let r=" ",s=new Map(i.metrics.map(l=>[String(l.id),String(l.name)]));return`${i.metrics.map(l=>{let u=ye(l),c=je(u),m=c===null?"":` \xB7 version ${c}`,d=typeof l.goals_using=="number"&&l.goals_using>0?l.goals_using:0,h=d?` \xB7 measured by ${d} goal metric${d===1?"":"s"}`:"";return[`${o.bold(String(l.name))}${o.dim(`${m}${h}`)}`,`${r}${we(u.calc)?`formula: ${Nn(u.calc,s)}`:vn(u.calc)}`,`${r}${dp(e,u.calc,l.latest_run,u)}`,E(o,r,l.id)].join(`
|
|
155
|
+
`)}).join(`
|
|
156
|
+
|
|
157
|
+
`)}
|
|
158
|
+
|
|
159
|
+
${o.dim("Measure a goal on one: closedloop goals metrics add <goal> --metric <id> --period month")}
|
|
160
|
+
`+o.dim("Delete one: closedloop metrics delete <id>")})}var ni=/^\d{4}-\d{2}-\d{2}$/,Ee=e=>typeof e=="string"&&ni.test(e)?V(`${e}T00:00:00Z`):String(e),Ja={measured:"measured that night",rebuilt:"computed now from the history"};async function pp(e,t,n,i){let o=I(t,"metric",vo);if(n===void 0||!ni.test(n))throw ne("invalid_day","--as-of must be a day before today, YYYY-MM-DD");if(i!==void 0&&!/^[1-9][0-9]*$/.test(i))throw ne("invalid_version","--metric-version must be a version number");let r=await Ni(e,o,n,i===void 0?void 0:Number(i)),a=typeof r.member_count=="number"&&r.value===r.member_count&&r.numerator===null&&!Array.isArray(r.inputs)&&typeof r.version=="number"?await Ar(e,o,r.version):null,l={...r,...a?{calc:a}:{},next:"closedloop metrics list"};e.print.result(l,u=>{let c=e.print.style,m=Ee(u.as_of);if(u.basis==="not_available")return`No value on ${m}: ${String(u.reason??"there is no history to read it from")}.`;let d=Array.isArray(u.inputs),h=(d?Ae({op:u.denominator===null?null:"divide"},u.value,u.numerator,u.denominator):null)??(typeof u.value=="number"?T(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)}`:"",_=d&&u.basis==="rebuilt"?"worked out now from the values of the metrics it uses":typeof u.basis=="string"&&Object.prototype.hasOwnProperty.call(Ja,u.basis)?Ja[u.basis]:String(u.basis),S=[`${h}${y} on ${m} (version ${String(u.version)}), ${_}.`,...d?za(u.inputs):[]];return u.basis==="rebuilt"&&!d&&S.push(c.dim("Read from the CRM history and the dated records; it is not stored.")),S.join(`
|
|
161
|
+
`)})}var gp=(e,t)=>new Date(Date.parse(`${e}T00:00:00Z`)-t*864e5).toISOString().slice(0,10),Tt=e=>typeof e=="number"&&Number.isFinite(e)?e:null,Va={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 hp(e,t,n){let i=n.days??"30";if(!/^[1-9][0-9]*$/.test(i))throw ne("invalid_days","--days must be a whole number of days, 1 or more");let o=n.every??"day";if(o!=="day"&&o!=="week")throw ne("invalid_step","--every must be day or week");if(n.metricVersion!==void 0&&!/^[1-9][0-9]*$/.test(n.metricVersion))throw ne("invalid_version","--metric-version must be a version number");let r=I(t,"metric",vo),s=await Rr(e,r),a=s.metric,l=s.versions.filter(w),u=n.metricVersion===void 0?je(ye(a)):Number(n.metricVersion),c=l.find(O=>O.version===u);if(!c||typeof c.id!="string")throw ne("unknown_version",`this metric has no version ${String(u)}`,"See its versions: closedloop metrics list");let m=typeof s.today=="string"&&ni.test(s.today)?s.today:null;if(!m)throw new p("api_error","The API did not say which day it is. Try again.",g.Failure);let d=o==="week"?7:1,h=new Map,f=null;for(let O of s.runs.filter(w))O.version_id!==c.id||O.basis!=="measured"||typeof O.as_of!="string"||(O.status==="ok"||O.status==="stale"?h.set(O.as_of,O):O.as_of===m&&(f=O));let y=[],_=null,S=null,b=0;for(let O=0;O<Number(i);O+=d){let oe=gp(m,O),re=h.get(oe);if(re){y.push({day:oe,value:Tt(re.value),numerator:Tt(re.numerator),denominator:Tt(re.denominator),basis:"measured",status:String(re.status),reason:typeof re.reason=="string"?re.reason:null});continue}if(oe===m){y.push(f?{day:oe,value:null,numerator:null,denominator:null,basis:"measured",status:String(f.status),reason:typeof f.reason=="string"?f.reason:null}:{day:oe,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 H;try{H=await Ni(e,r,oe,u??void 0)}catch(nt){if(!(nt instanceof p)||!Object.prototype.hasOwnProperty.call(Va,nt.code))throw nt;S={day:oe,reason:Va[nt.code]},b+=1;continue}y.push({day:oe,value:Tt(H.value),numerator:Tt(H.numerator),denominator:Tt(H.denominator),basis:String(H.basis),status:typeof H.status=="string"?H.status:null,reason:typeof H.reason=="string"?H.reason:null}),H.basis==="not_available"&&typeof H.history_from=="string"&&ni.test(H.history_from)&&(_=H.history_from)}y.reverse();let W=y.filter(O=>O.value!==null),ie=W[0],J=W[W.length-1],Rt=ie&&J&&ie!==J?{from:{day:ie.day,value:ie.value},to:{day:J.day,value:J.value},delta:J.value-ie.value}:null,tt=c.calc,oi={metric_id:r,name:a.name,version:u,today:m,time_zone:s.time_zone??null,every:o,days:y,change:Rt,history_from:_,complete:S===null,...S?{stopped:S,not_fetched:b}:{}};e.print.result(oi,O=>{let oe=e.print.style,re=(x,be,it)=>(we(tt)?Ae(tt,x,be,it):Ye(tt,x,be,it))??"no value",H=Math.max(...O.days.map(x=>Ee(x.day).length),0),nt=x=>x.basis==="measured"?x.status==="stale"?"measured, stale":"measured":"worked out from the history",Et=[`${String(O.name)} (version ${String(O.version)}), ${O.every==="week"?"weekly":"daily"} over the last ${i} days:`];for(let x of O.days){let be=x.reason?ti(x.reason):"",it=x.basis==="not_measured"?`not measured yet (${x.reason})`:x.value===null?`no value${be?`: ${be}`:""}`:`${re(x.value,x.numerator,x.denominator)} (${nt(x)})`;Et.push(`${Ee(x.day).padEnd(H)} ${it}`)}if(O.change){let x=O.change.delta,be=x>0?"+":x<0?"-":"",it=Ht(tt)?`${be}${Math.abs(x*100).toFixed(1)} points`:`${be}${T(Math.abs(x))}${O.change.from.value?` (${be}${Math.abs(x/O.change.from.value*100).toFixed(1)}%)`:""}`;Et.push("",`From ${re(O.change.from.value,null,null)} on ${Ee(O.change.from.day)} to ${re(O.change.to.value,null,null)} on ${Ee(O.change.to.day)}: ${it}.`)}return O.history_from&&Et.push(oe.dim(`No values can be worked out before ${Ee(O.history_from)}, where the history starts.`)),S&&Et.push(oe.orange(`Stopped at ${Ee(S.day)}: ${S.reason}; ${b} earlier day${b===1?"":"s"} without a measured value ${b===1?"was":"were"} not fetched.`)),Et.join(`
|
|
162
|
+
`)})}async function fp(e,t,n){let i=I(t,"metric",vo);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 ${i} --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:i},()=>"Nothing changed.");return}}let o=await Tr(e,i,n.reason),r=o.removed.filter(w),s={status:"deleted",id:i,archived_at:w(o.metric)?o.metric.archived_at:null,removed:r};e.print.result(s,a=>{let l=a.removed.map(c=>{let m=Array.isArray(c.kpi_ids)?c.kpi_ids.length:0;return`${String(c.title)} (${m} metric${m===1?"":"s"})`});return`Deleted the metric.${l.length?` It came off ${l.length} goal${l.length===1?"":"s"}: ${l.join(", ")}.`:""} Its history stays.`})}function Xa(e,t){let n="The segment: its id (from closedloop segments list), or its rule as a JSON file, inline JSON or - for standard input.",i=`The calculation: count, or JSON (inline, a file, or -). ${Qt}.`,o=`A formula over saved metrics instead of a segment and a calculation. ${Vt}`;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>",i).option("--formula <formula>",o).option("--description <text>","What the metric shows.").action(async(r,s)=>up(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>",i).option("--formula <formula>",o).action(async r=>lp(t,r)),e.command("list").description("Your workspace's metrics, each with its calculation, segment, latest measured value and id.").action(async()=>mp(t)),e.command("value").argument("<metric>","The metric's id, from closedloop metrics list.").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)=>pp(t,r,s.asOf,s.metricVersion)),e.command("history").argument("<metric>","The metric's id, from closedloop metrics list.").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)=>hp(t,r,s)),e.command("delete").argument("<metric>","The metric's id, from closedloop metrics list.").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)=>fp(t,r,s))}var Qa=(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.");Xa(n,t),Cs(n,t)};var yp="closedloop company --domains <domain,...>",wp=7200,Za=30,_p=new Set(["waiting","researching","retrying"]);async function et(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 Zt(e,t,n,i){let o=await $(e,t,"GET",n),r=o.body[i];if(o.status!==200||!Array.isArray(r))throw new p("api_error",`${n} could not be read`,g.Failure);return r}async function el(e,t){let[n,i,o,r,s,a,l]=await Promise.all([et(()=>Ke(e,t)),et(()=>Zt(e,t,"/api/cli/backfills?limit=100","backfills")),et(()=>Zt(e,t,"/api/cli/integrations","integrations")),et(()=>Zt(e,t,"/api/cli/ingest?limit=100","records")),et(()=>ht(e,t)),et(()=>Zt(e,t,"/api/user-goals?include=team","goals")),et(()=>Zt(e,t,"/api/team-members/all","members"))]),u={company:n,backfills:i,integrations:o,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 en=(e,t)=>`${k(e)} ${t}${e===1?"":"s"}`;function bp(e,t){let n=`${ji} ${qr}`,i=e.suggested_products.length;return i>0?`${Oe} (${k(i)} suggested by your company research). ${n}`:t&&t.research.state!=="ready"?`Suggestions appear once your company research is done: closedloop company --watch. ${n}`:`${En}. ${n}`}function tl(e,t){let{company:n,backfills:i,integrations:o,records:r,structure:s,goals:a,members:l}=e,u=s?s.structure.products:null,c=(i??[]).filter(b=>pt.has(fe(b))),m=r?{waiting:r.filter(b=>b.state==="waiting").length,processing:r.filter(b=>b.state==="processing").length}:null,d=o?o.filter(b=>b.connected):null,h=r?r.length>0:null,f=n?.research.state??null,y=n?.email_domains,_=d===null&&h===null?null:(d?.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?Z:null},{step:"research",title:"Company research",optional:!1,done:n?f==="ready":null,detail:n?yt(n,t):null,next:n&&f!=="ready"?f==="no_website"?Z:"closedloop company --watch":null},{step:"email_domains",title:"Email domains",optional:!1,done:n?y?y.confirmed:!0:null,detail:n?Hi(n):null,next:y&&!y.confirmed?yp:null},{step:"product_structure",title:"Product structure",optional:!1,done:u?u.length>0:null,detail:u?`${en(u.length,"product")} confirmed`:null,next:s&&u&&u.length===0?bp(s,n):null},{step:"source",title:"A source of customer conversations",optional:!1,done:_,detail:d&&d.length>0?d.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:i===null||_===null?null:_===!0&&c.length===0,detail:i===null?null:c.length>0?`${en(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?en(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?en(l.length,"member"):null,next:l&&l.length<=1?`In the app: Settings, then Team. ${D}`:null}];return{running:{research:f&&_p.has(f)?f:null,backfills:c,own_data:m},onboarding:S,instructions:n?Jt(n):null}}function $p(e){let t=e.running.own_data;return e.running.research!==null||e.running.backfills.length>0||!!(t&&t.processing>0)}function vp(e,t,n){let i=t.print.style,o=[],r=e.running.own_data,s=[...e.running.backfills.map(c=>` ${bn(c,n,i)}`),...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}`),o.push("Running now"),o.push(...s.length>0?s:[" Nothing."]),o.push("","Onboarding");for(let c of e.onboarding){let m=c.done===!0?i.green("\u2713"):c.done===!1?c.optional?i.dim("\u25CB"):i.orange("\u25CB"):i.dim("?"),d=c.optional?`${c.title} (optional)`:c.title,h=c.done===!0&&c.detail?i.dim(` \xB7 ${c.detail}`):"";o.push(` ${m} ${d}${h}`),c.done!==!0&&c.next&&o.push(` ${i.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 o.push("",l===0&&u===0?"Onboarding is complete.":[l>0?`${en(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&&o.push("",e.instructions),o.join(`
|
|
163
|
+
`)}async function Cp(e,t){let n=v(e.runtime),i=tl(await el(e.runtime,n),Date.now());if(t.watch){let o=e.print.spinner("Checking every 30 seconds\u2026");try{for(let r=0;r<wp&&$p(i);r+=Za)await e.runtime.sleep(Za*1e3),i=tl(await el(e.runtime,n),Date.now())}finally{o.stop()}}e.print.result(i,o=>vp(o,e,Date.now()))}var nl=(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=>Cp(t,n))};import{existsSync as kp,writeFileSync as Sp}from"node:fs";import{resolve as Op}from"node:path";var Tp=/^\d{4}-(0[1-9]|1[0-2])$/;async function Co(e,t){let n=await Je(e.runtime,t),i=await $(e.runtime,t,"GET",`/api/teams-billing/${n}/billing`);if(i.status!==200||typeof i.body.credits_used!="number")throw C(i,"api_error","Your usage could not be loaded. Try again.");return i.body}function Ap(){return new Date().toISOString().slice(0,7)}function il(e){let t=e??Ap();if(!Tp.test(t))throw new p("invalid_month",`'${t}' is not a month`,g.Usage,"Write it like 2026-10.");return t}function Rp(e,t){let n=k(e.credits_used),i=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}${i?` The period ends ${i}.`:""}`}let o=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 ${o} credits used this period (${a}%, ${e.plan_label} plan).`];return e.limit_reached?u.push(`The limit is reached: new conversations wait${i?` until ${i}`:" until the next period"}, or until you upgrade (closedloop plan upgrade).`):e.limit_enforced?(i&&u.push(`It resets ${i}.`),e.limit_reached_on&&u.push(`At this pace you reach the limit around ${N(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."),i&&u.push(`The period ends ${i}.`)),u.join(`
|
|
164
|
+
`)}async function Ep(e){let t=v(e.runtime),n=await Co(e,t),i={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(i,()=>Rp(n,e.print.style))}async function Ip(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 i=v(e.runtime),o=await Je(e.runtime,i),r=await $(e.runtime,i,"GET",`/api/teams-billing/${o}/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=>Dt(y.month)),m=a.months.map(y=>k(y.credits)),d=Math.max(...c.map(y=>y.length)),h=Math.max(...m.map(y=>y.length)),f=a.months.length>1?`
|
|
165
|
+
${l.dim(`Trend, oldest to newest: ${l.sparkline([...a.months].reverse().map(y=>y.credits))}`)}`:"";return a.months.map((y,_)=>`${c[_].padEnd(d)} ${m[_].padStart(h)} credits ${l.green(l.bar(u>0?y.credits/u:0,20))}`).join(`
|
|
166
|
+
`)+f})}async function xp(e,t){let n=il(t),i=v(e.runtime),o=await Je(e.runtime,i),r=await $(e.runtime,i,"GET",`/api/teams-billing/${o}/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 ${Dt(a.month)}.`:[`${Dt(a.month)}: ${k(a.total)} credits`,...a.sources.map(l=>` ${l.name}: ${k(l.credits)}`)].join(`
|
|
167
|
+
`))}async function Pp(e,t){let n=il(t.month),i=Op(t.output??`closedloop-ai-usage-${n}.csv`);if(kp(i)&&!t.force)throw new p("file_exists",`${i} already exists`,g.Usage,"Add --force to replace it, or pick another file with --output.");let o=v(e.runtime),r=await Je(e.runtime,o),s=await $(e.runtime,o,"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.");Sp(i,s.text,{mode:384});let a=s.text.trimEnd().split(`
|
|
168
|
+
`).length-1;e.print.result({status:"saved",month:n,path:i,rows:a},l=>`Saved ${Dt(l.month)}'s usage to ${l.path} (${l.rows} rows: date, integration, credits).`)}async function Np(e){let t=v(e.runtime),n=await Co(e,t),i={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(i,o=>{let r=o.credits_included===null?"no monthly limit; you pay for the credits you use":`${k(o.credits_included)} credits a month included${o.limit_enforced?"; new conversations wait once they are used":""}`;return[`${o.plan_label} plan: ${r}.`,"See usage: closedloop usage. Change plan: closedloop plan upgrade"].join(`
|
|
169
|
+
`)})}async function Lp(e){let t=v(e.runtime),n=await M(e.runtime,t),i=await Co(e,t),o=i.plan_url;if(!o)throw new p("no_plan_link","the plan page link is not available",g.Failure,`Open Settings, Plan & billing in the app. ${D}`);let r=n.permissions.can_manage_billing,s=r&&e.interactive?await e.runtime.openUrl(o):!1;e.print.result({status:s?"opened":"link",url:o,plan_label:i.plan_label,can_change_plan:r},a=>{let l=Se(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.
|
|
170
|
+
${D}`:`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}
|
|
171
|
+
${D}`})}var ol=(e,t)=>{let n=e.command("usage").description("Credits used this period; history, by source and export below.").action(async()=>Ep(t));n.command("history").description("Credits used per month, newest first.").option("--months <n>","How many months, up to 12.").action(async i=>Ip(t,i.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 i=>xp(t,i.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 i=>Pp(t,i))},rl=(e,t)=>{e.command("plan").description("Your plan and what it includes.").action(async()=>Np(t)).command("upgrade").description("Open Plan & billing to change your plan (payment happens in the browser).").action(async()=>Lp(t))};function At(e){return typeof e=="string"&&e!==""?e:null}function sl(e){let t=e;return!t||typeof t!="object"||!At(t.team_id)||!At(t.team_name)?null:{id:t.team_id,name:t.team_name,role:At(t.role),plan:At(t.plan)}}var cl=[["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 Dp(e){let t=e?.permissions??{};return Object.fromEntries(cl.map(([n])=>[n,t[n]===!0]))}var al={password:"a password",email_code:"an email code",google:"Google SSO",microsoft:"Microsoft SSO",okta:"Okta SSO",saml:"SAML SSO"};function ll(e,t){return e==="email_code"?al.email_code:e!=="browser"?null:t?`${al[t]} in the browser`:"the browser"}function jp(e){return typeof e=="string"&&!Number.isNaN(Date.parse(e))?e:null}function Fp(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 ul(e,t,n){if(e.status===403){let i=C(e,"forbidden","you cannot access this account or workspace"),o=i.code==="sso_required"?`Run \`${n}\` to sign in with your company account.`:["two_fa_verification_required","two_fa_setup_session_expired"].includes(i.code)?"Run `closedloop login` to sign in again and complete authentication.":"Ask a workspace owner or admin to check your access.";return new p(i.code,i.message,g.Failure,o)}if(e.status===404&&e.body.code==="USER_NOT_FOUND"){let i=C(e,"user_not_found","your saved account could not be found");return new p(i.code,i.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. ${D}`):new p("api_error","ClosedLoop AI could not load your account",g.Failure,"Try again in a moment.")}var dl=(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 i=v(n),o=`${Le(i.email,n.platform)} --browser`,r=await Ei(n,i,"/api/auth/profile");if(r.status!==200)throw ul(r,"profile",o);let s=await Ei(n,i,"/api/team-info");if(s.status!==200)throw ul(s,"workspace",o);let a=r.body.profile??{},l=sl(s.body.teamInfo),u=Array.isArray(s.body.allTeams)?s.body.allTeams.map(sl).filter(d=>d!==null):[],c=l?await ze(n,i):null,m={status:"signed_in",email:At(a.email)??i.email,name:At(a.full_name),two_factor:a.totp_enabled===!0,workspace:l,other_workspaces:u.filter(d=>d.id!==l?.id),permissions:Dp(s.body.teamInfo),company_research:c?c.research.state:null,analysis_waiting:c?c.analysis_waiting:null,region:typeof i.region=="string"?i.region:null,api_url:i.api_url,signed_in_at:jp(i.signed_in_at),sign_in_method:i.sign_in_method==="email_code"||i.sign_in_method==="browser"?i.sign_in_method:null,signed_in_with:wn(i.signed_in_with),session_expires_at:or(i),cli:{version:ae,latest_version:n.release?.latest??null,update_available:ki(n)!==null}};t.result(m,d=>[`Signed in as ${t.style.bold(d.name?`${d.name} <${d.email}>`:d.email)}${d.two_factor?" (two-factor on)":""}`,`Workspace: ${d.workspace?Fp(d.workspace):"none"}`,...d.other_workspaces.length?[`Also in: ${d.other_workspaces.map(h=>h.name).join(", ")}`]:[],(()=>{let h=cl.filter(([f])=>d.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&&qt(c)?[`Your data: ${qt(c)}`]:[],`Region: ${(d.region??"unknown").toUpperCase()} \xB7 API: ${d.api_url}`,d.signed_in_at?`Signed in here: ${R(d.signed_in_at)}${ll(d.sign_in_method,d.signed_in_with)?` with ${ll(d.sign_in_method,d.signed_in_with)}`:""}`:"Signed in here: time not recorded",...d.session_expires_at?[`Stays signed in while you use the CLI; ends ${R(d.session_expires_at)} if unused.`]:[]].join(`
|
|
172
|
+
`))})};var ml=[Ua,dl,Ha,sr,nl,ns,Yr,as,Zs,aa,Vs,ms,qa,Qa,Sa,wr,ol,rl,ps];function So(e){let t=e.indexOf("--");return(t===-1?e:e.slice(0,t)).includes("--json")}var hl={update:()=>{},stop:()=>{}},pl="\u280B\u2819\u2839\u2838\u283C\u2834\u2826\u2827\u2807\u280F";function Up(e,t){let n=t,i=0,o=()=>e.stderr(`\r\x1B[2K\x1B[2m${pl[i++%pl.length]} ${n}\x1B[22m`);o();let r=setInterval(o,80);return r.unref?.(),{update:s=>{n=s},stop:()=>{clearInterval(r),e.stderr("\r\x1B[2K")}}}function fl(e,t){return So(e)||!t.stdoutIsTTY?Bp(t):Yp(t)}function Wp(e){return e.hint===void 0?{code:e.code,message:e.message}:{code:e.code,message:e.message,hint:e.hint}}function ko(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}
|
|
173
|
+
`}function yl(e){return e.options.filter(t=>!t.hidden&&t.long!=="--help")}function wl(e){let t=e.aliases()[0],n=e.registeredArguments.map(o=>{let r=`${o.name()}${o.variadic?"...":""}`;return o.required?`<${r}>`:`[${r}]`}),i=yl(e).map(o=>`[${o.long&&o.short?o.flags.replace(`${o.short}, `,""):o.flags}]`);return[`${e.name()}${t?`|${t}`:""}`,...n,...i].join(" ")}var Hp=`Add --help after a command for details.
|
|
174
|
+
`,Mp=30,gl=6;function Gp(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 qp(e,t){return Math.max(0,...Gp(e,t).map(n=>t.displayWidth(n)).filter(n=>n<=Mp))}function Jp(e,t,n,i){if(i.displayWidth(e)<=t)return Ie.prototype.formatItem.call(i,e,t,n,i);if(!n)return` ${e}`;let o=(i.helpWidth??80)-gl,r=" ".repeat(gl);return` ${e}
|
|
175
|
+
${r}${i.boxWrap(n,o).replace(/\n/g,`
|
|
176
|
+
${r}`)}`}function Vp(e,t){let n=[];for(let o=e;o!==null;o=o.parent)n.unshift(o.name());let i=o=>({flags:o.flags,description:t.optionDescription(o)});return{command:n.join(" "),description:t.commandDescription(e),usage:t.commandUsage(e),arguments:t.visibleArguments(e).map(o=>({name:o.name(),required:o.required,variadic:o.variadic,description:t.argumentDescription(o)})),options:t.visibleOptions(e).map(i),globalOptions:t.visibleGlobalOptions(e).map(i),commands:t.visibleCommands(e).map(o=>({name:o.name(),usage:t.subcommandTerm(o),description:t.subcommandDescription(o),options:yl(o).map(i)}))}}function Bp(e){return{result:t=>e.stdout(ko(t)),error:t=>e.stderr(ko({error:Wp(t)})),note:()=>{},style:po({}),spinner:()=>hl,help:{showGlobalOptions:!0,subcommandTerm:wl,formatHelp:(t,n)=>ko(Vp(t,n))},writeHelp:t=>e.stdout(t),writeHelpOnError:()=>{}}}function Yp(e){let t=po({color:e.color,columns:e.columns});return{style:t,spinner:n=>e.animate?Up(e,n):hl,result:(n,i)=>{let o=i(n);e.stdout(o.endsWith(`
|
|
177
|
+
`)?o:`${o}
|
|
171
178
|
`)},error:n=>e.stderr(`${t.red(t.bold("error:"))} ${n.message}
|
|
172
179
|
${n.hint===void 0?"":`${t.dim(n.hint)}
|
|
173
180
|
`}`),note:n=>e.stderr(`${n}
|
|
174
|
-
`),help:{showGlobalOptions:!0,subcommandTerm:
|
|
175
|
-
${
|
|
176
|
-
`)}},readStdin:async(e=
|
|
181
|
+
`),help:{showGlobalOptions:!0,subcommandTerm:wl,padWidth:qp,formatItem:Jp,formatHelp:(n,i)=>{let o=Ie.prototype.formatHelp.call(i,n,i);return i.visibleCommands(n).length>0?`${o}
|
|
182
|
+
${Hp}`:o}},writeHelp:n=>e.stdout(n),writeHelpOnError:n=>e.stderr(n)}}var Oo="Run `closedloop --help` to see the commands and options.";function _l(e,t){let{print:n}=e,i=ae,o=new ot("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){o.outputHelp();return}throw new p("unknown_command",`unknown command '${r[0]}'`,g.Usage,Oo)});o.on("option:version",()=>{throw n.result({version:i},r=>r.version),new B(0,"commander.version",i)}),o.hook("preAction",()=>{let r=o.opts().as;typeof r=="string"&&(e.runtime.accountOption=r)});for(let r of t)r(o,e);return o}import{spawn as $l}from"node:child_process";import{existsSync as Kp}from"node:fs";import{createServer as zp}from"node:http";import{homedir as Xp}from"node:os";import{createInterface as To}from"node:readline/promises";import{Writable as Qp}from"node:stream";function Zp(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 ii(e,t,n){return new Promise(i=>{try{let o=$l(e,t,{stdio:["pipe","ignore","ignore"],windowsHide:!0}),r=!1,s=u=>{r||(r=!0,clearTimeout(l),i(u))},a=()=>{if(!r){s(!1);try{o.kill("SIGKILL")}catch{}}},l=setTimeout(a,5e3);o.on("error",()=>s(!1)),o.on("close",u=>s(u===0)),o.stdin.on("error",a),o.stdin.end(n)}catch{i(!1)}})}function Ao(e,t){return new Promise(n=>{try{let i=$l(e,t,{stdio:"ignore",detached:!0});i.on("error",()=>n(!1)),i.on("spawn",()=>{i.unref(),n(!0)})}catch{n(!1)}})}async function eg(e){return process.platform==="darwin"?ii("pbcopy",[],e):process.platform==="win32"?ii("powershell.exe",["-NoProfile","-NonInteractive","-Command","[Console]::InputEncoding = [System.Text.UTF8Encoding]::new($false); Set-Clipboard -Value ([Console]::In.ReadToEnd())"],e):process.env.WAYLAND_DISPLAY&&await ii("wl-copy",[],e)?!0:ii("xclip",["-selection","clipboard"],e)}async function tg(e){let t;try{t=new URL(e)}catch{return!1}return t.protocol!=="https:"&&t.protocol!=="http:"?!1:process.platform==="darwin"?Ao("open",[t.href]):process.platform==="win32"?Ao("rundll32",["url.dll,FileProtocolHandler",t.href]):Ao("xdg-open",[t.href])}var ng=10*1024*1024,bl={"Content-Type":"text/plain; charset=utf-8","Cache-Control":"no-store","Referrer-Policy":"no-referrer"};function ig(e){return new Promise((t,n)=>{let i=0,o=!1,r=zp((a,l)=>{let u=m=>{l.writeHead(m.status,m.location?{...bl,Location:m.location}:bl),l.end(m.body??"",()=>{m.last&&s()})},c;try{c=new URL(a.url??"/",`http://127.0.0.1:${i}`)}catch{u({status:400,body:"Bad request."});return}if(a.headers.host!==`127.0.0.1:${i}`){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=()=>{o||(o=!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}i=a.port,r.unref(),t({port:i,close:s})})})}function vl(){return{env:process.env,fetch:globalThis.fetch,homeDir:Xp(),platform:process.platform,stdinIsTTY:process.stdin.isTTY===!0,supportsHyperlinks:Zp(process.env),sleep:e=>new Promise(t=>setTimeout(t,e)),prompt:async e=>{let t=To({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 Qp({write:(i,o,r)=>r()}),n=To({input:process.stdin,output:t,terminal:!0});try{return(await n.question("")).trim()}finally{n.close(),process.stderr.write(`
|
|
183
|
+
`)}},readStdin:async(e=ng)=>{let t=[],n=0;for await(let i of process.stdin){if(n+=i.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(i)}return Buffer.concat(t).toString("utf8")},copyToClipboard:eg,openUrl:tg,now:()=>Date.now(),release:{latest:null},onEnter:e=>{let t=To({input:process.stdin});return t.once("line",()=>e()),()=>t.close()},listenLoopback:ig,fileExists:e=>Kp(e)}}var og="Run it again; if it keeps failing, report it with the output of `closedloop --version`.",rg="Add --help to the same command to see its commands.";function sg(e,t,n){if(e instanceof p)return e;if(e instanceof B){let i=e.code==="commander.excessArguments"?jr(t):null;if(i!==null)return $n(i.kind,n,i.option);if(e.code==="commander.help")return new p("missing_command","this command needs one of its commands",g.Usage,rg);let o=e.code==="commander.unknownOption"?"unknown_option":"usage_error";return new p(o,e.message.replace(/^error: /,""),g.Usage,Oo)}return new p("internal_error","something went wrong inside the CLI",g.Failure,og)}async function Cl(e,t,n=ml,i=vl()){let o=fl(e,t),r=t.stdoutIsTTY&&i.stdinIsTTY&&!So(e),s={...i,account:void 0,accountOption:void 0},a=_l({print:o,runtime:s,interactive:r},n),l=await ag(a,e,o,i.platform),u=tr(s,r);return u!==null&&t.stderr(`
|
|
177
184
|
${u}
|
|
178
|
-
`),l}async function
|
|
185
|
+
`),l}async function ag(e,t,n,i){try{return await e.parseAsync([...t],{from:"user"}),g.Success}catch(o){if(o instanceof B&&o.exitCode===0)return g.Success;let r=sg(o,t,i);return n.error(r),r.exitCode}}function kl(e=process.env){let n=e.FORCE_COLOR==="1"||process.stdout.isTTY===!0&&!e.NO_COLOR&&e.TERM!=="dumb";return{stdout:i=>{process.stdout.write(i)},stderr:i=>{process.stderr.write(i)},stdoutIsTTY:process.stdout.isTTY===!0,color:n,animate:n&&process.stderr.isTTY===!0&&!e.CI,columns:process.stdout.columns||void 0}}process.exitCode=await Cl(process.argv.slice(2),kl());
|