conduyt 1.0.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/README.md ADDED
@@ -0,0 +1,50 @@
1
+ # conduyt
2
+
3
+ Command-line interface for [Conduyt CRM](https://conduyt.app) — manage contacts, deals, pipelines, and run AI insight queries from your terminal.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install -g conduyt
9
+ # or run without installing
10
+ npx conduyt --help
11
+ ```
12
+
13
+ ## Authentication
14
+
15
+ Generate an API key in Conduyt under **Settings → API** (keys start with `cdy_`), then either save it:
16
+
17
+ ```bash
18
+ conduyt config set --key cdy_your_key_here
19
+ ```
20
+
21
+ or set environment variables (these override the saved config):
22
+
23
+ ```bash
24
+ export CONDUYT_API_KEY=cdy_your_key_here
25
+ export CONDUYT_API_URL=https://conduyt.app # optional, defaults to https://conduyt.app
26
+ ```
27
+
28
+ The saved config lives at `~/.conduyt/config.json` and is written owner-only (`chmod 600`).
29
+
30
+ ## Commands
31
+
32
+ ```bash
33
+ conduyt whoami # show the authenticated user
34
+ conduyt contacts list --limit 25 # list contacts (supports --cursor, --query)
35
+ conduyt contacts get <id> # fetch one contact
36
+ conduyt deals list --pipeline <id> # list deals
37
+ conduyt pipelines # list pipelines and stages
38
+ conduyt search "acme corp" # search across the CRM
39
+ conduyt insights summary # run an AI insight query
40
+ conduyt api GET /api/v1/companies # raw authenticated request (escape hatch)
41
+ conduyt config show # show resolved config (key masked)
42
+ ```
43
+
44
+ Pass `--input '{"...":"..."}'` to `insights` and `--body '{"...":"..."}'` to `api` to send a JSON payload.
45
+
46
+ All output is JSON, ready to pipe into `jq`.
47
+
48
+ ## License
49
+
50
+ MIT
package/dist/client.js ADDED
@@ -0,0 +1,57 @@
1
+ import { requireAuth } from "./config.js";
2
+ export class ConduytClient {
3
+ baseUrl;
4
+ apiKey;
5
+ constructor() {
6
+ const cfg = requireAuth();
7
+ this.baseUrl = cfg.apiUrl.replace(/\/+$/, "");
8
+ this.apiKey = cfg.apiKey;
9
+ }
10
+ async request(method, path, body) {
11
+ const url = `${this.baseUrl}${path.startsWith("/") ? path : `/${path}`}`;
12
+ const res = await fetch(url, {
13
+ method,
14
+ headers: {
15
+ Authorization: `Bearer ${this.apiKey}`,
16
+ "Content-Type": "application/json",
17
+ },
18
+ body: body !== undefined ? JSON.stringify(body) : undefined,
19
+ });
20
+ const text = await res.text();
21
+ const json = text ? safeParse(text) : null;
22
+ if (!res.ok) {
23
+ const msg = json?.error || res.statusText || `HTTP ${res.status}`;
24
+ throw new Error(`Conduyt API ${res.status}: ${msg}`);
25
+ }
26
+ return json;
27
+ }
28
+ get(path) {
29
+ return this.request("GET", path);
30
+ }
31
+ post(path, body) {
32
+ return this.request("POST", path, body);
33
+ }
34
+ patch(path, body) {
35
+ return this.request("PATCH", path, body);
36
+ }
37
+ del(path) {
38
+ return this.request("DELETE", path);
39
+ }
40
+ }
41
+ function safeParse(text) {
42
+ try {
43
+ return JSON.parse(text);
44
+ }
45
+ catch {
46
+ return text;
47
+ }
48
+ }
49
+ export function buildQuery(params) {
50
+ const usp = new URLSearchParams();
51
+ for (const [k, v] of Object.entries(params)) {
52
+ if (v !== undefined && v !== "")
53
+ usp.set(k, String(v));
54
+ }
55
+ const qs = usp.toString();
56
+ return qs ? `?${qs}` : "";
57
+ }
package/dist/config.js ADDED
@@ -0,0 +1,57 @@
1
+ import { homedir } from "node:os";
2
+ import { join } from "node:path";
3
+ import { mkdirSync, readFileSync, writeFileSync, existsSync, chmodSync } from "node:fs";
4
+ const CONFIG_DIR = join(homedir(), ".conduyt");
5
+ const CONFIG_PATH = join(CONFIG_DIR, "config.json");
6
+ const DEFAULT_API_URL = "https://conduyt.app";
7
+ export function configPath() {
8
+ return CONFIG_PATH;
9
+ }
10
+ function readFile() {
11
+ if (!existsSync(CONFIG_PATH))
12
+ return {};
13
+ try {
14
+ return JSON.parse(readFileSync(CONFIG_PATH, "utf8"));
15
+ }
16
+ catch {
17
+ return {};
18
+ }
19
+ }
20
+ export function saveConfig(update) {
21
+ const current = readFile();
22
+ const next = {
23
+ apiUrl: update.apiUrl ?? current.apiUrl ?? DEFAULT_API_URL,
24
+ apiKey: update.apiKey ?? current.apiKey ?? "",
25
+ };
26
+ mkdirSync(CONFIG_DIR, { recursive: true });
27
+ writeFileSync(CONFIG_PATH, JSON.stringify(next, null, 2));
28
+ // Config holds an API key — keep it owner-only.
29
+ try {
30
+ chmodSync(CONFIG_PATH, 0o600);
31
+ }
32
+ catch {
33
+ // best effort on platforms without POSIX perms
34
+ }
35
+ return next;
36
+ }
37
+ /**
38
+ * Resolve config from environment first (CONDUYT_API_URL / CONDUYT_API_KEY),
39
+ * falling back to the saved config file.
40
+ */
41
+ export function resolveConfig() {
42
+ const file = readFile();
43
+ return {
44
+ apiUrl: process.env.CONDUYT_API_URL ?? file.apiUrl ?? DEFAULT_API_URL,
45
+ apiKey: process.env.CONDUYT_API_KEY ?? file.apiKey ?? "",
46
+ };
47
+ }
48
+ export function requireAuth() {
49
+ const cfg = resolveConfig();
50
+ if (!cfg.apiKey) {
51
+ throw new Error("No API key configured. Run `conduyt config set --key cdy_...` or set CONDUYT_API_KEY.");
52
+ }
53
+ if (!cfg.apiKey.startsWith("cdy_")) {
54
+ throw new Error("CONDUYT_API_KEY must start with 'cdy_'. Generate one at Settings > API in Conduyt.");
55
+ }
56
+ return cfg;
57
+ }
package/dist/index.js ADDED
@@ -0,0 +1,124 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from "commander";
3
+ import { ConduytClient, buildQuery } from "./client.js";
4
+ import { saveConfig, resolveConfig, configPath } from "./config.js";
5
+ import { print, fail } from "./output.js";
6
+ const program = new Command();
7
+ program
8
+ .name("conduyt")
9
+ .description("Command-line interface for Conduyt CRM — manage contacts, deals, pipelines, and run insight queries from your terminal.")
10
+ .version("1.0.0");
11
+ // ---- config ----
12
+ const config = program.command("config").description("Manage CLI configuration");
13
+ config
14
+ .command("set")
15
+ .description("Save your API key and (optionally) API URL")
16
+ .option("--key <apiKey>", "API key (starts with cdy_)")
17
+ .option("--url <apiUrl>", "API base URL")
18
+ .action((opts) => {
19
+ if (!opts.key && !opts.url)
20
+ fail("Nothing to set. Pass --key and/or --url.");
21
+ const next = saveConfig({ apiKey: opts.key, apiUrl: opts.url });
22
+ process.stdout.write(`Saved to ${configPath()}\n`);
23
+ print({ apiUrl: next.apiUrl, apiKey: mask(next.apiKey) });
24
+ });
25
+ config
26
+ .command("show")
27
+ .description("Show the resolved configuration (API key masked)")
28
+ .action(() => {
29
+ const cfg = resolveConfig();
30
+ print({ apiUrl: cfg.apiUrl, apiKey: mask(cfg.apiKey), source: configPath() });
31
+ });
32
+ // ---- whoami ----
33
+ program
34
+ .command("whoami")
35
+ .description("Show the authenticated user")
36
+ .action(run(async (client) => client.get("/api/v1/users/me")));
37
+ // ---- contacts ----
38
+ const contacts = program.command("contacts").description("Manage contacts");
39
+ contacts
40
+ .command("list")
41
+ .description("List contacts")
42
+ .option("--limit <n>", "max results")
43
+ .option("--cursor <cursor>", "pagination cursor")
44
+ .option("--query <q>", "search query")
45
+ .action(run(async (client, opts) => client.get(`/api/v1/contacts${buildQuery({ limit: opts.limit, cursor: opts.cursor, query: opts.query })}`)));
46
+ contacts
47
+ .command("get <id>")
48
+ .description("Get a single contact by id")
49
+ .action(run(async (client, id) => client.get(`/api/v1/contacts/${encodeURIComponent(id)}`)));
50
+ // ---- deals ----
51
+ const deals = program.command("deals").description("Manage deals");
52
+ deals
53
+ .command("list")
54
+ .description("List deals")
55
+ .option("--limit <n>", "max results")
56
+ .option("--cursor <cursor>", "pagination cursor")
57
+ .option("--pipeline <id>", "filter by pipeline id")
58
+ .action(run(async (client, opts) => client.get(`/api/v1/deals${buildQuery({ limit: opts.limit, cursor: opts.cursor, pipelineId: opts.pipeline })}`)));
59
+ // ---- pipelines ----
60
+ program
61
+ .command("pipelines")
62
+ .description("List pipelines and their stages")
63
+ .action(run(async (client) => client.get("/api/v1/pipelines")));
64
+ // ---- search ----
65
+ program
66
+ .command("search <query>")
67
+ .description("Search across the CRM")
68
+ .option("--limit <n>", "max results")
69
+ .action(run(async (client, query, opts) => client.get(`/api/v1/search${buildQuery({ q: query, limit: opts.limit })}`)));
70
+ // ---- insights ----
71
+ program
72
+ .command("insights <type>")
73
+ .description("Run an AI insight query (e.g. summary, forecast)")
74
+ .option("--input <json>", "JSON payload to send with the request")
75
+ .action(run(async (client, type, opts) => {
76
+ let extra = {};
77
+ if (opts.input) {
78
+ try {
79
+ extra = JSON.parse(opts.input);
80
+ }
81
+ catch {
82
+ fail("--input must be valid JSON");
83
+ }
84
+ }
85
+ return client.post("/api/v1/ai/insights", { type, ...extra });
86
+ }));
87
+ // ---- raw escape hatch ----
88
+ program
89
+ .command("api <method> <path>")
90
+ .description("Make a raw authenticated API request (escape hatch)")
91
+ .option("--body <json>", "JSON request body")
92
+ .action(run(async (client, method, path, opts) => {
93
+ let body;
94
+ if (opts.body) {
95
+ try {
96
+ body = JSON.parse(opts.body);
97
+ }
98
+ catch {
99
+ fail("--body must be valid JSON");
100
+ }
101
+ }
102
+ return client.request(method.toUpperCase(), path, body);
103
+ }));
104
+ program.parseAsync(process.argv).catch(fail);
105
+ // helpers
106
+ function run(handler) {
107
+ return async (...args) => {
108
+ try {
109
+ const client = new ConduytClient();
110
+ const res = await handler(client, ...args);
111
+ print(res);
112
+ }
113
+ catch (err) {
114
+ fail(err);
115
+ }
116
+ };
117
+ }
118
+ function mask(key) {
119
+ if (!key)
120
+ return "(not set)";
121
+ if (key.length <= 12)
122
+ return "cdy_***";
123
+ return `${key.slice(0, 8)}…${key.slice(-4)}`;
124
+ }
package/dist/output.js ADDED
@@ -0,0 +1,16 @@
1
+ /** Unwrap the standard `{ data: ... }` API envelope when present. */
2
+ export function unwrap(res) {
3
+ if (res && typeof res === "object" && "data" in res) {
4
+ return res.data;
5
+ }
6
+ return res;
7
+ }
8
+ export function print(res) {
9
+ const data = unwrap(res);
10
+ process.stdout.write(JSON.stringify(data, null, 2) + "\n");
11
+ }
12
+ export function fail(err) {
13
+ const msg = err instanceof Error ? err.message : String(err);
14
+ process.stderr.write(`Error: ${msg}\n`);
15
+ process.exit(1);
16
+ }
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "conduyt",
3
+ "version": "1.0.0",
4
+ "description": "Command-line interface for Conduyt CRM — manage contacts, deals, pipelines, and run insight queries from your terminal.",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "conduyt": "dist/index.js"
9
+ },
10
+ "files": [
11
+ "dist",
12
+ "README.md"
13
+ ],
14
+ "engines": {
15
+ "node": ">=18"
16
+ },
17
+ "scripts": {
18
+ "build": "tsc",
19
+ "typecheck": "tsc --noEmit",
20
+ "start": "tsx src/index.ts",
21
+ "prepublishOnly": "npm run build"
22
+ },
23
+ "keywords": [
24
+ "conduyt",
25
+ "crm",
26
+ "cli",
27
+ "sales",
28
+ "ai",
29
+ "api"
30
+ ],
31
+ "homepage": "https://conduyt.app",
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "git+https://github.com/ptaramona/conduyt-cli.git"
35
+ },
36
+ "bugs": {
37
+ "url": "https://github.com/ptaramona/conduyt-cli/issues"
38
+ },
39
+ "license": "MIT",
40
+ "author": "Conduyt",
41
+ "dependencies": {
42
+ "commander": "^12.1.0"
43
+ },
44
+ "devDependencies": {
45
+ "typescript": "^5.5.0",
46
+ "tsx": "^4.19.0",
47
+ "@types/node": "^22.0.0"
48
+ }
49
+ }