@oxygen-agent/cli 1.1014.985 → 1.1014.1087
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 +1 -1
- package/dist/command-families/program.d.ts +2 -0
- package/dist/command-families/program.js +241 -0
- package/dist/command-families/tables/automation.d.ts +22 -0
- package/dist/command-families/tables/automation.js +250 -0
- package/dist/command-families/tables/bundle-export.d.ts +7 -0
- package/dist/command-families/tables/bundle-export.js +198 -0
- package/dist/command-families/tables/bundle-format.d.ts +66 -0
- package/dist/command-families/tables/bundle-format.js +274 -0
- package/dist/command-families/tables/bundle-import.d.ts +20 -0
- package/dist/command-families/tables/bundle-import.js +260 -0
- package/dist/command-families/tables/display.d.ts +107 -0
- package/dist/command-families/tables/display.js +229 -0
- package/dist/command-families/tables/import-batches.d.ts +26 -0
- package/dist/command-families/tables/import-batches.js +94 -0
- package/dist/command-families/tables/import-columns.d.ts +24 -0
- package/dist/command-families/tables/import-columns.js +131 -0
- package/dist/command-families/tables/import-upload.d.ts +30 -0
- package/dist/command-families/tables/import-upload.js +203 -0
- package/dist/command-families/tables/import-values.d.ts +10 -0
- package/dist/command-families/tables/import-values.js +60 -0
- package/dist/command-families/tables/imports.d.ts +19 -0
- package/dist/command-families/tables/imports.js +200 -0
- package/dist/command-families/tables/options.d.ts +314 -0
- package/dist/command-families/tables/options.js +1 -0
- package/dist/command-families/tables/register-automation.d.ts +2 -0
- package/dist/command-families/tables/register-automation.js +127 -0
- package/dist/command-families/tables/register-external-imports.d.ts +2 -0
- package/dist/command-families/tables/register-external-imports.js +293 -0
- package/dist/command-families/tables/register-files.d.ts +2 -0
- package/dist/command-families/tables/register-files.js +72 -0
- package/dist/command-families/tables/register-followers.d.ts +2 -0
- package/dist/command-families/tables/register-followers.js +73 -0
- package/dist/command-families/tables/register-lifecycle.d.ts +2 -0
- package/dist/command-families/tables/register-lifecycle.js +114 -0
- package/dist/command-families/tables/register-reads.d.ts +2 -0
- package/dist/command-families/tables/register-reads.js +187 -0
- package/dist/command-families/tables/register-relations.d.ts +2 -0
- package/dist/command-families/tables/register-relations.js +244 -0
- package/dist/command-families/tables/register-row-actions.d.ts +2 -0
- package/dist/command-families/tables/register-row-actions.js +53 -0
- package/dist/command-families/tables/register-row-writes.d.ts +2 -0
- package/dist/command-families/tables/register-row-writes.js +140 -0
- package/dist/command-families/tables/register-views.d.ts +2 -0
- package/dist/command-families/tables/register-views.js +83 -0
- package/dist/command-families/tables/register-watchers.d.ts +2 -0
- package/dist/command-families/tables/register-watchers.js +65 -0
- package/dist/command-families/tables/register-webhooks.d.ts +2 -0
- package/dist/command-families/tables/register-webhooks.js +126 -0
- package/dist/command-families/tables/registration-context.d.ts +8 -0
- package/dist/command-families/tables/registration-context.js +1 -0
- package/dist/command-families/tables/registration.d.ts +3 -0
- package/dist/command-families/tables/registration.js +30 -0
- package/dist/command-families/tables/row-selection.d.ts +5 -0
- package/dist/command-families/tables/row-selection.js +82 -0
- package/dist/command-families/tables/send.d.ts +6 -0
- package/dist/command-families/tables/send.js +111 -0
- package/dist/command-manifest.js +1 -0
- package/dist/commands/copilot.js +8 -0
- package/dist/commands/deliverability.js +4 -2
- package/dist/commands/sequences.js +30 -2
- package/dist/commands/tables.d.ts +1 -3
- package/dist/commands/tables.js +1 -3505
- package/dist/credentials.js +1 -1
- package/dist/customer-operation-commands.js +62 -3
- package/dist/help.js +1 -1
- package/dist/index.d.ts +1 -99
- package/dist/index.js +5 -337
- package/dist/knowledge-mirror.js +1 -1
- package/dist/skills.js +4 -2
- package/dist/update.js +2 -1
- package/dist/workspace-placement-commands.d.ts +3 -0
- package/dist/workspace-placement-commands.js +32 -0
- package/node_modules/@oxygen/shared/dist/agent-sessions.d.ts +4 -0
- package/node_modules/@oxygen/shared/dist/agent-sessions.js +11 -3
- package/node_modules/@oxygen/shared/dist/agents-enabled.d.ts +29 -0
- package/node_modules/@oxygen/shared/dist/agents-enabled.js +51 -0
- package/node_modules/@oxygen/shared/dist/capability-discovery.js +39 -2
- package/node_modules/@oxygen/shared/dist/contracts/agent-sessions.js +1 -1
- package/node_modules/@oxygen/shared/dist/contracts/customer-operations.js +1 -1
- package/node_modules/@oxygen/shared/dist/contracts/guided-activation.js +5 -5
- package/node_modules/@oxygen/shared/dist/contracts/guided-company-files.d.ts +30 -0
- package/node_modules/@oxygen/shared/dist/contracts/guided-company-files.js +53 -0
- package/node_modules/@oxygen/shared/dist/contracts/guided-first-sourcing.d.ts +120 -0
- package/node_modules/@oxygen/shared/dist/contracts/guided-first-sourcing.js +48 -0
- package/node_modules/@oxygen/shared/dist/contracts/guided-onboarding-journey.d.ts +267 -0
- package/node_modules/@oxygen/shared/dist/contracts/guided-onboarding-journey.js +63 -0
- package/node_modules/@oxygen/shared/dist/contracts/operations.d.ts +6 -0
- package/node_modules/@oxygen/shared/dist/contracts/operations.js +9 -4
- package/node_modules/@oxygen/shared/dist/contracts/table-query-raw.d.ts +2 -0
- package/node_modules/@oxygen/shared/dist/contracts/table-query-raw.js +3 -0
- package/node_modules/@oxygen/shared/dist/contracts/teammates.js +17 -15
- package/node_modules/@oxygen/shared/dist/email-tracking-token.d.ts +2 -0
- package/node_modules/@oxygen/shared/dist/email-tracking-token.js +1 -0
- package/node_modules/@oxygen/shared/dist/feature-gates.d.ts +4 -0
- package/node_modules/@oxygen/shared/dist/feature-gates.js +5 -0
- package/node_modules/@oxygen/shared/dist/file-import.d.ts +46 -0
- package/node_modules/@oxygen/shared/dist/file-import.js +138 -0
- package/node_modules/@oxygen/shared/dist/guided-activation.d.ts +8 -0
- package/node_modules/@oxygen/shared/dist/guided-company-files.d.ts +13 -0
- package/node_modules/@oxygen/shared/dist/guided-company-files.js +14 -0
- package/node_modules/@oxygen/shared/dist/guided-first-sourcing.d.ts +116 -0
- package/node_modules/@oxygen/shared/dist/guided-first-sourcing.js +11 -0
- package/node_modules/@oxygen/shared/dist/guided-onboarding-answers.d.ts +4 -0
- package/node_modules/@oxygen/shared/dist/guided-onboarding-answers.js +42 -0
- package/node_modules/@oxygen/shared/dist/guided-onboarding-company-context.d.ts +22 -0
- package/node_modules/@oxygen/shared/dist/guided-onboarding-company-context.js +38 -0
- package/node_modules/@oxygen/shared/dist/guided-onboarding-journey.d.ts +147 -0
- package/node_modules/@oxygen/shared/dist/guided-onboarding-journey.fixture.d.ts +12 -0
- package/node_modules/@oxygen/shared/dist/guided-onboarding-journey.fixture.js +83 -0
- package/node_modules/@oxygen/shared/dist/guided-onboarding-journey.js +44 -0
- package/node_modules/@oxygen/shared/dist/guided-onboarding-prospects.d.ts +7 -0
- package/node_modules/@oxygen/shared/dist/guided-onboarding-prospects.js +13 -0
- package/node_modules/@oxygen/shared/dist/guided-onboarding-signals.d.ts +4 -0
- package/node_modules/@oxygen/shared/dist/guided-onboarding-signals.js +34 -0
- package/node_modules/@oxygen/shared/dist/guided-trial-billing.d.ts +26 -0
- package/node_modules/@oxygen/shared/dist/guided-trial-billing.js +19 -0
- package/node_modules/@oxygen/shared/dist/index.d.ts +0 -1
- package/node_modules/@oxygen/shared/dist/index.js +0 -6
- package/node_modules/@oxygen/shared/dist/table-limits.d.ts +1 -0
- package/node_modules/@oxygen/shared/dist/table-limits.js +6 -0
- package/node_modules/@oxygen/shared/dist/teammates.d.ts +13 -8
- package/node_modules/@oxygen/shared/dist/teammates.js +20 -11
- package/node_modules/@oxygen/shared/dist/user-capability-routing.js +78 -0
- package/node_modules/@oxygen/shared/dist/version.generated.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/version.generated.js +1 -1
- package/node_modules/@oxygen/shared/dist/workspace-placement-view.d.ts +149 -0
- package/node_modules/@oxygen/shared/dist/workspace-placement-view.js +101 -0
- package/node_modules/@oxygen/shared/dist/workspace-placement.d.ts +77 -0
- package/node_modules/@oxygen/shared/dist/workspace-placement.js +94 -0
- package/package.json +1 -1
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { parseJsonObject } from "../../cli-values.js";
|
|
2
|
+
import { parseJsonArray, readFileIfPresent } from "../../command-support.js";
|
|
3
|
+
import { requestOxygen } from "../../http-client.js";
|
|
4
|
+
import { readOption } from "../../util.js";
|
|
5
|
+
import { Command } from "commander";
|
|
6
|
+
import { writeRowDeleteWarnings } from "./display.js";
|
|
7
|
+
import { readDeleteRowIdsOption } from "./row-selection.js";
|
|
8
|
+
export function registerRowWrites({ tablesCommand, handleAsyncAction }) {
|
|
9
|
+
tablesCommand.addCommand(new Command("create")
|
|
10
|
+
.description("Create a table private to the active workspace. Free — 0 Oxygen credits. To create a table and import a file in one step, use `tables import --create <name>`.")
|
|
11
|
+
.argument("<name>", "Display name for the table.")
|
|
12
|
+
.requiredOption("--columns-json <json>", 'Required nonempty JSON array of column definitions, e.g. [{"key":"name","label":"Name","dataType":"text"},{"key":"domain","label":"Domain","dataType":"text"}]. A column may name a field type instead, as `columns add --type` does: {"label":"Stage","type":"select","options":["Lead","Won"]} (text, number, checkbox, date, json, markdown, image, select, multi_select, status). Inspect an existing shape with `oxygen tables describe <table>`.')
|
|
13
|
+
.option("--project <project>", "Project id or slug. Defaults to General.")
|
|
14
|
+
.option("--json", "Print a JSON envelope.")
|
|
15
|
+
.action(async (name, options) => {
|
|
16
|
+
await handleAsyncAction("tables create", options, () => requestOxygen("/api/cli/tables", {
|
|
17
|
+
method: "POST",
|
|
18
|
+
body: {
|
|
19
|
+
name,
|
|
20
|
+
columns: parseJsonArray(options.columnsJson),
|
|
21
|
+
...(readOption(options.project) ? { project: readOption(options.project) } : {}),
|
|
22
|
+
},
|
|
23
|
+
}));
|
|
24
|
+
}));
|
|
25
|
+
tablesCommand.addCommand(new Command("duplicate")
|
|
26
|
+
.description("Duplicate a workspace table. Copies row values unless --schema-only is set.")
|
|
27
|
+
.argument("<table>", "Source table id or slug.")
|
|
28
|
+
.option("--name <name>", "Display name for the duplicated table. Defaults to '<source> Copy'.")
|
|
29
|
+
.option("--schema-only", "Duplicate columns and definitions without copying row values.")
|
|
30
|
+
.option("--json", "Print a JSON envelope.")
|
|
31
|
+
.action(async (table, options) => {
|
|
32
|
+
await handleAsyncAction("tables duplicate", options, () => requestOxygen(`/api/cli/tables/${encodeURIComponent(table)}/duplicate`, {
|
|
33
|
+
method: "POST",
|
|
34
|
+
body: {
|
|
35
|
+
...(readOption(options.name) ? { name: readOption(options.name) } : {}),
|
|
36
|
+
...(options.schemaOnly ? { include_rows: false } : {}),
|
|
37
|
+
},
|
|
38
|
+
}));
|
|
39
|
+
}));
|
|
40
|
+
tablesCommand.addCommand(new Command("insert")
|
|
41
|
+
.description("Insert rows into a workspace table. Table writes cost 0 Oxygen credits; paid column runs are separate.")
|
|
42
|
+
.argument("<table>", "Table id or slug.")
|
|
43
|
+
.requiredOption("--rows-json <json>", 'JSON array of row objects keyed by column key, e.g. [{"name":"Acme","domain":"acme.test"},{"name":"Globex","domain":"globex.test"}].')
|
|
44
|
+
.option("--request-id <id>", "Stable retry key. Reuse it after a timeout to return the committed write without duplicating rows.")
|
|
45
|
+
.option("--json", "Print a JSON envelope.")
|
|
46
|
+
.action(async (table, options) => {
|
|
47
|
+
await handleAsyncAction("tables insert", options, () => requestOxygen("/api/cli/tables/rows", {
|
|
48
|
+
method: "POST",
|
|
49
|
+
body: {
|
|
50
|
+
table,
|
|
51
|
+
rows: parseJsonArray(options.rowsJson),
|
|
52
|
+
...(readOption(options.requestId) ? { request_id: readOption(options.requestId) } : {}),
|
|
53
|
+
},
|
|
54
|
+
}));
|
|
55
|
+
}));
|
|
56
|
+
tablesCommand.addCommand(new Command("update")
|
|
57
|
+
.description("Update one workspace table row.")
|
|
58
|
+
.argument("<table>", "Table id or slug.")
|
|
59
|
+
.argument("<row_id>", "Workspace row UUID.")
|
|
60
|
+
.requiredOption("--values-json <json>", "JSON object of values keyed by column key.")
|
|
61
|
+
.option("--json", "Print a JSON envelope.")
|
|
62
|
+
.action(async (table, rowId, options) => {
|
|
63
|
+
await handleAsyncAction("tables update", options, () => requestOxygen("/api/cli/tables/rows/update", {
|
|
64
|
+
method: "POST",
|
|
65
|
+
body: {
|
|
66
|
+
table,
|
|
67
|
+
row_id: rowId,
|
|
68
|
+
values: parseJsonObject(options.valuesJson),
|
|
69
|
+
},
|
|
70
|
+
}));
|
|
71
|
+
}));
|
|
72
|
+
tablesCommand.addCommand(new Command("delete-row")
|
|
73
|
+
.description("Delete one workspace table row. A lead enrolled in a campaign stays enrolled; the response names those campaigns.")
|
|
74
|
+
.argument("<table>", "Table id or slug.")
|
|
75
|
+
.argument("<row_id>", "Workspace row UUID.")
|
|
76
|
+
.option("--metadata-json <json-or-file>", "Durable row deletion provenance.")
|
|
77
|
+
.option("--json", "Print a JSON envelope.")
|
|
78
|
+
.action(async (table, rowId, options) => {
|
|
79
|
+
await handleAsyncAction("tables delete-row", options, async () => {
|
|
80
|
+
const data = await requestOxygen("/api/cli/tables/rows/delete", {
|
|
81
|
+
method: "POST",
|
|
82
|
+
body: {
|
|
83
|
+
table,
|
|
84
|
+
row_id: rowId,
|
|
85
|
+
...(options.metadataJson !== undefined ? { metadata: parseJsonObject(readFileIfPresent(options.metadataJson)) } : {}),
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
if (!options.json)
|
|
89
|
+
writeRowDeleteWarnings(data);
|
|
90
|
+
return data;
|
|
91
|
+
});
|
|
92
|
+
}));
|
|
93
|
+
tablesCommand.addCommand(new Command("delete-rows")
|
|
94
|
+
.description("Delete exact workspace row IDs. For a matching filtered selection, use tables rows remove-selection. Campaign enrollments stay enrolled; the response names those campaigns.")
|
|
95
|
+
.addHelpText("after", "\nFor a matching filtered selection, use tables rows remove-selection --help.")
|
|
96
|
+
.argument("<table>", "Table id or slug.")
|
|
97
|
+
.option("--row-ids-json <json>", "JSON array of workspace row UUIDs.")
|
|
98
|
+
.option("--row-ids-file <path>", "File containing row UUIDs or rows with a _row_id column.")
|
|
99
|
+
.option("--row-id-column <key>", "Column to read from --row-ids-file. Defaults to _row_id.")
|
|
100
|
+
.option("--format <format>", "File format for --row-ids-file: json, jsonl, csv, or xlsx.")
|
|
101
|
+
.option("--sheet <name>", "Worksheet name when reading row IDs from an XLSX file.")
|
|
102
|
+
.option("--metadata-json <json-or-file>", "Durable row deletion provenance.")
|
|
103
|
+
.option("--json", "Print a JSON envelope.")
|
|
104
|
+
.action(async (table, options) => {
|
|
105
|
+
await handleAsyncAction("tables delete-rows", options, async () => {
|
|
106
|
+
const rowIds = await readDeleteRowIdsOption(options);
|
|
107
|
+
const data = await requestOxygen("/api/cli/tables/rows/delete", {
|
|
108
|
+
method: "POST",
|
|
109
|
+
body: {
|
|
110
|
+
table,
|
|
111
|
+
row_ids: rowIds,
|
|
112
|
+
...(options.metadataJson !== undefined ? { metadata: parseJsonObject(readFileIfPresent(options.metadataJson)) } : {}),
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
if (!options.json)
|
|
116
|
+
writeRowDeleteWarnings(data);
|
|
117
|
+
return data;
|
|
118
|
+
});
|
|
119
|
+
}));
|
|
120
|
+
tablesCommand.addCommand(new Command("upsert")
|
|
121
|
+
.description("Insert or update rows in a workspace table by a column key.")
|
|
122
|
+
.argument("<table>", "Table id or slug.")
|
|
123
|
+
.requiredOption("--key <key>", "Column key used to match existing rows.")
|
|
124
|
+
.requiredOption("--rows-json <json>", "JSON array of row objects keyed by column key.")
|
|
125
|
+
.option("--return <mode>", "Response shape: summary, ids, or rows. Defaults to summary.")
|
|
126
|
+
.option("--dry-run", "Preview inserts, updates, duplicate keys, and field conflicts without writing rows.")
|
|
127
|
+
.option("--json", "Print a JSON envelope.")
|
|
128
|
+
.action(async (table, options) => {
|
|
129
|
+
await handleAsyncAction("tables upsert", options, () => requestOxygen("/api/cli/tables/rows/upsert", {
|
|
130
|
+
method: "POST",
|
|
131
|
+
body: {
|
|
132
|
+
table,
|
|
133
|
+
key: options.key,
|
|
134
|
+
rows: parseJsonArray(options.rowsJson),
|
|
135
|
+
...(readOption(options["return"]) ? { return: readOption(options["return"]) } : {}),
|
|
136
|
+
...(options.dryRun ? { dry_run: true } : {}),
|
|
137
|
+
},
|
|
138
|
+
}));
|
|
139
|
+
}));
|
|
140
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { readNonNegativeInt } from "../../cli-values.js";
|
|
2
|
+
import { parseJsonValue } from "../../command-support.js";
|
|
3
|
+
import { requestOxygen } from "../../http-client.js";
|
|
4
|
+
import { readOption } from "../../util.js";
|
|
5
|
+
import { Command } from "commander";
|
|
6
|
+
export function registerViews({ tablesCommand, handleAsyncAction }) {
|
|
7
|
+
// One authoritative statement of the config shape, on the flag that stores
|
|
8
|
+
// it. The previous copy — "(filters, sorts, columns, group). Usually written
|
|
9
|
+
// by the app." — named the LEGACY key and disowned the flag; a blind-eval
|
|
10
|
+
// agent (2026-09-20) followed it, wrote {"group":{"columnKey":...}}, and got a
|
|
11
|
+
// board that grouped by accident. --group-by is the plain path; this is the
|
|
12
|
+
// full one, and it says which key is the grouping key.
|
|
13
|
+
const TABLE_VIEW_CONFIG_HELP = 'JSON view config, typed v2 shape: {"version":2,"groupBy":"<select column key>","filterTree":{...},"sorts":[{"columnKey":"...","direction":"asc|desc"}],"visibleColumns":["..."],"cardFields":["..."]}. '
|
|
14
|
+
+ 'groupBy is the kanban group-by column (--group-by sets it for you); a complete board config is \'{"version":2,"groupBy":"lifecycle_stage"}\'. Malformed v2 is rejected with invalid_view_config.';
|
|
15
|
+
tablesCommand.addCommand(new Command("views")
|
|
16
|
+
.description("Create and manage saved table views (grid / kanban configurations).")
|
|
17
|
+
.addCommand(new Command("list")
|
|
18
|
+
.description("List a table's saved views.")
|
|
19
|
+
.argument("<table>", "Table id or slug.")
|
|
20
|
+
.option("--json", "Print a JSON envelope.")
|
|
21
|
+
.action((table, options) => handleAsyncAction("tables views list", options, () => requestOxygen(`/api/cli/tables/${encodeURIComponent(table)}/views`))))
|
|
22
|
+
.addCommand(new Command("get")
|
|
23
|
+
.description("Get one saved view by id.")
|
|
24
|
+
.argument("<table>", "Table id or slug.")
|
|
25
|
+
.argument("<view>", "View id.")
|
|
26
|
+
.option("--json", "Print a JSON envelope.")
|
|
27
|
+
.action((table, view, options) => handleAsyncAction("tables views get", options, () => requestOxygen(`/api/cli/tables/views?table=${encodeURIComponent(table)}&view=${encodeURIComponent(view)}`))))
|
|
28
|
+
.addCommand(new Command("create")
|
|
29
|
+
.description("Create a saved view.")
|
|
30
|
+
.argument("<table>", "Table id or slug.")
|
|
31
|
+
.requiredOption("--name <name>", "View name.")
|
|
32
|
+
.option("--view-type <type>", "table (grid) or kanban (board split into lanes by a select/status column). Defaults to table; --group-by implies kanban. A kanban created without --group-by is not refused: the board renders on the table's first groupable column and every read reports that fallback as effective_group_by plus a warning, so pin the column with --group-by.")
|
|
33
|
+
.option("--group-by <column>", "Kanban: the editable select/status column key the board splits on, e.g. --group-by lifecycle_stage. Unknown or non-groupable keys are refused with the groupable candidates. The response's effective_group_by is the column the board actually uses.")
|
|
34
|
+
.option("--config <json>", TABLE_VIEW_CONFIG_HELP)
|
|
35
|
+
.option("--default", "Make this the table's default view.")
|
|
36
|
+
.option("--no-default", "Clear this view's default status.")
|
|
37
|
+
.option("--position <n>", "Zero-based order among the table's saved views.")
|
|
38
|
+
.option("--json", "Print a JSON envelope.")
|
|
39
|
+
.action((table, options) => handleAsyncAction("tables views create", options, () => requestOxygen(`/api/cli/tables/${encodeURIComponent(table)}/views`, {
|
|
40
|
+
method: "POST",
|
|
41
|
+
body: {
|
|
42
|
+
name: readOption(options.name),
|
|
43
|
+
...(readOption(options.viewType) ? { view_type: readOption(options.viewType) } : {}),
|
|
44
|
+
...(readOption(options.groupBy) ? { group_by: readOption(options.groupBy) } : {}),
|
|
45
|
+
...(readOption(options.config)
|
|
46
|
+
? { config: parseJsonValue(readOption(options.config) ?? "", "--config") }
|
|
47
|
+
: {}),
|
|
48
|
+
...(typeof options.default === "boolean" ? { is_default: options.default } : {}),
|
|
49
|
+
...(readOption(options.position) !== null ? { position: readNonNegativeInt(options.position) } : {}),
|
|
50
|
+
},
|
|
51
|
+
}))))
|
|
52
|
+
.addCommand(new Command("update")
|
|
53
|
+
.description("Update a saved view.")
|
|
54
|
+
.argument("<table>", "Table id or slug.")
|
|
55
|
+
.argument("<view>", "View id.")
|
|
56
|
+
.option("--name <name>", "Rename the view.")
|
|
57
|
+
.option("--view-type <type>", "table (grid) or kanban (board). A kanban whose config names no group column renders on the first groupable column; reads report that fallback as effective_group_by plus a warning.")
|
|
58
|
+
.option("--group-by <column>", "Kanban: change the select/status column the board splits on; folds into the stored config and keeps the rest of it. Refused for a grid view unless --view-type kanban is passed with it.")
|
|
59
|
+
.option("--config <json>", `${TABLE_VIEW_CONFIG_HELP} Replaces the stored config.`)
|
|
60
|
+
.option("--default", "Make this the table's default view.")
|
|
61
|
+
.option("--no-default", "Clear this view's default status.")
|
|
62
|
+
.option("--position <n>", "0-based order among the table's views.")
|
|
63
|
+
.option("--json", "Print a JSON envelope.")
|
|
64
|
+
.action((table, view, options) => handleAsyncAction("tables views update", options, () => requestOxygen(`/api/cli/tables/${encodeURIComponent(table)}/views/${encodeURIComponent(view)}`, {
|
|
65
|
+
method: "PATCH",
|
|
66
|
+
body: {
|
|
67
|
+
...(readOption(options.name) ? { name: readOption(options.name) } : {}),
|
|
68
|
+
...(readOption(options.viewType) ? { view_type: readOption(options.viewType) } : {}),
|
|
69
|
+
...(readOption(options.groupBy) ? { group_by: readOption(options.groupBy) } : {}),
|
|
70
|
+
...(readOption(options.config)
|
|
71
|
+
? { config: parseJsonValue(readOption(options.config) ?? "", "--config") }
|
|
72
|
+
: {}),
|
|
73
|
+
...(typeof options.default === "boolean" ? { is_default: options.default } : {}),
|
|
74
|
+
...(readOption(options.position) ? { position: Number(readOption(options.position)) } : {}),
|
|
75
|
+
},
|
|
76
|
+
}))))
|
|
77
|
+
.addCommand(new Command("delete")
|
|
78
|
+
.description("Delete a saved view by id.")
|
|
79
|
+
.argument("<table>", "Table id or slug.")
|
|
80
|
+
.argument("<view>", "View id.")
|
|
81
|
+
.option("--json", "Print a JSON envelope.")
|
|
82
|
+
.action((table, view, options) => handleAsyncAction("tables views delete", options, () => requestOxygen(`/api/cli/tables/${encodeURIComponent(table)}/views/${encodeURIComponent(view)}`, { method: "DELETE" })))));
|
|
83
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { readPositiveNumber, parseJsonArray } from "../../command-support.js";
|
|
2
|
+
import { requestOxygen } from "../../http-client.js";
|
|
3
|
+
import { readOption } from "../../util.js";
|
|
4
|
+
import { OxygenError } from "@oxygen/shared/cli-result";
|
|
5
|
+
export function registerWatchers({ tablesCommand, handleAsyncAction }) {
|
|
6
|
+
const watcherCommand = tablesCommand.command("watcher")
|
|
7
|
+
.description("Professional Network Post Engagers: one editable table of daily engagers, source attribution, and current employers from posts in the past 7 days. Start with preview (free); create/resume activate paid daily monitoring after exact approval.");
|
|
8
|
+
watcherCommand.command("get")
|
|
9
|
+
.description("Read a table's Professional Network Post Engagers configuration and status. Free.")
|
|
10
|
+
.argument("<table>", "Watcher table id or slug.")
|
|
11
|
+
.option("--json", "Print a JSON envelope.")
|
|
12
|
+
.action(async (table, options) => {
|
|
13
|
+
await handleAsyncAction("tables watcher get", options, () => requestOxygen(`/api/cli/tables/linkedin-profile-watcher?table=${encodeURIComponent(table)}`, { method: "GET" }));
|
|
14
|
+
});
|
|
15
|
+
for (const action of ["preview", "create", "update", "pause", "resume"]) {
|
|
16
|
+
const command = watcherCommand.command(action)
|
|
17
|
+
.description({
|
|
18
|
+
preview: "Free configuration and credit preview: no provider calls, writes, or scheduling. Omit --max-credits for a recommendation and preview hash. Ask for real profile URLs; never invent them.",
|
|
19
|
+
create: "Create and activate a Professional Network Post Engagers table after approval of its exact preview. Starts collection now and daily at 07:00 UTC under the approved per-cycle cap.",
|
|
20
|
+
update: "Edit watched profiles or the per-cycle cap from the table. Preview the changes first; approval binds the exact new configuration.",
|
|
21
|
+
pause: "Pause daily monitoring while retaining the table and collected rows.",
|
|
22
|
+
resume: "Resume paid daily monitoring after reviewing a fresh preview and approving its per-cycle credit cap.",
|
|
23
|
+
}[action])
|
|
24
|
+
.option("--table <table>", "Existing watcher table id or slug; required for get/update/pause/resume.")
|
|
25
|
+
.option("--json", "Print a JSON envelope.");
|
|
26
|
+
if (action !== "pause") {
|
|
27
|
+
command
|
|
28
|
+
.option("--name <name>", "Watcher table display name.")
|
|
29
|
+
.option("--project <project>", "Project id or slug; defaults to General.")
|
|
30
|
+
.option("--profiles-json <json>", "JSON array of 1–10 real public LinkedIn profile URLs; replaces the watched list.")
|
|
31
|
+
.option("--max-credits <credits>", "Hard credit ceiling for each daily cycle; never a monthly ceiling.")
|
|
32
|
+
.option("--credential-mode <mode>", "managed (default): Oxygen credits per cycle. user_api_key: every LinkedIn call runs on your own Up2Data key saved in Connections, billed by Up2Data; Oxygen charges only its own-key platform fee per successful call.");
|
|
33
|
+
if (action !== "preview")
|
|
34
|
+
command
|
|
35
|
+
.option("--preview-hash <hash>", "Exact configuration hash returned by preview; re-preview after any change.")
|
|
36
|
+
.option("--approved", "Approve this exact configuration and recurring per-cycle spending.");
|
|
37
|
+
}
|
|
38
|
+
if (action === "create") {
|
|
39
|
+
command.requiredOption("--request-id <uuid>", "One stable UUID for this new watcher. Reuse it and the identical configuration after a timeout; never generate a new retry key.");
|
|
40
|
+
}
|
|
41
|
+
command.action(async (options) => {
|
|
42
|
+
await handleAsyncAction(`tables watcher ${action}`, options, () => {
|
|
43
|
+
const profiles = options.profilesJson === undefined ? undefined : parseJsonArray(options.profilesJson);
|
|
44
|
+
if (profiles !== undefined && profiles.some((profile) => typeof profile !== "string")) {
|
|
45
|
+
throw new OxygenError("invalid_input", "--profiles-json must be an array of LinkedIn profile URL strings.");
|
|
46
|
+
}
|
|
47
|
+
return requestOxygen("/api/cli/tables/linkedin-profile-watcher", {
|
|
48
|
+
method: "POST",
|
|
49
|
+
body: {
|
|
50
|
+
action,
|
|
51
|
+
...(readOption(options.table) ? { table: readOption(options.table) } : {}),
|
|
52
|
+
...(readOption(options.name) ? { name: readOption(options.name) } : {}),
|
|
53
|
+
...(readOption(options.project) ? { project: readOption(options.project) } : {}),
|
|
54
|
+
...(profiles !== undefined ? { profiles } : {}),
|
|
55
|
+
...(options.maxCredits !== undefined ? { max_credits_per_cycle: readPositiveNumber(options.maxCredits) } : {}),
|
|
56
|
+
...(readOption(options.requestId) ? { request_id: readOption(options.requestId) } : {}),
|
|
57
|
+
...(readOption(options.previewHash) ? { preview_hash: readOption(options.previewHash) } : {}),
|
|
58
|
+
...(options.approved ? { approved: true } : {}),
|
|
59
|
+
...(readOption(options.credentialMode) ? { credential_mode: readOption(options.credentialMode) } : {}),
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { tableWebhookDeliveriesPath } from "../../command-support.js";
|
|
2
|
+
import { requestOxygen } from "../../http-client.js";
|
|
3
|
+
import { readOption } from "../../util.js";
|
|
4
|
+
import { OxygenError } from "@oxygen/shared/cli-result";
|
|
5
|
+
import { Command, Option } from "commander";
|
|
6
|
+
import { TABLE_WEBHOOK_RATE_HELP, readTableWebhookAutoRunOptions, tableWebhookListPath } from "./automation.js";
|
|
7
|
+
export function registerWebhooks({ tablesCommand, handleAsyncAction }) {
|
|
8
|
+
tablesCommand.addCommand(new Command("webhook")
|
|
9
|
+
.description("Create and manage direct table webhooks.")
|
|
10
|
+
.addCommand(new Command("list")
|
|
11
|
+
.description("List direct table webhooks.")
|
|
12
|
+
.argument("[table]", "Optional table id or slug.")
|
|
13
|
+
.option("--status <status>", "Filter by active or disabled.")
|
|
14
|
+
.option("--limit <n>", "Maximum webhooks to return. Defaults to 100.")
|
|
15
|
+
.option("--json", "Print a JSON envelope.")
|
|
16
|
+
.action((table, options) => handleAsyncAction("tables webhook list", options, () => requestOxygen(tableWebhookListPath({
|
|
17
|
+
table: table ?? options.table,
|
|
18
|
+
status: options.status,
|
|
19
|
+
limit: options.limit,
|
|
20
|
+
})))))
|
|
21
|
+
.addCommand(new Command("get")
|
|
22
|
+
.description("Get one direct table webhook by endpoint id.")
|
|
23
|
+
.argument("<endpoint_id>", "Webhook endpoint id, such as tw_...")
|
|
24
|
+
.option("--json", "Print a JSON envelope.")
|
|
25
|
+
.action((endpointId, options) => handleAsyncAction("tables webhook get", options, () => {
|
|
26
|
+
const params = new URLSearchParams({ endpoint_id: endpointId });
|
|
27
|
+
return requestOxygen(`/api/cli/tables/webhooks?${params.toString()}`);
|
|
28
|
+
})))
|
|
29
|
+
.addCommand(new Command("create")
|
|
30
|
+
.description(`Create a direct webhook endpoint that writes inbound JSON into a table (${TABLE_WEBHOOK_RATE_HELP}).`)
|
|
31
|
+
.argument("<table>", "Table id or slug.")
|
|
32
|
+
.option("--name <name>", "Display name for the webhook.")
|
|
33
|
+
.option("--mode <mode>", "insert or upsert. Defaults to upsert when --upsert-key is set, otherwise insert.")
|
|
34
|
+
.option("--auth-mode <mode>", "none or secret. Defaults to none; use secret when the sender can include an Oxygen webhook secret.")
|
|
35
|
+
.option("--upsert-key <key>", "Column key used to upsert rows. Required for upsert mode.")
|
|
36
|
+
.option("--items-path <path>", "Dot path to an array of row objects in the webhook payload.")
|
|
37
|
+
.option("--event-id-path <path>", "Dot path to the event id. Defaults to event_id, eventId, id, or payload hash.")
|
|
38
|
+
.option("--event-type-path <path>", "Dot path to the event type. Defaults to type or event.")
|
|
39
|
+
.option("--occurred-at-path <path>", "Dot path to the event timestamp.")
|
|
40
|
+
.option("--source <source>", "http (default) writes inbound JSON anyone can POST. unipile subscribes the table to your connected LinkedIn/WhatsApp accounts' events instead — no endpoint URL, no secret to share, and 0 credits: rows appear as the events reach Oxygen.")
|
|
41
|
+
.option("--account <unipile_account_id>", "unipile source only. Listen to ONE connected account. Omit to listen to every connected account in the workspace.")
|
|
42
|
+
.option("--events <csv>", "unipile source only. Comma-separated event types, e.g. relation.new,message.new. Omit for every event the receiver handles. Post reactions, post comments, profile viewers and followers are NOT pushed by the provider — capture those with a scheduled feed (`oxygen feeds bind`).")
|
|
43
|
+
.option("--mapping <mapping>", "unipile source only. curated (default) flattens known events into stable named columns; passthrough lands the raw provider event so you can map any type yourself.")
|
|
44
|
+
.option("--auto-run-columns <csv>", "Comma-separated enrichment, tool, AI, or formula columns to queue for webhook-written rows.")
|
|
45
|
+
.option("--auto-run-max-credits <n>", "Credit ceiling per webhook-triggered auto-run batch; items beyond it are skipped with credit_limit_reached. Defaults to your plan's per-delivery ceiling when omitted.")
|
|
46
|
+
.option("--auto-run-max-concurrency <n>", "Maximum concurrent row items for webhook-triggered auto-runs.")
|
|
47
|
+
.option("--auto-run-force", "Run auto-run columns even when the target cell already has a value.")
|
|
48
|
+
.option("--auto-run-connection-id <connection_id>", "Optional provider integration connection id for auto-runs.")
|
|
49
|
+
.addOption(new Option("--auto-run-approved", "Deprecated no-op. Auto-run is enabled by --auto-run-columns.").hideHelp())
|
|
50
|
+
.option("--json", "Print a JSON envelope.")
|
|
51
|
+
.action((table, options) => {
|
|
52
|
+
const autoRun = readTableWebhookAutoRunOptions(options);
|
|
53
|
+
const unipile = readOption(options.source) === "unipile";
|
|
54
|
+
return handleAsyncAction("tables webhook create", options, () => requestOxygen("/api/cli/tables/webhooks", {
|
|
55
|
+
method: "POST",
|
|
56
|
+
body: {
|
|
57
|
+
table,
|
|
58
|
+
...(readOption(options.name) ? { name: readOption(options.name) } : {}),
|
|
59
|
+
...(readOption(options.mode) ? { mode: readOption(options.mode) } : {}),
|
|
60
|
+
...(readOption(options.authMode) ? { auth_mode: readOption(options.authMode) } : {}),
|
|
61
|
+
...(readOption(options.upsertKey) ? { upsert_key: readOption(options.upsertKey) } : {}),
|
|
62
|
+
...(readOption(options.itemsPath) ? { items_path: readOption(options.itemsPath) } : {}),
|
|
63
|
+
...(readOption(options.eventIdPath) ? { event_id_path: readOption(options.eventIdPath) } : {}),
|
|
64
|
+
...(readOption(options.eventTypePath) ? { event_type_path: readOption(options.eventTypePath) } : {}),
|
|
65
|
+
...(readOption(options.occurredAtPath) ? { occurred_at_path: readOption(options.occurredAtPath) } : {}),
|
|
66
|
+
...(unipile
|
|
67
|
+
? {
|
|
68
|
+
feed_kind: "unipile",
|
|
69
|
+
...(readOption(options.account) ? { unipile_account_id: readOption(options.account) } : {}),
|
|
70
|
+
...(readOption(options.events) ? { event_types: readOption(options.events) } : {}),
|
|
71
|
+
...(readOption(options.mapping) ? { mapping: readOption(options.mapping) } : {}),
|
|
72
|
+
}
|
|
73
|
+
: {}),
|
|
74
|
+
...(autoRun ? { auto_run: autoRun } : {}),
|
|
75
|
+
},
|
|
76
|
+
}));
|
|
77
|
+
}))
|
|
78
|
+
.addCommand(new Command("update")
|
|
79
|
+
.description("Update a direct table webhook endpoint.")
|
|
80
|
+
.argument("<endpoint_id>", "Webhook endpoint id, such as tw_...")
|
|
81
|
+
.option("--name <name>", "Update the webhook display name.")
|
|
82
|
+
.option("--status <status>", "active or disabled.")
|
|
83
|
+
.option("--auth-mode <mode>", "none or secret. Setting secret rotates and returns a new webhook secret.")
|
|
84
|
+
.option("--auto-run-columns <csv>", "Comma-separated enrichment, tool, AI, or formula columns to queue for webhook-written rows.")
|
|
85
|
+
.option("--auto-run-max-credits <n>", "Credit ceiling per webhook-triggered auto-run batch; items beyond it are skipped with credit_limit_reached. Defaults to your plan's per-delivery ceiling when omitted.")
|
|
86
|
+
.option("--auto-run-max-concurrency <n>", "Maximum concurrent row items for webhook-triggered auto-runs.")
|
|
87
|
+
.option("--auto-run-force", "Run auto-run columns even when the target cell already has a value.")
|
|
88
|
+
.option("--auto-run-connection-id <connection_id>", "Optional provider integration connection id for auto-runs.")
|
|
89
|
+
.addOption(new Option("--auto-run-approved", "Deprecated no-op. Auto-run is enabled by --auto-run-columns.").hideHelp())
|
|
90
|
+
.option("--clear-auto-run", "Remove the webhook auto-run configuration.")
|
|
91
|
+
.option("--json", "Print a JSON envelope.")
|
|
92
|
+
.action((endpointId, options) => {
|
|
93
|
+
const autoRun = readTableWebhookAutoRunOptions(options);
|
|
94
|
+
if (autoRun && options.clearAutoRun) {
|
|
95
|
+
throw new OxygenError("invalid_table_webhook", "Pass either auto-run options or --clear-auto-run, not both.", {
|
|
96
|
+
exitCode: 1,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
return handleAsyncAction("tables webhook update", options, () => requestOxygen("/api/cli/tables/webhooks", {
|
|
100
|
+
method: "PATCH",
|
|
101
|
+
body: {
|
|
102
|
+
endpoint_id: endpointId,
|
|
103
|
+
...(readOption(options.name) ? { name: readOption(options.name) } : {}),
|
|
104
|
+
...(readOption(options.status) ? { status: readOption(options.status) } : {}),
|
|
105
|
+
...(readOption(options.authMode) ? { auth_mode: readOption(options.authMode) } : {}),
|
|
106
|
+
...(autoRun ? { auto_run: autoRun } : {}),
|
|
107
|
+
...(options.clearAutoRun ? { clear_auto_run: true } : {}),
|
|
108
|
+
},
|
|
109
|
+
}));
|
|
110
|
+
}))
|
|
111
|
+
.addCommand(new Command("deliveries")
|
|
112
|
+
.description("List direct table webhook deliveries and auto-run enqueue status.")
|
|
113
|
+
.argument("[endpoint_id]", "Optional webhook endpoint id, such as tw_...")
|
|
114
|
+
.option("--table <table>", "Filter by table id or slug.")
|
|
115
|
+
.option("--status <status>", "Filter by received, completed, failed, or rejected (rejected = arrived while the feed was paused and was turned away; no rows written). A delivery refused with HTTP 429 for exceeding the plan's rate is not recorded here: the sender redelivers it.")
|
|
116
|
+
.option("--auto-run-status <status>", "Filter by not_configured, queued, skipped, or failed_to_enqueue.")
|
|
117
|
+
.option("--limit <n>", "Maximum deliveries to return. Defaults to 50.")
|
|
118
|
+
.option("--json", "Print a JSON envelope.")
|
|
119
|
+
.action((endpointId, options) => handleAsyncAction("tables webhook deliveries", options, () => requestOxygen(tableWebhookDeliveriesPath({
|
|
120
|
+
endpointId,
|
|
121
|
+
table: options.table,
|
|
122
|
+
status: options.status,
|
|
123
|
+
autoRunStatus: options.autoRunStatus,
|
|
124
|
+
limit: options.limit,
|
|
125
|
+
}))))));
|
|
126
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Command } from "commander";
|
|
2
|
+
import type { ExtractedCommandActionHandler } from "../../command-support.js";
|
|
3
|
+
/** The actual command parent, shared action/error boundary and executable name. */
|
|
4
|
+
export type TablesRegistrationContext = {
|
|
5
|
+
tablesCommand: Command;
|
|
6
|
+
handleAsyncAction: ExtractedCommandActionHandler;
|
|
7
|
+
binaryName: any;
|
|
8
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { registerExternalImports } from "./register-external-imports.js";
|
|
2
|
+
import { registerRowWrites } from "./register-row-writes.js";
|
|
3
|
+
import { registerFiles } from "./register-files.js";
|
|
4
|
+
import { registerReads } from "./register-reads.js";
|
|
5
|
+
import { registerLifecycle } from "./register-lifecycle.js";
|
|
6
|
+
import { registerWatchers } from "./register-watchers.js";
|
|
7
|
+
import { registerFollowers } from "./register-followers.js";
|
|
8
|
+
import { registerRelations } from "./register-relations.js";
|
|
9
|
+
import { registerRowActions } from "./register-row-actions.js";
|
|
10
|
+
import { registerWebhooks } from "./register-webhooks.js";
|
|
11
|
+
import { registerViews } from "./register-views.js";
|
|
12
|
+
import { registerAutomation } from "./register-automation.js";
|
|
13
|
+
export function registerTablesFamily(program, handleAsyncAction, binaryName) {
|
|
14
|
+
const tablesCommand = program
|
|
15
|
+
.command("tables")
|
|
16
|
+
.description("Tenant workspace tables: create, import rows (CSV, a link, a HubSpot saved list, a Pipedrive saved filter, Supabase), enrich with columns, and run them. `tables sources` lists every way a table can start.");
|
|
17
|
+
const context = { tablesCommand, handleAsyncAction, binaryName };
|
|
18
|
+
registerExternalImports(context);
|
|
19
|
+
registerRowWrites(context);
|
|
20
|
+
registerFiles(context);
|
|
21
|
+
registerReads(context);
|
|
22
|
+
registerLifecycle(context);
|
|
23
|
+
registerWatchers(context);
|
|
24
|
+
registerFollowers(context);
|
|
25
|
+
registerRelations(context);
|
|
26
|
+
registerRowActions(context);
|
|
27
|
+
registerWebhooks(context);
|
|
28
|
+
registerViews(context);
|
|
29
|
+
registerAutomation(context);
|
|
30
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { TablesDeleteRowsOptions } from "./options.js";
|
|
2
|
+
export declare function readDeleteRowIdsOption(options: TablesDeleteRowsOptions): Promise<string[]>;
|
|
3
|
+
export declare function tryParseJsonStringArray(value: string): string[] | null;
|
|
4
|
+
export declare function parsePlainRowIdList(value: string): string[];
|
|
5
|
+
export declare function normalizeDeleteRowIds(values: unknown[]): string[];
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { parseJsonArray } from "../../command-support.js";
|
|
2
|
+
import { readOption } from "../../util.js";
|
|
3
|
+
import { OxygenError } from "@oxygen/shared/cli-result";
|
|
4
|
+
import { normalizeRowsFormat, inferRowsFileFormat, parseRowsFileBuffer } from "@oxygen/shared/file-import";
|
|
5
|
+
import { readFileSync } from "node:fs";
|
|
6
|
+
import { resolve } from "node:path";
|
|
7
|
+
export async function readDeleteRowIdsOption(options) {
|
|
8
|
+
const rowIdsJson = readOption(options.rowIdsJson);
|
|
9
|
+
const rowIdsFile = readOption(options.rowIdsFile);
|
|
10
|
+
if (rowIdsJson && rowIdsFile) {
|
|
11
|
+
throw new OxygenError("invalid_request", "Pass either --row-ids-json or --row-ids-file, not both.", {
|
|
12
|
+
exitCode: 1,
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
if (!rowIdsJson && !rowIdsFile) {
|
|
16
|
+
throw new OxygenError("invalid_request", "Pass --row-ids-json or --row-ids-file.", {
|
|
17
|
+
exitCode: 1,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
if (rowIdsJson)
|
|
21
|
+
return normalizeDeleteRowIds(parseJsonArray(rowIdsJson));
|
|
22
|
+
const filePath = resolve(rowIdsFile ?? "");
|
|
23
|
+
const buffer = readFileSync(filePath);
|
|
24
|
+
const format = normalizeRowsFormat(options.format, inferRowsFileFormat(filePath));
|
|
25
|
+
if (format === "json") {
|
|
26
|
+
const text = buffer.toString("utf8");
|
|
27
|
+
const inlineIds = tryParseJsonStringArray(text);
|
|
28
|
+
if (inlineIds)
|
|
29
|
+
return normalizeDeleteRowIds(inlineIds);
|
|
30
|
+
if (!options.format && !text.trimStart().startsWith("[")) {
|
|
31
|
+
return normalizeDeleteRowIds(parsePlainRowIdList(text));
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const sheet = readOption(options.sheet);
|
|
35
|
+
const rows = await parseRowsFileBuffer(buffer, format, sheet ? { sheet } : {});
|
|
36
|
+
const rowIdColumn = readOption(options.rowIdColumn) ?? "_row_id";
|
|
37
|
+
return normalizeDeleteRowIds(rows.map((row, index) => {
|
|
38
|
+
const value = row[rowIdColumn];
|
|
39
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
40
|
+
throw new OxygenError("invalid_request", `Row id column "${rowIdColumn}" must contain row UUID strings.`, {
|
|
41
|
+
details: { row_number: index + 1, column: rowIdColumn },
|
|
42
|
+
exitCode: 1,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
return value.trim();
|
|
46
|
+
}));
|
|
47
|
+
}
|
|
48
|
+
export function tryParseJsonStringArray(value) {
|
|
49
|
+
let parsed;
|
|
50
|
+
try {
|
|
51
|
+
parsed = JSON.parse(value);
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
if (!Array.isArray(parsed) || !parsed.every((entry) => typeof entry === "string"))
|
|
57
|
+
return null;
|
|
58
|
+
return parsed;
|
|
59
|
+
}
|
|
60
|
+
export function parsePlainRowIdList(value) {
|
|
61
|
+
return value
|
|
62
|
+
.split(/[\n,]/)
|
|
63
|
+
.map((entry) => entry.trim())
|
|
64
|
+
.filter(Boolean);
|
|
65
|
+
}
|
|
66
|
+
export function normalizeDeleteRowIds(values) {
|
|
67
|
+
const rowIds = values.map((value, index) => {
|
|
68
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
69
|
+
throw new OxygenError("invalid_request", "Row IDs must be non-empty strings.", {
|
|
70
|
+
details: { index },
|
|
71
|
+
exitCode: 1,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
return value.trim();
|
|
75
|
+
});
|
|
76
|
+
if (rowIds.length === 0) {
|
|
77
|
+
throw new OxygenError("invalid_request", "Row ID list cannot be empty.", {
|
|
78
|
+
exitCode: 1,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
return rowIds;
|
|
82
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { TablesSendOptions } from "./options.js";
|
|
2
|
+
export declare function buildTablesSendRequest(source: string, target: string, options: TablesSendOptions): Record<string, unknown>;
|
|
3
|
+
export declare function parseTablesSendMapping(options: TablesSendOptions): Array<Record<string, unknown>> | null;
|
|
4
|
+
export declare function parseTablesSendFilters(value: string | undefined): Array<Record<string, unknown>> | null;
|
|
5
|
+
export declare function normalizeTablesSendMode(value: string | undefined): "insert" | "upsert";
|
|
6
|
+
export declare function writeTablesSendHint(data: unknown): void;
|