@tenonhq/dovetail-servicenow 0.0.30 → 0.0.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -406,6 +406,45 @@ Programmatic: `invokeRest({ method, path, body, confirm })` is exported, and the
406
406
  client gained `now.put` / `now.delete` / `now.invoke` (the latter returns
407
407
  `{ status, body }` verbatim) alongside the existing `now.get` / `now.post`.
408
408
 
409
+ ### Publish an app to the Store / application repository
410
+
411
+ Publish a scoped application to the **ServiceNow Store**, the **company
412
+ application repository**, or both — headlessly, with the publish's progress
413
+ tracker polled to completion.
414
+
415
+ ```bash
416
+ # Dry-run — the DEFAULT: resolves the app, prints the plan, publishes NOTHING
417
+ npx dove-sn publish-app --app x_cadso_filter --version 6.0.20260716 --target both
418
+
419
+ # Publish for real (store, then repo, same version)
420
+ npx dove-sn publish-app --app x_cadso_filter --version 6.0.20260716 \
421
+ --target both --dev-notes "July release" --confirm --json
422
+ ```
423
+
424
+ **Store publish is EXTERNALLY VISIBLE on the ServiceNow Store — treat
425
+ `--target store --confirm` as a release.** `publish-app` is dry-run by default:
426
+ without `--confirm` it prints the resolved plan and exits `1` (a deliberate
427
+ refusal). `--target both` runs store then repo sequentially and short-circuits
428
+ if the store leg fails.
429
+
430
+ The two targets ride different transports:
431
+
432
+ - **store** replays the `sys_app` form's upload flow (`xmlhttp.do` +
433
+ `sn_appauthor.ScopedAppUploaderAJAX`) over a form-login session — basic auth
434
+ alone no-ops there. It needs the Store account credentials in the loaded env
435
+ file as `SN_STORE_USERNAME` / `SN_STORE_PASSWORD`. The password is **never
436
+ accepted as a flag** and never appears in output, dry-run previews included.
437
+ - **repo** uses the supported CI/CD REST API (`POST /api/sn_cicd/app_repo/publish`
438
+ + `GET /api/sn_cicd/progress/{id}`) over basic auth. The API user needs the
439
+ `sn_cicd` role (or admin).
440
+
441
+ `--app` accepts a scope name, `sys_app` sys_id, or app name; `--version` must be
442
+ above the currently published version. The result carries the progress-tracker
443
+ id, per-step states ("Packaging application", "Uploading application"), the
444
+ Store `appLink`, and the publish's update-set sys_id where the instance reports
445
+ one. Exit codes: `0` published or dry-run, `1` bad args/unconfirmed, `2`
446
+ failed/timeout. Programmatic: `publishApp({ app, version, target, confirm })`.
447
+
409
448
  `test-flow` defaults to **validate** — a safe pre-flight (published? inputs match
410
449
  declared variables?) that never runs the flow; `--execute --confirm` runs it via
411
450
  the server-side FlowAPI runner (deploy `resources/runFlow.md` first).
@@ -488,8 +527,10 @@ publish is read back and verified), `flow_publish` (compile a flow/subflow snaps
488
527
  publish, grafting a template), `flow_test` (validate or run a flow), and
489
528
  `flow_edit` (patch a flow), plus `invoke_rest` (invoke an arbitrary authenticated
490
529
  REST operation — Scripted REST included — with GET/POST/PUT/DELETE; dry-run by
491
- default, response passed through verbatim, bodies never logged). It reads
492
- ServiceNow credentials from the same env vars as the CLI.
530
+ default, response passed through verbatim, bodies never logged) and `app_publish`
531
+ (publish a scoped app to the ServiceNow Store and/or the application repository;
532
+ dry-run by default, Store credentials env-only — they never transit tool
533
+ arguments). It reads ServiceNow credentials from the same env vars as the CLI.
493
534
 
