@closedloop-ai/cli 0.2.4 → 0.3.1
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 +176 -270
- package/dist/THIRD_PARTY_NOTICES.txt +24 -0
- package/dist/closedloop.js +156 -0
- package/package.json +31 -62
- package/CONTRIBUTING.md +0 -437
- package/LICENSE +0 -21
- package/docs/CLI_COMMANDS.md +0 -522
- package/packages/cli/dist/__tests__/api.test.js +0 -151
- package/packages/cli/dist/__tests__/api.test.js.map +0 -1
- package/packages/cli/dist/__tests__/cli.test.js +0 -37
- package/packages/cli/dist/__tests__/cli.test.js.map +0 -1
- package/packages/cli/dist/__tests__/config.test.js +0 -33
- package/packages/cli/dist/__tests__/config.test.js.map +0 -1
- package/packages/cli/dist/__tests__/errors.test.js +0 -109
- package/packages/cli/dist/__tests__/errors.test.js.map +0 -1
- package/packages/cli/dist/__tests__/performance.test.js +0 -221
- package/packages/cli/dist/__tests__/performance.test.js.map +0 -1
- package/packages/cli/dist/__tests__/setup.js +0 -50
- package/packages/cli/dist/__tests__/setup.js.map +0 -1
- package/packages/cli/dist/__tests__/team-simple.test.js +0 -38
- package/packages/cli/dist/__tests__/team-simple.test.js.map +0 -1
- package/packages/cli/dist/__tests__/test-factories.js +0 -148
- package/packages/cli/dist/__tests__/test-factories.js.map +0 -1
- package/packages/cli/dist/__tests__/validation.test.js +0 -124
- package/packages/cli/dist/__tests__/validation.test.js.map +0 -1
- package/packages/cli/dist/api/auth.js +0 -27
- package/packages/cli/dist/api/auth.js.map +0 -1
- package/packages/cli/dist/api/feedback.js +0 -70
- package/packages/cli/dist/api/feedback.js.map +0 -1
- package/packages/cli/dist/api/ingest.js +0 -60
- package/packages/cli/dist/api/ingest.js.map +0 -1
- package/packages/cli/dist/api/input.js +0 -60
- package/packages/cli/dist/api/input.js.map +0 -1
- package/packages/cli/dist/api/team.js +0 -59
- package/packages/cli/dist/api/team.js.map +0 -1
- package/packages/cli/dist/api/usage.js +0 -49
- package/packages/cli/dist/api/usage.js.map +0 -1
- package/packages/cli/dist/cli.js +0 -201
- package/packages/cli/dist/cli.js.map +0 -1
- package/packages/cli/dist/commands/config.js +0 -186
- package/packages/cli/dist/commands/config.js.map +0 -1
- package/packages/cli/dist/commands/feedback.js +0 -234
- package/packages/cli/dist/commands/feedback.js.map +0 -1
- package/packages/cli/dist/commands/ingest.js +0 -391
- package/packages/cli/dist/commands/ingest.js.map +0 -1
- package/packages/cli/dist/commands/input.js +0 -370
- package/packages/cli/dist/commands/input.js.map +0 -1
- package/packages/cli/dist/commands/insight.js +0 -234
- package/packages/cli/dist/commands/insight.js.map +0 -1
- package/packages/cli/dist/commands/list.js +0 -81
- package/packages/cli/dist/commands/list.js.map +0 -1
- package/packages/cli/dist/commands/status.js +0 -93
- package/packages/cli/dist/commands/status.js.map +0 -1
- package/packages/cli/dist/commands/submit.js +0 -225
- package/packages/cli/dist/commands/submit.js.map +0 -1
- package/packages/cli/dist/commands/team.js +0 -82
- package/packages/cli/dist/commands/team.js.map +0 -1
- package/packages/cli/dist/commands/usage.js +0 -168
- package/packages/cli/dist/commands/usage.js.map +0 -1
- package/packages/cli/dist/config/constants.js +0 -38
- package/packages/cli/dist/config/constants.js.map +0 -1
- package/packages/cli/dist/config/index.js +0 -84
- package/packages/cli/dist/config/index.js.map +0 -1
- package/packages/cli/dist/types/index.js +0 -3
- package/packages/cli/dist/types/index.js.map +0 -1
- package/packages/cli/dist/utils/config.js +0 -55
- package/packages/cli/dist/utils/config.js.map +0 -1
- package/packages/cli/dist/utils/errors.js +0 -70
- package/packages/cli/dist/utils/errors.js.map +0 -1
- package/packages/cli/dist/utils/validation.js +0 -133
- package/packages/cli/dist/utils/validation.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,279 +1,185 @@
|
|
|
1
1
|
# ClosedLoop AI CLI
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
`closedloop`: ClosedLoop AI on the command line, for people and for AI coding agents (Claude Code, Codex, Cursor).
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
[](https://opensource.org/licenses/MIT)
|
|
7
|
-
[](http://www.typescriptlang.org/)
|
|
8
|
-
[](https://nodejs.org/)
|
|
5
|
+
Requires Node.js 22.12 or newer.
|
|
9
6
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
**Product teams are drowning in scattered product feedback** from multiple channels:
|
|
13
|
-
- Support tickets scattered across Zendesk, Intercom, and email
|
|
14
|
-
- Sales and customer success call insights buried in transcripts
|
|
15
|
-
- User surveys and feedback forms in different tools
|
|
16
|
-
- Social media mentions and app store reviews
|
|
17
|
-
- Internal team discussions in Slack
|
|
18
|
-
|
|
19
|
-
**The result?** Critical customer product feedback get lost, patterns go unnoticed, and product decisions are made without data.
|
|
20
|
-
|
|
21
|
-
## ✨ How ClosedLoop AI CLI Solves This
|
|
22
|
-
|
|
23
|
-
**Transform chaos into clarity** with autonomous AI that:
|
|
24
|
-
|
|
25
|
-
- 🔍 **Discovers Hidden Patterns** - AI finds insights humans would miss across all your feedback sources
|
|
26
|
-
- ⚡ **Real-Time Analysis** - Get instant insights as new customer product feedback comes in
|
|
27
|
-
- 📊 **Strategic Intelligence** - Convert customer voice into actionable product decisions
|
|
28
|
-
- 🎯 **Revenue Impact** - Quantify the business value of each customer feedback
|
|
29
|
-
- 🔄 **Source Agnostic** - Works with any data source through flexible APIs
|
|
30
|
-
|
|
31
|
-
## 🚀 Quick Start
|
|
32
|
-
|
|
33
|
-
### Installation
|
|
34
|
-
|
|
35
|
-
```bash
|
|
36
|
-
# Install globally for CLI usage
|
|
7
|
+
```sh
|
|
37
8
|
npm install -g @closedloop-ai/cli
|
|
9
|
+
closedloop login
|
|
10
|
+
closedloop --help
|
|
38
11
|
```
|
|
39
12
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
-
|
|
81
|
-
-
|
|
82
|
-
-
|
|
83
|
-
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
-
|
|
87
|
-
-
|
|
88
|
-
-
|
|
89
|
-
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
-
|
|
93
|
-
-
|
|
94
|
-
-
|
|
95
|
-
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
### Environment Variables (Advanced)
|
|
154
|
-
|
|
155
|
-
**For developers and CI/CD systems**, you can also set your API key as an environment variable instead of using the config file:
|
|
156
|
-
|
|
157
|
-
```bash
|
|
158
|
-
# Set the API key as an environment variable
|
|
159
|
-
export CLOSEDLOOP_API_KEY="your-api-key-here"
|
|
160
|
-
|
|
161
|
-
# Now you can use the CLI without running 'cl config set'
|
|
162
|
-
cl input "Customer feedback here"
|
|
163
|
-
```
|
|
164
|
-
|
|
165
|
-
**When to use this:**
|
|
166
|
-
- **CI/CD pipelines**: Set the API key in your deployment environment
|
|
167
|
-
- **Docker containers**: Pass the API key as an environment variable
|
|
168
|
-
- **Server scripts**: When running automated scripts on servers
|
|
169
|
-
- **Multiple users**: When different users need different API keys on the same machine
|
|
170
|
-
|
|
171
|
-
**Note:** The CLI will automatically use the environment variable if it's set, otherwise it falls back to the config file.
|
|
172
|
-
|
|
173
|
-
## 💡 Simple Workflow
|
|
174
|
-
|
|
175
|
-
Here's how to get started with ClosedLoop AI CLI:
|
|
176
|
-
|
|
177
|
-
```bash
|
|
178
|
-
# 1. Set your API key
|
|
179
|
-
cl config set --api-key your-api-key-here
|
|
180
|
-
|
|
181
|
-
# 2. Set your product website (helps AI understand your product)
|
|
182
|
-
cl team website "https://yourproduct.com/feature"
|
|
183
|
-
|
|
184
|
-
# 3. Ingest customer feedback
|
|
185
|
-
cl ingest "The new dashboard is confusing and hard to navigate"
|
|
186
|
-
|
|
187
|
-
# 4. Check what feedback you've ingested
|
|
188
|
-
cl ingest
|
|
189
|
-
|
|
190
|
-
# 5. View AI analysis of your feedback
|
|
191
|
-
cl insight
|
|
192
|
-
|
|
193
|
-
# 6. Get details on specific feedback
|
|
194
|
-
cl insight <insight-id>
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
That's it! ClosedLoop AI will automatically analyze your feedback and provide actionable insights.
|
|
198
|
-
|
|
199
|
-
## 📚 Documentation
|
|
200
|
-
|
|
201
|
-
- **[Complete CLI Commands Reference](docs/CLI_COMMANDS.md)** - All commands, parameters, and examples
|
|
202
|
-
- **[Contributing Guide](CONTRIBUTING.md)** - Development and contribution guidelines
|
|
203
|
-
|
|
204
|
-
## 🎯 Why This Matters
|
|
205
|
-
|
|
206
|
-
### Before ClosedLoop AI CLI
|
|
207
|
-
- ❌ **Scattered feedback** across 5+ different tools
|
|
208
|
-
- ❌ **Manual analysis** taking 20+ hours per week
|
|
209
|
-
- ❌ **Missed patterns** and critical insights
|
|
210
|
-
- ❌ **Reactive decisions** based on incomplete data
|
|
211
|
-
- ❌ **Lost revenue** from unaddressed customer needs
|
|
212
|
-
|
|
213
|
-
### After ClosedLoop AI CLI
|
|
214
|
-
- ✅ **Unified intelligence** from all feedback sources
|
|
215
|
-
- ✅ **Automated analysis** with AI pattern recognition
|
|
216
|
-
- ✅ **Proactive insights** delivered in real-time
|
|
217
|
-
- ✅ **Data-driven decisions** with quantified business impact
|
|
218
|
-
- ✅ **Increased revenue** from better customer understanding
|
|
219
|
-
|
|
220
|
-
## 📊 Business Impact
|
|
221
|
-
|
|
222
|
-
- **80% faster** analysis time (from 20 hours to 4 hours per week)
|
|
223
|
-
- **40% faster** feature prioritization decisions
|
|
224
|
-
- **25% increase** in customer satisfaction scores
|
|
225
|
-
- **35% increase** in feature adoption rates
|
|
226
|
-
- **50% reduction** in failed feature launches
|
|
227
|
-
|
|
228
|
-
## 🧪 Development
|
|
229
|
-
|
|
230
|
-
### Prerequisites
|
|
231
|
-
|
|
232
|
-
- Node.js 16+
|
|
233
|
-
- npm or yarn
|
|
234
|
-
- TypeScript 5+
|
|
235
|
-
|
|
236
|
-
### Setup
|
|
237
|
-
|
|
238
|
-
```bash
|
|
239
|
-
# Clone the repository
|
|
240
|
-
git clone https://github.com/closed-loop-ai/mcp-client.git
|
|
241
|
-
cd mcp-client
|
|
242
|
-
|
|
243
|
-
# Install dependencies
|
|
244
|
-
npm install
|
|
245
|
-
|
|
246
|
-
# Build the CLI
|
|
247
|
-
npm run build:cli
|
|
248
|
-
|
|
249
|
-
# Run tests
|
|
250
|
-
npm test
|
|
13
|
+
The command is `closedloop`, with `cl` as a short form. Both use the same minified bundle; the source repository stays private.
|
|
14
|
+
|
|
15
|
+
`npm run build` writes the CLI as one minified file, `dist/closedloop.js`, with its one library (commander) inside; that file is all the package installs.
|
|
16
|
+
|
|
17
|
+
## Updates
|
|
18
|
+
|
|
19
|
+
- Every call sends the CLI's version (`X-ClosedLoop-CLI-Version`), and the API answers with the newest version and the oldest one it still serves (`X-ClosedLoop-CLI-Latest`, `X-ClosedLoop-CLI-Minimum`).
|
|
20
|
+
- In a terminal, a newer version prints one line on stderr after the answer, at most once a day per version. It stays quiet for JSON output, in CI, and with `NO_UPDATE_NOTIFIER=1` or `CLOSEDLOOP_NO_UPDATE_NOTIFIER=1`. An agent reads `cli` from `closedloop whoami --json` (`version`, `latest_version`, `update_available`). Nothing updates itself.
|
|
21
|
+
- A copy older than the minimum is refused with the error `upgrade_required`, which names the version to update to.
|
|
22
|
+
|
|
23
|
+
The npm package is `@closedloop-ai/cli`; users of the earlier `cl` CLI can update with the same install command.
|
|
24
|
+
|
|
25
|
+
## Commands
|
|
26
|
+
|
|
27
|
+
- `closedloop login` (also `closedloop signup`): sign in, or create an account on the Free plan, with a code sent to your email.
|
|
28
|
+
- In a terminal, `closedloop login` asks for the email and the code. Before creating a new account it asks for the data region: United States (US, the default) or European Union (EU). Existing accounts keep their region and are not asked to choose another one.
|
|
29
|
+
- `--region us|eu` explicitly selects where a new account's data should live. For an agent creating an account, two runs: `closedloop signup --email you@example.com --region eu` sends the code; `closedloop login --email you@example.com --region eu --code BCDFG-HJKLM` finishes. Use `us` for the United States. Without a region, an agent creating a new account is refused with `region_required` before an email code is requested; existing accounts can still sign in without the flag. Add `--totp <code>` (or `--backup-code <code>`) when the account uses two-factor authentication.
|
|
30
|
+
- `closedloop login --sso`: sign in through your browser instead, for workspaces that sign in with SSO (Entra, Okta). Add `--region eu` or `--region us` to choose the initial sign-in region. It prints a link and a short code; open the link, sign in to the app, type the code and approve. An existing account automatically completes sign-in in its home region, even when the command started elsewhere. In a terminal it also copies the code to your clipboard and opens the link when you press Enter, then waits; an agent runs the printed command again after you approve, keeping its `--region` flag.
|
|
31
|
+
- An email whose account is in the other region (US or EU) signs in there, and the session stays with that region.
|
|
32
|
+
- `--region` never moves an existing account. A saved sign-in and `closedloop whoami` show the account's actual region. If a connection fails after browser approval in another region, repeat the same printed command: the CLI retains that pending sign-in and its original expiry. A rejected or expired request starts again with a fresh link and code.
|
|
33
|
+
- A new account gets its own Free workspace unless the email was invited to one. One network can create three such accounts a day.
|
|
34
|
+
- A new account is asked for its company website right after the code (in a terminal), or takes `--website <url>` (an agent passes it with `--code`). ClosedLoop AI researches the company from it before it analyses anything you send in; without it, imports run but nothing becomes insights. A website that cannot be saved does not undo the sign-in: the result names it and says to run `closedloop company --website <url>`.
|
|
35
|
+
- Then its email domains: the domains of your colleagues' work addresses, which tell ClosedLoop AI who is a colleague and who is a customer. In a terminal it suggests them (from the website and your own address, never a personal one such as gmail.com) and you confirm or correct them, or type `none` and confirm that your company has none. An agent gets `domains_instructions`: plain sentences telling it what to ask its user and which command to run with their answer. Nothing in a new workspace is analysed until a person has confirmed them.
|
|
36
|
+
- The session is saved in `credentials.json` in the config folder. Tokens are stored as local JSON, not in an OS keychain; passwords are not saved. Unix directories use mode `0700` and files `0600`; Windows uses the account's local application-data folder and its inherited access permissions.
|
|
37
|
+
- The API checks the session on every call. The CLI renews it once it is past half its 30 days, so it stays signed in while you use it; if the API refuses it, the CLI renews once more, and otherwise asks you to sign in again.
|
|
38
|
+
- `closedloop whoami`: who you are signed in as, your workspace (role and plan), the other workspaces you belong to, the region, when and how this computer signed in (an email code, or Google, Microsoft, Okta or SAML SSO, or a password, in the browser), and when the sign-in ends if the CLI is not used.
|
|
39
|
+
- `closedloop status`: what is running now (company research, history imports with what is left and when they should finish, your own data still waiting or processing) and the onboarding checklist: company website, company research, email domains, product structure, a source, history imported, and the optional goal and team. Each open step names the command or the place in the app for it; a part the CLI could not read shows as unknown. No insights here: see `closedloop insights`.
|
|
40
|
+
- `--watch` checks every 30 seconds while anything is running, for up to 2 hours.
|
|
41
|
+
- `closedloop company`: your company website and how far the research of it has got (waiting to start, in progress, trying again, failed, done), and its email domains. Every source still imports before both are done, but nothing is analysed into insights until they are (the feedback portal is the one exception).
|
|
42
|
+
- `--website <url>` sets it, with the same checks as the app (the site must be readable). Setting it needs the right to change the workspace's settings (an owner or admin).
|
|
43
|
+
- `--domains acme.example,acme.example.org` confirms your colleagues' email domains and `--no-domains` confirms your company has none (every colleague then counts as a customer, so results are wrong). Only a person confirms them: in a terminal the CLI asks you to type `yes`; anyone else adds `--confirmed-by-user`, which an agent does only after its user said yes. Without it nothing is saved and the result's `instructions` say what to ask. A personal email domain (gmail.com and the like) is refused. Needs the right to change the workspace's settings.
|
|
44
|
+
- As JSON, a workspace whose domains still wait carries `instructions`: what an agent should ask its user.
|
|
45
|
+
- `--watch` checks every 30 seconds until the research is done or fails, for up to an hour.
|
|
46
|
+
- `closedloop ingest [text]`: sends your own data in (feedback, notes, survey answers, transcripts) for the pipeline to turn into insights.
|
|
47
|
+
- One record from the words given, from `--stdin`, or from a text file; `--title`, `--from`, `--email` (ties the insight to their company when it matches a CRM contact), `--customer`, `--date`, `--url`, `--id` (a record with an id already sent is skipped) and `--tag` describe it.
|
|
48
|
+
- Many records from `--file` with a CSV (a header row; a `content` column, and any of `title, reporter_name, reporter_email, customer, date, url, id, tags` with tags separated by `;`), a JSON object or list, or JSON Lines. Sent in batches of 100 records or 4 MB.
|
|
49
|
+
- `--wait` follows up to 100 records until they are processed and says how many insights they made.
|
|
50
|
+
- `ingest list` shows the latest records and their state (waiting, processing, done with N insights, failed); `ingest status <id>` one record.
|
|
51
|
+
- The data arrives through the workspace's CLI source, created on the first send; it shows in `integrations list`, where it can be paused, resumed or disconnected, and `insights search --source cli` finds what it produced. Sending needs the right to manage integrations.
|
|
52
|
+
- `closedloop insights search [words]`: searches insights as the Insights page does (every field, or one with `--in title|quote|reporter|account`), with `--source`, `--category`, `--severity`, `--last week|month|3months`, `--customer <email>`, `--account <domain>`, `--sort newest|oldest|severity|relevance`, `--limit` and `--page`. While you work on a goal (`goals use`) it reads only that goal's insights; `--goal <goal>` reads another goal's once and `--all` reads every insight.
|
|
53
|
+
- `closedloop insights show <id>`: one insight in full (summary, quote and its source-language original, who, where, feature and product area); `--transcript` adds the conversation.
|
|
54
|
+
- `closedloop goals`: your own goals, each a number to move by a date (onboarding completion to 60% by December) scoped to the insights it is about (customers in Germany and Austria, the checkout area).
|
|
55
|
+
- `goals list` shows yours as a tree (each sub-goal under its parent) with their state (draft, active, closed, archived), each led by its first key result (its target and newest value; for a closed goal the one it ended with) or, for a goal without one, the number typed when it was created with its current value and progress, and how many insights each matches; `--team` adds the ones teammates share, `--archived` the ones you deleted, `--state draft,active` only those states. `goals show <number or id>` gives one with its parent, its recorded values and, for a draft, what it still needs to be activated.
|
|
56
|
+
- `goals use <number or id>` picks the goal you work on (`--clear` drops it). `goals context [goal]` shows the deal blockers in its scope when it bears on new sales and the churn risks when it bears on retention, the newest of each and how many there are.
|
|
57
|
+
- `goals fields` lists what a goal can be scoped by in this workspace (product, area, feature, category, severity, outcome, source, country, account owner, company size, deal stage, lifecycle stage) with the values in use and how many insights each matches; `--field <name>` lists every value of one.
|
|
58
|
+
- `goals create --file goals.json` (or `--stdin`) creates one goal, a list, or `{ "goals": [...] }`, each as a draft; `goals example` prints a file to copy. Every goal is checked against the workspace and nothing is saved unless all pass; the answer names each problem and the allowed values. A goal with a `key` used before replaces that goal (a field left out takes its default), as far as its state allows: an active goal keeps its title, scope, parent and what its number counts, and a new target needs a `reason`. `--parent <goal>` makes each goal a sub-goal of another (or give `parent` in the file). `--dry-run` checks and counts without saving.
|
|
59
|
+
- `goals activate <goal> [--reason]` activates one of your drafts once it has a key result with a target (and every metric its target); its metrics' first period starts that day and their baselines are measured then. `goals close <goal> [--reason] [--yes]` closes an active goal: it is finished, keeps its results and history, and cannot be opened again; its sub-goals keep their own state.
|
|
60
|
+
- `goals set <number or id> <value> [--at <date>]` records the goal's number as measured; earlier values stay as its history, and a second value for the same moment replaces the first (`push` still works).
|
|
61
|
+
- `goals delete <number or id>` archives one of your drafts or active goals: its values, metrics and history stay, and it leaves your list (`--yes` skips the question). A closed goal stays as it is.
|
|
62
|
+
- `goals metrics list <goal>` shows a goal's metrics: each a saved metric (`metrics list`) at the version it was added with, as a key result or as an indicator of one key result, with its target, the baseline measured when the target was set, its newest value, and every finished period with the value measured the night after it ended and whether it met the target (`--ended` adds archived ones). `goals metrics add <goal> --metric <number|id|name> --period week|month|quarter|days [--days <n>] [--target <target>]` adds one as a key result (`--role indicator --key-result <kpi>` for an indicator, `--reason`, `--dry-run`); a target is one line: `>=1400`, `<=0.3`, `between 40 and 60`, a change from the baseline `+500` or `+10%`, or for a share `+5pts`. `goals metrics target <goal> <kpi> --target <target> --reason <text> [--period ...]` sets a new target and measures a new baseline (a draft's baselines are measured when it is activated, and until then its metrics show their current value as a preview); `goals metrics archive <goal> <kpi>` stops one (a key result takes its indicators) and keeps its history. `goals show` lists the metrics too. The old name `goals kpis` still works.
|
|
63
|
+
- `goals measure <goal>` measures a goal's metrics now instead of waiting for the night; `goals history <goal>` lists every change to the goal, its metrics and its values, with who made it, through what and why.
|
|
64
|
+
- `closedloop fields`: the named fields segments and metrics are written in (`customer.country`, `deal.value`, `insight.area`, a CRM field by its label such as `customer.crm["Property Type"]`). `fields list [--entity customer|deal|person|insight|context|conversation|usage]` shows every field that holds values, one line each: its key, type and label, by the kind of record it describes (a field no record has a value of is left out; the JSON answer carries each field's guide, built every night: its meaning, a preview of this workspace's values, the operators it takes and one example condition); `fields search <words>` finds fields by key, label or description, empty ones included. When two fields fit a request, ask the user which one they mean (the JSON answer carries an `ask` line saying so); `fields show <key>` gives one in full, read from this workspace's records at that moment: every value with how many records have it, the range of numbers or dates, how many records have none, and the fields it is often confused with (for example `deal.value` and `deal.annual_value`). Email and phone fields show only a pattern.
|
|
65
|
+
- `closedloop segments`: who is in scope, as a rule over named fields (`{"of":"customer","where":{"and":[...]}}`; `has` and `subtotal` for a customer's deals, people, insights or conversations). A date takes `within_last` (days) or `within_period` (`week`, `month`, `quarter` or `year` so far). Changes over time read the customer history: `{"moved":"customer.stage","into":"churned","within_period":"month"}` (optionally `"from"`) for customers who moved into a value, and `{"change":{"field":"customer.arr","within_last":90,"start":{"op":">","value":0}},"op":">","value":0}` for a number's value at the window's end minus its value at the start. A rule includes another saved segment of the same kind by its name, `{"segment":"Groups"}` (or its id): define *Groups* once and use it in every rule about groups; inside `has` it takes a segment of that kind (`{"has":"deal","where":{"segment":"Big deals"}}`). Saving keeps the version the included segment had then, so a number measured with it keeps its meaning; save the rule again to use a newer version. The rule is given with `--rule` as a JSON file, inline JSON, or `-` for standard input.
|
|
66
|
+
- `segments preview --rule <rule>` counts without saving: how many match, how many are left after each condition, a few examples, the insights in scope, and warnings (a value no record has, with the closest ones; a source that is late). For a field often confused with another it also gives the count with the other field; with `--json` an `ask` line tells an agent to ask which one is meant, showing both. Nothing is guessed: a rule that would take too long stops and says so.
|
|
67
|
+
- `segments define <name> --rule <rule> [--description <text>]` saves it as version 1; a segment in use is never changed in place, and other rules include it by its name. `segments list` numbers your segments with each rule in words, the segments it includes (and when one has a newer version) and the segments that include it.
|
|
68
|
+
- `closedloop metrics`: one calculation over one segment, or a formula over saved metrics, measured every night as one number.
|
|
69
|
+
- `metrics preview --segment <name|number|id|rule> --calc <calc>` measures it now: `--calc count`, or JSON such as `{"fn":"sum","field":"customer.open_pipeline"}` or a share `{"fn":"ratio","numerator":{...},"denominator":{...}}` (totals over the whole segment, then divided). It says how many members have no value for the field, when a source it reads is late, and for a field often confused with another the value with the other field (and, with `--json`, an `ask` line).
|
|
70
|
+
- `metrics define <name> --segment ... --calc ... [--description <text>]` saves it (a rule given inline is saved as a segment of its own, named after the metric, with a number when a segment already has that name; the metric's later inline rules are new versions of it); `metrics list` shows each with its calculation and its latest measured value, or why it is late (stale, with when the source was last updated); `metrics value <number|id> --as-of <day> [--metric-version <n>]` gives its value on a past day: the number measured that night, else computed now from the CRM history and the dated records (labelled rebuilt, not stored), else why there is none; a goal measures a saved metric with `goals metrics add --metric`, and keeps the version it was added with. `metrics history <number|id|name> [--days <n>] [--every day|week] [--metric-version <n>]` shows it day by day over the last 30 days (or the days given), oldest first: the value measured that night, else worked out from the history (labelled), else why there is none, and the change from the first value to the last; it asks nothing before the day the history starts, and when the API stops answering (the per-person limit, a busy workspace, a value too slow to work out) it still shows every stored day and says how many were not fetched. `metrics delete <number|id|name> [--reason <text>] [--yes]` deletes one: it comes off each of your goals that measures it (a key result takes its indicators) and its history stays; it is refused, with nothing changed, when a teammate's goal measures it, when it is an active goal's only key result, or when a formula uses it.
|
|
71
|
+
- `metrics preview --formula <formula>` and `metrics define <name> --formula <formula>` combine saved metrics with `+ - * /` and brackets, with no segment: `"[Accounts churned this quarter] / [Paying accounts]"`, `"#2 * 12"` (a number from the last `metrics list`) or `"([MRR] - [MRR]@90d) / [MRR]@90d"` (`@90d`: its value 90 days earlier); a metric is named by its exact name in brackets, its list number or its id. The preview shows each input's value and how it was obtained. Saving pins each metric at its current version; the formula is measured every night after the metrics it uses, and has no value (with the reason) when one of them has none or a division is by 0. The same formula as JSON (`{"fn":"formula","op":"divide","args":[{"metric":"<id>"},{"metric":"<id>","days_ago":90}]}`) works through `--calc` without `--segment`.
|
|
72
|
+
- With `--json` every answer is the API's, with the rule and calculation that scoped it and a `next` command.
|
|
73
|
+
- `closedloop integrations list`: every integration, in three groups: needing attention (with the reason), connected (with the last sync), and available to connect (with how, and the plan it needs). `--connected` or `--available` shows one side only.
|
|
74
|
+
- `closedloop integrations show <name>`: one integration's status, and what connecting it asks for.
|
|
75
|
+
- `closedloop integrations connect <name>`: connects it the way it connects.
|
|
76
|
+
- With an API key, it asks for the key without showing it, plus any settings (site, region). An agent pipes the key in with `--stdin` (a secret on a second line) and passes settings as `--field key=value`; a key is never accepted as an argument.
|
|
77
|
+
- In the browser (OAuth), it opens the app's connect page and waits until the connection shows; an agent gets the link and runs the command again to check.
|
|
78
|
+
- Asana, Attio, Discord, Front, Granola, Pipedrive, Plaud, Read.ai, Stripe, Zendesk and Microsoft Teams connect in the browser without signing in to the app. The CLI opens a one-time link (valid for up to 10 minutes) that names the workspace; Continue there sends your browser back to the CLI on this computer, which finishes the connect, so a link someone forwards connects nothing. The command waits up to 15 minutes for the connection: in a terminal it shows the link and opens it when you press Enter; otherwise (an agent, a pipe) it opens the browser itself and prints one answer at the end, so an agent runs it with a long timeout or in the background. Asana needs `--field asana_workspace_gid=<id>` and Zendesk `--field zendesk_subdomain=<subdomain>` (a terminal asks for them).
|
|
79
|
+
- When the browser cannot come back to this computer, it gives the app's connect page instead (`status: browser_needed`, exit 0), with a `reason`: `remote_browser` (over SSH, in a Codespace, dev container, Gitpod, Cloud Shell, a container or Kubernetes), `no_local_listener` (the CLI could not listen on 127.0.0.1) or `browser_not_opened` (the browser could not be opened). `--in-app` asks for the app's connect page directly, for example when the confirm page's next step does not load. Settings given with `--field` are not used there; the app asks for them.
|
|
80
|
+
- Integrations set up only in the app get a link to it.
|
|
81
|
+
- `--reconnect` connects again, for example with a new key.
|
|
82
|
+
- `closedloop integrations pause <name>` / `resume <name>`: stop syncing without disconnecting, and start again.
|
|
83
|
+
- `closedloop integrations disconnect <name>`: stops syncing after confirmation (`--yes` for agents); what was already imported stays.
|
|
84
|
+
- `closedloop backfills`: the workspace's history imports, running ones first: source, window, state (queued, importing, done, failed, cancelled, or stalled when nothing moved for 30 minutes), progress in days or records, and for a running one an estimate of when it finishes, from the pace so far. `backfills show <id or source> [--watch]` gives one in detail, and `--watch` follows it every 30 seconds. Progress covers importing; insights appear as the records are analysed.
|
|
85
|
+
- `closedloop usage`: credits used this period, against the limit on the Free plan (when it resets, and when this pace reaches it) or with the cost so far on Pay as you go. The numbers are the Plan & billing page's.
|
|
86
|
+
- `usage history [--months 12]`: credits per month, newest first.
|
|
87
|
+
- `usage by-source [--month 2026-10]`: a month's credits per integration or feature, largest first.
|
|
88
|
+
- `usage export [--month 2026-10] [--output file] [--force]`: saves the month's usage per day and integration as a CSV, never over an existing file unless `--force`.
|
|
89
|
+
- `closedloop plan`: your plan and what it includes; `plan upgrade` opens Plan & billing (payment happens in the browser), or gives an agent the link.
|
|
90
|
+
- `closedloop logout`: forgets the saved sign-in on this computer. Your other sessions, the web app included, stay signed in.
|
|
91
|
+
|
|
92
|
+
## How it looks
|
|
93
|
+
|
|
94
|
+
In a terminal the CLI writes for people: insights as short numbered blocks (`insights show 2` opens the second): the title, then who, where and when, then the start of the quote, in one column up to 100 characters wide; bars for usage, backfills and history; colour only where it means something (severity, problems, done), never blue; and a spinner while it waits. Colour and animation are off without a terminal, with `NO_COLOR`, or in a dumb terminal (`FORCE_COLOR=1` turns colour on). A program or an agent always gets plain JSON, so none of this reaches it.
|
|
95
|
+
|
|
96
|
+
## Rights
|
|
97
|
+
|
|
98
|
+
Every command does only what the person may do in the app, and the API decides: connecting, pausing, resuming and disconnecting integrations, and sending data in, need the right to manage integrations; reading insights, integrations, usage and backfills is open to every member. The CLI reads the person's rights from the API first, so someone without one is told before anything is read, sent or opened, and whom to ask; `plan upgrade` gives them the plan page without opening it. `whoami` lists what the person can do.
|
|
99
|
+
|
|
100
|
+
## Settings
|
|
101
|
+
|
|
102
|
+
- `CLOSEDLOOP_API_URL`: the API to talk to. Defaults to the production API (`https://api.closedloop.sh`); existing EU accounts sign in through the EU API automatically. Use `http://localhost:3017` for an API running on your machine. An explicit API selects the deployment for sign-up and skips the production region question. Combining it with `--region` is a usage error; unset it to choose a production region.
|
|
103
|
+
- `CLOSEDLOOP_CONFIG_DIR`: explicitly selects the folder for saved sign-in and settings; it takes precedence over every default.
|
|
104
|
+
- `XDG_CONFIG_HOME`: when explicitly set, retains the existing `$XDG_CONFIG_HOME/closedloop` location on every platform.
|
|
105
|
+
- Otherwise the default is `~/.config/closedloop` on Linux, `~/Library/Application Support/closedloop` on macOS, and `%LOCALAPPDATA%\closedloop` on Windows (falling back to the account's `AppData\Local` folder).
|
|
106
|
+
|
|
107
|
+
On the first use of a new native default, the CLI copies an existing
|
|
108
|
+
`~/.config/closedloop` folder to the new location if that location does not yet
|
|
109
|
+
exist. The copy is published only when complete; original files remain in place.
|
|
110
|
+
An existing destination is authoritative, including after logout, so an old
|
|
111
|
+
copy cannot silently restore a removed sign-in. Explicit directory overrides do
|
|
112
|
+
not trigger migration. Treat retained copies and backups as credentials too.
|
|
113
|
+
|
|
114
|
+
The earlier API-key CLI's `config.json` is retained during installation and
|
|
115
|
+
upgrades, but its team API key does not prove a person's identity: run
|
|
116
|
+
`closedloop login` to sign in. Existing person sessions from version 0.3.0
|
|
117
|
+
are preserved; team API keys are never converted into a person's session.
|
|
118
|
+
|
|
119
|
+
## Upgrade and troubleshoot
|
|
120
|
+
|
|
121
|
+
```sh
|
|
122
|
+
npm install --global @closedloop-ai/cli@latest
|
|
123
|
+
closedloop --version
|
|
124
|
+
cl --version
|
|
125
|
+
closedloop whoami --json
|
|
251
126
|
```
|
|
252
127
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
-
|
|
266
|
-
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
-
|
|
271
|
-
-
|
|
272
|
-
-
|
|
273
|
-
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
128
|
+
Upgrading replaces the installed package and preserves the separate config
|
|
129
|
+
folder. Uninstalling the package also leaves that folder intact; use
|
|
130
|
+
`closedloop logout` when you want this computer to forget its current sign-in.
|
|
131
|
+
Logout also cancels pending email and browser sign-in requests. It does not
|
|
132
|
+
revoke sessions on other computers or remove retained migration backups.
|
|
133
|
+
|
|
134
|
+
To return to an earlier compatible version, install its explicit version with
|
|
135
|
+
`npm install --global @closedloop-ai/cli@<version>`. The API can require a newer
|
|
136
|
+
minimum version; in that case follow the `upgrade_required` hint. If npm fails
|
|
137
|
+
during an upgrade, check both commands' versions and repeat the installation
|
|
138
|
+
before assuming the package changed successfully.
|
|
139
|
+
|
|
140
|
+
- `not_signed_in` or `session_expired`: run `closedloop login`; the CLI renews an existing session automatically when possible.
|
|
141
|
+
- `region_required`: choose `--region us` or `--region eu` before creating a new account in a noninteractive flow. `region_check_failed` means the CLI could not establish whether an account already exists; retry or choose a region explicitly. Existing accounts still sign in in their original region.
|
|
142
|
+
- `config_invalid`: back up the named JSON file and follow the recovery hint; the error never prints its contents. `closedloop logout` can remove damaged credentials and pending sign-in files.
|
|
143
|
+
- `config_write_failed`: check folder permissions and free disk space; a failed replacement keeps the previous saved file.
|
|
144
|
+
- `config_busy`: wait for other CLI commands to finish and retry. If a process stopped while holding a lock, inspect the named lock and confirm no CLI process is using it before removing it.
|
|
145
|
+
- `config_migration_failed`: use `CLOSEDLOOP_CONFIG_DIR` to select the original folder; the original files were kept.
|
|
146
|
+
- `session_changed`: a logout or newer sign-in took precedence while a request was running; repeat the command with the current sign-in. A delayed response does not restore a cancelled login.
|
|
147
|
+
- `unknown_number`: list again in the current account and workspace, then select its number; saved numbers are not portable across accounts or workspaces.
|
|
148
|
+
- `api_unreachable` or `api_timeout`: check connectivity and retry; inspect an ingestion failure's accepted-record hint before resending a partially sent batch.
|
|
149
|
+
- `rate_limited`: wait for the delay in the hint; the CLI does not automatically repeat a write.
|
|
150
|
+
|
|
151
|
+
On Windows desktop editions, Windows PowerShell 5.1 starts with the `Restricted`
|
|
152
|
+
execution policy, which refuses the `closedloop.ps1` launcher npm installs:
|
|
153
|
+
`closedloop` in PowerShell then fails with "running scripts is disabled on this
|
|
154
|
+
system". Run `closedloop.cmd` (or `cl.cmd`) in PowerShell, use Command Prompt,
|
|
155
|
+
where `closedloop` works as is, or allow local scripts for your account once with
|
|
156
|
+
`Set-ExecutionPolicy -Scope CurrentUser RemoteSigned`.
|
|
157
|
+
|
|
158
|
+
The JSON options (`--rule`, `--segment` and `--calc`) take inline JSON, a file path,
|
|
159
|
+
or `-` for standard input. On Windows, give a file. Windows PowerShell 5.1, any
|
|
160
|
+
PowerShell running `closedloop.cmd`, and Command Prompt remove the double quotes from
|
|
161
|
+
JSON typed in a command: in Windows PowerShell 5.1, `--rule '{"of":"customer"}'`
|
|
162
|
+
arrives as `{of:customer}` and fails with `invalid_rule`. Save the JSON in a UTF-8
|
|
163
|
+
file instead, for example with
|
|
164
|
+
`Set-Content rule.json '{"of":"customer"}' -Encoding UTF8` in PowerShell, then run
|
|
165
|
+
`closedloop segments preview --rule rule.json`. The commands the CLI suggests on
|
|
166
|
+
Windows pipe their JSON in, as in `'{"of":"customer"}' | closedloop segments preview --rule -`,
|
|
167
|
+
and write it in plain ASCII with other characters as `\u` escapes, because Windows
|
|
168
|
+
PowerShell 5.1 sends piped text to a program as ASCII.
|
|
169
|
+
|
|
170
|
+
Browser and clipboard helpers need the OS tools and a desktop session. On a
|
|
171
|
+
headless host or in a restricted session, follow the printed sign-in link and
|
|
172
|
+
code instead. Path simulations alone do not verify desktop behavior on every
|
|
173
|
+
OS edition; Windows Server guest coverage does not establish Windows desktop
|
|
174
|
+
coverage.
|
|
175
|
+
|
|
176
|
+
## Principles
|
|
177
|
+
|
|
178
|
+
- **The API holds the logic.** A command calls the ClosedLoop AI API and shapes the answer. The CLI itself only does what has to happen on the user's machine: signing in, reading local files, and printing.
|
|
179
|
+
- **Agents are first-class users.** Every command works without prompts, prints JSON when a program reads it, and fails with a stable code and a hint the agent can act on.
|
|
180
|
+
|
|
181
|
+
## Exit codes
|
|
182
|
+
|
|
183
|
+
`0` success, `1` failure, `2` invalid command or options. Piped output is JSON; add `--json` to request JSON in a terminal.
|
|
184
|
+
|
|
185
|
+
All addresses and company names shown in examples are invented placeholders.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
The ClosedLoop AI CLI bundles commander (https://github.com/tj/commander.js):
|
|
2
|
+
|
|
3
|
+
(The MIT License)
|
|
4
|
+
|
|
5
|
+
Copyright (c) 2011 TJ Holowaychuk <tj@vision-media.ca>
|
|
6
|
+
|
|
7
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
8
|
+
a copy of this software and associated documentation files (the
|
|
9
|
+
'Software'), to deal in the Software without restriction, including
|
|
10
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
11
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
12
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
13
|
+
the following conditions:
|
|
14
|
+
|
|
15
|
+
The above copyright notice and this permission notice shall be
|
|
16
|
+
included in all copies or substantial portions of the Software.
|
|
17
|
+
|
|
18
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
|
19
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
20
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
21
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
22
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
23
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
24
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|