@audienti/cli 0.1.0

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/CHANGELOG.md ADDED
@@ -0,0 +1,11 @@
1
+ # Changelog
2
+
3
+ All notable changes to the Audienti CLI are documented here.
4
+
5
+ ## [Unreleased]
6
+
7
+ ## [0.1.0] - 2026-07-10
8
+
9
+ ### Added
10
+
11
+ - Initial public release of the agent-first Audienti CLI.
package/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2026 OMALab, Inc.
2
+ All rights reserved.
3
+
4
+ This repository and its original source code, documentation, workflows,
5
+ prompts, scripts, plugin metadata, and related materials are proprietary to
6
+ OMALab, Inc.
7
+
8
+ No permission is granted to use, copy, modify, merge, publish, distribute,
9
+ sublicense, sell, or publish substantial portions of this repository or its
10
+ contents without prior written permission from OMALab, Inc.
11
+
12
+ Nothing in this notice limits rights available under applicable law, including
13
+ fair use. Short quotations, references, summaries, links, commentary, and other
14
+ legally permitted uses are allowed under those rights. If you quote or reference
15
+ this work, attribution to OMALab, Inc. and a link to this repository are
16
+ requested.
17
+
18
+ Third-party content and dependencies remain owned by their respective owners
19
+ and are governed by their own licenses.
package/README.md ADDED
@@ -0,0 +1,62 @@
1
+ # Audienti CLI
2
+
3
+ Audienti CLI is the agent-first command-line client for the Audienti production
4
+ API. It lets local coding agents and operators inspect accounts, create and
5
+ manage plays, import prospects, build lists, and work supported operator flows.
6
+
7
+ ## Install
8
+
9
+ Requires Node.js 20 or newer.
10
+
11
+ ```bash
12
+ npm install --global @audienti/cli
13
+ audienti --help
14
+ ```
15
+
16
+ For one-off use, run `npx @audienti/cli --help`.
17
+
18
+ ## Authenticate
19
+
20
+ Create an Audienti API token through the product, then configure this machine:
21
+
22
+ ```bash
23
+ audienti auth token <token>
24
+ audienti accounts list --json
25
+ audienti accounts select <acct_id>
26
+ ```
27
+
28
+ The CLI writes its local configuration to `~/.config/audienti/config.json` with
29
+ owner-only permissions. Do not place a production token in an agent prompt,
30
+ repository file, issue, or CI secret.
31
+
32
+ ## Agent Workflows
33
+
34
+ Start with the built-in, production-safe workflow guide:
35
+
36
+ ```bash
37
+ audienti help agent-workflows
38
+ ```
39
+
40
+ Use `--json` whenever another program or agent will consume the result. Inspect
41
+ the target state before mutations, and use the command-specific help before
42
+ creating, changing, or deleting data.
43
+
44
+ ## Compatibility
45
+
46
+ The CLI talks to the versioned Audienti `/api/v1` contract at
47
+ `https://app.audienti.com` by default. A release occurs only after the matching
48
+ server deploy succeeds.
49
+
50
+ The canonical source lives with the Audienti application under
51
+ `packages/audienti-cli`. This public repository is a CI-managed mirror; direct
52
+ changes are unsupported and cause the next source release to fail safely.
53
+
54
+ ## Plugins
55
+
56
+ This repository includes Codex and Claude Code plugin manifests plus an
57
+ `audienti` skill. The plugin provides workflow instructions; it does not grant
58
+ credentials or silently authenticate an agent.
59
+
60
+ ## License
61
+
62
+ Copyright (c) 2026 OMALab, Inc. All rights reserved. See [LICENSE](LICENSE).
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { run } from "../src/cli.js";
4
+
5
+ const exitCode = await run(process.argv.slice(2));
6
+ process.exitCode = exitCode;
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@audienti/cli",
3
+ "version": "0.1.0",
4
+ "description": "Agent-first command-line client for Audienti.",
5
+ "type": "module",
6
+ "bin": {
7
+ "audienti": "./bin/audienti.js"
8
+ },
9
+ "files": [
10
+ "bin/",
11
+ "src/",
12
+ "README.md",
13
+ "LICENSE",
14
+ "CHANGELOG.md"
15
+ ],
16
+ "scripts": {
17
+ "test": "node --test test/*.test.js",
18
+ "verify": "node scripts/verify-package.mjs",
19
+ "check": "npm run verify && npm test"
20
+ },
21
+ "engines": {
22
+ "node": ">=20"
23
+ },
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "git+https://github.com/audienti/cli.git"
27
+ },
28
+ "homepage": "https://github.com/audienti/cli#readme",
29
+ "bugs": {
30
+ "url": "https://github.com/audienti/cli/issues"
31
+ },
32
+ "license": "SEE LICENSE IN LICENSE",
33
+ "keywords": [
34
+ "audienti",
35
+ "cli",
36
+ "agents",
37
+ "go-to-market"
38
+ ],
39
+ "publishConfig": {
40
+ "access": "public"
41
+ }
42
+ }
@@ -0,0 +1,295 @@
1
+ export const DEFAULT_HOST = "https://app.audienti.com";
2
+
3
+ export class ApiError extends Error {
4
+ constructor(message, { status, body } = {}) {
5
+ super(message);
6
+ this.name = "ApiError";
7
+ this.status = status;
8
+ this.body = body;
9
+ }
10
+ }
11
+
12
+ export function normalizeHost(host = DEFAULT_HOST) {
13
+ const trimmed = String(host || DEFAULT_HOST).trim();
14
+ const withProtocol = /^https?:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`;
15
+ const url = new URL(withProtocol);
16
+ url.pathname = url.pathname.replace(/\/+$/, "");
17
+ url.search = "";
18
+ url.hash = "";
19
+
20
+ return url.toString().replace(/\/$/, "");
21
+ }
22
+
23
+ export class AudientiClient {
24
+ constructor({ host = DEFAULT_HOST, token, fetchImpl = globalThis.fetch } = {}) {
25
+ if (!fetchImpl) {
26
+ throw new Error("This Node runtime does not provide fetch.");
27
+ }
28
+
29
+ this.host = normalizeHost(host);
30
+ this.token = token;
31
+ this.fetchImpl = fetchImpl;
32
+ }
33
+
34
+ me() {
35
+ return this.requestJson("/api/v1/me.json");
36
+ }
37
+
38
+ accounts() {
39
+ return this.requestJson("/api/v1/accounts.json");
40
+ }
41
+
42
+ users(accountId) {
43
+ return this.requestJson(accountPath(accountId, ["users"]));
44
+ }
45
+
46
+ offers(accountId) {
47
+ return this.requestJson(accountPath(accountId, ["offers"]));
48
+ }
49
+
50
+ createOffer(accountId, body) {
51
+ return this.requestJson(accountPath(accountId, ["offers"]), {
52
+ method: "POST",
53
+ body
54
+ });
55
+ }
56
+
57
+ icps(accountId) {
58
+ return this.requestJson(accountPath(accountId, ["icps"]));
59
+ }
60
+
61
+ createIcp(accountId, body) {
62
+ return this.requestJson(accountPath(accountId, ["icps"]), {
63
+ method: "POST",
64
+ body
65
+ });
66
+ }
67
+
68
+ companies(accountId, query = {}) {
69
+ return this.requestJson(accountPath(accountId, ["companies"], query));
70
+ }
71
+
72
+ lists(accountId) {
73
+ return this.requestJson(accountPath(accountId, ["lists"]));
74
+ }
75
+
76
+ createList(accountId, body) {
77
+ return this.requestJson(accountPath(accountId, ["lists"]), {
78
+ method: "POST",
79
+ body
80
+ });
81
+ }
82
+
83
+ list(accountId, listId) {
84
+ return this.requestJson(accountPath(accountId, ["lists", listId]));
85
+ }
86
+
87
+ updateList(accountId, listId, body) {
88
+ return this.requestJson(accountPath(accountId, ["lists", listId]), {
89
+ method: "PATCH",
90
+ body
91
+ });
92
+ }
93
+
94
+ deleteList(accountId, listId) {
95
+ return this.requestJson(accountPath(accountId, ["lists", listId]), {
96
+ method: "DELETE"
97
+ });
98
+ }
99
+
100
+ listProspects(accountId, listId, query = {}) {
101
+ return this.requestJson(accountPath(accountId, ["lists", listId, "prospects"], query));
102
+ }
103
+
104
+ addListProspects(accountId, listId, body) {
105
+ return this.requestJson(accountPath(accountId, ["lists", listId, "prospects"]), {
106
+ method: "POST",
107
+ body
108
+ });
109
+ }
110
+
111
+ removeListProspects(accountId, listId, body) {
112
+ return this.requestJson(accountPath(accountId, ["lists", listId, "prospects"]), {
113
+ method: "DELETE",
114
+ body
115
+ });
116
+ }
117
+
118
+ motions(accountId) {
119
+ return this.requestJson(accountPath(accountId, ["motions"]));
120
+ }
121
+
122
+ motion(accountId, motionId) {
123
+ return this.requestJson(accountPath(accountId, ["motions", motionId]));
124
+ }
125
+
126
+ createMotion(accountId, body) {
127
+ return this.requestJson(accountPath(accountId, ["motions"]), {
128
+ method: "POST",
129
+ body
130
+ });
131
+ }
132
+
133
+ motionStatus(accountId, motionId) {
134
+ return this.requestJson(accountPath(accountId, ["motions", motionId, "status"]));
135
+ }
136
+
137
+ motionProspects(accountId, motionId, query = {}) {
138
+ return this.requestJson(accountPath(accountId, ["motions", motionId, "prospects"], query));
139
+ }
140
+
141
+ addMotionProspects(accountId, motionId, body) {
142
+ return this.requestJson(accountPath(accountId, ["motions", motionId, "prospects"]), {
143
+ method: "POST",
144
+ body
145
+ });
146
+ }
147
+
148
+ prospects(accountId, query = {}) {
149
+ return this.requestJson(accountPath(accountId, ["prospects"], query));
150
+ }
151
+
152
+ prospect(accountId, prospectId) {
153
+ return this.requestJson(accountPath(accountId, ["prospects", prospectId]));
154
+ }
155
+
156
+ prospectMessageTypes(accountId, prospectId) {
157
+ return this.requestJson(accountPath(accountId, ["prospects", prospectId, "message_types"]));
158
+ }
159
+
160
+ writeProspectMessage(accountId, prospectId, body) {
161
+ return this.requestJson(accountPath(accountId, ["prospects", prospectId, "write_message"]), {
162
+ method: "POST",
163
+ body
164
+ });
165
+ }
166
+
167
+ prospectSequencePreview(accountId, prospectId, body = {}) {
168
+ return this.requestJson(accountPath(accountId, ["prospects", prospectId, "sequence_preview"]), {
169
+ method: "POST",
170
+ body
171
+ });
172
+ }
173
+
174
+ addProspectNote(accountId, prospectId, body) {
175
+ return this.requestJson(accountPath(accountId, ["prospects", prospectId, "add_note"]), {
176
+ method: "POST",
177
+ body
178
+ });
179
+ }
180
+
181
+ prospectImport(accountId, body) {
182
+ return this.requestJson(accountPath(accountId, ["prospect_imports"]), {
183
+ method: "POST",
184
+ body
185
+ });
186
+ }
187
+
188
+ prospectImportStatus(accountId, importId) {
189
+ return this.requestJson(accountPath(accountId, ["prospect_imports", importId]));
190
+ }
191
+
192
+ operatorQueue(accountId, query = {}) {
193
+ return this.requestJson(accountPath(accountId, ["operator"], query));
194
+ }
195
+
196
+ operatorNext(accountId, query = {}) {
197
+ return this.requestJson(accountPath(accountId, ["operator", "next"], query));
198
+ }
199
+
200
+ operatorOutcome(accountId, body) {
201
+ return this.requestJson(accountPath(accountId, ["operator", "outcome"]), {
202
+ method: "POST",
203
+ body
204
+ });
205
+ }
206
+
207
+ async requestJson(path, { method = "GET", body } = {}) {
208
+ const response = await this.fetchImpl(new URL(path, `${this.host}/`), {
209
+ method,
210
+ headers: this.headers(body),
211
+ body: body === undefined ? undefined : JSON.stringify(body)
212
+ });
213
+
214
+ const responseBody = await parseBody(response);
215
+
216
+ if (!response.ok) {
217
+ throw new ApiError(errorMessage(response.status, responseBody), {
218
+ status: response.status,
219
+ body: responseBody
220
+ });
221
+ }
222
+
223
+ return responseBody;
224
+ }
225
+
226
+ headers(body) {
227
+ const headers = {
228
+ Accept: "application/json"
229
+ };
230
+
231
+ if (this.token) {
232
+ headers.Authorization = `Bearer ${this.token}`;
233
+ }
234
+
235
+ if (body !== undefined) {
236
+ headers["Content-Type"] = "application/json";
237
+ }
238
+
239
+ return headers;
240
+ }
241
+ }
242
+
243
+ function accountPath(accountId, segments, query = {}) {
244
+ const encodedSegments = [
245
+ "api",
246
+ "v1",
247
+ "accounts",
248
+ accountId,
249
+ ...segments
250
+ ].map((segment) => encodeURIComponent(segment));
251
+ const searchParams = new URLSearchParams();
252
+
253
+ for (const [key, value] of Object.entries(query)) {
254
+ if (value !== undefined && value !== null && String(value).trim() !== "") {
255
+ searchParams.set(key, String(value));
256
+ }
257
+ }
258
+
259
+ const path = `/${encodedSegments.join("/")}.json`;
260
+ const search = searchParams.toString();
261
+ return search ? `${path}?${search}` : path;
262
+ }
263
+
264
+ async function parseBody(response) {
265
+ const text = await response.text();
266
+ if (!text) return null;
267
+
268
+ try {
269
+ return JSON.parse(text);
270
+ } catch {
271
+ return text;
272
+ }
273
+ }
274
+
275
+ function errorMessage(status, body) {
276
+ if (status === 401) {
277
+ return "Authentication failed. Run `audienti auth token <token>` with a valid API token.";
278
+ }
279
+
280
+ if (status === 403) {
281
+ return "The API token is not allowed to access that Audienti resource.";
282
+ }
283
+
284
+ if (status === 404) {
285
+ return "The requested Audienti resource was not found.";
286
+ }
287
+
288
+ if (status === 422) {
289
+ const reasons = [body?.errors, body?.details].find(Array.isArray);
290
+ const details = reasons?.length > 0 ? reasons.join(", ") : body?.error;
291
+ return details ? `Audienti rejected the request: ${details}` : "Audienti rejected the request.";
292
+ }
293
+
294
+ return `Audienti API request failed with HTTP ${status}.`;
295
+ }