494
535
  ```bash
495
536
  npx dove-sn mcp --smoke # list the registered tools and exit
package/dist/cli.js CHANGED
@@ -83,6 +83,7 @@ const table_1 = require("./table");
83
83
  const setField_1 = require("./setField");
84
84
  const createRecord_1 = require("./createRecord");
85
85
  const invokeRest_1 = require("./invokeRest");
86
+ const publishApp_1 = require("./publishApp");
86
87
  const hostAssets_1 = require("./hostAssets");
87
88
  const flowDesigner_formatter_2 = require("./flowDesigner-formatter");
88
89
  function parseArgs(argv) {
@@ -894,6 +895,14 @@ function printHelp() {
894
895
  " Shorten or clear those values first, then re-run.\n" +
895
896
  " --element / --internal-type are REFUSED with an explanation:\n" +
896
897
  " ServiceNow silently ignores both on an existing column.\n" +
898
+ " set-table Update an EXISTING TABLE's own dictionary row (the collection row),\n" +
899
+ " into an update set, then verify against the instance\n" +
900
+ " (--table <t> --update-set <sys_id> [--audit true|false]\n" +
901
+ " [--dry-run] [--json])\n" +
902
+ " --audit turns RECORD AUDITING on/off for the whole table: with it\n" +
903
+ " true ServiceNow writes a sys_audit row per changed field on every\n" +
904
+ " insert and update — a real cost on a high-write table.\n" +
905
+ " Column attributes belong to set-column, record values to set-field.\n" +
897
906
  " invoke-rest Invoke an arbitrary authenticated REST operation (Scripted REST incl.)\n" +
898
907
  " DRY-RUN BY DEFAULT — nothing is sent without --confirm\n" +
899
908
  " (--method <GET|POST|PUT|DELETE> --path /api/<scope>/<service>/<resource>\n" +
@@ -918,6 +927,15 @@ function printHelp() {
918
927
  " [--script-input <name>] [--update-set <sys_id>] [--apply] [--json])\n" +
919
928
  " edit-flow Patch a flow/subflow (rename, description, step inputs)\n" +
920
929
  " (--sys-id <sys_id> --from-json <ops.json> [--apply] [--update-set <sys_id>] [--scope <sys_id>] [--json])\n" +
930
+ " publish-app Publish a scoped app to the ServiceNow Store and/or the company\n" +
931
+ " application repository, then poll the publish to completion.\n" +
932
+ " STORE PUBLISH IS EXTERNALLY VISIBLE on the ServiceNow Store.\n" +
933
+ " DRY-RUN BY DEFAULT — nothing is published without --confirm\n" +
934
+ " (--app <scope|sys_id|name> --version <v> --target store|repo|both\n" +
935
+ " [--dev-notes <text>] [--store-user <email>] [--timeout-ms <n>]\n" +
936
+ " [--dry-run] [--json] [--confirm])\n" +
937
+ " Store creds: SN_STORE_USERNAME/SN_STORE_PASSWORD in the --env file;\n" +
938
+ " the password is never a flag. Repo publish needs the sn_cicd role.\n" +
921
939
  " mcp Run the MCP stdio server (--smoke lists tools and exits)\n" +
922
940
  "\nGlobal flags:\n" +
923
941
  " --env <path> Load credentials from a specific .env file (also --env-file,\n" +
@@ -1108,12 +1126,12 @@ async function runAddColumn(flags) {
1108
1126
  }
1109
1127
  /** Parse a CLI boolean flag. Bare `--mandatory` means true; `--mandatory false` means
1110
1128
  * false. Anything else is rejected rather than quietly coerced to `true`. */
1111
- function parseBoolFlag(name, raw) {
1129
+ function parseBoolFlag(name, raw, verb = "set-column") {
1112
1130
  if (raw === "true")
1113
1131
  return true;
1114
1132
  if (raw === "false")
1115
1133
  return false;
1116
- throw new Error("set-column: --" + name + " must be true or false (got '" + raw + "').");
1134
+ throw new Error(verb + ": --" + name + " must be true or false (got '" + raw + "').");
1117
1135
  }
1118
1136
  /**
1119
1137
  * dove-sn set-column:
@@ -1211,6 +1229,71 @@ async function runSetColumn(flags, bare) {
1211
1229
  return 2;
1212
1230
  return 0;
1213
1231
  }
1232
+ /**
1233
+ * dove-sn set-table:
1234
+ * --table x_cadso_core_setting --audit true --update-set <sys_id>
1235
+ * [--dry-run] [--json]
1236
+ *
1237
+ * Updates the TABLE's own dictionary row (the `internal_type=collection` row, whose
1238
+ * `element` is empty) — not a column's. Column attributes belong to set-column; a
1239
+ * record's values belong to set-field.
1240
+ */
1241
+ async function runSetTable(flags, bare) {
1242
+ // Guard the string flags AND the updateSetSysId alias: a value-less string flag
1243
+ // arrives as the literal "true", so --update-set (or its alias) with nothing after
1244
+ // it would silently become the sys_id "true" and later fail as "not found".
1245
+ var stringFlags = ["table", "update-set", "updateSetSysId"];
1246
+ for (var f = 0; f < stringFlags.length; f += 1) {
1247
+ if (bare[stringFlags[f]]) {
1248
+ process.stderr.write("set-table: --" + stringFlags[f] + " needs a value (it was given none).\n");
1249
+ return 1;
1250
+ }
1251
+ }
1252
+ var table = flags.table;
1253
+ if (!table) {
1254
+ process.stderr.write("set-table: --table is required " +
1255
+ "(--update-set is required too, unless --dry-run)\n");
1256
+ return 1;
1257
+ }
1258
+ var attributes = {};
1259
+ if (flags.audit !== undefined) {
1260
+ attributes.audit = parseBoolFlag("audit", flags.audit, "set-table");
1261
+ }
1262
+ var params = {
1263
+ client: (0, client_1.createClient)({}),
1264
+ table: table,
1265
+ attributes: attributes,
1266
+ };
1267
+ var setTableUs = flags["update-set"] || flags.updateSetSysId;
1268
+ if (setTableUs)
1269
+ params.updateSetSysId = setTableUs;
1270
+ if (flags["dry-run"] === "true")
1271
+ params.dryRun = true;
1272
+ var result = await (0, table_1.setTable)(params);
1273
+ if (flags.json === "true") {
1274
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
1275
+ }
1276
+ else {
1277
+ process.stdout.write("[" +
1278
+ result.status +
1279
+ "] " +
1280
+ result.table +
1281
+ (result.verified && result.status === "applied" ? " — verified" : "") +
1282
+ (result.status === "applied" && !result.capturedInUpdateSet
1283
+ ? " — NOT CAPTURED"
1284
+ : "") +
1285
+ "\n" +
1286
+ result.note +
1287
+ "\n");
1288
+ }
1289
+ // 2 = the write landed but the instance does not reflect it (or it was not
1290
+ // captured), which must not read as success to a script.
1291
+ if (result.status === "failed")
1292
+ return 2;
1293
+ if (result.status === "applied" && !result.capturedInUpdateSet)
1294
+ return 2;
1295
+ return 0;
1296
+ }
1214
1297
  /** Parse inline `--fields "k=v, k2=v2"` into a field map. */
1215
1298
  function parseFieldsInline(input) {
1216
1299
  var out = {};
@@ -1511,6 +1594,104 @@ async function runInvokeRest(flags) {
1511
1594
  }
1512
1595
  return 0;
1513
1596
  }
1597
+ /**
1598
+ * dove-sn publish-app:
1599
+ * --app <scope|sys_id|name> Required. The sys_app to publish.
1600
+ * --version <v> Required. Version to publish (e.g. 6.0.20260716).
1601
+ * --target store|repo|both Required. STORE PUBLISH IS EXTERNALLY VISIBLE.
1602
+ * [--dev-notes <text>] Optional developer notes.
1603
+ * [--store-user <email>] Store account email (else SN_STORE_USERNAME).
1604
+ * The password comes ONLY from SN_STORE_PASSWORD —
1605
+ * there is no flag for it, ever.
1606
+ * [--timeout-ms <n>] Progress-poll budget (default 120000).
1607
+ * [--dry-run] [--json] [--confirm]
1608
+ *
1609
+ * DRY-RUN unless --confirm: without it the resolved plan is printed and the
1610
+ * command exits 1 (a deliberate refusal, not success). --target both publishes
1611
+ * store then repo sequentially with the same version and short-circuits if the
1612
+ * store leg fails; --json emits an array of per-target results.
1613
+ * Exit codes: 0 published/dry-run, 1 bad args/unconfirmed, 2 failed/timeout.
1614
+ */
1615
+ async function runPublishApp(flags) {
1616
+ var app = flags.app;
1617
+ var version = flags.version;
1618
+ var target = flags.target;
1619
+ if (!app || !version || !target) {
1620
+ process.stderr.write("publish-app: --app, --version and --target store|repo|both are required\n");
1621
+ return 1;
1622
+ }
1623
+ if (target !== "store" && target !== "repo" && target !== "both") {
1624
+ process.stderr.write("publish-app: --target must be store, repo or both (got '" +
1625
+ target +
1626
+ "')\n");
1627
+ return 1;
1628
+ }
1629
+ var targets = target === "both" ? ["store", "repo"] : [target];
1630
+ var dryRun = flags["dry-run"] === "true";
1631
+ var confirmed = flags.confirm === "true";
1632
+ var client = (0, client_1.createClient)({});
1633
+ var results = [];
1634
+ var exitCode = 0;
1635
+ for (var i = 0; i < targets.length; i += 1) {
1636
+ var params = {
1637
+ client: client,
1638
+ app: app,
1639
+ version: version,
1640
+ target: targets[i],
1641
+ confirm: confirmed,
1642
+ dryRun: dryRun,
1643
+ };
1644
+ if (flags["dev-notes"])
1645
+ params.devNotes = flags["dev-notes"];
1646
+ if (flags["store-user"])
1647
+ params.storeUsername = flags["store-user"];
1648
+ if (flags["timeout-ms"]) {
1649
+ // A NaN timeout would make the poll-loop budget check always false —
1650
+ // an infinite loop. Validate here, exit 1 on garbage.
1651
+ var timeoutMs = Number(flags["timeout-ms"]);
1652
+ if (!Number.isInteger(timeoutMs) || timeoutMs <= 0) {
1653
+ process.stderr.write("publish-app: --timeout-ms must be a positive integer (got '" +
1654
+ flags["timeout-ms"] +
1655
+ "')\n");
1656
+ return 1;
1657
+ }
1658
+ params.timeoutMs = timeoutMs;
1659
+ }
1660
+ var result = await (0, publishApp_1.publishApp)(params);
1661
+ results.push(result);
1662
+ if (flags.json !== "true") {
1663
+ process.stdout.write("[" +
1664
+ result.status +
1665
+ "] " +
1666
+ result.target +
1667
+ " — " +
1668
+ result.appName +
1669
+ " (" +
1670
+ result.appScope +
1671
+ ") v" +
1672
+ result.version +
1673
+ (result.appLink ? " — " + result.appLink : "") +
1674
+ (result.updateSetSysId
1675
+ ? " — update set " + result.updateSetSysId
1676
+ : "") +
1677
+ "\n" +
1678
+ result.note +
1679
+ "\n");
1680
+ }
1681
+ if (result.status === "failed" || result.status === "timeout") {
1682
+ exitCode = 2;
1683
+ break; // Short-circuit: never repo-publish after a failed store leg.
1684
+ }
1685
+ }
1686
+ if (flags.json === "true") {
1687
+ process.stdout.write(JSON.stringify(results, null, 2) + "\n");
1688
+ }
1689
+ if (exitCode === 0 && !dryRun && !confirmed) {
1690
+ process.stderr.write("publish-app: refusing to publish without --confirm (the plan above is a dry-run).\n");
1691
+ return 1;
1692
+ }
1693
+ return exitCode;
1694
+ }
1514
1695
  async function main() {
1515
1696
  var parsed = parseArgs(process.argv.slice(2));
1516
1697
  // Load credentials before any command runs. `--env`/`--env-file` (or the
@@ -1551,6 +1732,9 @@ async function main() {
1551
1732
  if (parsed.command === "set-column") {
1552
1733
  return await runSetColumn(parsed.flags, parsed.bare);
1553
1734
  }
1735
+ if (parsed.command === "set-table") {
1736
+ return await runSetTable(parsed.flags, parsed.bare);
1737
+ }
1554
1738
  if (parsed.command === "set-field") {
1555
1739
  return await runSetField(parsed.flags);
1556
1740
  }
@@ -1585,6 +1769,9 @@ async function main() {
1585
1769
  await runSetRelatedLists(parsed.flags);
1586
1770
  return 0;
1587
1771
  }
1772
+ if (parsed.command === "publish-app") {
1773
+ return await runPublishApp(parsed.flags);
1774
+ }
1588
1775
  if (parsed.command === "mcp") {
1589
1776
  return await runMcp(parsed.flags);
1590
1777
  }
package/dist/index.d.ts CHANGED
@@ -19,11 +19,13 @@ export { sincPlugin } from "./plugin";
19
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
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
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";
22
+ export { createTable, projectTableGraph, buildColumnXml, normalizeColumns, resolveType, applyTableSaveOverlay, defaultAccessFlags, TYPE_MAP, DEFAULT_SUPER_CLASS, DEFAULT_SAVE_ACTION, addColumn, deriveElement, setColumn, resolveAttributes, toStoredValue, setTable, resolveTableAttributes, } from "./table";
23
+ export type { CreateTableParams, CreateTableResult, AddColumnParams, AddColumnResult, SetColumnParams, SetColumnResult, ColumnAttributes, AttributeChange, SetTableParams, SetTableResult, TableAttributes, TableGraph, NormalizedColumn, ColumnSpec, AccessFlags, OverlaySpec, } from "./table";
24
24
  export { setField } from "./setField";
25
25
  export { createRecord } from "./createRecord";
26
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";
30
+ export { publishApp, buildStartFields, parseXmlAnswer, parseProgressTree, classifyProgress, flattenSteps, harvestProgressResults, parseCicdPublishResponse, parseCicdProgress, DEFAULT_PUBLISH_TIMEOUT_MS, PUBLISH_POLL_DELAYS_MS, } from "./publishApp";
31
+ export type { PublishAppParams, PublishAppResult, PublishTarget, PublishStep, ProgressNode, CicdProgress, PublishTransport, } from "./publishApp";
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.toStoredValue = exports.resolveAttributes = exports.setColumn = exports.deriveElement = void 0;
10
+ exports.PUBLISH_POLL_DELAYS_MS = exports.DEFAULT_PUBLISH_TIMEOUT_MS = exports.parseCicdProgress = exports.parseCicdPublishResponse = exports.harvestProgressResults = exports.flattenSteps = exports.classifyProgress = exports.parseProgressTree = exports.parseXmlAnswer = exports.buildStartFields = exports.publishApp = exports.INVOKE_REST_METHODS = exports.invokeRest = exports.createRecord = exports.setField = exports.resolveTableAttributes = exports.setTable = 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");
@@ -75,6 +75,8 @@ Object.defineProperty(exports, "deriveElement", { enumerable: true, get: functio
75
75
  Object.defineProperty(exports, "setColumn", { enumerable: true, get: function () { return table_1.setColumn; } });
76
76
  Object.defineProperty(exports, "resolveAttributes", { enumerable: true, get: function () { return table_1.resolveAttributes; } });
77
77
  Object.defineProperty(exports, "toStoredValue", { enumerable: true, get: function () { return table_1.toStoredValue; } });
78
+ Object.defineProperty(exports, "setTable", { enumerable: true, get: function () { return table_1.setTable; } });
79
+ Object.defineProperty(exports, "resolveTableAttributes", { enumerable: true, get: function () { return table_1.resolveTableAttributes; } });
78
80
  var setField_1 = require("./setField");
79
81
  Object.defineProperty(exports, "setField", { enumerable: true, get: function () { return setField_1.setField; } });
80
82
  var createRecord_1 = require("./createRecord");
@@ -82,3 +84,15 @@ Object.defineProperty(exports, "createRecord", { enumerable: true, get: function
82
84
  var invokeRest_1 = require("./invokeRest");
83
85
  Object.defineProperty(exports, "invokeRest", { enumerable: true, get: function () { return invokeRest_1.invokeRest; } });
84
86
  Object.defineProperty(exports, "INVOKE_REST_METHODS", { enumerable: true, get: function () { return invokeRest_1.INVOKE_REST_METHODS; } });
87
+ var publishApp_1 = require("./publishApp");
88
+ Object.defineProperty(exports, "publishApp", { enumerable: true, get: function () { return publishApp_1.publishApp; } });
89
+ Object.defineProperty(exports, "buildStartFields", { enumerable: true, get: function () { return publishApp_1.buildStartFields; } });
90
+ Object.defineProperty(exports, "parseXmlAnswer", { enumerable: true, get: function () { return publishApp_1.parseXmlAnswer; } });
91
+ Object.defineProperty(exports, "parseProgressTree", { enumerable: true, get: function () { return publishApp_1.parseProgressTree; } });
92
+ Object.defineProperty(exports, "classifyProgress", { enumerable: true, get: function () { return publishApp_1.classifyProgress; } });
93
+ Object.defineProperty(exports, "flattenSteps", { enumerable: true, get: function () { return publishApp_1.flattenSteps; } });
94
+ Object.defineProperty(exports, "harvestProgressResults", { enumerable: true, get: function () { return publishApp_1.harvestProgressResults; } });
95
+ Object.defineProperty(exports, "parseCicdPublishResponse", { enumerable: true, get: function () { return publishApp_1.parseCicdPublishResponse; } });
96
+ Object.defineProperty(exports, "parseCicdProgress", { enumerable: true, get: function () { return publishApp_1.parseCicdProgress; } });
97
+ Object.defineProperty(exports, "DEFAULT_PUBLISH_TIMEOUT_MS", { enumerable: true, get: function () { return publishApp_1.DEFAULT_PUBLISH_TIMEOUT_MS; } });
98
+ Object.defineProperty(exports, "PUBLISH_POLL_DELAYS_MS", { enumerable: true, get: function () { return publishApp_1.PUBLISH_POLL_DELAYS_MS; } });
@@ -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_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_table", "set_field", "create_record", "host_assets", "invoke_rest", "app_publish"];
14
14
  export type ToolName = (typeof TOOL_NAMES)[number];
15
15
  export interface RegistryDeps {
16
16
  /** Optional client injection for tests; defaults to createClient({}). */
@@ -31,6 +31,7 @@ const hostAssets_1 = require("../hostAssets");
31
31
  const setField_1 = require("../setField");
32
32
  const createRecord_1 = require("../createRecord");
33
33
  const invokeRest_1 = require("../invokeRest");
34
+ const publishApp_1 = require("../publishApp");
34
35
  const schemas_1 = require("./schemas");
35
36
  exports.TOOL_NAMES = [
36
37
  "create_view",
@@ -49,10 +50,12 @@ exports.TOOL_NAMES = [
49
50
  "create_table",
50
51
  "add_column",
51
52
  "set_column",
53
+ "set_table",
52
54
  "set_field",
53
55
  "create_record",
54
56
  "host_assets",
55
57
  "invoke_rest",
58
+ "app_publish",
56
59
  ];
57
60
  // Annotation presets (READ_ONLY / WRITE_ADDITIVE_IDEMPOTENT / WRITE_CREATE /
58
61
  // WRITE_OVERWRITE / WRITE_EXECUTE) come from @tenonhq/dovetail-mcp-kit.
@@ -391,6 +394,42 @@ function buildDescriptors(deps = {}) {
391
394
  });
392
395
  },
393
396
  },
397
+ {
398
+ name: "set_table",
399
+ annotations: dovetail_mcp_kit_1.WRITE_OVERWRITE,
400
+ description: "Update an EXISTING ServiceNow TABLE's own dictionary attributes — the row where " +
401
+ "internal_type=collection and element is EMPTY — captured into a named update set, then " +
402
+ "READ BACK from the instance to verify. Completes the trio: set_field changes a RECORD's " +
403
+ "value, set_column changes a COLUMN's definition, set_table changes the TABLE's own. " +
404
+ "Currently settable: audit. audit=true turns RECORD AUDITING on for the whole table — " +
405
+ "ServiceNow then writes a sys_audit row per changed field on every insert and update, " +
406
+ "which is a real storage and write cost on a high-volume table, so weigh it before " +
407
+ "enabling. Attributes are a closed set, never an open field map; a COLUMN attribute " +
408
+ "(label/mandatory/default/maxLength/readOnly/element/internalType) is refused by name and " +
409
+ "redirected to set_column. When the requested value already matches, nothing is written " +
410
+ "and the status is 'unchanged' — note that an identical-value write also captures " +
411
+ "NOTHING, so there is no update-set row to promote. dryRun:true diffs against the " +
412
+ "instance and writes nothing.",
413
+ shape: schemas_1.setTableSchema.shape,
414
+ handler: async function (args) {
415
+ var p = schemas_1.setTableSchema.parse(args);
416
+ // Same live-path guard as set_column: the schema leaves updateSetSysId
417
+ // optional so dry-run works without one.
418
+ if (p.dryRun !== true &&
419
+ (!p.updateSetSysId || !p.updateSetSysId.trim())) {
420
+ throw new Error("set_table: updateSetSysId is required on the live path so the schema " +
421
+ "change is captured in a known update set — set dryRun:true to plan " +
422
+ "without one.");
423
+ }
424
+ return (0, table_1.setTable)({
425
+ client: client(),
426
+ table: p.table,
427
+ attributes: p.attributes,
428
+ updateSetSysId: p.updateSetSysId,
429
+ dryRun: p.dryRun,
430
+ });
431
+ },
432
+ },
394
433
  {
395
434
  name: "set_field",
396
435
  annotations: dovetail_mcp_kit_1.WRITE_OVERWRITE,
@@ -491,6 +530,39 @@ function buildDescriptors(deps = {}) {
491
530
  return (0, invokeRest_1.invokeRest)(params);
492
531
  },
493
532
  },
533
+ {
534
+ name: "app_publish",
535
+ annotations: dovetail_mcp_kit_1.WRITE_EXECUTE,
536
+ description: "Publish a scoped ServiceNow application to ONE target per call — the ServiceNow Store " +
537
+ "(target 'store') or the company Application Repository (target 'repo'); call twice to " +
538
+ "publish to both — then poll the publish to completion. STORE PUBLISH IS EXTERNALLY " +
539
+ "VISIBLE on the ServiceNow Store — treat it as a release. DRY-RUN BY DEFAULT: without " +
540
+ "confirm:true the resolved plan (app, current version, target) is returned and nothing is " +
541
+ "published. target 'store' replays the sys_app form's upload flow over a form-login session " +
542
+ "and requires SN_STORE_USERNAME/SN_STORE_PASSWORD in the server's env file — credentials " +
543
+ "never transit tool arguments. target 'repo' uses the supported CI/CD REST API " +
544
+ "(/api/sn_cicd/app_repo/publish) and requires the sn_cicd role. app is a scope name, " +
545
+ "sys_app sys_id, or app name; version must be above the currently published version. The " +
546
+ "result carries the progress-tracker id, per-step states, the Store appLink, and the " +
547
+ "publish's update-set sys_id where the instance reports one.",
548
+ shape: schemas_1.publishAppSchema.shape,
549
+ handler: async function (args) {
550
+ var p = schemas_1.publishAppSchema.parse(args);
551
+ var params = {
552
+ app: p.app,
553
+ version: p.version,
554
+ target: p.target,
555
+ devNotes: p.devNotes,
556
+ confirm: p.confirm,
557
+ dryRun: p.dryRun,
558
+ timeoutMs: p.timeoutMs,
559
+ };
560
+ if (deps.client) {
561
+ params.client = deps.client;
562
+ }
563
+ return (0, publishApp_1.publishApp)(params);
564
+ },
565
+ },
494
566
  ];
495
567
  }
496
568
  function registerAllTools(server, deps = {}) {
@@ -914,6 +914,43 @@ export declare var setColumnSchema: z.ZodObject<{
914
914
  dryRun?: boolean | undefined;
915
915
  updateSetSysId?: string | undefined;
916
916
  }>;
917
+ export declare var tableAttributesSchema: z.ZodObject<{
918
+ audit: z.ZodOptional<z.ZodBoolean>;
919
+ }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
920
+ audit: z.ZodOptional<z.ZodBoolean>;
921
+ }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
922
+ audit: z.ZodOptional<z.ZodBoolean>;
923
+ }, z.ZodTypeAny, "passthrough">>;
924
+ export declare var setTableSchema: z.ZodObject<{
925
+ table: z.ZodString;
926
+ attributes: z.ZodObject<{
927
+ audit: z.ZodOptional<z.ZodBoolean>;
928
+ }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
929
+ audit: z.ZodOptional<z.ZodBoolean>;
930
+ }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
931
+ audit: z.ZodOptional<z.ZodBoolean>;
932
+ }, z.ZodTypeAny, "passthrough">>;
933
+ updateSetSysId: z.ZodOptional<z.ZodString>;
934
+ dryRun: z.ZodOptional<z.ZodBoolean>;
935
+ }, "strip", z.ZodTypeAny, {
936
+ table: string;
937
+ attributes: {
938
+ audit?: boolean | undefined;
939
+ } & {
940
+ [k: string]: unknown;
941
+ };
942
+ dryRun?: boolean | undefined;
943
+ updateSetSysId?: string | undefined;
944
+ }, {
945
+ table: string;
946
+ attributes: {
947
+ audit?: boolean | undefined;
948
+ } & {
949
+ [k: string]: unknown;
950
+ };
951
+ dryRun?: boolean | undefined;
952
+ updateSetSysId?: string | undefined;
953
+ }>;
917
954
  export declare var setFieldSchema: z.ZodObject<{
918
955
  table: z.ZodString;
919
956
  sysId: z.ZodOptional<z.ZodString>;
@@ -958,6 +995,31 @@ export declare var createRecordSchema: z.ZodObject<{
958
995
  dryRun?: boolean | undefined;
959
996
  ifAbsentQuery?: string | undefined;
960
997
  }>;
998
+ export declare var publishAppSchema: z.ZodObject<{
999
+ app: z.ZodString;
1000
+ version: z.ZodString;
1001
+ devNotes: z.ZodOptional<z.ZodString>;
1002
+ target: z.ZodUnion<[z.ZodLiteral<"store">, z.ZodLiteral<"repo">]>;
1003
+ confirm: z.ZodOptional<z.ZodBoolean>;
1004
+ dryRun: z.ZodOptional<z.ZodBoolean>;
1005
+ timeoutMs: z.ZodOptional<z.ZodNumber>;
1006
+ }, "strip", z.ZodTypeAny, {
1007
+ target: "store" | "repo";
1008
+ version: string;
1009
+ app: string;
1010
+ confirm?: boolean | undefined;
1011
+ dryRun?: boolean | undefined;
1012
+ devNotes?: string | undefined;
1013
+ timeoutMs?: number | undefined;
1014
+ }, {
1015
+ target: "store" | "repo";
1016
+ version: string;
1017
+ app: string;
1018
+ confirm?: boolean | undefined;
1019
+ dryRun?: boolean | undefined;
1020
+ devNotes?: string | undefined;
1021
+ timeoutMs?: number | undefined;
1022
+ }>;
961
1023
  export declare var invokeRestSchema: z.ZodObject<{
962
1024
  method: z.ZodEffects<z.ZodEnum<["GET", "POST", "PUT", "DELETE"]>, "GET" | "DELETE" | "POST" | "PUT", unknown>;
963
1025
  path: z.ZodString;
@@ -4,7 +4,7 @@
4
4
  * own file so registry.ts stays focused on wiring.
5
5
  */
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.invokeRestSchema = exports.createRecordSchema = exports.setFieldSchema = exports.setColumnSchema = exports.columnAttributesSchema = exports.addColumnSchema = exports.createTableSchema = exports.columnSpecSchema = exports.hostAssetsSchema = exports.editFlowSchema = exports.editActionSchema = exports.stepInputPatchSchema = exports.testFlowSchema = exports.createFlowSchema = exports.copyFlowSchema = exports.publishFlowSchema = exports.viewActionSchema = exports.viewFlowSchema = exports.addChoicesToFieldSchema = exports.choiceValueSchema = exports.setRelatedListsSchema = exports.setFormLayoutSchema = exports.formSectionSchema = exports.setListLayoutSchema = exports.createViewSchema = void 0;
7
+ exports.invokeRestSchema = exports.publishAppSchema = exports.createRecordSchema = exports.setFieldSchema = exports.setTableSchema = exports.tableAttributesSchema = exports.setColumnSchema = exports.columnAttributesSchema = exports.addColumnSchema = exports.createTableSchema = exports.columnSpecSchema = exports.hostAssetsSchema = exports.editFlowSchema = exports.editActionSchema = exports.stepInputPatchSchema = exports.testFlowSchema = exports.createFlowSchema = exports.copyFlowSchema = exports.publishFlowSchema = exports.viewActionSchema = exports.viewFlowSchema = exports.addChoicesToFieldSchema = exports.choiceValueSchema = exports.setRelatedListsSchema = exports.setFormLayoutSchema = exports.formSectionSchema = exports.setListLayoutSchema = exports.createViewSchema = void 0;
8
8
  const zod_1 = require("zod");
9
9
  exports.createViewSchema = zod_1.z.object({
10
10
  name: zod_1.z.string().min(1),
@@ -241,6 +241,28 @@ exports.setColumnSchema = zod_1.z.object({
241
241
  updateSetSysId: zod_1.z.string().min(1).optional(),
242
242
  dryRun: zod_1.z.boolean().optional(),
243
243
  });
244
+ // set-table takes a CLOSED attribute set for the same reason set-column does: an
245
+ // unbounded write to sys_dictionary lets a caller silently corrupt schema. These are
246
+ // the TABLE's own attributes (the internal_type=collection row), not a column's.
247
+ //
248
+ // .passthrough() is deliberate: z.object() strips unknown keys by default, which would
249
+ // silently drop a column attribute (or a typo) BEFORE resolveTableAttributes could see
250
+ // it — so { audit:true, label:"x" } would succeed, quietly discarding label. Passing
251
+ // unknown keys through lets the core allowlist reject them (a column attribute earns the
252
+ // "use set-column" redirect; anything else, "not a settable table attribute").
253
+ exports.tableAttributesSchema = zod_1.z
254
+ .object({
255
+ audit: zod_1.z.boolean().optional(),
256
+ })
257
+ .passthrough();
258
+ exports.setTableSchema = zod_1.z.object({
259
+ table: zod_1.z.string().min(1),
260
+ attributes: exports.tableAttributesSchema,
261
+ // Optional because dryRun works without one; the live-path requirement is
262
+ // enforced at the tool boundary (registry.ts), matching set_column.
263
+ updateSetSysId: zod_1.z.string().min(1).optional(),
264
+ dryRun: zod_1.z.boolean().optional(),
265
+ });
244
266
  // Data-record write verbs. Kept as plain z.object (no .refine wrapper) so
245
267
  // registry.ts can read `.shape`; the deeper rules — one of sysId/query, at
246
268
  // least one field, the schema-table refusal — are enforced by the core
@@ -261,6 +283,19 @@ exports.createRecordSchema = zod_1.z.object({
261
283
  ifAbsentQuery: zod_1.z.string().optional(),
262
284
  dryRun: zod_1.z.boolean().optional(),
263
285
  });
286
+ // app_publish: publish a scoped app to the ServiceNow Store or the company
287
+ // application repository. Deliberately NO credential fields — the Store
288
+ // account resolves from SN_STORE_USERNAME/SN_STORE_PASSWORD inside the verb,
289
+ // so credentials never transit MCP arguments or telemetry.
290
+ exports.publishAppSchema = zod_1.z.object({
291
+ app: zod_1.z.string().min(1),
292
+ version: zod_1.z.string().min(1),
293
+ devNotes: zod_1.z.string().optional(),
294
+ target: zod_1.z.union([zod_1.z.literal("store"), zod_1.z.literal("repo")]),
295
+ confirm: zod_1.z.boolean().optional(),
296
+ dryRun: zod_1.z.boolean().optional(),
297
+ timeoutMs: zod_1.z.number().int().positive().optional(),
298
+ });
264
299
  // invoke_rest: transport primitive for arbitrary authenticated REST operations
265
300
  // (Scripted REST included). The dry-run-unless-confirm gate lives in invokeRest
266
301
  // itself; the regex here rejects absolute URLs and non-/api/ paths early.