@tenonhq/dovetail-core 0.0.85

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.
Files changed (78) hide show
  1. package/README.md +6 -0
  2. package/dist/FileLogger.js +207 -0
  3. package/dist/FileUtils.js +387 -0
  4. package/dist/Logger.js +88 -0
  5. package/dist/MultiScopeWatcher.js +615 -0
  6. package/dist/PluginManager.js +125 -0
  7. package/dist/_defaultOptionsOLD.js +44 -0
  8. package/dist/allScopesCommands.js +418 -0
  9. package/dist/appUtils.js +929 -0
  10. package/dist/benchmark.js +104 -0
  11. package/dist/bootstrap.js +62 -0
  12. package/dist/claudeCommand.js +60 -0
  13. package/dist/clickupCommands.js +472 -0
  14. package/dist/clickupPushHelper.js +33 -0
  15. package/dist/commander.js +466 -0
  16. package/dist/commands.js +378 -0
  17. package/dist/config.js +697 -0
  18. package/dist/constants.js +15 -0
  19. package/dist/createRecordCommand.js +314 -0
  20. package/dist/dashboardCommand.js +38 -0
  21. package/dist/defaultOptions.js +14 -0
  22. package/dist/deleteRecordCommand.js +282 -0
  23. package/dist/flowDesigner/values.js +55 -0
  24. package/dist/genericUtils.js +189 -0
  25. package/dist/gitUtils.js +99 -0
  26. package/dist/index.js +35 -0
  27. package/dist/initSystem/corePlugin.js +344 -0
  28. package/dist/initSystem/discovery.js +107 -0
  29. package/dist/initSystem/orchestrator.js +408 -0
  30. package/dist/logMessages.js +82 -0
  31. package/dist/loginCommand.js +20 -0
  32. package/dist/migrateCommand.js +123 -0
  33. package/dist/projectFiles.js +32 -0
  34. package/dist/recentEdits.js +66 -0
  35. package/dist/schemaCommand.js +85 -0
  36. package/dist/snClient.js +519 -0
  37. package/dist/tests/benchmarkRefresh.test.js +265 -0
  38. package/dist/tests/clickupCommands.test.js +384 -0
  39. package/dist/tests/clickupPushHelper.test.js +124 -0
  40. package/dist/tests/configFallback.test.js +68 -0
  41. package/dist/tests/cred.test.js +59 -0
  42. package/dist/tests/discovery.test.js +67 -0
  43. package/dist/tests/ensureUpdateSetWarnings.test.js +218 -0
  44. package/dist/tests/errorLogLevels.test.js +273 -0
  45. package/dist/tests/example.test.js +6 -0
  46. package/dist/tests/fileContextSkipReason.test.js +116 -0
  47. package/dist/tests/globalDebounce.test.js +308 -0
  48. package/dist/tests/migrateCommand.test.js +113 -0
  49. package/dist/tests/multi-scope-watcher.test.js +558 -0
  50. package/dist/tests/orchestrator.test.js +23 -0
  51. package/dist/tests/pushFiles.test.js +248 -0
  52. package/dist/tests/rateLimitCoordination.test.js +271 -0
  53. package/dist/tests/resolveConfigReadOnly.test.js +67 -0
  54. package/dist/tests/retryOnHttpErr.test.js +154 -0
  55. package/dist/tests/scopeCaching.test.js +115 -0
  56. package/dist/tests/serializeUpdateSetConfig.test.js +325 -0
  57. package/dist/tests/snClient.fallback.test.js +120 -0
  58. package/dist/tests/syncManifestRefresh.test.js +287 -0
  59. package/dist/tests/syncManifestWhitelist.test.js +192 -0
  60. package/dist/tests/taskClear.test.js +170 -0
  61. package/dist/tests/taskStaleness.test.js +220 -0
  62. package/dist/tests/unwrapSNResponseError.test.js +145 -0
  63. package/dist/tests/v2Values.test.js +79 -0
  64. package/dist/tests/validateTaskId.test.js +304 -0
  65. package/dist/tests/verifyUpdateSetSwitch.test.js +277 -0
  66. package/dist/tests/writeEnvVar.test.js +119 -0
  67. package/dist/updateSetCommands.js +702 -0
  68. package/dist/wizard.js +40 -0
  69. package/package.json +69 -0
  70. package/skills/dove-configure-pipeline.md +158 -0
  71. package/skills/dove-create-plugin.md +174 -0
  72. package/skills/dove-create-record.md +135 -0
  73. package/skills/dove-debug-build.md +117 -0
  74. package/skills/dove-delete-record.md +104 -0
  75. package/skills/dove-manage-tables.md +174 -0
  76. package/skills/dove-manage-update-sets.md +88 -0
  77. package/skills/dove-setup-project.md +154 -0
  78. package/skills/dove-troubleshoot-sync.md +113 -0
