@customerio/cli 0.0.0-bootstrap → 0.0.2

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.
Files changed (3) hide show
  1. package/.npm/run.js +73 -0
  2. package/README.md +221 -2
  3. package/package.json +75 -10
package/.npm/run.js ADDED
@@ -0,0 +1,73 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { spawnSync } = require("child_process");
4
+ const { existsSync } = require("fs");
5
+ const path = require("path");
6
+
7
+ const pkgRoot = path.join(__dirname, "..");
8
+ const rootPkg = require(path.join(pkgRoot, "package.json"));
9
+ const optionalDependencies = rootPkg.optionalDependencies || {};
10
+ const platform = (rootPkg.customerioCli?.platforms || []).find(
11
+ (candidate) => candidate.os === process.platform && candidate.cpu === process.arch
12
+ );
13
+
14
+ if (!platform) {
15
+ console.error(
16
+ `Unsupported platform for ${rootPkg.name}: ${process.platform}-${process.arch}`
17
+ );
18
+ process.exit(1);
19
+ }
20
+
21
+ const platformPackage = Object.keys(optionalDependencies).find((packageName) =>
22
+ packageName.endsWith(`-${platform.npm}`)
23
+ );
24
+ let platformPackageRoot;
25
+
26
+ if (!platformPackage) {
27
+ console.error(
28
+ `Missing optional dependency metadata for ${process.platform}-${process.arch}.`
29
+ );
30
+ process.exit(1);
31
+ }
32
+
33
+ try {
34
+ platformPackageRoot = path.dirname(
35
+ require.resolve(`${platformPackage}/package.json`, { paths: [pkgRoot] })
36
+ );
37
+ } catch {
38
+ console.error(
39
+ `Missing optional dependency ${platformPackage} for ${process.platform}-${process.arch}.\n` +
40
+ "Reinstall without disabling optional dependencies."
41
+ );
42
+ process.exit(1);
43
+ }
44
+
45
+ const binPath = path.join(platformPackageRoot, "bin", `cio${platform.ext || ""}`);
46
+
47
+ if (!existsSync(binPath)) {
48
+ console.error(
49
+ `Missing ${rootPkg.name} binary at ${binPath}.\n` +
50
+ "Reinstall without disabling optional dependencies."
51
+ );
52
+ process.exit(1);
53
+ }
54
+
55
+ const result = spawnSync(binPath, process.argv.slice(2), {
56
+ stdio: "inherit",
57
+ });
58
+
59
+ if (result.error) {
60
+ console.error(result.error.message);
61
+ process.exit(1);
62
+ }
63
+
64
+ if (result.signal) {
65
+ try {
66
+ process.kill(process.pid, result.signal);
67
+ } catch {
68
+ // Some platforms cannot re-raise child signals; use a generic failure below.
69
+ }
70
+ process.exit(1);
71
+ }
72
+
73
+ process.exit(result.status ?? 1);
package/README.md CHANGED
@@ -1,3 +1,222 @@
1
- # @customerio/cli
1
+ # Customer.io CLI (`cio`)
2
2
 
