@tryhermes/cli 0.1.3 → 0.1.5
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/dist/index.js +277 -54
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -6872,21 +6872,36 @@ function formatConnectionTable(items) {
|
|
|
6872
6872
|
if (items.length === 0) return "No connections found.";
|
|
6873
6873
|
const rows = items.map((c) => ({
|
|
6874
6874
|
id: c.id,
|
|
6875
|
+
label: c.label ?? "\u2014",
|
|
6875
6876
|
source: c.source_type,
|
|
6877
|
+
default: c.is_default ? "yes" : "no",
|
|
6876
6878
|
status: c.status,
|
|
6877
6879
|
synced: c.last_synced_at ? c.last_synced_at.slice(0, 19).replace("T", " ") : "\u2014",
|
|
6878
6880
|
created: c.created_at ? c.created_at.slice(0, 10) : "\u2014"
|
|
6879
6881
|
}));
|
|
6880
6882
|
const headers = {
|
|
6881
6883
|
id: "ID",
|
|
6884
|
+
label: "LABEL",
|
|
6882
6885
|
source: "SOURCE",
|
|
6886
|
+
default: "DEFAULT",
|
|
6883
6887
|
status: "STATUS",
|
|
6884
6888
|
synced: "LAST SYNCED",
|
|
6885
6889
|
created: "CREATED"
|
|
6886
6890
|
};
|
|
6887
|
-
const cols = [
|
|
6891
|
+
const cols = [
|
|
6892
|
+
"id",
|
|
6893
|
+
"label",
|
|
6894
|
+
"source",
|
|
6895
|
+
"default",
|
|
6896
|
+
"status",
|
|
6897
|
+
"synced",
|
|
6898
|
+
"created"
|
|
6899
|
+
];
|
|
6888
6900
|
const widths = Object.fromEntries(
|
|
6889
|
-
cols.map((c) => [
|
|
6901
|
+
cols.map((c) => [
|
|
6902
|
+
c,
|
|
6903
|
+
Math.max(headers[c].length, ...rows.map((r) => r[c].length))
|
|
6904
|
+
])
|
|
6890
6905
|
);
|
|
6891
6906
|
const fmt = (vals) => cols.map((c) => vals[c].padEnd(widths[c])).join(" ").trimEnd();
|
|
6892
6907
|
return [fmt(headers), ...rows.map(fmt)].join("\n");
|
|
@@ -6894,17 +6909,29 @@ function formatConnectionTable(items) {
|
|
|
6894
6909
|
function renderConnectionDetail(c) {
|
|
6895
6910
|
const rows = [
|
|
6896
6911
|
["ID", c.id],
|
|
6912
|
+
["Label", c.label ?? "\u2014"],
|
|
6897
6913
|
["Source", c.source_type],
|
|
6914
|
+
["Default", c.is_default ? "yes" : "no"],
|
|
6898
6915
|
["Status", c.status],
|
|
6899
6916
|
["Last synced", c.last_synced_at ?? "\u2014"],
|
|
6900
6917
|
["Created", c.created_at ?? "\u2014"]
|
|
6901
6918
|
];
|
|
6902
|
-
if (c.source_type === "postgres")
|
|
6919
|
+
if (c.source_type === "postgres")
|
|
6920
|
+
rows.push(["Postgres URL", c.postgres_url ?? "\u2014"]);
|
|
6903
6921
|
if (c.source_type === "mysql") rows.push(["MySQL URL", c.mysql_url ?? "\u2014"]);
|
|
6904
6922
|
if (c.source_type === "firestore") {
|
|
6905
6923
|
rows.push(["Firestore project", c.firestore_project_id ?? "\u2014"]);
|
|
6906
6924
|
rows.push(["Firestore creds", c.firestore_credentials ?? "\u2014"]);
|
|
6907
6925
|
}
|
|
6926
|
+
if (c.source_type === "posthog") {
|
|
6927
|
+
rows.push(["PostHog host", c.posthog_host ?? "\u2014"]);
|
|
6928
|
+
rows.push(["PostHog project", c.posthog_project_id ?? "\u2014"]);
|
|
6929
|
+
rows.push(["PostHog API key", c.posthog_api_key ?? "\u2014"]);
|
|
6930
|
+
}
|
|
6931
|
+
if (c.source_type === "stripe") {
|
|
6932
|
+
rows.push(["Stripe account", c.stripe_account_id ?? "\u2014"]);
|
|
6933
|
+
rows.push(["Stripe API key", c.stripe_api_key ?? "\u2014"]);
|
|
6934
|
+
}
|
|
6908
6935
|
const labelWidth = Math.max(...rows.map(([k]) => k.length));
|
|
6909
6936
|
const lines = ["Identity"];
|
|
6910
6937
|
for (const [k, v] of rows) {
|
|
@@ -6941,7 +6968,11 @@ function renderQueryResult(r) {
|
|
|
6941
6968
|
indent(sampleJson)
|
|
6942
6969
|
].join("\n");
|
|
6943
6970
|
}
|
|
6944
|
-
|
|
6971
|
+
function explicitConnectionId(opts, positional) {
|
|
6972
|
+
const flag = opts.source ?? opts.connection;
|
|
6973
|
+
return flag ?? positional;
|
|
6974
|
+
}
|
|
6975
|
+
async function resolveConnectionId(explicit, opts = {}) {
|
|
6945
6976
|
if (explicit) return explicit;
|
|
6946
6977
|
const list = await apiFetch("/api/v1/connections");
|
|
6947
6978
|
if (list.connections.length === 0) {
|
|
@@ -6951,16 +6982,29 @@ async function resolveConnectionId(explicit) {
|
|
|
6951
6982
|
hint: `Run \`${cmd("connections add --postgres-url ...")}\` (or another source flag) first.`
|
|
6952
6983
|
});
|
|
6953
6984
|
}
|
|
6954
|
-
|
|
6985
|
+
const chosen = list.connections[0];
|
|
6986
|
+
if (!opts.json && list.connections.length > 1) {
|
|
6987
|
+
const others = list.connections.length - 1;
|
|
6988
|
+
const label = chosen.label ?? chosen.source_type;
|
|
6989
|
+
process.stderr.write(
|
|
6990
|
+
`Using default source ${label} (${chosen.id}). ${others} other source${others === 1 ? "" : "s"} connected \u2014 pass --source <id> to target a different one.
|
|
6991
|
+
`
|
|
6992
|
+
);
|
|
6993
|
+
}
|
|
6994
|
+
return chosen.id;
|
|
6955
6995
|
}
|
|
6956
6996
|
function registerConnectionsCommand(program3) {
|
|
6957
|
-
const conns = program3.command("connections").description(
|
|
6997
|
+
const conns = program3.command("connections").description(
|
|
6998
|
+
"Manage the customer data sources Hermes scans (Postgres, MySQL, CSV, Firestore, PostHog, Stripe)"
|
|
6999
|
+
).action(function() {
|
|
6958
7000
|
this.help();
|
|
6959
7001
|
});
|
|
6960
7002
|
conns.command("list").description("List connections in the linked org").action(async function() {
|
|
6961
7003
|
const opts = this.optsWithGlobals();
|
|
6962
7004
|
try {
|
|
6963
|
-
const response = await apiFetch(
|
|
7005
|
+
const response = await apiFetch(
|
|
7006
|
+
"/api/v1/connections"
|
|
7007
|
+
);
|
|
6964
7008
|
if (opts.json) {
|
|
6965
7009
|
console.log(JSON.stringify(response, null, 2));
|
|
6966
7010
|
} else {
|
|
@@ -6971,10 +7015,13 @@ function registerConnectionsCommand(program3) {
|
|
|
6971
7015
|
emitApiError(err, Boolean(opts.json));
|
|
6972
7016
|
}
|
|
6973
7017
|
});
|
|
6974
|
-
conns.command("show [id]").description("Show full connection row (defaults to the org
|
|
7018
|
+
conns.command("show [id]").description("Show full connection row (defaults to the org default)").option("--source <id>", SOURCE_OPTION_DESC).option("--connection <id>", "alias for --source").action(async function(id) {
|
|
6975
7019
|
const opts = this.optsWithGlobals();
|
|
6976
7020
|
try {
|
|
6977
|
-
const resolved = await resolveConnectionId(
|
|
7021
|
+
const resolved = await resolveConnectionId(
|
|
7022
|
+
explicitConnectionId(opts, id),
|
|
7023
|
+
{ json: Boolean(opts.json) }
|
|
7024
|
+
);
|
|
6978
7025
|
const conn = await apiFetch(
|
|
6979
7026
|
`/api/v1/connections/${encodeURIComponent(resolved)}`
|
|
6980
7027
|
);
|
|
@@ -6988,10 +7035,15 @@ function registerConnectionsCommand(program3) {
|
|
|
6988
7035
|
emitApiError(err, Boolean(opts.json));
|
|
6989
7036
|
}
|
|
6990
7037
|
});
|
|
6991
|
-
conns.command("schema [id]").description(
|
|
7038
|
+
conns.command("schema [id]").description(
|
|
7039
|
+
"Print source_type + dialect + example query + cached schema (the contract for writing a detection_query)"
|
|
7040
|
+
).option("--source <id>", SOURCE_OPTION_DESC).option("--connection <id>", "alias for --source").action(async function(id) {
|
|
6992
7041
|
const opts = this.optsWithGlobals();
|
|
6993
7042
|
try {
|
|
6994
|
-
const resolved = await resolveConnectionId(
|
|
7043
|
+
const resolved = await resolveConnectionId(
|
|
7044
|
+
explicitConnectionId(opts, id),
|
|
7045
|
+
{ json: Boolean(opts.json) }
|
|
7046
|
+
);
|
|
6995
7047
|
const result = await apiFetch(
|
|
6996
7048
|
`/api/v1/connections/${encodeURIComponent(resolved)}/schema`
|
|
6997
7049
|
);
|
|
@@ -7006,21 +7058,38 @@ function registerConnectionsCommand(program3) {
|
|
|
7006
7058
|
}
|
|
7007
7059
|
});
|
|
7008
7060
|
conns.command("add").description(
|
|
7009
|
-
"Tell Hermes about
|
|
7010
|
-
).option("--postgres-url <url>", "\u2192 source_type=postgres
|
|
7061
|
+
"Tell Hermes about another data source. Existing sources and triggers are left intact."
|
|
7062
|
+
).option("--label <label>", "human-readable label for this source").option("--default", "make this the default source for new triggers").option("--postgres-url <url>", "\u2192 source_type=postgres").option("--mysql-url <url>", "\u2192 source_type=mysql").option("--posthog-host <url>", "\u2192 source_type=posthog").option("--posthog-project-id <id>", "PostHog project id").option("--posthog-api-key <key>", "PostHog personal API key").option(
|
|
7063
|
+
"--stripe-api-key <key>",
|
|
7064
|
+
"Stripe secret (sk_...) or restricted (rk_...) key \u2192 source_type=stripe"
|
|
7065
|
+
).option(
|
|
7011
7066
|
"--firestore-credentials <json-or-@file>",
|
|
7012
|
-
"Service account JSON (raw, or @path/to/sa.json) \u2192 source_type=firestore
|
|
7013
|
-
).option(
|
|
7067
|
+
"Service account JSON (raw, or @path/to/sa.json) \u2192 source_type=firestore"
|
|
7068
|
+
).option(
|
|
7069
|
+
"--firestore-project-id <id>",
|
|
7070
|
+
"explicit project_id (otherwise inferred from creds)"
|
|
7071
|
+
).option("--csv-file <path>", "\u2192 source_type=csv").option(
|
|
7072
|
+
"--connection <id>",
|
|
7073
|
+
"when used with --csv-file, append the table to an existing CSV source"
|
|
7074
|
+
).option(
|
|
7075
|
+
"--table-name <name>",
|
|
7076
|
+
"explicit table name for the CSV upload (otherwise inferred from filename)"
|
|
7077
|
+
).option(
|
|
7078
|
+
"--confirm-switch",
|
|
7079
|
+
"deprecated; connections add no longer replaces existing sources"
|
|
7080
|
+
).action(async function() {
|
|
7014
7081
|
const opts = this.optsWithGlobals();
|
|
7015
7082
|
const credentialFlags = [
|
|
7016
7083
|
opts.postgresUrl ? "--postgres-url" : null,
|
|
7017
7084
|
opts.mysqlUrl ? "--mysql-url" : null,
|
|
7085
|
+
opts.posthogHost || opts.posthogProjectId || opts.posthogApiKey ? "--posthog-host/--posthog-project-id/--posthog-api-key" : null,
|
|
7086
|
+
opts.stripeApiKey ? "--stripe-api-key" : null,
|
|
7018
7087
|
opts.firestoreCredentials ? "--firestore-credentials" : null,
|
|
7019
7088
|
opts.csvFile ? "--csv-file" : null
|
|
7020
7089
|
].filter(Boolean);
|
|
7021
7090
|
if (credentialFlags.length === 0) {
|
|
7022
7091
|
console.error(
|
|
7023
|
-
"Error: provide one of --postgres-url, --mysql-url, --firestore-credentials, --csv-file."
|
|
7092
|
+
"Error: provide one of --postgres-url, --mysql-url, --posthog-host/--posthog-project-id/--posthog-api-key, --stripe-api-key, --firestore-credentials, --csv-file."
|
|
7024
7093
|
);
|
|
7025
7094
|
process.exit(1);
|
|
7026
7095
|
}
|
|
@@ -7030,10 +7099,23 @@ function registerConnectionsCommand(program3) {
|
|
|
7030
7099
|
);
|
|
7031
7100
|
process.exit(1);
|
|
7032
7101
|
}
|
|
7102
|
+
if (opts.connection && !opts.csvFile) {
|
|
7103
|
+
console.error("Error: --connection is only valid with --csv-file.");
|
|
7104
|
+
process.exit(1);
|
|
7105
|
+
}
|
|
7106
|
+
if (opts.connection && opts.default) {
|
|
7107
|
+
console.error(
|
|
7108
|
+
"Error: --default only applies when creating a new source, not when appending to an existing CSV source."
|
|
7109
|
+
);
|
|
7110
|
+
process.exit(1);
|
|
7111
|
+
}
|
|
7033
7112
|
try {
|
|
7034
7113
|
const conn = opts.csvFile ? await uploadCsvConnection({
|
|
7035
7114
|
filePath: String(opts.csvFile),
|
|
7115
|
+
connectionId: opts.connection ? String(opts.connection) : void 0,
|
|
7036
7116
|
tableName: opts.tableName ? String(opts.tableName) : void 0,
|
|
7117
|
+
label: opts.label ? String(opts.label) : void 0,
|
|
7118
|
+
setDefault: Boolean(opts.default),
|
|
7037
7119
|
confirmSwitch: Boolean(opts.confirmSwitch)
|
|
7038
7120
|
}) : await apiFetch("/api/v1/connections", {
|
|
7039
7121
|
method: "POST",
|
|
@@ -7053,10 +7135,15 @@ function registerConnectionsCommand(program3) {
|
|
|
7053
7135
|
emitApiError(err, Boolean(opts.json));
|
|
7054
7136
|
}
|
|
7055
7137
|
});
|
|
7056
|
-
conns.command("query <q> [id]").description(
|
|
7138
|
+
conns.command("query <q> [id]").description(
|
|
7139
|
+
"Run a read-only query against the source \u2014 returns count + sample. Iterate here before `triggers create`."
|
|
7140
|
+
).option("--source <id>", SOURCE_OPTION_DESC).option("--connection <id>", "alias for --source").action(async function(q, id) {
|
|
7057
7141
|
const opts = this.optsWithGlobals();
|
|
7058
7142
|
try {
|
|
7059
|
-
const resolved = await resolveConnectionId(
|
|
7143
|
+
const resolved = await resolveConnectionId(
|
|
7144
|
+
explicitConnectionId(opts, id),
|
|
7145
|
+
{ json: Boolean(opts.json) }
|
|
7146
|
+
);
|
|
7060
7147
|
const body = { query: parseQueryArg(q) };
|
|
7061
7148
|
const result = await apiFetch(
|
|
7062
7149
|
`/api/v1/connections/${encodeURIComponent(resolved)}/query`,
|
|
@@ -7072,10 +7159,15 @@ function registerConnectionsCommand(program3) {
|
|
|
7072
7159
|
emitApiError(err, Boolean(opts.json));
|
|
7073
7160
|
}
|
|
7074
7161
|
});
|
|
7075
|
-
conns.command("inspect-schema [id]").description(
|
|
7162
|
+
conns.command("inspect-schema [id]").description(
|
|
7163
|
+
"Re-run schema introspection against the live source (use after the source schema changes)"
|
|
7164
|
+
).option("--source <id>", SOURCE_OPTION_DESC).option("--connection <id>", "alias for --source").action(async function(id) {
|
|
7076
7165
|
const opts = this.optsWithGlobals();
|
|
7077
7166
|
try {
|
|
7078
|
-
const resolved = await resolveConnectionId(
|
|
7167
|
+
const resolved = await resolveConnectionId(
|
|
7168
|
+
explicitConnectionId(opts, id),
|
|
7169
|
+
{ json: Boolean(opts.json) }
|
|
7170
|
+
);
|
|
7079
7171
|
const conn = await apiFetch(
|
|
7080
7172
|
`/api/v1/connections/${encodeURIComponent(resolved)}/inspect-schema`,
|
|
7081
7173
|
{ method: "POST" }
|
|
@@ -7092,10 +7184,16 @@ function registerConnectionsCommand(program3) {
|
|
|
7092
7184
|
});
|
|
7093
7185
|
conns.command("remove [id]").description(
|
|
7094
7186
|
"Without --csv-table: delete the whole connection. With --csv-table: drop one CSV table (csv connections only)."
|
|
7095
|
-
).requiredOption("--confirm", "required for destructive ops").option(
|
|
7187
|
+
).requiredOption("--confirm", "required for destructive ops").option(
|
|
7188
|
+
"--csv-table <name>",
|
|
7189
|
+
"drop a single CSV table instead of the whole connection"
|
|
7190
|
+
).option("--source <id>", SOURCE_OPTION_DESC).option("--connection <id>", "alias for --source").action(async function(id) {
|
|
7096
7191
|
const opts = this.optsWithGlobals();
|
|
7097
7192
|
try {
|
|
7098
|
-
const resolved = await resolveConnectionId(
|
|
7193
|
+
const resolved = await resolveConnectionId(
|
|
7194
|
+
explicitConnectionId(opts, id),
|
|
7195
|
+
{ json: Boolean(opts.json) }
|
|
7196
|
+
);
|
|
7099
7197
|
if (opts.csvTable) {
|
|
7100
7198
|
const result2 = await apiFetch(
|
|
7101
7199
|
`/api/v1/connections/${encodeURIComponent(resolved)}/csv-tables/${encodeURIComponent(String(opts.csvTable))}`,
|
|
@@ -7104,7 +7202,9 @@ function registerConnectionsCommand(program3) {
|
|
|
7104
7202
|
if (opts.json) {
|
|
7105
7203
|
console.log(JSON.stringify(result2, null, 2));
|
|
7106
7204
|
} else {
|
|
7107
|
-
console.log(
|
|
7205
|
+
console.log(
|
|
7206
|
+
`Removed CSV table "${result2.table_name}" from connection ${result2.connection_id}.`
|
|
7207
|
+
);
|
|
7108
7208
|
}
|
|
7109
7209
|
process.exit(0);
|
|
7110
7210
|
}
|
|
@@ -7115,7 +7215,9 @@ function registerConnectionsCommand(program3) {
|
|
|
7115
7215
|
if (opts.json) {
|
|
7116
7216
|
console.log(JSON.stringify(result, null, 2));
|
|
7117
7217
|
} else {
|
|
7118
|
-
console.log(
|
|
7218
|
+
console.log(
|
|
7219
|
+
`Removed ${result.source_type} connection (${result.id}).`
|
|
7220
|
+
);
|
|
7119
7221
|
}
|
|
7120
7222
|
process.exit(0);
|
|
7121
7223
|
} catch (err) {
|
|
@@ -7125,11 +7227,49 @@ function registerConnectionsCommand(program3) {
|
|
|
7125
7227
|
}
|
|
7126
7228
|
async function buildJsonCreateBody(opts) {
|
|
7127
7229
|
const confirmSwitch = opts.confirmSwitch ? { confirm_switch: true } : {};
|
|
7230
|
+
const metadata = {
|
|
7231
|
+
...opts.label ? { label: String(opts.label) } : {},
|
|
7232
|
+
...opts.default ? { set_default: true } : {}
|
|
7233
|
+
};
|
|
7128
7234
|
if (opts.postgresUrl) {
|
|
7129
|
-
return {
|
|
7235
|
+
return {
|
|
7236
|
+
source_type: "postgres",
|
|
7237
|
+
postgres_url: String(opts.postgresUrl),
|
|
7238
|
+
...metadata,
|
|
7239
|
+
...confirmSwitch
|
|
7240
|
+
};
|
|
7130
7241
|
}
|
|
7131
7242
|
if (opts.mysqlUrl) {
|
|
7132
|
-
return {
|
|
7243
|
+
return {
|
|
7244
|
+
source_type: "mysql",
|
|
7245
|
+
mysql_url: String(opts.mysqlUrl),
|
|
7246
|
+
...metadata,
|
|
7247
|
+
...confirmSwitch
|
|
7248
|
+
};
|
|
7249
|
+
}
|
|
7250
|
+
if (opts.posthogHost || opts.posthogProjectId || opts.posthogApiKey) {
|
|
7251
|
+
if (!opts.posthogHost || !opts.posthogProjectId || !opts.posthogApiKey) {
|
|
7252
|
+
throw new CliApiError(400, {
|
|
7253
|
+
code: "VALIDATION_FAILED",
|
|
7254
|
+
message: "PostHog requires --posthog-host, --posthog-project-id, and --posthog-api-key."
|
|
7255
|
+
});
|
|
7256
|
+
}
|
|
7257
|
+
return {
|
|
7258
|
+
source_type: "posthog",
|
|
7259
|
+
posthog_host: String(opts.posthogHost),
|
|
7260
|
+
posthog_project_id: String(opts.posthogProjectId),
|
|
7261
|
+
posthog_api_key: String(opts.posthogApiKey),
|
|
7262
|
+
...metadata,
|
|
7263
|
+
...confirmSwitch
|
|
7264
|
+
};
|
|
7265
|
+
}
|
|
7266
|
+
if (opts.stripeApiKey) {
|
|
7267
|
+
return {
|
|
7268
|
+
source_type: "stripe",
|
|
7269
|
+
stripe_api_key: String(opts.stripeApiKey),
|
|
7270
|
+
...metadata,
|
|
7271
|
+
...confirmSwitch
|
|
7272
|
+
};
|
|
7133
7273
|
}
|
|
7134
7274
|
const raw = String(opts.firestoreCredentials);
|
|
7135
7275
|
const credentials = raw.startsWith("@") ? await (0, import_promises.readFile)(raw.slice(1), "utf8") : raw;
|
|
@@ -7137,6 +7277,7 @@ async function buildJsonCreateBody(opts) {
|
|
|
7137
7277
|
source_type: "firestore",
|
|
7138
7278
|
firestore_credentials: credentials,
|
|
7139
7279
|
...opts.firestoreProjectId ? { firestore_project_id: String(opts.firestoreProjectId) } : {},
|
|
7280
|
+
...metadata,
|
|
7140
7281
|
...confirmSwitch
|
|
7141
7282
|
};
|
|
7142
7283
|
}
|
|
@@ -7156,7 +7297,10 @@ async function uploadCsvConnection(opts) {
|
|
|
7156
7297
|
new Blob([new Uint8Array(buffer)], { type: "text/csv" }),
|
|
7157
7298
|
(0, import_node_path.basename)(opts.filePath)
|
|
7158
7299
|
);
|
|
7300
|
+
if (opts.connectionId) form.append("connection_id", opts.connectionId);
|
|
7159
7301
|
if (opts.tableName) form.append("table_name", opts.tableName);
|
|
7302
|
+
if (opts.label) form.append("label", opts.label);
|
|
7303
|
+
if (opts.setDefault) form.append("set_default", "true");
|
|
7160
7304
|
if (opts.confirmSwitch) form.append("confirm_switch", "true");
|
|
7161
7305
|
return apiFetch("/api/v1/connections/csv", {
|
|
7162
7306
|
method: "POST",
|
|
@@ -7176,7 +7320,7 @@ function parseQueryArg(arg) {
|
|
|
7176
7320
|
}
|
|
7177
7321
|
return arg;
|
|
7178
7322
|
}
|
|
7179
|
-
var import_promises, import_node_path;
|
|
7323
|
+
var import_promises, import_node_path, SOURCE_OPTION_DESC;
|
|
7180
7324
|
var init_connections = __esm({
|
|
7181
7325
|
"src/commands/connections.ts"() {
|
|
7182
7326
|
"use strict";
|
|
@@ -7186,6 +7330,7 @@ var init_connections = __esm({
|
|
|
7186
7330
|
init_api_client();
|
|
7187
7331
|
init_emit_error();
|
|
7188
7332
|
init_invocation();
|
|
7333
|
+
SOURCE_OPTION_DESC = "connection id to act on \u2014 overrides the positional [id]; defaults to the org default source";
|
|
7189
7334
|
}
|
|
7190
7335
|
});
|
|
7191
7336
|
|
|
@@ -8045,10 +8190,13 @@ function parseDetectionQuery(arg) {
|
|
|
8045
8190
|
return arg;
|
|
8046
8191
|
}
|
|
8047
8192
|
async function patchTrigger(id, body) {
|
|
8048
|
-
return apiFetch(
|
|
8049
|
-
|
|
8050
|
-
|
|
8051
|
-
|
|
8193
|
+
return apiFetch(
|
|
8194
|
+
`/api/v1/triggers/${encodeURIComponent(id)}`,
|
|
8195
|
+
{
|
|
8196
|
+
method: "PATCH",
|
|
8197
|
+
body
|
|
8198
|
+
}
|
|
8199
|
+
);
|
|
8052
8200
|
}
|
|
8053
8201
|
function indentBlock(text, indent2 = " ") {
|
|
8054
8202
|
return text.split("\n").map((line) => indent2 + line).join("\n");
|
|
@@ -8063,6 +8211,7 @@ function renderTriggerDetail(t) {
|
|
|
8063
8211
|
["Type", t.type],
|
|
8064
8212
|
["Status", t.status],
|
|
8065
8213
|
["Category", t.category ?? "\u2014"],
|
|
8214
|
+
["Source", t.source_connection_id ?? "\u2014"],
|
|
8066
8215
|
["Created", t.created_at ?? "\u2014"],
|
|
8067
8216
|
["Updated", t.updated_at ?? "\u2014"]
|
|
8068
8217
|
]
|
|
@@ -8129,7 +8278,15 @@ function formatTriggerTable(items) {
|
|
|
8129
8278
|
auto: "AUTO",
|
|
8130
8279
|
created: "CREATED"
|
|
8131
8280
|
};
|
|
8132
|
-
const cols = [
|
|
8281
|
+
const cols = [
|
|
8282
|
+
"id",
|
|
8283
|
+
"status",
|
|
8284
|
+
"name",
|
|
8285
|
+
"type",
|
|
8286
|
+
"scan",
|
|
8287
|
+
"auto",
|
|
8288
|
+
"created"
|
|
8289
|
+
];
|
|
8133
8290
|
const widths = Object.fromEntries(
|
|
8134
8291
|
cols.map((c) => [
|
|
8135
8292
|
c,
|
|
@@ -8164,7 +8321,9 @@ Count: ${p.count}`;
|
|
|
8164
8321
|
cols.map((c) => [c, Math.max(c.length, ...rows.map((r) => r[c].length))])
|
|
8165
8322
|
);
|
|
8166
8323
|
const fmt = (vals) => cols.map((c) => vals[c].padEnd(widths[c])).join(" ").trimEnd();
|
|
8167
|
-
const headerLine = fmt(
|
|
8324
|
+
const headerLine = fmt(
|
|
8325
|
+
Object.fromEntries(cols.map((c) => [c, c.toUpperCase()]))
|
|
8326
|
+
);
|
|
8168
8327
|
const note = p.sample.length < p.count ? `
|
|
8169
8328
|
|
|
8170
8329
|
(showing first ${p.sample.length} of ${p.count})` : "";
|
|
@@ -8181,7 +8340,9 @@ function registerTriggersCommand(program3) {
|
|
|
8181
8340
|
const opts = this.optsWithGlobals();
|
|
8182
8341
|
const status = opts.status;
|
|
8183
8342
|
if (status && status !== "active" && status !== "paused") {
|
|
8184
|
-
console.error(
|
|
8343
|
+
console.error(
|
|
8344
|
+
`Error: --status must be "active" or "paused" (got "${status}").`
|
|
8345
|
+
);
|
|
8185
8346
|
process.exit(1);
|
|
8186
8347
|
}
|
|
8187
8348
|
const query = new URLSearchParams();
|
|
@@ -8221,8 +8382,32 @@ function registerTriggersCommand(program3) {
|
|
|
8221
8382
|
"Create a trigger. You write the detection query (source-native) + email prompt; Hermes validates against the live source and persists."
|
|
8222
8383
|
).requiredOption(
|
|
8223
8384
|
"--detection-query <q>",
|
|
8224
|
-
"source-native query \u2014 SQL string, or Firestore JSON (test it first with `hermes connections query`)"
|
|
8225
|
-
).requiredOption(
|
|
8385
|
+
"source-native query \u2014 SQL/HogQL string, or Firestore/Stripe JSON (test it first with `hermes connections query`)"
|
|
8386
|
+
).requiredOption(
|
|
8387
|
+
"--email-prompt <text>",
|
|
8388
|
+
"the drafting brief for per-user email copy"
|
|
8389
|
+
).option(
|
|
8390
|
+
"--name <name>",
|
|
8391
|
+
"human-readable name (auto-generated from the query if omitted)"
|
|
8392
|
+
).option("--type <type>", "category tag", "custom").option(
|
|
8393
|
+
"--source <id>",
|
|
8394
|
+
"connection id to use as this trigger's detection source"
|
|
8395
|
+
).option("--sender <id>", "sender_identity_id").option(
|
|
8396
|
+
"--auto-send",
|
|
8397
|
+
"send on detection without human approval (default off)"
|
|
8398
|
+
).option(
|
|
8399
|
+
"--cooldown-hours <n>",
|
|
8400
|
+
"don't re-fire for the same user within this window",
|
|
8401
|
+
(v) => Number(v)
|
|
8402
|
+
).option("--recurring", "allow re-fire after recurrence-cooldown-days").option(
|
|
8403
|
+
"--recurrence-cooldown-days <n>",
|
|
8404
|
+
"days before a user is eligible again",
|
|
8405
|
+
(v) => Number(v)
|
|
8406
|
+
).option(
|
|
8407
|
+
"--scan-interval-minutes <n>",
|
|
8408
|
+
"how often the scheduler fires",
|
|
8409
|
+
(v) => Number(v)
|
|
8410
|
+
).option("--category <category>", "transactional | marketing").option("--no-track-opens", "disable open tracking").option("--no-track-clicks", "disable click tracking").action(async function() {
|
|
8226
8411
|
const opts = this.optsWithGlobals();
|
|
8227
8412
|
const body = {
|
|
8228
8413
|
detection_query: parseDetectionQuery(String(opts.detectionQuery)),
|
|
@@ -8230,22 +8415,30 @@ function registerTriggersCommand(program3) {
|
|
|
8230
8415
|
};
|
|
8231
8416
|
if (opts.name !== void 0) body.name = opts.name;
|
|
8232
8417
|
if (opts.type !== void 0) body.type = opts.type;
|
|
8418
|
+
if (opts.source !== void 0) body.source_connection_id = opts.source;
|
|
8233
8419
|
if (opts.sender !== void 0) body.sender_identity_id = opts.sender;
|
|
8234
8420
|
if (opts.autoSend !== void 0) body.auto_send = Boolean(opts.autoSend);
|
|
8235
|
-
if (opts.cooldownHours !== void 0)
|
|
8236
|
-
|
|
8421
|
+
if (opts.cooldownHours !== void 0)
|
|
8422
|
+
body.cooldown_hours = Number(opts.cooldownHours);
|
|
8423
|
+
if (opts.recurring !== void 0)
|
|
8424
|
+
body.is_recurring = Boolean(opts.recurring);
|
|
8237
8425
|
if (opts.recurrenceCooldownDays !== void 0)
|
|
8238
8426
|
body.recurrence_cooldown_days = Number(opts.recurrenceCooldownDays);
|
|
8239
8427
|
if (opts.scanIntervalMinutes !== void 0)
|
|
8240
8428
|
body.scan_interval_minutes = Number(opts.scanIntervalMinutes);
|
|
8241
8429
|
if (opts.category !== void 0) body.category = opts.category;
|
|
8242
|
-
if (opts.trackOpens !== void 0)
|
|
8243
|
-
|
|
8430
|
+
if (opts.trackOpens !== void 0)
|
|
8431
|
+
body.track_opens = Boolean(opts.trackOpens);
|
|
8432
|
+
if (opts.trackClicks !== void 0)
|
|
8433
|
+
body.track_clicks = Boolean(opts.trackClicks);
|
|
8244
8434
|
try {
|
|
8245
|
-
const result = await apiFetch(
|
|
8246
|
-
|
|
8247
|
-
|
|
8248
|
-
|
|
8435
|
+
const result = await apiFetch(
|
|
8436
|
+
"/api/v1/triggers",
|
|
8437
|
+
{
|
|
8438
|
+
method: "POST",
|
|
8439
|
+
body
|
|
8440
|
+
}
|
|
8441
|
+
);
|
|
8249
8442
|
if (opts.json) {
|
|
8250
8443
|
console.log(JSON.stringify(result, null, 2));
|
|
8251
8444
|
} else {
|
|
@@ -8270,26 +8463,50 @@ function registerTriggersCommand(program3) {
|
|
|
8270
8463
|
emitApiError(err, Boolean(opts.json));
|
|
8271
8464
|
}
|
|
8272
8465
|
});
|
|
8273
|
-
triggers.command("update <id>").description(
|
|
8466
|
+
triggers.command("update <id>").description(
|
|
8467
|
+
"Update operational fields, plus the detection query / email prompt"
|
|
8468
|
+
).option("--name <name>", "\u2192 name").option(
|
|
8469
|
+
"--detection-query <q>",
|
|
8470
|
+
"\u2192 detection_query (re-validated against the live source)"
|
|
8471
|
+
).option(
|
|
8472
|
+
"--source <id>",
|
|
8473
|
+
"\u2192 source_connection_id (re-validates the detection query)"
|
|
8474
|
+
).option("--email-prompt <text>", "\u2192 email_prompt").option("--sender <id>", "\u2192 sender_identity_id").option("--auto-send", "\u2192 auto_send=true").option("--no-auto-send", "\u2192 auto_send=false").option("--cooldown-hours <n>", "\u2192 cooldown_hours", (v) => Number(v)).option("--recurring", "\u2192 is_recurring=true").option("--no-recurring", "\u2192 is_recurring=false").option(
|
|
8475
|
+
"--recurrence-cooldown-days <n>",
|
|
8476
|
+
"\u2192 recurrence_cooldown_days",
|
|
8477
|
+
(v) => Number(v)
|
|
8478
|
+
).option(
|
|
8479
|
+
"--scan-interval-minutes <n>",
|
|
8480
|
+
"\u2192 scan_interval_minutes",
|
|
8481
|
+
(v) => Number(v)
|
|
8482
|
+
).option("--category <category>", "\u2192 category (transactional | marketing)").option("--track-opens", "\u2192 track_opens=true").option("--no-track-opens", "\u2192 track_opens=false").option("--track-clicks", "\u2192 track_clicks=true").option("--no-track-clicks", "\u2192 track_clicks=false").action(async function(id) {
|
|
8274
8483
|
const opts = this.optsWithGlobals();
|
|
8275
8484
|
const body = {};
|
|
8276
8485
|
if (opts.name !== void 0) body.name = opts.name;
|
|
8277
8486
|
if (opts.detectionQuery !== void 0)
|
|
8278
8487
|
body.detection_query = parseDetectionQuery(String(opts.detectionQuery));
|
|
8279
|
-
if (opts.
|
|
8488
|
+
if (opts.source !== void 0) body.source_connection_id = opts.source;
|
|
8489
|
+
if (opts.emailPrompt !== void 0)
|
|
8490
|
+
body.email_prompt = String(opts.emailPrompt);
|
|
8280
8491
|
if (opts.sender !== void 0) body.sender_identity_id = opts.sender;
|
|
8281
8492
|
if (opts.autoSend !== void 0) body.auto_send = Boolean(opts.autoSend);
|
|
8282
|
-
if (opts.cooldownHours !== void 0)
|
|
8283
|
-
|
|
8493
|
+
if (opts.cooldownHours !== void 0)
|
|
8494
|
+
body.cooldown_hours = Number(opts.cooldownHours);
|
|
8495
|
+
if (opts.recurring !== void 0)
|
|
8496
|
+
body.is_recurring = Boolean(opts.recurring);
|
|
8284
8497
|
if (opts.recurrenceCooldownDays !== void 0)
|
|
8285
8498
|
body.recurrence_cooldown_days = Number(opts.recurrenceCooldownDays);
|
|
8286
8499
|
if (opts.scanIntervalMinutes !== void 0)
|
|
8287
8500
|
body.scan_interval_minutes = Number(opts.scanIntervalMinutes);
|
|
8288
8501
|
if (opts.category !== void 0) body.category = opts.category;
|
|
8289
|
-
if (opts.trackOpens !== void 0)
|
|
8290
|
-
|
|
8502
|
+
if (opts.trackOpens !== void 0)
|
|
8503
|
+
body.track_opens = Boolean(opts.trackOpens);
|
|
8504
|
+
if (opts.trackClicks !== void 0)
|
|
8505
|
+
body.track_clicks = Boolean(opts.trackClicks);
|
|
8291
8506
|
if (Object.keys(body).length === 0) {
|
|
8292
|
-
console.error(
|
|
8507
|
+
console.error(
|
|
8508
|
+
"Error: provide at least one field to update (e.g. --name, --cooldown-hours)."
|
|
8509
|
+
);
|
|
8293
8510
|
process.exit(1);
|
|
8294
8511
|
}
|
|
8295
8512
|
try {
|
|
@@ -8298,14 +8515,18 @@ function registerTriggersCommand(program3) {
|
|
|
8298
8515
|
console.log(JSON.stringify(trigger, null, 2));
|
|
8299
8516
|
} else {
|
|
8300
8517
|
const changed = Object.keys(body).join(", ");
|
|
8301
|
-
console.log(
|
|
8518
|
+
console.log(
|
|
8519
|
+
`Updated "${trigger.name}" (${trigger.id}). Fields: ${changed}.`
|
|
8520
|
+
);
|
|
8302
8521
|
}
|
|
8303
8522
|
process.exit(0);
|
|
8304
8523
|
} catch (err) {
|
|
8305
8524
|
emitApiError(err, Boolean(opts.json));
|
|
8306
8525
|
}
|
|
8307
8526
|
});
|
|
8308
|
-
triggers.command("preview <id>").description(
|
|
8527
|
+
triggers.command("preview <id>").description(
|
|
8528
|
+
"Dry-run the detection query \u2014 returns count + sample, no drafts created"
|
|
8529
|
+
).action(async function(id) {
|
|
8309
8530
|
const opts = this.optsWithGlobals();
|
|
8310
8531
|
try {
|
|
8311
8532
|
const result = await apiFetch(
|
|
@@ -8360,7 +8581,9 @@ function registerTriggersCommand(program3) {
|
|
|
8360
8581
|
if (opts.json) {
|
|
8361
8582
|
console.log(JSON.stringify(result, null, 2));
|
|
8362
8583
|
} else {
|
|
8363
|
-
console.log(
|
|
8584
|
+
console.log(
|
|
8585
|
+
`Enqueued trigger ${result.trigger_id} (job ${result.job_id ?? "?"}).`
|
|
8586
|
+
);
|
|
8364
8587
|
}
|
|
8365
8588
|
process.exit(0);
|
|
8366
8589
|
} catch (err) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tryhermes/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "Hermes CLI — drive the agentic email platform (connections, triggers, drafts, sends) from the terminal.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
"tsx": "^4.19.2",
|
|
24
24
|
"typescript": "^5.6.3",
|
|
25
25
|
"@workspace/api-types": "0.0.1",
|
|
26
|
-
"@workspace/
|
|
27
|
-
"@workspace/
|
|
26
|
+
"@workspace/typescript-config": "0.0.0",
|
|
27
|
+
"@workspace/eslint-config": "0.0.0"
|
|
28
28
|
},
|
|
29
29
|
"scripts": {
|
|
30
30
|
"build": "tsup && chmod +x dist/index.js",
|