@tenonhq/dovetail-servicenow 0.0.28 → 0.0.30
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/choices.d.ts +1 -0
- package/dist/choices.js +34 -16
- package/dist/cli.js +174 -11
- package/dist/fieldsFromJson.d.ts +22 -0
- package/dist/fieldsFromJson.js +107 -0
- package/dist/index.d.ts +9 -9
- package/dist/index.js +4 -1
- package/dist/mcp/registry.d.ts +1 -1
- package/dist/mcp/registry.js +53 -11
- package/dist/mcp/schemas.d.ts +84 -0
- package/dist/mcp/schemas.js +94 -47
- package/dist/table/index.d.ts +7 -5
- package/dist/table/index.js +6 -1
- package/dist/table/setColumn.d.ts +153 -0
- package/dist/table/setColumn.js +639 -0
- package/package.json +1 -1
package/dist/choices.d.ts
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import type { ServiceNowClient } from "./client";
|
|
11
11
|
import type { AddChoicesParams, AddChoicesResult } from "./types";
|
|
12
|
+
export declare function encodeQueryValue(v: string): string;
|
|
12
13
|
/**
|
|
13
14
|
* Upsert choices for a field and (optionally) toggle sys_dictionary.choice.
|
|
14
15
|
* Idempotent: re-running with the same inputs returns `action: "unchanged"`
|
package/dist/choices.js
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* so choices stay in the same application as the field.
|
|
10
10
|
*/
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.encodeQueryValue = encodeQueryValue;
|
|
12
13
|
exports.addChoicesToField = addChoicesToField;
|
|
13
14
|
function encodeQueryValue(v) {
|
|
14
15
|
// ServiceNow encoded-query values: commas/carets/equals are special. We
|
|
@@ -39,7 +40,10 @@ async function resolveScopeName(client, scopeSysId) {
|
|
|
39
40
|
async function fetchDictionary(client, table, column) {
|
|
40
41
|
var rows = await client.table.query("sys_dictionary", "name=" + encodeQueryValue(table) + "^element=" + encodeQueryValue(column), 1);
|
|
41
42
|
if (rows.length === 0) {
|
|
42
|
-
throw new Error("sys_dictionary record not found for " +
|
|
43
|
+
throw new Error("sys_dictionary record not found for " +
|
|
44
|
+
table +
|
|
45
|
+
"." +
|
|
46
|
+
column +
|
|
43
47
|
" — verify the field exists and your user has read access.");
|
|
44
48
|
}
|
|
45
49
|
var row = rows[0];
|
|
@@ -53,7 +57,7 @@ async function fetchDictionary(client, table, column) {
|
|
|
53
57
|
name: row.name,
|
|
54
58
|
element: row.element,
|
|
55
59
|
choice: String(row.choice || "0"),
|
|
56
|
-
sys_scope: scope || ""
|
|
60
|
+
sys_scope: scope || "",
|
|
57
61
|
};
|
|
58
62
|
}
|
|
59
63
|
async function fetchUpdateSet(client, sysId) {
|
|
@@ -63,14 +67,16 @@ async function fetchUpdateSet(client, sysId) {
|
|
|
63
67
|
}
|
|
64
68
|
var row = rows[0];
|
|
65
69
|
if (row.state && row.state !== "in progress" && row.state !== "in_progress") {
|
|
66
|
-
throw new Error("Update set " +
|
|
70
|
+
throw new Error("Update set " +
|
|
71
|
+
row.name +
|
|
72
|
+
" is in state '" +
|
|
73
|
+
row.state +
|
|
67
74
|
"' — only 'in progress' update sets can capture new changes.");
|
|
68
75
|
}
|
|
69
76
|
return row;
|
|
70
77
|
}
|
|
71
78
|
async function fetchExistingChoices(client, table, column) {
|
|
72
|
-
return client.table.query("sys_choice", "name=" + encodeQueryValue(table) +
|
|
73
|
-
"^element=" + encodeQueryValue(column), 1000);
|
|
79
|
+
return client.table.query("sys_choice", "name=" + encodeQueryValue(table) + "^element=" + encodeQueryValue(column), 1000);
|
|
74
80
|
}
|
|
75
81
|
function buildChoiceFields(table, column, choice, scope) {
|
|
76
82
|
var fields = {
|
|
@@ -79,7 +85,7 @@ function buildChoiceFields(table, column, choice, scope) {
|
|
|
79
85
|
value: choice.value,
|
|
80
86
|
label: choice.label,
|
|
81
87
|
language: choice.language || "en",
|
|
82
|
-
inactive: "false"
|
|
88
|
+
inactive: "false",
|
|
83
89
|
};
|
|
84
90
|
if (choice.sequence != null) {
|
|
85
91
|
fields.sequence = String(choice.sequence);
|
|
@@ -114,7 +120,9 @@ async function addChoicesToField(client, params) {
|
|
|
114
120
|
var scopeName = await resolveScopeName(client, dict.sys_scope);
|
|
115
121
|
var targetChoiceType = params.choiceType === null
|
|
116
122
|
? Number(dict.choice)
|
|
117
|
-
:
|
|
123
|
+
: params.choiceType != null
|
|
124
|
+
? params.choiceType
|
|
125
|
+
: 3;
|
|
118
126
|
var choiceWas = Number(dict.choice);
|
|
119
127
|
var choiceNow = choiceWas;
|
|
120
128
|
if (params.choiceType !== null && Number(dict.choice) !== targetChoiceType) {
|
|
@@ -122,7 +130,7 @@ async function addChoicesToField(client, params) {
|
|
|
122
130
|
update_set_sys_id: params.updateSetSysId,
|
|
123
131
|
table: "sys_dictionary",
|
|
124
132
|
record_sys_id: dict.sys_id,
|
|
125
|
-
fields: { choice: String(targetChoiceType) }
|
|
133
|
+
fields: { choice: String(targetChoiceType) },
|
|
126
134
|
});
|
|
127
135
|
choiceNow = targetChoiceType;
|
|
128
136
|
}
|
|
@@ -138,14 +146,19 @@ async function addChoicesToField(client, params) {
|
|
|
138
146
|
var key = (choice.language || "en") + "::" + choice.value;
|
|
139
147
|
var match = existingByValue[key];
|
|
140
148
|
if (match && isUnchanged(match, choice)) {
|
|
141
|
-
results.push({
|
|
149
|
+
results.push({
|
|
150
|
+
value: choice.value,
|
|
151
|
+
label: choice.label,
|
|
152
|
+
sysId: match.sys_id,
|
|
153
|
+
action: "unchanged",
|
|
154
|
+
});
|
|
142
155
|
continue;
|
|
143
156
|
}
|
|
144
157
|
if (match) {
|
|
145
158
|
var updFields = {
|
|
146
159
|
label: choice.label,
|
|
147
160
|
language: choice.language || "en",
|
|
148
|
-
inactive: "false"
|
|
161
|
+
inactive: "false",
|
|
149
162
|
};
|
|
150
163
|
if (choice.sequence != null) {
|
|
151
164
|
updFields.sequence = String(choice.sequence);
|
|
@@ -154,22 +167,27 @@ async function addChoicesToField(client, params) {
|
|
|
154
167
|
update_set_sys_id: params.updateSetSysId,
|
|
155
168
|
table: "sys_choice",
|
|
156
169
|
record_sys_id: match.sys_id,
|
|
157
|
-
fields: updFields
|
|
170
|
+
fields: updFields,
|
|
171
|
+
});
|
|
172
|
+
results.push({
|
|
173
|
+
value: choice.value,
|
|
174
|
+
label: choice.label,
|
|
175
|
+
sysId: match.sys_id,
|
|
176
|
+
action: "updated",
|
|
158
177
|
});
|
|
159
|
-
results.push({ value: choice.value, label: choice.label, sysId: match.sys_id, action: "updated" });
|
|
160
178
|
continue;
|
|
161
179
|
}
|
|
162
180
|
var created = await client.claude.createRecord({
|
|
163
181
|
table: "sys_choice",
|
|
164
182
|
fields: buildChoiceFields(params.table, params.column, choice, dict.sys_scope),
|
|
165
183
|
scope: scopeName,
|
|
166
|
-
update_set_sys_id: params.updateSetSysId
|
|
184
|
+
update_set_sys_id: params.updateSetSysId,
|
|
167
185
|
});
|
|
168
186
|
results.push({
|
|
169
187
|
value: choice.value,
|
|
170
188
|
label: choice.label,
|
|
171
189
|
sysId: created.sys_id,
|
|
172
|
-
action: "created"
|
|
190
|
+
action: "created",
|
|
173
191
|
});
|
|
174
192
|
}
|
|
175
193
|
return {
|
|
@@ -177,9 +195,9 @@ async function addChoicesToField(client, params) {
|
|
|
177
195
|
sysId: dict.sys_id,
|
|
178
196
|
scope: dict.sys_scope,
|
|
179
197
|
choiceWas: choiceWas,
|
|
180
|
-
choiceNow: choiceNow
|
|
198
|
+
choiceNow: choiceNow,
|
|
181
199
|
},
|
|
182
200
|
updateSet: { sysId: updateSet.sys_id, name: updateSet.name },
|
|
183
|
-
choices: results
|
|
201
|
+
choices: results,
|
|
184
202
|
};
|
|
185
203
|
}
|
package/dist/cli.js
CHANGED
|
@@ -60,6 +60,7 @@ const fs = __importStar(require("fs"));
|
|
|
60
60
|
const path = __importStar(require("path"));
|
|
61
61
|
const loadEnv_1 = require("./loadEnv");
|
|
62
62
|
const client_1 = require("./client");
|
|
63
|
+
const fieldsFromJson_1 = require("./fieldsFromJson");
|
|
63
64
|
const choices_1 = require("./choices");
|
|
64
65
|
const formatter_1 = require("./formatter");
|
|
65
66
|
const views_1 = require("./layout/views");
|
|
@@ -87,24 +88,30 @@ const flowDesigner_formatter_2 = require("./flowDesigner-formatter");
|
|
|
87
88
|
function parseArgs(argv) {
|
|
88
89
|
var command = argv[0] || "";
|
|
89
90
|
var flags = {};
|
|
91
|
+
var bare = {};
|
|
90
92
|
for (var i = 1; i < argv.length; i += 1) {
|
|
91
93
|
var arg = argv[i];
|
|
92
94
|
if (arg.indexOf("--") !== 0)
|
|
93
95
|
continue;
|
|
94
96
|
var key = arg.slice(2);
|
|
95
97
|
var value = "true";
|
|
98
|
+
var isBare = true;
|
|
96
99
|
var eq = key.indexOf("=");
|
|
97
100
|
if (eq !== -1) {
|
|
98
101
|
value = key.slice(eq + 1);
|
|
99
102
|
key = key.slice(0, eq);
|
|
103
|
+
isBare = false;
|
|
100
104
|
}
|
|
101
105
|
else if (i + 1 < argv.length && argv[i + 1].indexOf("--") !== 0) {
|
|
102
106
|
value = argv[i + 1];
|
|
103
107
|
i += 1;
|
|
108
|
+
isBare = false;
|
|
104
109
|
}
|
|
105
110
|
flags[key] = value;
|
|
111
|
+
if (isBare)
|
|
112
|
+
bare[key] = true;
|
|
106
113
|
}
|
|
107
|
-
return { command: command, flags: flags };
|
|
114
|
+
return { command: command, flags: flags, bare: bare };
|
|
108
115
|
}
|
|
109
116
|
function parseChoicesInline(input) {
|
|
110
117
|
return input.split(",").map(function (pair) {
|
|
@@ -876,15 +883,27 @@ function printHelp() {
|
|
|
876
883
|
" [--name <element>] [--max-length <n>] [--reference <table>]\n" +
|
|
877
884
|
" [--mandatory] [--default <v>] [--scope <s>] [--dry-run] [--json])\n" +
|
|
878
885
|
" --update-set is REQUIRED on the live path (not for --dry-run).\n" +
|
|
886
|
+
" set-column Update an EXISTING column's SCHEMA (label/mandatory/default/read-only/max-length),\n" +
|
|
887
|
+
" into an update set, then verify against the instance\n" +
|
|
888
|
+
" (--table <t> --column <c> --update-set <sys_id>\n" +
|
|
889
|
+
" [--label <l>] [--mandatory true|false] [--default <v>]\n" +
|
|
890
|
+
" [--read-only true|false] [--max-length <n>]\n" +
|
|
891
|
+
" [--dry-run] [--json])\n" +
|
|
892
|
+
" A max-length SHRINK is REFUSED while rows hold longer values —\n" +
|
|
893
|
+
" ServiceNow silently ignores such a shrink (200 OK, no change).\n" +
|
|
894
|
+
" Shorten or clear those values first, then re-run.\n" +
|
|
895
|
+
" --element / --internal-type are REFUSED with an explanation:\n" +
|
|
896
|
+
" ServiceNow silently ignores both on an existing column.\n" +
|
|
879
897
|
" invoke-rest Invoke an arbitrary authenticated REST operation (Scripted REST incl.)\n" +
|
|
880
898
|
" DRY-RUN BY DEFAULT — nothing is sent without --confirm\n" +
|
|
881
899
|
" (--method <GET|POST|PUT|DELETE> --path /api/<scope>/<service>/<resource>\n" +
|
|
882
900
|
" [--body '<json>' | --body-json <path>] [--confirm] [--dry-run] [--json])\n" +
|
|
883
|
-
" set-field Set
|
|
884
|
-
|
|
885
|
-
|
|
901
|
+
" set-field Set field value(s) on an EXISTING record, into an update set, then verify\n" +
|
|
902
|
+
" (--table <t> --sys-id <id>|--query <q> --update-set <sys_id>\n" +
|
|
903
|
+
' (--fields "k=v,k2=v2" | --from-json <path>) [--dry-run] [--json])\n' +
|
|
886
904
|
" create-record Create ONE NEW record in a data table, into an update set, then verify\n" +
|
|
887
|
-
|
|
905
|
+
" (--table <t> --scope <s> --update-set <sys_id>\n" +
|
|
906
|
+
' (--fields "k=v,k2=v2" | --from-json <path>)\n' +
|
|
888
907
|
" [--if-absent <encoded-query>] [--dry-run] [--json])\n" +
|
|
889
908
|
" host-assets Deploy a built dist/ to ServiceNow (carrier sys_ui_script + attachment + m2m)\n" +
|
|
890
909
|
" (--dir <dist> --app <sys_id> --scope <namespace>\n" +
|
|
@@ -1087,6 +1106,111 @@ async function runAddColumn(flags) {
|
|
|
1087
1106
|
return 2;
|
|
1088
1107
|
return 0;
|
|
1089
1108
|
}
|
|
1109
|
+
/** Parse a CLI boolean flag. Bare `--mandatory` means true; `--mandatory false` means
|
|
1110
|
+
* false. Anything else is rejected rather than quietly coerced to `true`. */
|
|
1111
|
+
function parseBoolFlag(name, raw) {
|
|
1112
|
+
if (raw === "true")
|
|
1113
|
+
return true;
|
|
1114
|
+
if (raw === "false")
|
|
1115
|
+
return false;
|
|
1116
|
+
throw new Error("set-column: --" + name + " must be true or false (got '" + raw + "').");
|
|
1117
|
+
}
|
|
1118
|
+
/**
|
|
1119
|
+
* dove-sn set-column:
|
|
1120
|
+
* --table x_cadso_journey --column description --update-set <sys_id>
|
|
1121
|
+
* [--label "Description"] [--mandatory true|false] [--default <v>]
|
|
1122
|
+
* [--read-only true|false] [--max-length 4000] [--dry-run] [--json]
|
|
1123
|
+
*
|
|
1124
|
+
* Updates an EXISTING column's schema. `internal_type` and a rename are refused —
|
|
1125
|
+
* ServiceNow silently ignores both on an existing column. To CREATE one, use add-column;
|
|
1126
|
+
* to set a RECORD's value, use set-field.
|
|
1127
|
+
*/
|
|
1128
|
+
async function runSetColumn(flags, bare) {
|
|
1129
|
+
// A string flag whose value was forgotten arrives as the literal "true" — `--label`
|
|
1130
|
+
// with nothing after it would rename the column to "true". Booleans legitimately do
|
|
1131
|
+
// that, strings never do, so refuse rather than silently write nonsense.
|
|
1132
|
+
var stringFlags = ["label", "default", "table", "column", "update-set"];
|
|
1133
|
+
for (var f = 0; f < stringFlags.length; f += 1) {
|
|
1134
|
+
if (bare[stringFlags[f]]) {
|
|
1135
|
+
process.stderr.write("set-column: --" +
|
|
1136
|
+
stringFlags[f] +
|
|
1137
|
+
" needs a value (it was given none).\n");
|
|
1138
|
+
return 1;
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
var table = flags.table;
|
|
1142
|
+
var column = flags.column;
|
|
1143
|
+
if (!table || !column) {
|
|
1144
|
+
process.stderr.write("set-column: --table and --column are required " +
|
|
1145
|
+
"(--update-set is required too, unless --dry-run)\n");
|
|
1146
|
+
return 1;
|
|
1147
|
+
}
|
|
1148
|
+
var attributes = {};
|
|
1149
|
+
// Accepted so that setColumn can REFUSE them by name with the reason. Dropping them
|
|
1150
|
+
// silently would leave someone who asked for a rename believing it happened.
|
|
1151
|
+
if (flags.element !== undefined)
|
|
1152
|
+
attributes.element = flags.element;
|
|
1153
|
+
if (flags["internal-type"] !== undefined) {
|
|
1154
|
+
attributes.internalType = flags["internal-type"];
|
|
1155
|
+
}
|
|
1156
|
+
if (flags.label !== undefined)
|
|
1157
|
+
attributes.label = flags.label;
|
|
1158
|
+
if (flags.default !== undefined)
|
|
1159
|
+
attributes.default = flags.default;
|
|
1160
|
+
if (flags.mandatory !== undefined) {
|
|
1161
|
+
attributes.mandatory = parseBoolFlag("mandatory", flags.mandatory);
|
|
1162
|
+
}
|
|
1163
|
+
if (flags["read-only"] !== undefined) {
|
|
1164
|
+
attributes.readOnly = parseBoolFlag("read-only", flags["read-only"]);
|
|
1165
|
+
}
|
|
1166
|
+
if (flags["max-length"] !== undefined) {
|
|
1167
|
+
var len = Number(flags["max-length"]);
|
|
1168
|
+
// sys_dictionary.max_length is an integer; the MCP schema enforces int() too.
|
|
1169
|
+
if (!Number.isInteger(len) || len < 1) {
|
|
1170
|
+
process.stderr.write("set-column: --max-length must be a positive integer\n");
|
|
1171
|
+
return 1;
|
|
1172
|
+
}
|
|
1173
|
+
attributes.maxLength = len;
|
|
1174
|
+
}
|
|
1175
|
+
var params = {
|
|
1176
|
+
client: (0, client_1.createClient)({}),
|
|
1177
|
+
table: table,
|
|
1178
|
+
column: column,
|
|
1179
|
+
attributes: attributes,
|
|
1180
|
+
};
|
|
1181
|
+
// Accept the same alias pair as the other verbs (create-view, set-list-layout, …).
|
|
1182
|
+
var setColumnUs = flags["update-set"] || flags.updateSetSysId;
|
|
1183
|
+
if (setColumnUs)
|
|
1184
|
+
params.updateSetSysId = setColumnUs;
|
|
1185
|
+
if (flags["dry-run"] === "true")
|
|
1186
|
+
params.dryRun = true;
|
|
1187
|
+
var result = await (0, table_1.setColumn)(params);
|
|
1188
|
+
if (flags.json === "true") {
|
|
1189
|
+
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
1190
|
+
}
|
|
1191
|
+
else {
|
|
1192
|
+
process.stdout.write("[" +
|
|
1193
|
+
result.status +
|
|
1194
|
+
"] " +
|
|
1195
|
+
result.table +
|
|
1196
|
+
"." +
|
|
1197
|
+
result.column +
|
|
1198
|
+
(result.verified && result.status === "applied" ? " — verified" : "") +
|
|
1199
|
+
(result.status === "applied" && !result.capturedInUpdateSet
|
|
1200
|
+
? " — NOT CAPTURED"
|
|
1201
|
+
: "") +
|
|
1202
|
+
"\n" +
|
|
1203
|
+
result.note +
|
|
1204
|
+
"\n");
|
|
1205
|
+
}
|
|
1206
|
+
// 2 = the write landed but the instance does not reflect it (or it was not captured),
|
|
1207
|
+
// which must not read as success to a script.
|
|
1208
|
+
if (result.status === "failed")
|
|
1209
|
+
return 2;
|
|
1210
|
+
if (result.status === "applied" && !result.capturedInUpdateSet)
|
|
1211
|
+
return 2;
|
|
1212
|
+
return 0;
|
|
1213
|
+
}
|
|
1090
1214
|
/** Parse inline `--fields "k=v, k2=v2"` into a field map. */
|
|
1091
1215
|
function parseFieldsInline(input) {
|
|
1092
1216
|
var out = {};
|
|
@@ -1106,24 +1230,48 @@ function parseFieldsInline(input) {
|
|
|
1106
1230
|
}
|
|
1107
1231
|
return out;
|
|
1108
1232
|
}
|
|
1233
|
+
/**
|
|
1234
|
+
* Merge the two field sources for the record-write verbs: inline `--fields "k=v,k2=v2"`
|
|
1235
|
+
* and `--from-json <path>` (a JSON object — the only form that can carry a large or
|
|
1236
|
+
* multiline value, since the inline form splits on commas and trims). On a key present in
|
|
1237
|
+
* both, `--from-json` wins: it is the explicit spec file. Throws on an unreadable file,
|
|
1238
|
+
* malformed JSON, or a non-scalar value (surfaced by the caller as a bad-args error).
|
|
1239
|
+
*/
|
|
1240
|
+
function mergeFields(flags) {
|
|
1241
|
+
var inline = parseFieldsInline(flags.fields || "");
|
|
1242
|
+
if (!flags["from-json"])
|
|
1243
|
+
return inline;
|
|
1244
|
+
return Object.assign(inline, (0, fieldsFromJson_1.readFieldsFromJsonFile)(flags["from-json"]));
|
|
1245
|
+
}
|
|
1109
1246
|
/**
|
|
1110
1247
|
* dove-sn set-field:
|
|
1111
1248
|
* --table x_cadso_core_metric_point_type
|
|
1112
1249
|
* --sys-id <id> | --query "name=send_size" (query must resolve to exactly 1 row)
|
|
1113
|
-
* --fields "order=20"
|
|
1250
|
+
* [--fields "order=20"] (comma-separated key=value pairs)
|
|
1251
|
+
* [--from-json <path>] (JSON { field: value }; carries large or
|
|
1252
|
+
* multiline values the inline form can't;
|
|
1253
|
+
* overrides --fields on a shared key)
|
|
1254
|
+
* — at least one of --fields / --from-json is required
|
|
1114
1255
|
* --update-set <sys_id> (required — the change is captured here)
|
|
1115
1256
|
* [--dry-run] [--json]
|
|
1116
1257
|
* Exit codes: 0 applied/dry-run, 1 bad args, 2 write landed but read-back unverified.
|
|
1117
1258
|
*/
|
|
1118
1259
|
async function runSetField(flags) {
|
|
1119
1260
|
var table = flags.table;
|
|
1120
|
-
var fields
|
|
1261
|
+
var fields;
|
|
1262
|
+
try {
|
|
1263
|
+
fields = mergeFields(flags);
|
|
1264
|
+
}
|
|
1265
|
+
catch (err) {
|
|
1266
|
+
process.stderr.write("set-field: " + (err instanceof Error ? err.message : String(err)) + "\n");
|
|
1267
|
+
return 1;
|
|
1268
|
+
}
|
|
1121
1269
|
var hasTarget = Boolean(flags["sys-id"] || flags.query);
|
|
1122
1270
|
if (!table ||
|
|
1123
1271
|
Object.keys(fields).length === 0 ||
|
|
1124
1272
|
!hasTarget ||
|
|
1125
1273
|
!flags["update-set"]) {
|
|
1126
|
-
process.stderr.write('set-field: --table,
|
|
1274
|
+
process.stderr.write('set-field: --table, one of --sys-id/--query, --update-set, and at least one field (--fields "k=v" or --from-json <path>) are required\n');
|
|
1127
1275
|
return 1;
|
|
1128
1276
|
}
|
|
1129
1277
|
var params = {
|
|
@@ -1163,7 +1311,10 @@ async function runSetField(flags) {
|
|
|
1163
1311
|
/**
|
|
1164
1312
|
* dove-sn create-record:
|
|
1165
1313
|
* --table x_cadso_core_metric_point_type
|
|
1166
|
-
* --fields "name=avg_message_parts,label=Avg. Message Parts,order=35"
|
|
1314
|
+
* [--fields "name=avg_message_parts,label=Avg. Message Parts,order=35"]
|
|
1315
|
+
* [--from-json <path>] (JSON { field: value }; carries large or
|
|
1316
|
+
* multiline values the inline form can't)
|
|
1317
|
+
* — at least one of --fields / --from-json is required
|
|
1167
1318
|
* --scope x_cadso_core (the app that owns the new record)
|
|
1168
1319
|
* --update-set <sys_id> (required — the insert is captured here)
|
|
1169
1320
|
* [--if-absent "name=avg_message_parts"] (skip the insert when this query already matches)
|
|
@@ -1173,12 +1324,21 @@ async function runSetField(flags) {
|
|
|
1173
1324
|
*/
|
|
1174
1325
|
async function runCreateRecord(flags) {
|
|
1175
1326
|
var table = flags.table;
|
|
1176
|
-
var fields
|
|
1327
|
+
var fields;
|
|
1328
|
+
try {
|
|
1329
|
+
fields = mergeFields(flags);
|
|
1330
|
+
}
|
|
1331
|
+
catch (err) {
|
|
1332
|
+
process.stderr.write("create-record: " +
|
|
1333
|
+
(err instanceof Error ? err.message : String(err)) +
|
|
1334
|
+
"\n");
|
|
1335
|
+
return 1;
|
|
1336
|
+
}
|
|
1177
1337
|
if (!table ||
|
|
1178
1338
|
Object.keys(fields).length === 0 ||
|
|
1179
1339
|
!flags.scope ||
|
|
1180
1340
|
!flags["update-set"]) {
|
|
1181
|
-
process.stderr.write('create-record: --table, --fields "k=v"
|
|
1341
|
+
process.stderr.write('create-record: --table, --scope, --update-set, and at least one field (--fields "k=v" or --from-json <path>) are required\n');
|
|
1182
1342
|
return 1;
|
|
1183
1343
|
}
|
|
1184
1344
|
var params = {
|
|
@@ -1388,6 +1548,9 @@ async function main() {
|
|
|
1388
1548
|
if (parsed.command === "add-column") {
|
|
1389
1549
|
return await runAddColumn(parsed.flags);
|
|
1390
1550
|
}
|
|
1551
|
+
if (parsed.command === "set-column") {
|
|
1552
|
+
return await runSetColumn(parsed.flags, parsed.bare);
|
|
1553
|
+
}
|
|
1391
1554
|
if (parsed.command === "set-field") {
|
|
1392
1555
|
return await runSetField(parsed.flags);
|
|
1393
1556
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `--from-json` field-map loader for the record-write verbs (set-field, create-record).
|
|
3
|
+
*
|
|
4
|
+
* The inline `--fields "k=v,k2=v2"` form splits on commas and trims each piece — so it
|
|
5
|
+
* cannot carry a value that contains a comma, a newline, an `=`, or leading/trailing
|
|
6
|
+
* whitespace. That rules out every large field: a script body, an HTML/XML/CSS field, a
|
|
7
|
+
* JSON blob. `--from-json <path>` closes that gap: the file is a flat JSON object of
|
|
8
|
+
* `field → value`, and JSON quoting carries any character faithfully.
|
|
9
|
+
*
|
|
10
|
+
* Values may be string | number | boolean. Numbers/booleans are stringified — matching
|
|
11
|
+
* the inline parser and the wire format, where ServiceNow scalar fields are strings.
|
|
12
|
+
* `null`/`undefined` values are skipped. Nested objects and arrays are rejected: a scalar
|
|
13
|
+
* field cannot take one, and silently JSON-encoding it would write garbage into the record.
|
|
14
|
+
*/
|
|
15
|
+
export declare function coerceFieldsFromJson(parsed: unknown): Record<string, string>;
|
|
16
|
+
/**
|
|
17
|
+
* Read + parse a `--from-json` file into a validated field map. The path is resolved
|
|
18
|
+
* against the process cwd, matching the other `--from-json` consumers in the CLI.
|
|
19
|
+
* Throws a clean, prefixed message on a missing file or malformed JSON so the caller can
|
|
20
|
+
* surface it as a bad-args error rather than an uncaught stack trace.
|
|
21
|
+
*/
|
|
22
|
+
export declare function readFieldsFromJsonFile(filePath: string): Record<string, string>;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.coerceFieldsFromJson = coerceFieldsFromJson;
|
|
37
|
+
exports.readFieldsFromJsonFile = readFieldsFromJsonFile;
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
/**
|
|
41
|
+
* `--from-json` field-map loader for the record-write verbs (set-field, create-record).
|
|
42
|
+
*
|
|
43
|
+
* The inline `--fields "k=v,k2=v2"` form splits on commas and trims each piece — so it
|
|
44
|
+
* cannot carry a value that contains a comma, a newline, an `=`, or leading/trailing
|
|
45
|
+
* whitespace. That rules out every large field: a script body, an HTML/XML/CSS field, a
|
|
46
|
+
* JSON blob. `--from-json <path>` closes that gap: the file is a flat JSON object of
|
|
47
|
+
* `field → value`, and JSON quoting carries any character faithfully.
|
|
48
|
+
*
|
|
49
|
+
* Values may be string | number | boolean. Numbers/booleans are stringified — matching
|
|
50
|
+
* the inline parser and the wire format, where ServiceNow scalar fields are strings.
|
|
51
|
+
* `null`/`undefined` values are skipped. Nested objects and arrays are rejected: a scalar
|
|
52
|
+
* field cannot take one, and silently JSON-encoding it would write garbage into the record.
|
|
53
|
+
*/
|
|
54
|
+
function coerceFieldsFromJson(parsed) {
|
|
55
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
56
|
+
throw new Error("--from-json must contain a JSON object of field → value pairs");
|
|
57
|
+
}
|
|
58
|
+
var obj = parsed;
|
|
59
|
+
var out = {};
|
|
60
|
+
var keys = Object.keys(obj);
|
|
61
|
+
for (var i = 0; i < keys.length; i += 1) {
|
|
62
|
+
var key = keys[i];
|
|
63
|
+
var value = obj[key];
|
|
64
|
+
if (value === null || value === undefined)
|
|
65
|
+
continue;
|
|
66
|
+
var valueType = typeof value;
|
|
67
|
+
if (valueType === "string") {
|
|
68
|
+
out[key] = value;
|
|
69
|
+
}
|
|
70
|
+
else if (valueType === "number" || valueType === "boolean") {
|
|
71
|
+
out[key] = String(value);
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
throw new Error("--from-json field '" +
|
|
75
|
+
key +
|
|
76
|
+
"' must be a string, number, or boolean (got " +
|
|
77
|
+
valueType +
|
|
78
|
+
")");
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return out;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Read + parse a `--from-json` file into a validated field map. The path is resolved
|
|
85
|
+
* against the process cwd, matching the other `--from-json` consumers in the CLI.
|
|
86
|
+
* Throws a clean, prefixed message on a missing file or malformed JSON so the caller can
|
|
87
|
+
* surface it as a bad-args error rather than an uncaught stack trace.
|
|
88
|
+
*/
|
|
89
|
+
function readFieldsFromJsonFile(filePath) {
|
|
90
|
+
var raw;
|
|
91
|
+
try {
|
|
92
|
+
raw = fs.readFileSync(path.resolve(filePath), "utf8");
|
|
93
|
+
}
|
|
94
|
+
catch (err) {
|
|
95
|
+
var readMsg = err instanceof Error ? err.message : String(err);
|
|
96
|
+
throw new Error("--from-json: cannot read '" + filePath + "': " + readMsg);
|
|
97
|
+
}
|
|
98
|
+
var parsed;
|
|
99
|
+
try {
|
|
100
|
+
parsed = JSON.parse(raw);
|
|
101
|
+
}
|
|
102
|
+
catch (err) {
|
|
103
|
+
var parseMsg = err instanceof Error ? err.message : String(err);
|
|
104
|
+
throw new Error("--from-json: '" + filePath + "' is not valid JSON: " + parseMsg);
|
|
105
|
+
}
|
|
106
|
+
return coerceFieldsFromJson(parsed);
|
|
107
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
* REST API so every change lands in the target update set and scope.
|
|
6
6
|
*/
|
|
7
7
|
export { createClient } from "./client";
|
|
8
|
-
export { createClientFromEnvFile, resolveConfigFromEnvFile } from "./createClientFromEnvFile";
|
|
9
|
-
export type { ServiceNowClient, TableQueryOptions, TableSchema, TableSchemaField, AttachmentMeta, NowInvokeMethod, NowInvokeParams, NowInvokeResponse } from "./client";
|
|
8
|
+
export { createClientFromEnvFile, resolveConfigFromEnvFile, } from "./createClientFromEnvFile";
|
|
9
|
+
export type { ServiceNowClient, TableQueryOptions, TableSchema, TableSchemaField, AttachmentMeta, NowInvokeMethod, NowInvokeParams, NowInvokeResponse, } from "./client";
|
|
10
10
|
export { addChoicesToField } from "./choices";
|
|
11
|
-
export { hostAssets, classifyChunks, formatHostAssetsResult } from "./hostAssets";
|
|
11
|
+
export { hostAssets, classifyChunks, formatHostAssetsResult, } from "./hostAssets";
|
|
12
12
|
export { formatAddChoicesResult } from "./formatter";
|
|
13
13
|
export { createView } from "./layout/views";
|
|
14
14
|
export { setListLayout } from "./layout/listLayout";
|
|
@@ -16,14 +16,14 @@ export { setFormLayout } from "./layout/formLayout";
|
|
|
16
16
|
export { setRelatedLists } from "./layout/relatedLists";
|
|
17
17
|
export { formatLayoutResult, formatCreateViewResult } from "./layout/formatter";
|
|
18
18
|
export { sincPlugin } from "./plugin";
|
|
19
|
-
export { listTemplates, verifyArtifact, cloneSubflow, cloneActionType, triggerPublication, publishActionType, editActionType, applyStepOps, verifySteps, summarizeSteps, formatStepPill, readFlow, readActionType, publishFlow, copyFlow, createFlow, buildPublishModel, editFlow, testFlow, DEFAULT_RUN_FLOW_PATH, generateSysId, topoSort, executeWritePlan, WriteOrderError } from "./flowDesigner";
|
|
20
|
-
export type { TemplateRef, ListTemplatesParams, FlowKind, VerifyExpect, VerifyFound, VerifyFailure, VerifyReport, VerifyArtifactParams, CloneSubflowParams, CloneSubflowResult, CloneActionTypeParams, CloneActionTypeResult, TriggerPublicationParams, TriggerPublicationResult, PublishActionTypeParams, PublishActionTypeResult, EditActionTypeParams, EditActionTypeResult, EditActionTypeOps, StepOps, StepRecord, StepSummary, StepIoSummary, PatchStepScriptOp, AddStepOutputOp, AddStepInputOp, ApplyStepOpsResult, VerifyStepsResult, ReadFlowParams, ReadFlowResult, FlowStep, FlowVariable, ReadActionTypeParams, ReadActionTypeResult, ActionIo, PublishFlowParams, PublishFlowResult, CopyFlowParams, CopyFlowResult, CreateFlowParams, CreateFlowResult, EditFlowParams, EditFlowResult, EditFlowOps, StepInputPatch, TestFlowParams, TestFlowResult, WriteOp, WriteOpResult } from "./flowDesigner";
|
|
21
|
-
export type { ServiceNowClientConfig, ChoiceValue, ChoiceType, AddChoicesParams, AddChoicesResult, ChoiceActionResult, DictionaryRecord, UpdateSetRecord, LayoutAction, LayoutRecordResult, LayoutResult, CreateViewParams, CreateViewResult, FormSectionSpec, SetFormLayoutParams, SetListLayoutParams, SetRelatedListsParams, ChunkRole, ChunkInfo, ChunkResult, PrunedResult, HostAssetsParams, HostAssetsResult } from "./types";
|
|
22
|
-
export { createTable, projectTableGraph, buildColumnXml, normalizeColumns, resolveType, applyTableSaveOverlay, defaultAccessFlags, TYPE_MAP, DEFAULT_SUPER_CLASS, DEFAULT_SAVE_ACTION, addColumn, deriveElement } from "./table";
|
|
23
|
-
export type { CreateTableParams, CreateTableResult, AddColumnParams, AddColumnResult, TableGraph, NormalizedColumn, ColumnSpec, AccessFlags, OverlaySpec } from "./table";
|
|
19
|
+
export { listTemplates, verifyArtifact, cloneSubflow, cloneActionType, triggerPublication, publishActionType, editActionType, applyStepOps, verifySteps, summarizeSteps, formatStepPill, readFlow, readActionType, publishFlow, copyFlow, createFlow, buildPublishModel, editFlow, testFlow, DEFAULT_RUN_FLOW_PATH, generateSysId, topoSort, executeWritePlan, WriteOrderError, } from "./flowDesigner";
|
|
20
|
+
export type { TemplateRef, ListTemplatesParams, FlowKind, VerifyExpect, VerifyFound, VerifyFailure, VerifyReport, VerifyArtifactParams, CloneSubflowParams, CloneSubflowResult, CloneActionTypeParams, CloneActionTypeResult, TriggerPublicationParams, TriggerPublicationResult, PublishActionTypeParams, PublishActionTypeResult, EditActionTypeParams, EditActionTypeResult, EditActionTypeOps, StepOps, StepRecord, StepSummary, StepIoSummary, PatchStepScriptOp, AddStepOutputOp, AddStepInputOp, ApplyStepOpsResult, VerifyStepsResult, ReadFlowParams, ReadFlowResult, FlowStep, FlowVariable, ReadActionTypeParams, ReadActionTypeResult, ActionIo, PublishFlowParams, PublishFlowResult, CopyFlowParams, CopyFlowResult, CreateFlowParams, CreateFlowResult, EditFlowParams, EditFlowResult, EditFlowOps, StepInputPatch, TestFlowParams, TestFlowResult, WriteOp, WriteOpResult, } from "./flowDesigner";
|
|
21
|
+
export type { ServiceNowClientConfig, ChoiceValue, ChoiceType, AddChoicesParams, AddChoicesResult, ChoiceActionResult, DictionaryRecord, UpdateSetRecord, LayoutAction, LayoutRecordResult, LayoutResult, CreateViewParams, CreateViewResult, FormSectionSpec, SetFormLayoutParams, SetListLayoutParams, SetRelatedListsParams, ChunkRole, ChunkInfo, ChunkResult, PrunedResult, HostAssetsParams, HostAssetsResult, } from "./types";
|
|
22
|
+
export { createTable, projectTableGraph, buildColumnXml, normalizeColumns, resolveType, applyTableSaveOverlay, defaultAccessFlags, TYPE_MAP, DEFAULT_SUPER_CLASS, DEFAULT_SAVE_ACTION, addColumn, deriveElement, setColumn, resolveAttributes, toStoredValue, } from "./table";
|
|
23
|
+
export type { CreateTableParams, CreateTableResult, AddColumnParams, AddColumnResult, SetColumnParams, SetColumnResult, ColumnAttributes, AttributeChange, TableGraph, NormalizedColumn, ColumnSpec, AccessFlags, OverlaySpec, } from "./table";
|
|
24
24
|
export { setField } from "./setField";
|
|
25
25
|
export { createRecord } from "./createRecord";
|
|
26
|
-
export type { RecordWriteResult, SetFieldParams, SetFieldResult } from "./setField";
|
|
26
|
+
export type { RecordWriteResult, SetFieldParams, SetFieldResult, } from "./setField";
|
|
27
27
|
export type { CreateRecordParams, CreateRecordResult } from "./createRecord";
|
|
28
28
|
export { invokeRest, INVOKE_REST_METHODS } from "./invokeRest";
|
|
29
29
|
export type { InvokeRestParams, InvokeRestResult } from "./invokeRest";
|
package/dist/index.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
9
|
exports.addColumn = exports.DEFAULT_SAVE_ACTION = exports.DEFAULT_SUPER_CLASS = exports.TYPE_MAP = exports.defaultAccessFlags = exports.applyTableSaveOverlay = exports.resolveType = exports.normalizeColumns = exports.buildColumnXml = exports.projectTableGraph = exports.createTable = exports.WriteOrderError = exports.executeWritePlan = exports.topoSort = exports.generateSysId = exports.DEFAULT_RUN_FLOW_PATH = exports.testFlow = exports.editFlow = exports.buildPublishModel = exports.createFlow = exports.copyFlow = exports.publishFlow = exports.readActionType = exports.readFlow = exports.formatStepPill = exports.summarizeSteps = exports.verifySteps = exports.applyStepOps = exports.editActionType = exports.publishActionType = exports.triggerPublication = exports.cloneActionType = exports.cloneSubflow = exports.verifyArtifact = exports.listTemplates = exports.sincPlugin = exports.formatCreateViewResult = exports.formatLayoutResult = exports.setRelatedLists = exports.setFormLayout = exports.setListLayout = exports.createView = exports.formatAddChoicesResult = exports.formatHostAssetsResult = exports.classifyChunks = exports.hostAssets = exports.addChoicesToField = exports.resolveConfigFromEnvFile = exports.createClientFromEnvFile = exports.createClient = void 0;
|
|
10
|
-
exports.INVOKE_REST_METHODS = exports.invokeRest = exports.createRecord = exports.setField = exports.deriveElement = void 0;
|
|
10
|
+
exports.INVOKE_REST_METHODS = exports.invokeRest = exports.createRecord = exports.setField = exports.toStoredValue = exports.resolveAttributes = exports.setColumn = exports.deriveElement = void 0;
|
|
11
11
|
var client_1 = require("./client");
|
|
12
12
|
Object.defineProperty(exports, "createClient", { enumerable: true, get: function () { return client_1.createClient; } });
|
|
13
13
|
var createClientFromEnvFile_1 = require("./createClientFromEnvFile");
|
|
@@ -72,6 +72,9 @@ Object.defineProperty(exports, "DEFAULT_SUPER_CLASS", { enumerable: true, get: f
|
|
|
72
72
|
Object.defineProperty(exports, "DEFAULT_SAVE_ACTION", { enumerable: true, get: function () { return table_1.DEFAULT_SAVE_ACTION; } });
|
|
73
73
|
Object.defineProperty(exports, "addColumn", { enumerable: true, get: function () { return table_1.addColumn; } });
|
|
74
74
|
Object.defineProperty(exports, "deriveElement", { enumerable: true, get: function () { return table_1.deriveElement; } });
|
|
75
|
+
Object.defineProperty(exports, "setColumn", { enumerable: true, get: function () { return table_1.setColumn; } });
|
|
76
|
+
Object.defineProperty(exports, "resolveAttributes", { enumerable: true, get: function () { return table_1.resolveAttributes; } });
|
|
77
|
+
Object.defineProperty(exports, "toStoredValue", { enumerable: true, get: function () { return table_1.toStoredValue; } });
|
|
75
78
|
var setField_1 = require("./setField");
|
|
76
79
|
Object.defineProperty(exports, "setField", { enumerable: true, get: function () { return setField_1.setField; } });
|
|
77
80
|
var createRecord_1 = require("./createRecord");
|
package/dist/mcp/registry.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
10
10
|
import { z } from "zod";
|
|
11
11
|
import type { ToolAnnotations } from "@tenonhq/dovetail-mcp-kit";
|
|
12
12
|
import type { ServiceNowClient } from "../client";
|
|
13
|
-
export declare var TOOL_NAMES: readonly ["create_view", "set_list_layout", "set_form_layout", "set_related_lists", "add_choices_to_field", "flow_view", "action_view", "action_edit", "flow_publish", "flow_copy", "flow_create", "flow_test", "flow_edit", "create_table", "add_column", "set_field", "create_record", "host_assets", "invoke_rest"];
|
|
13
|
+
export declare var TOOL_NAMES: readonly ["create_view", "set_list_layout", "set_form_layout", "set_related_lists", "add_choices_to_field", "flow_view", "action_view", "action_edit", "flow_publish", "flow_copy", "flow_create", "flow_test", "flow_edit", "create_table", "add_column", "set_column", "set_field", "create_record", "host_assets", "invoke_rest"];
|
|
14
14
|
export type ToolName = (typeof TOOL_NAMES)[number];
|
|
15
15
|
export interface RegistryDeps {
|
|
16
16
|
/** Optional client injection for tests; defaults to createClient({}). */
|