@1db/cli 0.2.0 → 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 CHANGED
@@ -14,7 +14,10 @@ npx @1db/cli send acme.com # send a test lead
14
14
  npx @1db/cli leads acme.com # see it arrive
15
15
  npx @1db/cli keys acme.com # print the site's endpoint, key and form slug
16
16
  npx @1db/cli form acme.com --name "Quote request" # add another form, print its slug
17
+ npx @1db/cli site acme.com --product-type pallets # website settings
18
+ npx @1db/cli forwarding # lead forwarding rules
17
19
  ```
18
20
 
19
21
  Run `npx @1db/cli help` for every command, or `npm i -g @1db/cli` to get a plain `1db` command. Zero dependencies, Node 18+.
22
+ What a token may do depends on its scopes (`npx @1db/cli whoami`); see the [docs](https://www.1-db.com/docs/guides/ai-agents#scopes).
20
23
  Credentials live in `~/.config/1db/config.json` (mode 600); `ONEDB_TOKEN` overrides.
package/bin/1db.mjs CHANGED
@@ -20,6 +20,7 @@ import {
20
20
  writeConfig,
21
21
  } from "../lib/core.mjs";
22
22
  import { browserLogin, installMcp, printMcpCommand, promptToken, saveToken } from "../lib/login.mjs";
23
+ import { CONFIG_HELP, configCommands, parseFieldList } from "../lib/config-commands.mjs";
23
24
 
24
25
  // ---------- commands ----------
25
26
  const commands = {
@@ -45,6 +46,7 @@ const commands = {
45
46
  const me = await api("/v1/agent/me");
46
47
  if (flags.json) return json(me);
47
48
  print(`${me.organization?.name} ${dim(`(${me.user?.email ?? me.user?.name})`)}`);
49
+ print(dim(`Scopes: ${(me.scopes ?? []).join(", ")}`));
48
50
  print(dim(`API ${settings().apiUrl}`));
49
51
  },
50
52
 
@@ -162,6 +164,8 @@ const commands = {
162
164
  1db mcp [--install] Print (or run) the command that adds 1DB's MCP server to Claude Code
163
165
  1db logout
164
166
 
167
+ ${CONFIG_HELP}
168
+
165
169
  Every command takes --json. Env: ONEDB_TOKEN, ONEDB_API_URL, ONEDB_APP_URL.`);
166
170
  },
167
171
  };
@@ -190,22 +194,10 @@ function writeCredentials(result, flags) {
190
194
  print(dim(`Test it: 1db send ${site.slug}`));
191
195
  }
192
196
 
193
- /** "email*,name,phone:phone" → [{ key: "email", required: true }, { key: "name" }, { key: "phone", type: "phone" }] */
194
- function parseFieldList(spec) {
195
- return spec
196
- .split(",")
197
- .map((s) => s.trim())
198
- .filter(Boolean)
199
- .map((s) => {
200
- const required = s.endsWith("*");
201
- const [key, type] = s.replace(/\*$/, "").split(":");
202
- return { key, ...(type ? { type } : {}), ...(required ? { required } : {}) };
203
- });
204
- }
205
-
206
197
  // ---------- main ----------
207
198
  const [name = "help", ...rest] = process.argv.slice(2);
