@1db/cli 0.1.1 → 0.3.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 +4 -0
- package/bin/1db.mjs +34 -1
- package/lib/config-commands.mjs +157 -0
- package/lib/core.mjs +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -13,7 +13,11 @@ npx @1db/cli init acme.com # scan + add the site, write DB_* vars to .env.lo
|
|
|
13
13
|
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
|
+
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
|
|
16
19
|
```
|
|
17
20
|
|
|
18
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).
|
|
19
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
|
|
|
@@ -72,6 +74,31 @@ const commands = {
|
|
|
72
74
|
writeCredentials(result, { ...flags, env: flags["env-file"] ? flags.env : false });
|
|
73
75
|
},
|
|
74
76
|
|
|
77
|
+
async form({ positional, flags }) {
|
|
78
|
+
const website = positional[0];
|
|
79
|
+
if (!website || typeof flags.name !== "string") {
|
|
80
|
+
throw new CliError('Usage: 1db form <website> --name "Quote request" [--fields "email*,name,phone:phone,message:textarea"] [--preset "B2B"] [--slug quote]');
|
|
81
|
+
}
|
|
82
|
+
const result = await api("/v1/agent/forms", {
|
|
83
|
+
method: "POST",
|
|
84
|
+
body: {
|
|
85
|
+
website,
|
|
86
|
+
name: flags.name,
|
|
87
|
+
slug: typeof flags.slug === "string" ? flags.slug : undefined,
|
|
88
|
+
preset: typeof flags.preset === "string" ? flags.preset : undefined,
|
|
89
|
+
fields: typeof flags.fields === "string" ? parseFieldList(flags.fields) : undefined,
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
if (flags.json) return json(result);
|
|
93
|
+
const form = result.forms.find((f) => f.slug === result.formSlug);
|
|
94
|
+
print(`${result.created ? green("Created") : "Found"} ${bold(form.name)} on ${result.website.name}`);
|
|
95
|
+
print(dim(`Fields: ${form.fields.map((f) => f.key + (f.required ? "*" : "")).join(", ")}`));
|
|
96
|
+
// Its own env var, so DB_FORM_SLUG keeps pointing at the site's existing form
|
|
97
|
+
print(`\n${result.formEnvVar}=${form.slug}`);
|
|
98
|
+
print(dim(`\nPost its leads to ${result.leadsUrl} with {"formSlug": "${form.slug}", ...} and the site's DB_API_KEY.`));
|
|
99
|
+
print(dim(`Test it: ${CMD} send ${result.website.slug} --form ${form.slug}`));
|
|
100
|
+
},
|
|
101
|
+
|
|
75
102
|
async send({ positional, flags }) {
|
|
76
103
|
const [website, ...pairs] = positional;
|
|
77
104
|
if (!website) throw new CliError("Usage: 1db send <website> [email=jane@acme.com message=\"Hi\" ...] [--form slug]");
|
|
@@ -129,11 +156,16 @@ const commands = {
|
|
|
129
156
|
1db init <domain> Scan + add a website, write its keys to .env.local
|
|
130
157
|
[--name "Acme"] [--env-file .env] [--prefix VITE_|NEXT_PUBLIC_|PUBLIC_] [--no-env]
|
|
131
158
|
1db keys <website> Print a website's endpoint, key and form slug
|
|
159
|
+
1db form <website> --name "Quote request"
|
|
160
|
+
Add another form to a website and print its slug
|
|
161
|
+
[--fields "email*,name,phone:phone,message:textarea"] [--preset "B2B"] [--slug quote]
|
|
132
162
|
1db send <website> [k=v ...] Send a (test) lead through the real ingest endpoint
|
|
133
163
|
1db leads [website] [--limit 20] Most recent leads
|
|
134
164
|
1db mcp [--install] Print (or run) the command that adds 1DB's MCP server to Claude Code
|
|
135
165
|
1db logout
|
|
136
166
|
|
|
167
|
+
${CONFIG_HELP}
|
|
168
|
+
|
|
137
169
|
Every command takes --json. Env: ONEDB_TOKEN, ONEDB_API_URL, ONEDB_APP_URL.`);
|
|
138
170
|
},
|
|
139
171
|
};
|
|
@@ -164,7 +196,8 @@ function writeCredentials(result, flags) {
|
|
|
164
196
|
|
|
165
197
|
// ---------- main ----------
|
|
166
198
|
const [name = "help", ...rest] = process.argv.slice(2);
|
|
167
|
-
const
|
|
199
|
+
const all = { ...commands, ...configCommands };
|
|
200
|
+
const command = all[name === "--help" || name === "-h" ? "help" : name];
|
|
168
201
|
if (!command) {
|
|
169
202
|
console.error(`Unknown command "${name}". Run \`${CMD} help\`.`);
|
|
170
203
|
process.exit(1);
|
|
@@ -0,0 +1,157 @@
|
|
|
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 } = await api("/v1/agent/forwarding");
|
|
106
|
+
if (flags.json) return json(rules);
|
|
107
|
+
if (rules.length === 0) return print("No forwarding rules.");
|
|
108
|
+
return table(
|
|
109
|
+
rules.map((r) => ({ ...r, on: r.enabled ? "yes" : "no", auth: r.hasAuthToken ? r.authType : "none" })),
|
|
110
|
+
["id", "name", "endpoint", "on", "auth"],
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
let result;
|
|
114
|
+
if (action === "add") {
|
|
115
|
+
if (!s(flags.name) || !s(flags.endpoint)) {
|
|
116
|
+
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]");
|
|
117
|
+
}
|
|
118
|
+
result = await post("/v1/agent/forwarding", ruleBody(flags));
|
|
119
|
+
} else if (action === "update" && id) {
|
|
120
|
+
result = await post("/v1/agent/forwarding/update", { id, ...ruleBody(flags) });
|
|
121
|
+
} else if (action === "delete" && id) {
|
|
122
|
+
result = await post("/v1/agent/forwarding/delete", { id, confirm: s(flags.confirm) ?? "" });
|
|
123
|
+
} else {
|
|
124
|
+
throw new CliError("Usage: 1db forwarding [list | add .. | update <id> .. | delete <id> --confirm <rule name>]");
|
|
125
|
+
}
|
|
126
|
+
if (flags.json) return json(result);
|
|
127
|
+
if (result.deleted) return print(green(`Deleted rule ${result.deleted.rule}`));
|
|
128
|
+
const r = result.rule;
|
|
129
|
+
print(`${green("Saved")} ${r.name} ${dim(`(${r.id})`)} → ${r.endpoint}`);
|
|
130
|
+
print(dim(r.enabled ? "Enabled: new leads are forwarded." : "Disabled. Turn it on with --enable when ready."));
|
|
131
|
+
},
|
|
132
|
+
|
|
133
|
+
async delete({ positional, flags }) {
|
|
134
|
+
const [kind, target] = positional;
|
|
135
|
+
if (!["form", "site"].includes(kind) || !target || typeof flags.confirm !== "string") {
|
|
136
|
+
throw new CliError("Usage: 1db delete form <slug> --confirm <slug> | 1db delete site <website> --confirm <website slug>");
|
|
137
|
+
}
|
|
138
|
+
const result =
|
|
139
|
+
kind === "form"
|
|
140
|
+
? await post("/v1/agent/forms/delete", { form: target, confirm: flags.confirm })
|
|
141
|
+
: await post("/v1/agent/website/delete", { website: target, confirm: flags.confirm });
|
|
142
|
+
if (flags.json) return json(result);
|
|
143
|
+
print(green(`Deleted ${kind} ${Object.values(result.deleted)[0]}`));
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
export const CONFIG_HELP = `
|
|
148
|
+
Configure (needs a token with the right scope; see \`1db whoami\`)
|
|
149
|
+
1db site <website> [--status ..] [--product-type slug] [--name ..] [--domain ..]
|
|
150
|
+
1db product-types List the organization's product types
|
|
151
|
+
1db form-edit <form> [--name ..] [--slug ..] [--fields "email*,name"]
|
|
152
|
+
Show a form, or change it
|
|
153
|
+
1db notify <form> [--emails a@x.com,b@x.com | --no-emails] [--webhook https://.. | --no-webhook]
|
|
154
|
+
1db forwarding [list | add | update <id> | delete <id>]
|
|
155
|
+
add --name .. --endpoint https://.. [--auth-token ..] [--auth-header ..] [--enable]
|
|
156
|
+
1db delete form <slug> --confirm <slug>
|
|
157
|
+
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 = [];
|