kenso 0.0.0-stage → 0.1.8
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 +74 -2
- package/dist/kenso.js +12220 -0
- package/dist/operations.json +1 -0
- package/docs/README.md +74 -0
- package/docs/agents.md +124 -0
- package/docs/auth.md +108 -0
- package/docs/batch.md +152 -0
- package/docs/crm.md +111 -0
- package/docs/leads.md +214 -0
- package/docs/output-and-errors.md +154 -0
- package/docs/reference.md +31729 -0
- package/docs/studio.md +113 -0
- package/package.json +46 -4
- package/skill/kenso-cli/SKILL.md +141 -0
package/docs/README.md
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Kenso CLI
|
|
2
|
+
|
|
3
|
+
The Kenso CLI (`kenso`) is the command line for Kenso. It does bulk work in your Kenso workspace from files: add thousands of leads to a campaign, update or clean them up, import accounts and people, and build Studio templates. It is made for coding agents (Claude Code, Codex, Cursor) and the people who drive them: JSON on stdout, fixed exit codes, a dry run before every bulk write, and runs you can resume.
|
|
4
|
+
|
|
5
|
+
Every API operation a workspace member can call is also a command, such as `kenso campaigns list` or `kenso tags create`. So the CLI reaches what the Kenso MCP reaches, straight against `https://api.ken.so`.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
You need Node 22 or newer.
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm i -g kenso
|
|
13
|
+
kenso --version
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
To run it once without installing, use `npx kenso --version`.
|
|
17
|
+
|
|
18
|
+
## 60-second quickstart
|
|
19
|
+
|
|
20
|
+
1. Create an API key in app.ken.so at Settings > Integrations > API keys.
|
|
21
|
+
2. Save the key, pick a workspace, then plan and apply a CSV import:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
echo $KEY | kenso auth login --with-key
|
|
25
|
+
kenso workspace list --q acme
|
|
26
|
+
kenso workspace use 1234
|
|
27
|
+
kenso campaigns list --limit 5
|
|
28
|
+
kenso contacts import --campaign 812 --file leads.csv --dry-run
|
|
29
|
+
kenso contacts import --campaign 812 --file leads.csv --yes
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
`workspace list` and `workspace use` are only needed when your key reaches more than one workspace.
|
|
33
|
+
|
|
34
|
+
The dry run reads the file, maps the columns, checks every row, asks Kenso for a credit estimate, and writes nothing. It prints the plan, with the exact apply command in `next`:
|
|
35
|
+
|
|
36
|
+
```json
|
|
37
|
+
{"run":"r_tdi5cdnx","dryRun":true,"command":"contacts import","rows":4,"valid":2,"invalid":2,"chunks":1,"chunkSize":1000,"campaignId":812,"mapping":{"Email":"email","First Name":"firstName","Last Name":"lastName","Company":"companyName","Pain Point":"customVariables.pain_point"},"customVariableKeys":["pain_point"],"estimate":{"kind":"leads","totalMillicredits":2000,"availableMillicredits":500000,"sufficient":true},"credits":{"needed":2,"available":500,"sufficient":true},"next":"kenso contacts import --campaign 812 --file leads.csv --yes","plan":"/home/you/.kenso/runs/r_tdi5cdnx/plan.json","invalidRows":"/home/you/.kenso/runs/r_tdi5cdnx/invalid.csv"}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
The apply sends the rows and prints one summary. Every input row has one line in `results.csv`:
|
|
41
|
+
|
|
42
|
+
```json
|
|
43
|
+
{"run":"r_tdi5cdnx","rows":4,"ok":0,"created":1,"updated":0,"skipped":{"duplicate":1},"failed":0,"notFound":0,"invalid":2,"changed":1,"results":"/home/you/.kenso/runs/r_tdi5cdnx/results.csv","resultsNdjson":"/home/you/.kenso/runs/r_tdi5cdnx/results.ndjson","invalidRows":"/home/you/.kenso/runs/r_tdi5cdnx/invalid.csv","resumed":false,"durationMs":1840,"campaignId":812,"notSendable":0}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
This run exits 5 because two rows were invalid: open `invalid.csv` to see why, fix those rows, and import them again. Running the same command again is safe: an email that is already in the campaign comes back `updated` or `skipped`, never as a second lead.
|
|
47
|
+
|
|
48
|
+
## CLI or MCP
|
|
49
|
+
|
|
50
|
+
- **Use the CLI for volume:** anything that starts from a file, anything over about 50 records, anything you may need to rerun or resume. Records never pass through the model's context: the agent passes a file path and reads a short summary.
|
|
51
|
+
- **Use the MCP for conversation and single records:** look up a campaign, draft a reply, read a report. The MCP is at `https://mcp.ken.so/mcp`, signs in with OAuth and needs no install ([connection guide](https://ken.so/mcp)).
|
|
52
|
+
- **Both use the same API and the same permissions.** `kenso mcp call` reaches MCP-only tools, such as skills, from the shell.
|
|
53
|
+
|
|
54
|
+
## Docs
|
|
55
|
+
|
|
56
|
+
Every page is in the package and prints offline with `kenso docs <topic>`.
|
|
57
|
+
|
|
58
|
+
| Page | Topic | What it covers |
|
|
59
|
+
|---|---|---|
|
|
60
|
+
| [Authentication](auth.md) | `auth` | API keys, browser login, profiles, workspaces, `doctor` |
|
|
61
|
+
| [Leads](leads.md) | `leads` | Import, update, remove, pause, tag and export leads, with recipes |
|
|
62
|
+
| [Batch runs](batch.md) | `batch` | Dry run and apply, run folders, results files, resume, rate limits, `--each` |
|
|
63
|
+
| [Output and errors](output-and-errors.md) | `output-and-errors` | The output contract, error JSON, error codes, exit codes |
|
|
64
|
+
| [CRM (beta)](crm.md) | `crm` | Accounts, people, opportunities and CRM sync |
|
|
65
|
+
| [Studio (beta)](studio.md) | `studio` | Pull, push, validate, preview and fill templates |
|
|
66
|
+
| [Coding agents](agents.md) | `agents` | Claude Code, Codex, Cursor, the Agent Skill, environment variables, sandboxes |
|
|
67
|
+
| Command reference | `reference` | Every command with its flags, generated from the CLI at build time: `kenso docs reference`, or `docs/reference.md` in the installed package |
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
kenso docs
|
|
71
|
+
kenso docs leads
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
For machine-readable help, run `kenso describe`: it prints every command, its flags and its body schema as JSON.
|
package/docs/agents.md
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# Coding agents
|
|
2
|
+
|
|
3
|
+
The CLI is built to be driven by a coding agent: Claude Code, Codex, Cursor, Gemini CLI or anything else that runs shell commands. An agent learns it from the Agent Skill, `kenso --help` and `kenso describe`, and never needs these web pages.
|
|
4
|
+
|
|
5
|
+
## Set up an agent in three steps
|
|
6
|
+
|
|
7
|
+
1. Install the CLI where the agent runs: `npm i -g kenso` (Node 22 or newer).
|
|
8
|
+
2. Give it a key. Either set `KENSO_API_KEY` (and `KENSO_WORKSPACE_ID` when the key reaches several workspaces) in the agent's environment, or log in once on that machine:
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
echo $KEY | kenso auth login --with-key
|
|
12
|
+
kenso workspace use 1234
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
3. Install the Agent Skill, so the agent knows the rules before its first command:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
kenso skill install
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## The Agent Skill
|
|
22
|
+
|
|
23
|
+
`kenso skill install` writes the skill `kenso-cli` (one `SKILL.md`) where each agent looks for skills:
|
|
24
|
+
|
|
25
|
+
| Agent | `--client` | Folder |
|
|
26
|
+
|---|---|---|
|
|
27
|
+
| Claude Code | `claude-code` | `~/.claude/skills/kenso-cli/` |
|
|
28
|
+
| Codex | `codex` | `~/.agents/skills/kenso-cli/` |
|
|
29
|
+
| Cursor | `cursor` | `~/.cursor/skills/kenso-cli/` |
|
|
30
|
+
|
|
31
|
+
Without `--client`, it installs for the agent that runs it when that is Claude Code, Codex or Cursor (detected from the environment, see below), else for all three. It prints what it wrote:
|
|
32
|
+
|
|
33
|
+
```json
|
|
34
|
+
{"installed":[{"client":"claude-code","path":"/home/you/.claude/skills/kenso-cli/SKILL.md","status":"created"}]}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
kenso skill install --client claude-code
|
|
39
|
+
kenso skill install --client all
|
|
40
|
+
kenso skill install --dir .claude/skills/kenso-cli
|
|
41
|
+
kenso skill install --force
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
- `--dir` writes the skill into that folder instead, for example a project's own skills folder. Name the folder `kenso-cli`, as agents expect the folder to match the skill name.
|
|
45
|
+
- A different `SKILL.md` already in a target folder is replaced only with `--force`; without it the command exits 7, writes nothing, and its `next` adds `--force`. The same file is left alone (`"status":"unchanged"`).
|
|
46
|
+
- `--dry-run` writes nothing and exits 0. It prints each target's status (`replaced` marks a different file there) and a `next` that applies it, with `--force` when a file would be replaced.
|
|
47
|
+
- Cursor also reads `~/.claude/skills/` and `~/.agents/skills/`, so after a default install for all three it may list `kenso-cli` more than once. To avoid that, install with `--client cursor` only, or remove the copies you do not need.
|
|
48
|
+
|
|
49
|
+
The skill also ships in the Kenso plugin for Claude Code (`Ken-Technology/ken-ai-plugin`), so `npx skills add Ken-Technology/ken-ai-plugin` installs it along with the MCP skills. To read it without installing, open `skill/kenso-cli/SKILL.md` in the package.
|
|
50
|
+
|
|
51
|
+
## Rules the skill teaches
|
|
52
|
+
|
|
53
|
+
- **Data is on stdout, as JSON when piped.** Progress, warnings and errors are on stderr. Parse stdout; read stderr when the exit code is not 0.
|
|
54
|
+
- **Plan, then apply.** Run a write with `--dry-run`, read the plan, then run the exact command in its `next` (the same command with `--yes`).
|
|
55
|
+
- **Pass files, not rows.** Never paste a CSV into the conversation. Write the file, pass its path with `--file`, and read the summary.
|
|
56
|
+
- **Read failures only.** If the summary's `failed`, `notFound` and `invalid` are 0, the work is done. Otherwise read just the problem rows from `results.csv` or `invalid.csv`.
|
|
57
|
+
- **Follow `next`.** Every error with a known fix carries the exact command to run.
|
|
58
|
+
- **Branch on the exit code:** 0 ok, 2 fix the command, 3 fix the key, 5 some rows failed, 6 wait or raise a limit, 130 resume.
|
|
59
|
+
- **Look things up, do not guess.** `kenso describe <resource> <action>` prints any command's flags and body schema as JSON.
|
|
60
|
+
|
|
61
|
+
## The token-saving pattern
|
|
62
|
+
|
|
63
|
+
Adding 5,000 leads through the MCP sends every lead through the model, hundreds of thousands of tokens. Through the CLI it is two commands and two short JSON objects:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
kenso contacts import --campaign 812 --file leads.csv --dry-run
|
|
67
|
+
kenso contacts import --campaign 812 --file leads.csv --yes
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Then, only if the summary shows failures:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
grep -E '^[0-9]+,(failed|not_found|invalid),' ~/.kenso/runs/r_tdi5cdnx/results.csv | head -50
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Environment variables
|
|
77
|
+
|
|
78
|
+
| Variable | What it does |
|
|
79
|
+
|---|---|
|
|
80
|
+
| `KENSO_API_KEY` | The API key. Wins over any saved profile |
|
|
81
|
+
| `KENSO_WORKSPACE_ID` | The workspace for every command. `--workspace` wins over it |
|
|
82
|
+
| `KENSO_PROFILE` | The saved profile to use. `--profile` wins over it |
|
|
83
|
+
| `KENSO_API_URL` | The API, default `https://api.ken.so` |
|
|
84
|
+
| `KENSO_CONFIG_DIR` | Where the config file and cache live, default `~/.kenso` |
|
|
85
|
+
| `KENSO_RUNS_DIR` | Where run folders live, default `<config dir>/runs` |
|
|
86
|
+
| `KENSO_MCP_URL` | The MCP for `kenso mcp`, default `https://mcp.ken.so/mcp` |
|
|
87
|
+
|
|
88
|
+
## Cloud sandboxes
|
|
89
|
+
|
|
90
|
+
Cloud agents (Claude Code on the web, Codex cloud, Cursor background agents and similar) often run with a network allowlist. Allow:
|
|
91
|
+
|
|
92
|
+
- `api.ken.so`: every command
|
|
93
|
+
- `mcp.ken.so`: only `kenso mcp tools` and `kenso mcp call`
|
|
94
|
+
- `registry.npmjs.org`: to install the CLI with npm
|
|
95
|
+
|
|
96
|
+
Store the key as the platform's secret and expose it as `KENSO_API_KEY`. Run `kenso doctor` as the first command: it checks Node, the config, that the API is reachable and that the key works, and exits 0 when all four pass.
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
kenso doctor
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Point the run folders at the workspace if the home folder does not persist between sessions: `KENSO_RUNS_DIR=./.kenso-runs`. Keep that folder out of git.
|
|
103
|
+
|
|
104
|
+
## How Kenso knows an agent ran a command
|
|
105
|
+
|
|
106
|
+
The CLI checks these environment variables, in this order: `CLAUDECODE` and `CLAUDE_CODE_ENTRYPOINT` (Claude Code), `CODEX_SANDBOX` and `CODEX_THREAD_ID` (Codex), `CURSOR_AGENT` (Cursor), `GEMINI_CLI` (Gemini CLI), `OPENCODE` (OpenCode), then `AI_AGENT`, a free-form name any other agent can set. When one is set:
|
|
107
|
+
|
|
108
|
+
- every request carries `X-Ken-Client: Kenso CLI via <agent>`, and writes carry `X-Ken-Actor: agent`, so Kenso shows the change as made by you through that agent;
|
|
109
|
+
- help ends with a short "For agents" block;
|
|
110
|
+
- browser login never opens a browser itself: it prints the link for you to open.
|
|
111
|
+
|
|
112
|
+
`kenso doctor` shows the agent it detected.
|
|
113
|
+
|
|
114
|
+
## MCP-only tools
|
|
115
|
+
|
|
116
|
+
A few Kenso MCP tools have no API route, such as the skills Kenso's own agents use. Reach them from the shell with the same key:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
kenso mcp tools
|
|
120
|
+
kenso mcp call skills --operation list
|
|
121
|
+
kenso mcp call contacts --operation list --args '{"limit":5}'
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
`mcp call` prints the tool's structured result, else its text parsed as JSON, else the text. A tool error exits 1 with `mcp_tool_error`.
|
package/docs/auth.md
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# Authentication
|
|
2
|
+
|
|
3
|
+
The CLI calls the Kenso API with a Kenso API key (`ken_` plus 43 characters). It sends the key as `Authorization: Bearer <key>` and never prints it, logs it or puts it in an error.
|
|
4
|
+
|
|
5
|
+
## Log in with an API key
|
|
6
|
+
|
|
7
|
+
1. In app.ken.so, go to Settings > Integrations > API keys and create a key.
|
|
8
|
+
2. Pipe it to `kenso auth login --with-key`. The key is read from stdin, never from a flag, so it stays out of your shell history and process list.
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
echo $KEY | kenso auth login --with-key
|
|
12
|
+
kenso auth login --with-key --profile client-a --workspace 1234 < key.txt
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
The CLI checks the key with `GET /v1/me`, then saves it to `~/.kenso/config.json` (file mode 0600, folder mode 0700). It prints:
|
|
16
|
+
|
|
17
|
+
```json
|
|
18
|
+
{"profile":"default","email":"you@acme.io","workspaceId":"1234","workspaceCount":1}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
When the key reaches several workspaces and you did not pass `--workspace`, no workspace is saved and the output carries `"next":"kenso workspace list --q <name>"`. A key in the wrong format exits 2 (`invalid_key_format`). A key Kenso rejects exits 3.
|
|
22
|
+
|
|
23
|
+
## Browser login
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
kenso auth login
|
|
27
|
+
kenso auth login --no-browser --device-name "build box"
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Browser login is rolling out. Until it is on for your account, `kenso auth login` answers `browser_login_unavailable` (exit 3) with `"next":"kenso auth login --with-key"`: use an API key as above.
|
|
31
|
+
|
|
32
|
+
When your workspace has it, the CLI prints a link and a code on stderr. You open the link, check the code and approve. The CLI then saves a new API key to the profile, the same kind you create in the app: it shows in your API key list, where you can revoke it. The CLI opens the browser itself only when no coding agent is detected and the machine has a display. An agent prints the link and asks you to open it. The login expires after 10 minutes (`login_expired`, exit 3), and a denied login exits 3 with `access_denied`.
|
|
33
|
+
|
|
34
|
+
## CI, scripts and agents: use the environment
|
|
35
|
+
|
|
36
|
+
You do not need to log in on a machine that has the key in its environment:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
export KENSO_API_KEY=ken_...
|
|
40
|
+
export KENSO_WORKSPACE_ID=1234
|
|
41
|
+
kenso auth status
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
`KENSO_API_KEY` wins over any saved profile. Store it as a secret in your CI or agent platform, never in a file in the repo.
|
|
45
|
+
|
|
46
|
+
## Workspaces
|
|
47
|
+
|
|
48
|
+
A key reaches every workspace its user belongs to. Each command runs in one workspace, picked in this order:
|
|
49
|
+
|
|
50
|
+
1. `--workspace <id>` on the command
|
|
51
|
+
2. `KENSO_WORKSPACE_ID`
|
|
52
|
+
3. the workspace saved in the profile (`kenso workspace use`)
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
kenso workspace list --q acme
|
|
56
|
+
kenso workspace use 1234
|
|
57
|
+
kenso campaigns list --workspace 5678
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
`workspace list` prints one page and says on stderr when there are more: add `--all` for every page. A command that needs a workspace when none is picked fails with `workspace_required` (exit 2) and `"next":"kenso workspace list --q <name>"`.
|
|
61
|
+
|
|
62
|
+
## Profiles
|
|
63
|
+
|
|
64
|
+
A profile holds a key, a workspace, an optional API URL and the email. Use one per client or per key:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
kenso auth login --with-key --profile client-a < client-a-key.txt
|
|
68
|
+
kenso campaigns list --profile client-a
|
|
69
|
+
KENSO_PROFILE=client-a kenso auth status
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
The profile is `--profile`, else `KENSO_PROFILE`, else the active one (the last one you logged in to).
|
|
73
|
+
|
|
74
|
+
## Check the setup
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
kenso auth status
|
|
78
|
+
kenso doctor
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
`auth status` prints who the key belongs to and when it expires, and warns on stderr when that is within 14 days:
|
|
82
|
+
|
|
83
|
+
```json
|
|
84
|
+
{"email":"you@acme.io","workspaceCount":3,"activeWorkspace":{"id":"1234","name":"Acme"},"keyExpiresAt":"2027-01-31T00:00:00Z","keySource":"profile","profile":"default"}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
`doctor` prints the version, Node, the config folder, the profile, the key source, the API URL, the workspace and the detected agent, then four checks: `node_version`, `config_permissions`, `api_reachable` and `key_valid`. It exits 0 when every check passes, 3 when the key is missing or rejected, else 1.
|
|
88
|
+
|
|
89
|
+
## Log out
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
kenso auth logout
|
|
93
|
+
kenso auth logout --profile client-a
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
This removes the profile and its key from this machine. The key itself stays valid until you revoke it at Settings > Integrations > API keys.
|
|
97
|
+
|
|
98
|
+
## Where things live
|
|
99
|
+
|
|
100
|
+
| What | Default | Override |
|
|
101
|
+
|---|---|---|
|
|
102
|
+
| Config file with profiles and keys | `~/.kenso/config.json` | `KENSO_CONFIG_DIR` |
|
|
103
|
+
| Run folders | `~/.kenso/runs/` | `KENSO_RUNS_DIR` |
|
|
104
|
+
| Cached workspace features (1 hour) | `~/.kenso/cache/` | follows `KENSO_CONFIG_DIR` |
|
|
105
|
+
| API | `https://api.ken.so` | `KENSO_API_URL` |
|
|
106
|
+
| MCP (for `kenso mcp`) | `https://mcp.ken.so/mcp` | `KENSO_MCP_URL` |
|
|
107
|
+
|
|
108
|
+
`--verbose` prints each request line to stderr with the key redacted.
|
package/docs/batch.md
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# Batch runs
|
|
2
|
+
|
|
3
|
+
A batch command takes a file, sends its rows to Kenso in chunks, and prints one JSON summary. Every input row gets one line in a results file, every run can be resumed, and nothing is written until you pass `--yes`.
|
|
4
|
+
|
|
5
|
+
The batch commands:
|
|
6
|
+
|
|
7
|
+
- `contacts import`, `update`, `remove`, `restore`, `pause`, `resume`, `tag`, `untag` ([Leads](leads.md))
|
|
8
|
+
- `people import` and `accounts import` ([CRM](crm.md), beta)
|
|
9
|
+
- `studio fill` ([Studio](studio.md), beta)
|
|
10
|
+
- any generated command run with `--each <file>`, once per row (see below)
|
|
11
|
+
|
|
12
|
+
## Plan, then apply
|
|
13
|
+
|
|
14
|
+
Without `--yes` a batch command plans and changes nothing. `--dry-run` asks for the plan explicitly. `--yes` applies. Passing both is a usage error. Two exceptions to the plan below: `studio fill` without `--yes` prints its credit estimate and `next`, with no run or `plan.json` ([Studio](studio.md)), and a non-GET `--each` without `--yes` or `--dry-run` is a usage error (exit 2) rather than a plan.
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
kenso contacts import --campaign 812 --file leads.csv --dry-run
|
|
18
|
+
kenso contacts import --campaign 812 --file leads.csv --yes
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
The dry run parses and checks every row, may call read-only endpoints (a campaign read, a credit estimate, a contacts search for `--where`), writes `plan.json`, and prints the plan:
|
|
22
|
+
|
|
23
|
+
```json
|
|
24
|
+
{"run":"r_ubqu7wtx","dryRun":true,"command":"contacts remove","rows":3,"valid":3,"invalid":0,"chunks":1,"chunkSize":1000,"source":"ids","count":3,"firstIds":[11,12,13],"next":"kenso contacts remove --ids ids.txt --yes","plan":"/home/you/.kenso/runs/r_ubqu7wtx/plan.json"}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Every plan has `run`, `dryRun`, `command`, `rows`, `valid`, `invalid`, `chunks`, `chunkSize`, `next` (the exact command that applies it) and `plan` (the file), plus fields of its own command. It adds `invalidRows` when rows were refused, `chunksDone` when it continues an unfinished run, and `alreadyDone: true` when the apply would change nothing.
|
|
28
|
+
|
|
29
|
+
## The summary
|
|
30
|
+
|
|
31
|
+
An applied run prints one JSON object:
|
|
32
|
+
|
|
33
|
+
```json
|
|
34
|
+
{"run":"r_ubqu7wtx","rows":3,"ok":2,"created":0,"updated":0,"skipped":{},"failed":0,"notFound":1,"invalid":0,"changed":2,"results":"/home/you/.kenso/runs/r_ubqu7wtx/results.csv","resultsNdjson":"/home/you/.kenso/runs/r_ubqu7wtx/results.ndjson","resumed":false,"durationMs":412,"undo":"kenso contacts restore --run r_ubqu7wtx"}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
| Field | Meaning |
|
|
38
|
+
|---|---|
|
|
39
|
+
| `run` | The run id: `r_` and 8 characters |
|
|
40
|
+
| `rows` | Input rows |
|
|
41
|
+
| `ok`, `created`, `updated` | Rows that succeeded, by kind |
|
|
42
|
+
| `skipped` | Rows Kenso left alone on purpose, counted by reason, for example `{"duplicate":12}` |
|
|
43
|
+
| `failed`, `notFound`, `invalid` | Rows that did not go through: refused or errored, id not found, refused locally |
|
|
44
|
+
| `changed` | Rows this invocation changed. 0 when a rerun had nothing left to do |
|
|
45
|
+
| `results`, `resultsNdjson` | Absolute paths of the results files |
|
|
46
|
+
| `invalidRows` | Path of `invalid.csv`, when rows were refused locally |
|
|
47
|
+
| `resumed` | True when this invocation continued an earlier run |
|
|
48
|
+
| `durationMs` | Wall time of this invocation |
|
|
49
|
+
| `pending` | Rows without a result yet, when the run did not finish |
|
|
50
|
+
| `stopped` | `{code, message, hint}` when a stop condition ended the run (for example `limit_reached`) |
|
|
51
|
+
| `interrupted` | True after Ctrl-C |
|
|
52
|
+
| `next` | `kenso runs resume <runId>` when the run can continue |
|
|
53
|
+
|
|
54
|
+
Commands add their own fields: `contacts import` adds `campaignId` and `notSendable`, `contacts remove` adds `undo`, `people import` adds `matched`, `addedToAccounts` and `emailFindsQueued`, `accounts import` adds `researchQueued` and `researchWaitingCredits`, `studio fill` adds `templateId`, `runs` and the estimate.
|
|
55
|
+
|
|
56
|
+
Exit codes: 0 when every row succeeded or was skipped, 5 when some rows are `failed`, `not_found` or `invalid`, 6 when a limit stopped the run, 130 after Ctrl-C. The full table is in [Output and errors](output-and-errors.md).
|
|
57
|
+
|
|
58
|
+
## The run folder
|
|
59
|
+
|
|
60
|
+
Each run keeps its files in `~/.kenso/runs/<runId>/` (`KENSO_RUNS_DIR` moves the root), or in `--out <dir>` when you pass it. A run in `--out` is also linked under the runs folder, so `kenso runs` finds it. The folder is mode 0700 and its files 0600.
|
|
61
|
+
|
|
62
|
+
| File | What it holds |
|
|
63
|
+
|---|---|
|
|
64
|
+
| `run.json` | The command, its arguments, the input path and SHA-256, the workspace, the chunk plan and the status (`planned`, `running`, `done`, `failed`, `interrupted`) |
|
|
65
|
+
| `state.json` | The chunks done so far, rewritten after every chunk |
|
|
66
|
+
| `results.csv` | One line per input row: `row,status,id,reason,error`, then extra columns such as `email` or `sendable` |
|
|
67
|
+
| `results.ndjson` | The same rows as JSON lines |
|
|
68
|
+
| `invalid.csv` | Rows refused before sending, with the reason |
|
|
69
|
+
| `plan.json` | The last dry run's plan |
|
|
70
|
+
| `summary.json` | The last summary printed |
|
|
71
|
+
|
|
72
|
+
`row` is the 1-based number of the data row in the input (the CSV header is not counted). `status` is one of `created`, `updated`, `ok`, `skipped`, `failed`, `not_found` or `invalid`.
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
kenso runs list
|
|
76
|
+
kenso runs list --limit 5 -o json
|
|
77
|
+
kenso runs show r_ubqu7wtx
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Rerun and resume
|
|
81
|
+
|
|
82
|
+
A run is identified by its command, its arguments, the SHA-256 of its input file, the workspace and the chunk size. `--yes`, `--dry-run`, `--wait`, `--concurrency` and the output flags are not part of it. Running the same command again on the same file:
|
|
83
|
+
|
|
84
|
+
- **continues an unfinished run** (interrupted, stopped or crashed): finished chunks are skipped and the summary says `"resumed":true`.
|
|
85
|
+
- **answers from a finished run where every row succeeded:** it prints that run's summary with `"changed":0` and `"alreadyDone":true`, sends no writes and exits 0 (a command may still read first: `contacts import` reads the campaign, and `contacts update --match email` looks up the campaign's contacts).
|
|
86
|
+
- **starts a new run** when the last one finished with failed or invalid rows, or when the file changed. Lead, people and account writes match existing records (an email in a campaign, a person by email or LinkedIn URL, an account by domain), so sending a row twice never creates a duplicate.
|
|
87
|
+
|
|
88
|
+
`kenso runs resume <runId>` does the same from the run id: it rebuilds the command from `run.json` and runs it with `--yes` from the folder it started in. It refuses when the input was stdin, or the file is gone or changed. Two processes cannot work on one run: the second gets `run_in_progress` (exit 7).
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
kenso runs resume r_ubqu7wtx
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
`studio fill` is the exception: a fill charges for every page it builds, so it never fills the same ids twice. See [Studio](studio.md).
|
|
95
|
+
|
|
96
|
+
## Ctrl-C
|
|
97
|
+
|
|
98
|
+
The first Ctrl-C cancels the requests in flight and starts no new chunks. The CLI writes the state and prints the summary with `"interrupted":true` and `"next":"kenso runs resume <runId>"`, then exits 130. A chunk that did not finish is sent again when you resume, which is safe because the writes match existing records. A second Ctrl-C quits at once.
|
|
99
|
+
|
|
100
|
+
## Chunks, concurrency and rate limits
|
|
101
|
+
|
|
102
|
+
| Command | Rows per call | At once |
|
|
103
|
+
|---|---|---|
|
|
104
|
+
| `contacts import` | 1,000 | 1 per campaign |
|
|
105
|
+
| `contacts update`, `remove`, `restore`, `pause`, `resume` | 1,000 | 4 (`--concurrency`) |
|
|
106
|
+
| `contacts tag`, `untag` | 1 per call, 25 per chunk | 4 chunks (`--concurrency`) |
|
|
107
|
+
| `people import` | 200 | 4 (`--concurrency`) |
|
|
108
|
+
| `accounts import` | 100 | 4 (`--concurrency`) |
|
|
109
|
+
| `studio fill` | 500 ids per fill run | 1 |
|
|
110
|
+
| `--each` | 1 | 4 (`--concurrency`) |
|
|
111
|
+
|
|
112
|
+
`--concurrency` takes 1 to 16. When the API's `RateLimit-Remaining` header drops below 10, the CLI sends one chunk at a time until it is back above 30.
|
|
113
|
+
|
|
114
|
+
Retries:
|
|
115
|
+
|
|
116
|
+
- **429:** the CLI waits for `Retry-After` (at least 1 second, at most 60 seconds per wait) and tries again, with up to 15 minutes of waiting per command. After that it exits 6 (`rate_limited`).
|
|
117
|
+
- **502, 503, 504 and network errors:** up to 3 retries with backoff, for GET, PUT, PATCH and DELETE, and for the POST routes the CLI knows are safe to repeat.
|
|
118
|
+
- **`contacts import`:** no quick retry. After a 5xx or a dropped connection it waits 130 seconds, longer than the gateway's request timeout, and resends the chunk once.
|
|
119
|
+
- **`contacts remove`:** after a 5xx or a dropped connection it waits 2 seconds and resends once. Ids the first call already removed are reported `ok` (reason `already_removed`), so the undo still restores them.
|
|
120
|
+
- **Other 4xx answers:** never retried. The row or chunk is marked `failed` with the API's code.
|
|
121
|
+
|
|
122
|
+
Kenso answers a bulk call with 200 even when some items fail, so the CLI reads each item's result and never trusts the HTTP status alone.
|
|
123
|
+
|
|
124
|
+
## Progress
|
|
125
|
+
|
|
126
|
+
Progress goes to stderr, at most one line every 2 seconds (`chunk 7/20, 7000 rows, 6 failed`), and only when stderr is a terminal or you pass `--progress`. An agent gets only the summary.
|
|
127
|
+
|
|
128
|
+
## Run any API operation once per row: --each
|
|
129
|
+
|
|
130
|
+
Every generated command takes `--each <file>`, a CSV or JSONL file or `-` for stdin. The command runs once per row through the same engine: one results line per row, resume, `--concurrency`. Row keys fill the route, query and body by API name (`accountId`) or flag name (`account-id`). A blank cell is left out and the literal `null` sends null. A GET runs right away; any other method needs `--yes` or `--dry-run`.
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
kenso accounts update --each stages.csv --dry-run
|
|
134
|
+
kenso accounts update --each stages.csv --yes --concurrency 8
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
With `stages.csv`:
|
|
138
|
+
|
|
139
|
+
```csv
|
|
140
|
+
accountId,stage,overwritePinned
|
|
141
|
+
501,engaged,true
|
|
142
|
+
502,customer,true
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Each row is `ok` on a 2xx answer (with the created or returned id), `not_found` on 404, `failed` otherwise, or `invalid` when a required value is missing.
|
|
146
|
+
|
|
147
|
+
## The token-saving pattern for agents
|
|
148
|
+
|
|
149
|
+
1. Pass a file path, never the rows: `--file leads.csv`.
|
|
150
|
+
2. Run `--dry-run`, read the plan, then run its `next`.
|
|
151
|
+
3. Read the summary. If `failed`, `notFound` and `invalid` are 0, you are done.
|
|
152
|
+
4. Otherwise read only the problem rows, for example `grep -E '^[0-9]+,(failed|not_found|invalid),' results.csv | head -50`, or `invalid.csv`.
|
package/docs/crm.md
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# CRM (beta)
|
|
2
|
+
|
|
3
|
+
The CRM commands work on Kenso's Accounts lane: accounts (companies), the people on them, and opportunities. They are beta: they need the `accounts` feature flag on your workspace (opportunities need `opportunities`, CRM sync needs `ingestion`). Help and `kenso describe` hide them until your workspace has the flag; `--all-commands` shows them.
|
|
4
|
+
|
|
5
|
+
| Command | What it does |
|
|
6
|
+
|---|---|
|
|
7
|
+
| `accounts import` | Create accounts from a CSV or JSONL file. Research is charged per created account |
|
|
8
|
+
| `people import` | Create or match people from a file, optionally onto accounts |
|
|
9
|
+
| `accounts update --each`, `people update --each`, `opportunities update --each` | Edit many records from a file, one call per row |
|
|
10
|
+
| `crm-connections`, `crm-imports`, `crm-sync-issues` | Read and run CRM sync. Connecting a CRM stays in the app |
|
|
11
|
+
|
|
12
|
+
Import accounts first, then people: `people import` never creates an account.
|
|
13
|
+
|
|
14
|
+
## Import accounts
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
kenso accounts import --file accounts.csv
|
|
18
|
+
kenso accounts import --file accounts.csv --yes
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
```csv
|
|
22
|
+
name,domain,industry,employeeCount
|
|
23
|
+
Acme,acme.io,Software,120
|
|
24
|
+
Globex,globex.com,Manufacturing,"1,200"
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
- **Columns** map by name, ignoring case, spaces and dashes: `name` (or `company`, `companyName`, `accountName`), `domains` (several, split on `,` or `;`) or one `domain` (or `website`, `companyDomain`), `industry`, `employeeCount`, `hqLocation`, `linkedinUrl`, `stage`, `angle`, `whyNow`, `fitScore`, `fundingRound`, `cadenceDays`, `ownerMemberId`, `kenCompanyId`, `sendsFromInboxId`, `sendsFromLinkedInAccountId` and `source`.
|
|
28
|
+
- **A domain is required.** A file with no domain column is a usage error, and a row without a domain is `invalid` (`domain_required`).
|
|
29
|
+
- **The dry run prices the research.** It asks for a credit estimate per 1,000 accounts and puts the sum in the plan: `kind`, `accountCount`, `totalMillicredits`, `availableMillicredits` and `sufficient`.
|
|
30
|
+
- **Rows go 100 per call** to `POST /v1/accounts/bulk-create`.
|
|
31
|
+
|
|
32
|
+
Each row comes back:
|
|
33
|
+
|
|
34
|
+
| Status | Meaning |
|
|
35
|
+
|---|---|
|
|
36
|
+
| `created` | A new account; `id` is the account id. Research is queued (`researchQueued` in the summary), or waits for credits (`researchWaitingCredits`) |
|
|
37
|
+
| `skipped`, reason `domain_taken` | A company that is already an account; `id` is that account |
|
|
38
|
+
| `failed` | Kenso refused the row (`reason` is the API's code, `error` names the field) or failed to save it. An item Kenso failed to save is sent once more before it counts as failed |
|
|
39
|
+
|
|
40
|
+
Rerunning the same file is safe: accounts that exist come back `domain_taken`.
|
|
41
|
+
|
|
42
|
+
## Import people
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
kenso people import --file people.csv
|
|
46
|
+
kenso people import --file people.csv --find-emails --yes
|
|
47
|
+
kenso people import --file people.jsonl --dry-run
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
```csv
|
|
51
|
+
fullName,email,title,accountDomain,role
|
|
52
|
+
Ann Lee,ann@acme.io,VP Sales,acme.io,champion
|
|
53
|
+
Bob Ray,,CTO,acme.io,
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
- **Columns** map by name, ignoring case, spaces and dashes: `email`, `fullName` (or `name`), `firstName`, `lastName`, `title`, `linkedinUrl` (or `linkedin`, `profileUrl`), `accountDomain` (or `domain`, `website`, `companyDomain`), `accountId`, `role` and `priority`. Other columns are ignored with one warning.
|
|
57
|
+
- **A person needs a name:** `fullName`, or `firstName`, `lastName` or both (the CLI joins them). A row with none of them is `invalid` (`name_required`).
|
|
58
|
+
- **To put a person on an account,** give `accountDomain` or `accountId`. A row with both sends only `accountId`.
|
|
59
|
+
- **`--find-emails`** queues an email find (charged in credits) for each person without an email.
|
|
60
|
+
- **Rows go 200 per call** to `POST /v1/people/batch`.
|
|
61
|
+
|
|
62
|
+
People already in the workspace are matched by email, then LinkedIn URL, so running the same file again creates nobody twice.
|
|
63
|
+
|
|
64
|
+
| Result | Row status | reason |
|
|
65
|
+
|---|---|---|
|
|
66
|
+
| A new person | `created` | |
|
|
67
|
+
| An existing person, added to the account | `updated` | |
|
|
68
|
+
| An existing person already on the account | `skipped` | `already_on_account` |
|
|
69
|
+
| An existing person, no account asked | `skipped` | `matched` |
|
|
70
|
+
| The person was removed from the account before | `skipped` | `previously_removed` |
|
|
71
|
+
| The account does not exist | `failed` | `account_not_found`: the person was saved without an account. Import the account, then rerun to place them |
|
|
72
|
+
| Kenso refused the row | `failed` | the API's reason |
|
|
73
|
+
|
|
74
|
+
The summary adds `matched`, `addedToAccounts` and `emailFindsQueued`.
|
|
75
|
+
|
|
76
|
+
## Edit accounts, people and opportunities
|
|
77
|
+
|
|
78
|
+
Apart from `accounts bulk-update`, which sets one `accountType` on up to 500 accounts, there is no bulk edit route, so run the generated `update` command once per row with `--each`. Columns are the command's flags by API name or flag name, and the id column fills the route:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
kenso accounts update --each accounts-edit.csv --dry-run
|
|
82
|
+
kenso accounts update --each accounts-edit.csv --yes
|
|
83
|
+
kenso people update --each people-edit.csv --yes
|
|
84
|
+
kenso opportunities update --each deals.csv --yes
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
```csv
|
|
88
|
+
accountId,stage,ownerMemberId,overwritePinned
|
|
89
|
+
501,engaged,42,true
|
|
90
|
+
502,customer,42,true
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
A row that changes a field someone pinned in the app fails as a whole (`409 conflict`, `pinned_by_user`: nothing on that record changes, the other fields of the row included), unless the row sets `overwritePinned` to `true` (or you pass `--overwrite-pinned`). To see every flag of an update command:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
kenso describe accounts update
|
|
97
|
+
kenso accounts update --help
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## CRM sync
|
|
101
|
+
|
|
102
|
+
CRM sync has generated commands only: read connections, preview, start an import, and work through sync issues. Connect the CRM itself in the app.
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
kenso crm-connections list
|
|
106
|
+
kenso crm-connections get --connection-id 7
|
|
107
|
+
kenso crm-imports create --connection-id 7 --dry-run
|
|
108
|
+
kenso crm-sync-issues list --connection-id 7 --all
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Run `kenso crm-connections --help` (or add `--all-commands` when the actions are hidden) to see every action.
|