3
- Bootstrap placeholder package for trusted publishing setup.
3
+ An agent-first CLI for Customer.io APIs.
4
+
5
+ **800+ Journeys routes + 100+ CDP Pipelines routes, zero per-endpoint code.** A single `cio api <path>` command covers every endpoint. Every command returns structured JSON to stdout. Every error returns structured JSON to stderr.
6
+
7
+ AI agents are the primary consumer. Use `cio schema` to introspect endpoints before calling them.
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ npm i -g @customerio/cli
13
+ cio --help
14
+ ```
15
+
16
+ To build from source instead:
17
+
18
+ ```bash
19
+ go install github.com/customerio/cli@latest
20
+ ```
21
+
22
+ ## Install the agent skill
23
+
24
+ This repo ships a [SKILL.md](skills/cio/SKILL.md) so Claude Code, Cursor, Codex, Windsurf, and other agents that support [open agent skills](https://github.com/vercel-labs/skills) know how to drive the CLI. Install it with:
25
+
26
+ ```bash
27
+ npx skills add customerio/cli
28
+ ```
29
+
30
+ ## Authentication
31
+
32
+ The CLI uses **service account tokens** (`sa_live_...`) for authentication. These are exchanged for short-lived JWTs via OAuth 2.0 client credentials, just like `gh auth`.
33
+
34
+ ### Login
35
+
36
+ ```bash
37
+ # Interactive — prints the browser login URL, then prompts for the minted token
38
+ cio auth login
39
+
40
+ # Read from stdin (for CI/automation; login still auto-discovers region)
41
+ echo "$SA_TOKEN" | cio auth login --with-token
42
+
43
+ # Verify auth works
44
+ cio auth status
45
+
46
+ # Print raw token
47
+ cio auth token
48
+
49
+ # Logout
50
+ cio auth logout
51
+ ```
52
+
53
+ ### Token Resolution Order
54
+
55
+ 1. `--token` flag (highest priority)
56
+ 2. `CIO_TOKEN` environment variable
57
+ 3. `~/.cio/config.json` file (lowest priority)
58
+
59
+ When you use `CIO_TOKEN` or `--token` directly on normal commands, you may
60
+ also need `CIO_REGION=us|eu` or `--api-url`.
61
+
62
+ ### How It Works
63
+
64
+ 1. You provide a `sa_live_...` token (from Customer.io UI → Account Settings → Manage API Credentials → Service Accounts)
65
+ 2. The CLI exchanges it for a JWT via `POST /v1/service_accounts/oauth/token`
66
+ 3. The JWT is cached locally and refreshed automatically when it expires
67
+ 4. All API calls use `Authorization: Bearer <jwt>`
68
+
69
+ ### Override per-command
70
+
71
+ ```bash
72
+ cio --token sa_live_xxx api /v1/environments/{environment_id}/campaigns --params '{"environment_id": "123"}'
73
+ CIO_TOKEN=sa_live_xxx cio api /v1/environments/{environment_id}/campaigns --params '{"environment_id": "123"}'
74
+ ```
75
+
76
+ ## Usage
77
+
78
+ Use `cio api <path>` for any API endpoint. Path placeholders are resolved from `--params`. The HTTP method defaults to GET (or POST if `--json` is provided); override with `-X`:
79
+
80
+ ```bash
81
+ # List campaigns in workspace 123
82
+ cio api /v1/environments/{environment_id}/campaigns --params '{"environment_id": "123"}'
83
+
84
+ # Get a specific campaign
85
+ cio api /v1/environments/{environment_id}/campaigns/{campaign_id} \
86
+ --params '{"environment_id": "123", "campaign_id": "456"}'
87
+
88
+ # Create a campaign
89
+ cio api /v1/environments/{environment_id}/campaigns \
90
+ --params '{"environment_id": "123"}' \
91
+ --json '{"campaign": {"name": "Welcome Flow", "type": "none"}}'
92
+
93
+ # Explicit method override
94
+ cio api /v1/environments/{environment_id}/campaigns/{campaign_id} -X DELETE \
95
+ --params '{"environment_id": "123", "campaign_id": "456"}'
96
+
97
+ # Introspect endpoints
98
+ cio schema # list all resources
99
+ cio schema campaigns # list endpoints for a resource
100
+ cio schema campaigns.list # full schema for a method
101
+ cio schema GET /v1/environments/{environment_id}/campaigns # by HTTP method + path
102
+ ```
103
+
104
+ ### Account ID fallback
105
+
106
+ Paths that include `{account_id}` auto-fill from the account ID stored during `cio auth login`, so you don't need to pass it on every call:
107
+
108
+ ```bash
109
+ cio api /v1/accounts/{account_id}/environments
110
+ ```
111
+
112
+ Pass `--params '{"account_id": "..."}'` to override, or set `CIO_ACCESS_TOKEN` to disable the fallback (the pre-exchanged JWT may belong to a different account).
113
+
114
+ ### Filtering with jq
115
+
116
+ ```bash
117
+ # Filter with --jq to save context window
118
+ cio api /v1/environments/{environment_id}/campaigns \
119
+ --params '{"environment_id": "123"}' \
120
+ --jq '.campaigns[] | {id, name, state}'
121
+
122
+ # Complex filtering
123
+ cio api /v1/environments/{environment_id}/campaigns \
124
+ --params '{"environment_id": "123"}' \
125
+ --jq '.campaigns[] | select(.state == "active") | {id, name}'
126
+ ```
127
+
128
+ ### Write operations
129
+
130
+ ```bash
131
+ # Always dry-run first
132
+ cio api /v1/environments/{environment_id}/campaigns \
133
+ --params '{"environment_id": "123"}' \
134
+ --json '{"campaign": {"name": "Welcome Flow", "type": "none"}}' --dry-run
135
+
136
+ # Then execute (removes --dry-run)
137
+ cio api /v1/environments/{environment_id}/campaigns \
138
+ --params '{"environment_id": "123"}' \
139
+ --json '{"campaign": {"name": "Welcome Flow", "type": "none"}}'
140
+ ```
141
+
142
+ ### Pagination
143
+
144
+ ```bash
145
+ cio api /v1/environments/{environment_id}/campaigns \
146
+ --params '{"environment_id": "123"}' --page 2 --limit 50
147
+
148
+ # Auto-paginate (emits NDJSON — one JSON object per line)
149
+ cio api /v1/environments/{environment_id}/campaigns \
150
+ --params '{"environment_id": "123"}' --page-all
151
+ ```
152
+
153
+ ## Global Flags
154
+
155
+ | Flag | Env Var | Description |
156
+ |---|---|---|
157
+ | `--token <value>` | `CIO_TOKEN` | Service account token override |
158
+ | `-X, --method` | | HTTP method override (default: GET, or POST if --json) |
159
+ | `--json <payload>` | | Raw JSON request body or `@filename` to read from file |
160
+ | `--params <json>` | | Query parameters as JSON → query string |
161
+ | `--jq <expr>` | | jq expression filter (via gojq) |
162
+ | `--dry-run` | | Validate + print request, skip execution |
163
+ | `--api-url <url>` | | API base URL override |
164
+ | `--timeout <duration>` | `CIO_TIMEOUT` | HTTP request timeout (default: 30s) |
165
+ | `--page <n>` | | Page number |
166
+ | `--limit <n>` | | Page size |
167
+ | `--page-all` | | Auto-paginate, emit NDJSON |
168
+
169
+ ## Exit Codes
170
+
171
+ | Code | Meaning |
172
+ |---|---|
173
+ | 0 | Success |
174
+ | 1 | General error |
175
+ | 2 | Validation / input error |
176
+ | 3 | Authentication error |
177
+ | 4 | Authorization error |
178
+ | 5 | API error (4xx/5xx) |
179
+
180
+ ## Error Format
181
+
182
+ ```json
183
+ {"error":true,"code":"AUTH_ERROR","message":"Not authenticated.","details":{"status_code":401}}
184
+ ```
185
+
186
+ ## Architecture
187
+
188
+ The CLI uses a **generic `api` command + route registry** architecture:
189
+
190
+ 1. `cio api <path>` — a single command that takes any API path with `{placeholder}` params
191
+ 2. OpenAPI specs are downloaded from the live API on first use and cached locally under `~/.cio/cache/specs/` (24h TTL, ETag-based conditional refresh). Use `cio schema --refresh` to force re-download.
192
+ 3. `internal/routes/enrichment.json` — summaries, param descriptions, query params for routes not yet annotated in the OpenAPI spec
193
+
194
+ ```bash
195
+ # Discover resources
196
+ cio schema
197
+
198
+ # List endpoints for a resource
199
+ cio schema campaigns
200
+
201
+ # Inspect a method's parameters
202
+ cio schema campaigns.list
203
+
204
+ # Make an API call
205
+ cio api /v1/environments/{environment_id}/campaigns --params '{"environment_id": "123"}'
206
+
207
+ # CDP Pipelines (workspace_id = environment_id)
208
+ cio api /cdp/api/workspaces/{workspace_id}/sources --params '{"workspace_id": "123"}'
209
+ cio schema sources
210
+ ```
211
+
212
+ ## Development
213
+
214
+ ```bash
215
+ go build -o cio .
216
+ go test ./...
217
+ go test ./... -v -run TestAuthLogin
218
+ ```
219
+
220
+ ## License
221
+
222
+ Licensed under Apache License 2.0 with the Commons Clause Restriction. See [LICENSE](LICENSE).
package/package.json CHANGED
@@ -1,20 +1,85 @@
1
1
  {
2
2
  "name": "@customerio/cli",
3
- "version": "0.0.0-bootstrap",
4
- "description": "Bootstrap placeholder for Customer.io CLI trusted publishing setup",
5
- "main": "index.js",
6
- "scripts": {
7
- "test": "echo \"Error: no test specified\" && exit 1"
3
+ "version": "0.0.2",
4
+ "description": "Agent-first CLI for Customer.io APIs",
5
+ "bin": {
6
+ "cio": ".npm/run.js"
7
+ },
8
+ "files": [
9
+ ".npm/run.js",
10
+ "LICENSE",
11
+ "README.md"
12
+ ],
13
+ "optionalDependencies": {
14
+ "@customerio/cli-darwin-arm64": "0.0.2",
15
+ "@customerio/cli-darwin-x64": "0.0.2",
16
+ "@customerio/cli-linux-arm64": "0.0.2",
17
+ "@customerio/cli-linux-x64": "0.0.2",
18
+ "@customerio/cli-win32-arm64": "0.0.2",
19
+ "@customerio/cli-win32-x64": "0.0.2"
20
+ },
21
+ "publishConfig": {
22
+ "access": "public",
23
+ "provenance": true
24
+ },
25
+ "customerioCli": {
26
+ "platforms": [
27
+ {
28
+ "goos": "darwin",
29
+ "goarch": "arm64",
30
+ "npm": "darwin-arm64",
31
+ "os": "darwin",
32
+ "cpu": "arm64",
33
+ "ext": ""
34
+ },
35
+ {
36
+ "goos": "darwin",
37
+ "goarch": "amd64",
38
+ "npm": "darwin-x64",
39
+ "os": "darwin",
40
+ "cpu": "x64",
41
+ "ext": ""
42
+ },
43
+ {
44
+ "goos": "linux",
45
+ "goarch": "arm64",
46
+ "npm": "linux-arm64",
47
+ "os": "linux",
48
+ "cpu": "arm64",
49
+ "ext": ""
50
+ },
51
+ {
52
+ "goos": "linux",
53
+ "goarch": "amd64",
54
+ "npm": "linux-x64",
55
+ "os": "linux",
56
+ "cpu": "x64",
57
+ "ext": ""
58
+ },
59
+ {
60
+ "goos": "windows",
61
+ "goarch": "arm64",
62
+ "npm": "win32-arm64",
63
+ "os": "win32",
64
+ "cpu": "arm64",
65
+ "ext": ".exe"
66
+ },
67
+ {
68
+ "goos": "windows",
69
+ "goarch": "amd64",
70
+ "npm": "win32-x64",
71
+ "os": "win32",
72
+ "cpu": "x64",
73
+ "ext": ".exe"
74
+ }
75
+ ]
8
76
  },
9
- "keywords": [],
10
- "author": "",
11
77
  "license": "SEE LICENSE IN LICENSE",
12
- "type": "commonjs",
13
78
  "repository": {
14
79
  "type": "git",
15
80
  "url": "git+https://github.com/customerio/cli.git"
16
81
  },
17
- "publishConfig": {
18
- "access": "public"
82
+ "engines": {
83
+ "node": ">=16"
19
84
  }
20
85
  }