@@ -0,0 +1,702 @@
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
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.showCurrentScopeCommand = showCurrentScopeCommand;
40
+ exports.changeScopeCommand = changeScopeCommand;
41
+ exports.showCurrentUpdateSetCommand = showCurrentUpdateSetCommand;
42
+ exports.createUpdateSetCommand = createUpdateSetCommand;
43
+ exports.switchUpdateSetCommand = switchUpdateSetCommand;
44
+ exports.listUpdateSetsCommand = listUpdateSetsCommand;
45
+ const inquirer_1 = __importDefault(require("inquirer"));
46
+ const snClient_1 = require("./snClient");
47
+ const Logger_1 = require("./Logger");
48
+ const commands_1 = require("./commands");
49
+ const chalk_1 = __importDefault(require("chalk"));
50
+ const dovetail_clickup_1 = require("@tenonhq/dovetail-clickup");
51
+ const clickupCommands_1 = require("./clickupCommands");
52
+ /**
53
+ * Shows the current scope
54
+ */
55
+ async function showCurrentScopeCommand(args) {
56
+ (0, commands_1.setLogLevel)(args);
57
+ try {
58
+ const client = (0, snClient_1.defaultClient)();
59
+ const scopeObj = await (0, snClient_1.unwrapSNResponse)(client.getCurrentScope());
60
+ if (scopeObj && scopeObj.scope) {
61
+ Logger_1.logger.info(chalk_1.default.bold("\nCurrent Scope:"));
62
+ Logger_1.logger.info("─".repeat(40));
63
+ Logger_1.logger.info(chalk_1.default.green(`► ${scopeObj.displayName || scopeObj.scope}`));
64
+ Logger_1.logger.info(` Scope ID: ${scopeObj.scope}`);
65
+ Logger_1.logger.info(` Sys ID: ${scopeObj.sys_id}`);
66
+ // Also show current update set for this scope
67
+ const updateSet = await getCurrentUpdateSetDetails();
68
+ if (updateSet) {
69
+ Logger_1.logger.info(` Update Set: ${updateSet.name}`);
70
+ }
71
+ }
72
+ else {
73
+ Logger_1.logger.warn("Unable to retrieve current scope information");
74
+ }
75
+ }
76
+ catch (e) {
77
+ Logger_1.logger.error("Failed to get current scope");
78
+ if (e instanceof Error)
79
+ Logger_1.logger.error(e.message);
80
+ throw e;
81
+ }
82
+ }
83
+ /**
84
+ * Changes the current scope
85
+ */
86
+ async function changeScopeCommand(args) {
87
+ (0, commands_1.setLogLevel)(args);
88
+ try {
89
+ const client = (0, snClient_1.defaultClient)();
90
+ // Get scope from args or prompt
91
+ let scope = args.scope;
92
+ if (!scope) {
93
+ const inquirer = (await Promise.resolve().then(() => __importStar(require("inquirer")))).default;
94
+ const answers = await inquirer.prompt([{
95
+ type: "input",
96
+ name: "scope",
97
+ message: "Enter scope to switch to (e.g., x_cadso_core):",
98
+ validate: (input) => {
99
+ if (!input || input.trim() === "") {
100
+ return "Scope is required";
101
+ }
102
+ return true;
103
+ }
104
+ }]);
105
+ scope = answers.scope;
106
+ }
107
+ Logger_1.logger.info(`Switching to scope: ${scope}`);
108
+ // Call the changeScope API
109
+ const response = await client.changeScope(scope);
110
+ let result = await response.data;
111
+ // Handle wrapped response
112
+ if (result && result.result) {
113
+ result = result.result;
114
+ }
115
+ if (result && result.message === 'Success') {
116
+ Logger_1.logger.success(chalk_1.default.green(`✓ Successfully switched to scope: ${scope}`));
117
+ // Try to get and show the current update set for this scope
118
+ const updateSet = await getCurrentUpdateSetDetails(scope);
119
+ if (updateSet) {
120
+ Logger_1.logger.info(`Current update set: ${updateSet.name}`);
121
+ }
122
+ }
123
+ else if (result && result.error) {
124
+ throw new Error(`Failed to switch scope: ${result.error}`);
125
+ }
126
+ else {
127
+ throw new Error("Failed to switch scope - unexpected response");
128
+ }
129
+ }
130
+ catch (e) {
131
+ Logger_1.logger.error("Failed to change scope");
132
+ if (e instanceof Error) {
133
+ Logger_1.logger.error(e.message);
134
+ if (e.response) {
135
+ Logger_1.logger.error(`Response status: ${e.response.status}`);
136
+ Logger_1.logger.error(`Response data: ${JSON.stringify(e.response.data)}`);
137
+ }
138
+ }
139
+ throw e;
140
+ }
141
+ }
142
+ /**
143
+ * Shows the current active update set
144
+ */
145
+ async function showCurrentUpdateSetCommand(args) {
146
+ (0, commands_1.setLogLevel)(args);
147
+ try {
148
+ const client = (0, snClient_1.defaultClient)();
149
+ const response = await client.getCurrentUpdateSet(args.scope);
150
+ let result = await response.data;
151
+ // Handle wrapped response
152
+ if (result && result.result) {
153
+ result = result.result;
154
+ }
155
+ Logger_1.logger.debug(`API Response: ${JSON.stringify(result)}`);
156
+ if (result && result.sysId) {
157
+ Logger_1.logger.info(chalk_1.default.bold("\nCurrent Update Set:"));
158
+ Logger_1.logger.info("─".repeat(40));
159
+ Logger_1.logger.info(chalk_1.default.green(`► ${result.name || 'Unknown'}`));
160
+ Logger_1.logger.info(` ID: ${result.sysId}`);
161
+ if (args.scope) {
162
+ Logger_1.logger.info(` Scope: ${args.scope}`);
163
+ }
164
+ }
165
+ else if (result && result.error) {
166
+ Logger_1.logger.warn(`Error from API: ${result.error}`);
167
+ }
168
+ else {
169
+ Logger_1.logger.warn("No update set is currently active or unable to retrieve update set information");
170
+ }
171
+ }
172
+ catch (e) {
173
+ Logger_1.logger.error("Failed to get current update set");
174
+ if (e instanceof Error) {
175
+ Logger_1.logger.error(e.message);
176
+ if (e.response) {
177
+ Logger_1.logger.error(`Response status: ${e.response.status}`);
178
+ Logger_1.logger.error(`Response data: ${JSON.stringify(e.response.data)}`);
179
+ }
180
+ }
181
+ throw e;
182
+ }
183
+ }
184
+ /**
185
+ * Creates a new update set with the given name and switches to it
186
+ */
187
+ async function createUpdateSetCommand(args) {
188
+ (0, commands_1.setLogLevel)(args);
189
+ try {
190
+ const client = (0, snClient_1.defaultClient)();
191
+ // If --clickup is provided, fetch task data and generate name/description
192
+ if (args.clickup && !args.name) {
193
+ var clickupResult = await resolveClickUpTaskForUpdateSet(args.clickup);
194
+ args.name = clickupResult.name;
195
+ if (!args.description) {
196
+ args.description = clickupResult.description;
197
+ }
198
+ args._clickupTaskId = clickupResult.taskId;
199
+ }
200
+ // Get update set details from user
201
+ const { name, description, scope } = await promptForUpdateSetDetails(args);
202
+ let scopeSysId;
203
+ if (scope) {
204
+ // Get scope sys_id if scope name provided
205
+ const scopeResult = await (0, snClient_1.unwrapSNResponse)(client.getScopeId(scope));
206
+ if (scopeResult.length === 0) {
207
+ throw new Error(`Scope "${scope}" not found`);
208
+ }
209
+ scopeSysId = scopeResult[0].sys_id;
210
+ // Switch to the target scope first
211
+ Logger_1.logger.info(`Switching to scope: ${scope}`);
212
+ await switchToScope(scopeSysId, scope);
213
+ }
214
+ // Create the update set in the correct scope
215
+ Logger_1.logger.info(`Creating update set: ${name}`);
216
+ const createResponse = client.createUpdateSet(name, scopeSysId, description);
217
+ const createResult = await (0, snClient_1.unwrapSNResponse)(createResponse);
218
+ const updateSetSysId = createResult.sys_id;
219
+ // Switch to the new update set
220
+ Logger_1.logger.info(`Activating update set: ${name}`);
221
+ try {
222
+ await switchToUpdateSet(updateSetSysId, name, scope);
223
+ Logger_1.logger.info(chalk_1.default.green(`✓ Update set "${name}" created and activated`));
224
+ }
225
+ catch (switchError) {
226
+ Logger_1.logger.warn(`Update set "${name}" created but could not be activated automatically`);
227
+ Logger_1.logger.info(`You can manually switch to it using: npx dove switchUpdateSet --name "${name}"`);
228
+ if (switchError instanceof Error) {
229
+ Logger_1.logger.warn(`Switch error: ${switchError.message}`);
230
+ }
231
+ }
232
+ Logger_1.logger.info(`Update Set ID: ${updateSetSysId}`);
233
+ if (scope) {
234
+ Logger_1.logger.info(`Scope: ${scope}`);
235
+ }
236
+ // If created from a ClickUp task, offer to post a comment back
237
+ if (args._clickupTaskId) {
238
+ try {
239
+ var token = process.env.CLICKUP_API_TOKEN;
240
+ if (token) {
241
+ var commentAnswer = await inquirer_1.default.prompt([{
242
+ type: "confirm",
243
+ name: "postComment",
244
+ message: "Post a comment to the ClickUp task linking this update set?",
245
+ default: true,
246
+ }]);
247
+ if (commentAnswer.postComment) {
248
+ var clickupApi = (0, dovetail_clickup_1.createClickUpApi)({ token: token });
249
+ await clickupApi.addComment({
250
+ taskId: args._clickupTaskId,
251
+ commentText: "ServiceNow Update Set created: " + name + " (ID: " + updateSetSysId + ")",
252
+ });
253
+ Logger_1.logger.success(chalk_1.default.green("✓ Comment posted to ClickUp task"));
254
+ }
255
+ }
256
+ }
257
+ catch (commentErr) {
258
+ Logger_1.logger.warn("Could not post comment to ClickUp task");
259
+ if (commentErr instanceof Error)
260
+ Logger_1.logger.debug(commentErr.message);
261
+ }
262
+ }
263
+ }
264
+ catch (e) {
265
+ Logger_1.logger.error("Failed to create update set");
266
+ if (e instanceof Error)
267
+ Logger_1.logger.error(e.message);
268
+ throw e;
269
+ }
270
+ }
271
+ /**
272
+ * Switches to an existing update set
273
+ */
274
+ async function switchUpdateSetCommand(args) {
275
+ (0, commands_1.setLogLevel)(args);
276
+ try {
277
+ const client = (0, snClient_1.defaultClient)();
278
+ // Get update set to switch to
279
+ const targetUpdateSet = await selectUpdateSet(args.name, args.scope);
280
+ if (!targetUpdateSet) {
281
+ throw new Error("No update set selected");
282
+ }
283
+ // Extract the actual values from display_value objects
284
+ const sysId = typeof targetUpdateSet.sys_id === 'object' && targetUpdateSet.sys_id !== null ? targetUpdateSet.sys_id.value : targetUpdateSet.sys_id;
285
+ const name = typeof targetUpdateSet.name === 'object' && targetUpdateSet.name !== null ? targetUpdateSet.name.value : targetUpdateSet.name;
286
+ const applicationName = (targetUpdateSet.application && targetUpdateSet.application.display_value) || (typeof targetUpdateSet.application === 'object' && targetUpdateSet.application !== null ? targetUpdateSet.application.value : targetUpdateSet.application);
287
+ const applicationScope = typeof targetUpdateSet.application === 'object' && targetUpdateSet.application !== null ? targetUpdateSet.application.value : targetUpdateSet.application;
288
+ // Switch to the selected update set
289
+ await switchToUpdateSet(sysId, name, args.scope || applicationScope);
290
+ Logger_1.logger.info(chalk_1.default.green(`✓ Switched to update set: ${name}`));
291
+ Logger_1.logger.info(`Update Set ID: ${sysId}`);
292
+ if (applicationName) {
293
+ Logger_1.logger.info(`Scope: ${applicationName}`);
294
+ }
295
+ }
296
+ catch (e) {
297
+ Logger_1.logger.error("Failed to switch update set");
298
+ if (e instanceof Error)
299
+ Logger_1.logger.error(e.message);
300
+ throw e;
301
+ }
302
+ }
303
+ /**
304
+ * Lists all open/in-progress update sets
305
+ */
306
+ async function listUpdateSetsCommand(args) {
307
+ (0, commands_1.setLogLevel)(args);
308
+ try {
309
+ const client = (0, snClient_1.defaultClient)();
310
+ // Build query for update sets
311
+ let query = "state=in progress";
312
+ if (args.scope) {
313
+ // Get scope sys_id if scope name provided
314
+ const scopeResult = await (0, snClient_1.unwrapSNResponse)(client.getScopeId(args.scope));
315
+ if (scopeResult.length === 0) {
316
+ throw new Error(`Scope "${args.scope}" not found`);
317
+ }
318
+ query += `^application=${scopeResult[0].sys_id}`;
319
+ }
320
+ // Get list of update sets
321
+ const updateSets = await getUpdateSets(query);
322
+ if (updateSets.length === 0) {
323
+ Logger_1.logger.info("No in-progress update sets found");
324
+ return;
325
+ }
326
+ // Get current update set
327
+ const currentUpdateSetId = await getCurrentUpdateSetId();
328
+ // Display update sets
329
+ Logger_1.logger.info(chalk_1.default.bold("\nIn-Progress Update Sets:"));
330
+ Logger_1.logger.info("─".repeat(80));
331
+ updateSets.forEach((updateSet) => {
332
+ // Handle both plain values and display_value objects
333
+ const sysId = typeof updateSet.sys_id === 'object' && updateSet.sys_id !== null ? updateSet.sys_id.value : updateSet.sys_id;
334
+ const name = typeof updateSet.name === 'object' && updateSet.name !== null ? updateSet.name.value : updateSet.name;
335
+ const description = typeof updateSet.description === 'object' && updateSet.description !== null ? updateSet.description.value : updateSet.description;
336
+ const createdBy = typeof updateSet.sys_created_by === 'object' && updateSet.sys_created_by !== null ? updateSet.sys_created_by.display_value || updateSet.sys_created_by.value : updateSet.sys_created_by;
337
+ const applicationName = (updateSet.application && updateSet.application.display_value) || (typeof updateSet.application === 'object' && updateSet.application !== null ? updateSet.application.value : updateSet.application);
338
+ const isCurrent = sysId === currentUpdateSetId;
339
+ const marker = isCurrent ? chalk_1.default.green("► ") : " ";
340
+ const displayName = isCurrent ? chalk_1.default.green(name) : name;
341
+ console.log(`${marker}${displayName}`);
342
+ if (description) {
343
+ console.log(` Description: ${description}`);
344
+ }
345
+ if (applicationName) {
346
+ console.log(` Scope: ${applicationName}`);
347
+ }
348
+ console.log(` Created: ${formatDate(updateSet.sys_created_on)} by ${createdBy}`);
349
+ console.log(` ID: ${sysId}`);
350
+ console.log("");
351
+ });
352
+ if (currentUpdateSetId) {
353
+ Logger_1.logger.info(chalk_1.default.dim("► indicates current active update set"));
354
+ }
355
+ }
356
+ catch (e) {
357
+ Logger_1.logger.error("Failed to list update sets");
358
+ if (e instanceof Error)
359
+ Logger_1.logger.error(e.message);
360
+ throw e;
361
+ }
362
+ }
363
+ /**
364
+ * Helper function to prompt for update set details
365
+ */
366
+ async function promptForUpdateSetDetails(args) {
367
+ const questions = [];
368
+ if (!args.name) {
369
+ questions.push({
370
+ type: "input",
371
+ name: "name",
372
+ message: "Update set name:",
373
+ validate: (input) => {
374
+ if (!input || input.trim() === "") {
375
+ return "Update set name is required";
376
+ }
377
+ return true;
378
+ }
379
+ });
380
+ }
381
+ if (!args.description && !args.skipDescription) {
382
+ questions.push({
383
+ type: "input",
384
+ name: "description",
385
+ message: "Description (optional):"
386
+ });
387
+ }
388
+ if (!args.scope && !args.skipScope) {
389
+ questions.push({
390
+ type: "input",
391
+ name: "scope",
392
+ message: "Scope (optional, e.g., x_company_app):"
393
+ });
394
+ }
395
+ const answers = questions.length > 0 ? await inquirer_1.default.prompt(questions) : {};
396
+ return {
397
+ name: args.name || answers.name,
398
+ description: args.description || answers.description,
399
+ scope: args.scope || answers.scope
400
+ };
401
+ }
402
+ /**
403
+ * Helper function to switch to an update set using the new API endpoint.
404
+ * Verifies the switch was successful by reading back the current update set.
405
+ */
406
+ async function switchToUpdateSet(updateSetSysId, name, scope) {
407
+ const client = (0, snClient_1.defaultClient)();
408
+ Logger_1.logger.debug(`Switching to update set - sysId: ${updateSetSysId}, name: ${name}, scope: ${scope}`);
409
+ // Use the new changeUpdateSet endpoint
410
+ // Can use either sysId or name+scope combination
411
+ const params = {};
412
+ if (updateSetSysId) {
413
+ params.sysId = updateSetSysId;
414
+ }
415
+ if (name) {
416
+ params.name = name;
417
+ }
418
+ if (scope) {
419
+ params.scope = scope;
420
+ }
421
+ const response = await client.changeUpdateSet(params);
422
+ let result = await response.data;
423
+ // Handle wrapped response
424
+ if (result && result.result) {
425
+ result = result.result;
426
+ }
427
+ Logger_1.logger.debug(`Change update set response: ${JSON.stringify(result)}`);
428
+ if (result.error) {
429
+ throw new Error(result.error);
430
+ }
431
+ // Check if the message indicates success
432
+ if (result.message && !result.message.includes("Success") && !result.message.includes("changed")) {
433
+ throw new Error(result.message);
434
+ }
435
+ // Verify the switch was successful
436
+ var verified = await verifyActiveUpdateSet(client, updateSetSysId, scope);
437
+ if (!verified) {
438
+ // Retry once
439
+ Logger_1.logger.warn("Update set verification failed, retrying switch...");
440
+ var retryResponse = await client.changeUpdateSet(params);
441
+ var retryResult = await retryResponse.data;
442
+ if (retryResult && retryResult.result) {
443
+ retryResult = retryResult.result;
444
+ }
445
+ if (retryResult.error) {
446
+ throw new Error(retryResult.error);
447
+ }
448
+ var retryVerified = await verifyActiveUpdateSet(client, updateSetSysId, scope);
449
+ if (!retryVerified) {
450
+ var currentName = await getActiveUpdateSetName(client, scope);
451
+ throw new Error("Update set " + (name || updateSetSysId) + " was created but could not be activated. Current update set is " + (currentName || "unknown") + ".");
452
+ }
453
+ }
454
+ }
455
+ /**
456
+ * Verifies the currently active update set matches the expected sys_id.
457
+ */
458
+ async function verifyActiveUpdateSet(client, expectedSysId, scope) {
459
+ try {
460
+ var response = await client.getCurrentUpdateSet(scope);
461
+ var result = await response.data;
462
+ if (result && result.result) {
463
+ result = result.result;
464
+ }
465
+ if (result && result.sysId === expectedSysId) {
466
+ return true;
467
+ }
468
+ Logger_1.logger.warn("Verification mismatch: expected " + expectedSysId + ", got " + (result && result.sysId ? result.sysId : "null"));
469
+ return false;
470
+ }
471
+ catch (e) {
472
+ Logger_1.logger.warn("Verification check failed: " + (e instanceof Error ? e.message : String(e)));
473
+ return false;
474
+ }
475
+ }
476
+ /**
477
+ * Gets the name of the currently active update set for error messages.
478
+ */
479
+ async function getActiveUpdateSetName(client, scope) {
480
+ try {
481
+ var response = await client.getCurrentUpdateSet(scope);
482
+ var result = await response.data;
483
+ if (result && result.result) {
484
+ result = result.result;
485
+ }
486
+ return (result && result.name) ? result.name : null;
487
+ }
488
+ catch (e) {
489
+ return null;
490
+ }
491
+ }
492
+ /**
493
+ * Helper function to switch to a scope
494
+ */
495
+ async function switchToScope(scopeSysId, scopeName) {
496
+ const client = (0, snClient_1.defaultClient)();
497
+ // Get current user sys_id
498
+ const userSysId = await (0, snClient_1.unwrapTableAPIFirstItem)(client.getUserSysId(), "sys_id");
499
+ // Get or create user preference for current app
500
+ try {
501
+ const curAppUserPrefId = await (0, snClient_1.unwrapTableAPIFirstItem)(client.getCurrentAppUserPrefSysId(userSysId), "sys_id");
502
+ // Update existing preference
503
+ await client.updateCurrentAppUserPref(scopeSysId, curAppUserPrefId);
504
+ }
505
+ catch (e) {
506
+ // Create new preference if it doesn't exist
507
+ await client.createCurrentAppUserPref(scopeSysId, userSysId);
508
+ }
509
+ Logger_1.logger.info(`Switched to scope: ${scopeName}`);
510
+ }
511
+ /**
512
+ * Helper function to get update sets based on query
513
+ */
514
+ async function getUpdateSets(query) {
515
+ const client = (0, snClient_1.defaultClient)();
516
+ const endpoint = "api/now/table/sys_update_set";
517
+ const response = client.client.get(endpoint, {
518
+ params: {
519
+ sysparm_query: query,
520
+ sysparm_fields: "sys_id,name,description,state,application,application.name,sys_created_on,sys_created_by",
521
+ sysparm_limit: 100,
522
+ sysparm_display_value: "all"
523
+ }
524
+ });
525
+ return (0, snClient_1.unwrapSNResponse)(response);
526
+ }
527
+ /**
528
+ * Helper function to get current update set ID using the new endpoint
529
+ */
530
+ async function getCurrentUpdateSetId(scope) {
531
+ try {
532
+ const client = (0, snClient_1.defaultClient)();
533
+ const response = await client.getCurrentUpdateSet(scope);
534
+ let result = await response.data;
535
+ // Handle wrapped response
536
+ if (result && result.result) {
537
+ result = result.result;
538
+ }
539
+ if (result && result.sysId) {
540
+ return result.sysId;
541
+ }
542
+ return null;
543
+ }
544
+ catch (e) {
545
+ return null;
546
+ }
547
+ }
548
+ /**
549
+ * Helper function to get current update set details
550
+ */
551
+ async function getCurrentUpdateSetDetails(scope) {
552
+ try {
553
+ const client = (0, snClient_1.defaultClient)();
554
+ const response = await client.getCurrentUpdateSet(scope);
555
+ let result = await response.data;
556
+ // Handle wrapped response
557
+ if (result && result.result) {
558
+ result = result.result;
559
+ }
560
+ if (result && result.sysId) {
561
+ return {
562
+ sysId: result.sysId,
563
+ name: result.name || 'Unknown'
564
+ };
565
+ }
566
+ return null;
567
+ }
568
+ catch (e) {
569
+ return null;
570
+ }
571
+ }
572
+ /**
573
+ * Helper function to select an update set from a list
574
+ */
575
+ async function selectUpdateSet(nameFilter, scopeFilter) {
576
+ let query = "state=in progress";
577
+ if (scopeFilter) {
578
+ const client = (0, snClient_1.defaultClient)();
579
+ const scopeResult = await (0, snClient_1.unwrapSNResponse)(client.getScopeId(scopeFilter));
580
+ if (scopeResult.length === 0) {
581
+ Logger_1.logger.error(`Scope "${scopeFilter}" not found`);
582
+ return null;
583
+ }
584
+ query += `^application=${scopeResult[0].sys_id}`;
585
+ }
586
+ const updateSets = await getUpdateSets(query);
587
+ if (updateSets.length === 0) {
588
+ Logger_1.logger.info("No in-progress update sets found");
589
+ return null;
590
+ }
591
+ // Filter by name if provided
592
+ let filteredSets = updateSets;
593
+ if (nameFilter) {
594
+ filteredSets = updateSets.filter(us => {
595
+ const name = typeof us.name === 'object' && us.name !== null ? us.name.value : us.name;
596
+ return name.toLowerCase().includes(nameFilter.toLowerCase());
597
+ });
598
+ if (filteredSets.length === 0) {
599
+ Logger_1.logger.error(`No update sets found matching "${nameFilter}"`);
600
+ return null;
601
+ }
602
+ // If exact match found, use it
603
+ const exactMatch = filteredSets.find(us => {
604
+ const name = typeof us.name === 'object' && us.name !== null ? us.name.value : us.name;
605
+ return name.toLowerCase() === nameFilter.toLowerCase();
606
+ });
607
+ if (exactMatch) {
608
+ return exactMatch;
609
+ }
610
+ // If only one match, use it
611
+ if (filteredSets.length === 1) {
612
+ return filteredSets[0];
613
+ }
614
+ }
615
+ // Prompt user to select from list
616
+ const choices = filteredSets.map(us => {
617
+ const name = typeof us.name === 'object' && us.name !== null ? us.name.value : us.name;
618
+ const description = typeof us.description === 'object' && us.description !== null ? us.description.value : us.description;
619
+ const applicationName = (us.application && us.application.display_value) || (typeof us.application === 'object' && us.application !== null ? us.application.value : us.application);
620
+ return {
621
+ name: `${name}${applicationName ? ` (${applicationName})` : ""} - ${description || "No description"}`,
622
+ value: us
623
+ };
624
+ });
625
+ const { selectedUpdateSet } = await inquirer_1.default.prompt([
626
+ {
627
+ type: "list",
628
+ name: "selectedUpdateSet",
629
+ message: "Select an update set:",
630
+ choices,
631
+ pageSize: 15
632
+ }
633
+ ]);
634
+ return selectedUpdateSet;
635
+ }
636
+ /**
637
+ * Helper function to format date
638
+ */
639
+ /**
640
+ * Resolves a ClickUp task identifier into an update set name and description.
641
+ * Uses Claude CLI for name refinement with convention-based fallback.
642
+ */
643
+ async function resolveClickUpTaskForUpdateSet(clickupIdentifier) {
644
+ var token = process.env.CLICKUP_API_TOKEN;
645
+ if (!token || token === "") {
646
+ throw new Error("CLICKUP_API_TOKEN not set. Run 'dove clickup setup' or add it to your .env file.");
647
+ }
648
+ var parsed = (0, dovetail_clickup_1.parseClickUpIdentifier)(clickupIdentifier);
649
+ Logger_1.logger.info("Fetching ClickUp task: " + parsed.taskId + "...");
650
+ var api = (0, dovetail_clickup_1.createClickUpApi)({ token: token });
651
+ var task = await api.getTask({ taskId: parsed.taskId });
652
+ Logger_1.logger.success(chalk_1.default.green('✓ Found: "' + task.name + '"'));
653
+ Logger_1.logger.info("");
654
+ // Generate update set name via Claude CLI (with fallback)
655
+ Logger_1.logger.info("Generating update set name...");
656
+ var suggestedName = (0, clickupCommands_1.refineUpdateSetName)({
657
+ taskName: task.name,
658
+ taskId: task.id,
659
+ taskDescription: task.description || "",
660
+ });
661
+ Logger_1.logger.success(chalk_1.default.green('✓ Suggested name: "' + suggestedName + '"'));
662
+ // Generate description from task summary
663
+ var suggestedDescription = (0, dovetail_clickup_1.formatTaskSummary)({ task: task });
664
+ // Let user confirm or edit
665
+ var confirmAnswers = await inquirer_1.default.prompt([
666
+ {
667
+ type: "input",
668
+ name: "name",
669
+ message: "Update set name:",
670
+ default: suggestedName,
671
+ validate: function (input) {
672
+ if (!input || input.trim() === "")
673
+ return "Name is required";
674
+ return true;
675
+ },
676
+ },
677
+ {
678
+ type: "input",
679
+ name: "description",
680
+ message: "Description:",
681
+ default: suggestedDescription,
682
+ },
683
+ ]);
684
+ return {
685
+ name: confirmAnswers.name,
686
+ description: confirmAnswers.description,
687
+ taskId: parsed.taskId,
688
+ };
689
+ }
690
+ function formatDate(dateString) {
691
+ try {
692
+ const actualDateString = typeof dateString === 'object' && dateString !== null ? dateString.display_value || dateString.value : dateString;
693
+ const date = new Date(actualDateString);
694
+ if (isNaN(date.getTime())) {
695
+ return actualDateString || "Unknown";
696
+ }
697
+ return `${date.toLocaleDateString()} ${date.toLocaleTimeString()}`;
698
+ }
699
+ catch (e) {
700
+ return typeof dateString === 'string' ? dateString : "Unknown";
701
+ }
702
+ }