208
- const command = commands[name === "--help" || name === "-h" ? "help" : name];
199
+ const all = { ...commands, ...configCommands };
200
+ const command = all[name === "--help" || name === "-h" ? "help" : name];
209
201
  if (!command) {
210
202
  console.error(`Unknown command "${name}". Run \`${CMD} help\`.`);
211
203
  process.exit(1);
@@ -0,0 +1,161 @@
1
+ // Configuration commands: website settings, form edits and notifications,
2
+ // lead forwarding, deletes. Each maps to a route in convex/http/agentConfig.ts.
3
+ import { CliError, api, dim, green, json, print, table } from "./core.mjs";
4
+
5
+ const s = (value) => (typeof value === "string" ? value : undefined);
6
+ const list = (value) => (typeof value === "string" ? value.split(",").map((x) => x.trim()).filter(Boolean) : undefined);
7
+ const post = (path, body) => api(path, { method: "POST", body });
8
+
9
+ /** "email*,name,phone:phone" → [{ key: "email", required: true }, { key: "name" }, { key: "phone", type: "phone" }] */
10
+ export function parseFieldList(spec) {
11
+ return spec
12
+ .split(",")
13
+ .map((x) => x.trim())
14
+ .filter(Boolean)
15
+ .map((x) => {
16
+ const required = x.endsWith("*");
17
+ const [key, type] = x.replace(/\*$/, "").split(":");
18
+ return { key, ...(type ? { type } : {}), ...(required ? { required } : {}) };
19
+ });
20
+ }
21
+
22
+ function printForm(result) {
23
+ const { form, notifications: n } = result;
24
+ print(`${form.name} ${dim(`(${form.slug}${result.website ? `, ${result.website.slug}` : ""})`)}`);
25
+ print(dim(`Fields: ${form.fields.map((f) => f.key + (f.required ? "*" : "")).join(", ")}`));
26
+ if (!n) return;
27
+ print(dim(`Email: ${n.email.enabled ? n.email.recipients.join(", ") : "off"}`));
28
+ print(dim(`Webhook: ${n.webhook.enabled ? n.webhook.url : "off"}`));
29
+ }
30
+
31
+ /** --enable / --disable → true / false / undefined */
32
+ const enabledFlag = (flags) => (flags.enable ? true : flags.disable ? false : undefined);
33
+
34
+ function ruleBody(flags) {
35
+ return {
36
+ name: s(flags.name),
37
+ endpoint: s(flags.endpoint),
38
+ authType: s(flags["auth-type"]),
39
+ authHeader: s(flags["auth-header"]),
40
+ authToken: s(flags["auth-token"]),
41
+ sourceIdentifier: s(flags.source),
42
+ enabled: enabledFlag(flags),
43
+ };
44
+ }
45
+
46
+ export const configCommands = {
47
+ async "product-types"({ flags }) {
48
+ const { productTypes } = await api("/v1/agent/product-types");
49
+ if (flags.json) return json(productTypes);
50
+ if (productTypes.length === 0) return print("No product types in this organization's catalog.");
51
+ table(productTypes, ["slug", "name", "active"]);
52
+ },
53
+
54
+ async site({ positional, flags }) {
55
+ const website = positional[0];
56
+ if (!website) {
57
+ throw new CliError("Usage: 1db site <website> [--name ..] [--domain ..] [--status active|development|maintenance|archived] [--product-type slug] [--product-types a,b]");
58
+ }
59
+ const result = await post("/v1/agent/website/update", {
60
+ website,
61
+ name: s(flags.name),
62
+ domain: s(flags.domain),
63
+ status: s(flags.status),
64
+ defaultProductType: s(flags["product-type"]),
65
+ productTypes: list(flags["product-types"]),
66
+ });
67
+ if (flags.json) return json(result);
68
+ const w = result.website;
69
+ print(`${green("Saved")} ${w.name} ${dim(`(${w.slug}${w.domain ? `, ${w.domain}` : ""})`)}`);
70
+ print(dim(`Status: ${w.status ?? "active"} · Default product type: ${w.defaultProductType ?? "none"}`));
71
+ },
72
+
73
+ async "form-edit"({ positional, flags }) {
74
+ const form = positional[0];
75
+ if (!form) throw new CliError('Usage: 1db form-edit <form slug> [--name ..] [--slug ..] [--fields "email*,name,message:textarea"]');
76
+ const result = s(flags.name) || s(flags.slug) || s(flags.fields)
77
+ ? await post("/v1/agent/forms/update", {
78
+ form,
79
+ name: s(flags.name),
80
+ slug: s(flags.slug),
81
+ fields: s(flags.fields) ? parseFieldList(flags.fields) : undefined,
82
+ })
83
+ : await api(`/v1/agent/form?form=${encodeURIComponent(form)}`);
84
+ if (flags.json) return json(result);
85
+ printForm(result);
86
+ },
87
+
88
+ async notify({ positional, flags }) {
89
+ const form = positional[0];
90
+ if (!form || (flags.emails === undefined && flags.webhook === undefined)) {
91
+ throw new CliError("Usage: 1db notify <form slug> [--emails a@x.com,b@x.com | --no-emails] [--webhook https://... | --no-webhook]");
92
+ }
93
+ const result = await post("/v1/agent/forms/notifications", {
94
+ form,
95
+ email: flags.emails === undefined ? undefined : flags.emails === false ? { enabled: false, recipients: [] } : { enabled: true, recipients: list(flags.emails) },
96
+ webhook: flags.webhook === undefined ? undefined : flags.webhook === false ? { enabled: false } : { enabled: true, url: s(flags.webhook) },
97
+ });
98
+ if (flags.json) return json(result);
99
+ printForm(result);
100
+ },
101
+
102
+ async forwarding({ positional, flags }) {
103
+ const [action = "list", id] = positional;
104
+ if (action === "list") {
105
+ const { rules, warnings = [] } = await api("/v1/agent/forwarding");
106
+ if (flags.json) return json(rules);
107
+ if (rules.length === 0) print("No forwarding rules.");
108
+ else {
109
+ table(
110
+ rules.map((r) => ({ ...r, on: r.enabled ? "yes" : "no", auth: r.hasAuthToken ? r.authType : "none" })),
111
+ ["id", "name", "endpoint", "on", "auth"],
112
+ );
113
+ }
114
+ for (const warning of warnings) print(`! ${warning}`);
115
+ return;
116
+ }
117
+ let result;
118
+ if (action === "add") {
119
+ if (!s(flags.name) || !s(flags.endpoint)) {
120
+ throw new CliError("Usage: 1db forwarding add --name .. --endpoint https://.. [--auth-token ..] [--auth-header X-API-Key] [--auth-type api_key|bearer|none] [--source 1db] [--enable]");
121
+ }
122
+ result = await post("/v1/agent/forwarding", ruleBody(flags));
123
+ } else if (action === "update" && id) {
124
+ result = await post("/v1/agent/forwarding/update", { id, ...ruleBody(flags) });
125
+ } else if (action === "delete" && id) {
126
+ result = await post("/v1/agent/forwarding/delete", { id, confirm: s(flags.confirm) ?? "" });
127
+ } else {
128
+ throw new CliError("Usage: 1db forwarding [list | add .. | update <id> .. | delete <id> --confirm <rule name>]");
129
+ }
130
+ if (flags.json) return json(result);
131
+ if (result.deleted) return print(green(`Deleted rule ${result.deleted.rule}`));
132
+ const r = result.rule;
133
+ print(`${green("Saved")} ${r.name} ${dim(`(${r.id})`)} → ${r.endpoint}`);
134
+ print(dim(r.enabled ? "Enabled: new leads are forwarded." : "Disabled. Turn it on with --enable when ready."));
135
+ },
136
+
137
+ async delete({ positional, flags }) {
138
+ const [kind, target] = positional;
139
+ if (!["form", "site"].includes(kind) || !target || typeof flags.confirm !== "string") {
140
+ throw new CliError("Usage: 1db delete form <slug> --confirm <slug> | 1db delete site <website> --confirm <website slug>");
141
+ }
142
+ const result =
143
+ kind === "form"
144
+ ? await post("/v1/agent/forms/delete", { form: target, confirm: flags.confirm })
145
+ : await post("/v1/agent/website/delete", { website: target, confirm: flags.confirm });
146
+ if (flags.json) return json(result);
147
+ print(green(`Deleted ${kind} ${Object.values(result.deleted)[0]}`));
148
+ },
149
+ };
150
+
151
+ export const CONFIG_HELP = `
152
+ Configure (needs a token with the right scope; see \`1db whoami\`)
153
+ 1db site <website> [--status ..] [--product-type slug] [--name ..] [--domain ..]
154
+ 1db product-types List the organization's product types
155
+ 1db form-edit <form> [--name ..] [--slug ..] [--fields "email*,name"]
156
+ Show a form, or change it
157
+ 1db notify <form> [--emails a@x.com,b@x.com | --no-emails] [--webhook https://.. | --no-webhook]
158
+ 1db forwarding [list | add | update <id> | delete <id>]
159
+ add --name .. --endpoint https://.. [--auth-token ..] [--auth-header ..] [--enable]
160
+ 1db delete form <slug> --confirm <slug>
161
+ 1db delete site <website> --confirm <website slug>`;
package/lib/core.mjs CHANGED
@@ -36,7 +36,7 @@ export function settings() {
36
36
  }
37
37
 
38
38
  // ---------- args ----------
39
- export const BOOLEAN_FLAGS = new Set(["json", "help", "install", "paste", "relogin"]);
39
+ export const BOOLEAN_FLAGS = new Set(["json", "help", "install", "paste", "relogin", "enable", "disable"]);
40
40
 
41
41
  export function parseArgs(argv) {
42
42
  const positional = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@1db/cli",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "description": "Connect websites to 1DB from your terminal or an AI agent: onboard a domain, get its lead keys, send and list leads.",
5
5
  "type": "module",
6
6
  "bin": {