@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.
- package/README.md +6 -0
- package/dist/FileLogger.js +207 -0
- package/dist/FileUtils.js +387 -0
- package/dist/Logger.js +88 -0
- package/dist/MultiScopeWatcher.js +615 -0
- package/dist/PluginManager.js +125 -0
- package/dist/_defaultOptionsOLD.js +44 -0
- package/dist/allScopesCommands.js +418 -0
- package/dist/appUtils.js +929 -0
- package/dist/benchmark.js +104 -0
- package/dist/bootstrap.js +62 -0
- package/dist/claudeCommand.js +60 -0
- package/dist/clickupCommands.js +472 -0
- package/dist/clickupPushHelper.js +33 -0
- package/dist/commander.js +466 -0
- package/dist/commands.js +378 -0
- package/dist/config.js +697 -0
- package/dist/constants.js +15 -0
- package/dist/createRecordCommand.js +314 -0
- package/dist/dashboardCommand.js +38 -0
- package/dist/defaultOptions.js +14 -0
- package/dist/deleteRecordCommand.js +282 -0
- package/dist/flowDesigner/values.js +55 -0
- package/dist/genericUtils.js +189 -0
- package/dist/gitUtils.js +99 -0
- package/dist/index.js +35 -0
- package/dist/initSystem/corePlugin.js +344 -0
- package/dist/initSystem/discovery.js +107 -0
- package/dist/initSystem/orchestrator.js +408 -0
- package/dist/logMessages.js +82 -0
- package/dist/loginCommand.js +20 -0
- package/dist/migrateCommand.js +123 -0
- package/dist/projectFiles.js +32 -0
- package/dist/recentEdits.js +66 -0
- package/dist/schemaCommand.js +85 -0
- package/dist/snClient.js +519 -0
- package/dist/tests/benchmarkRefresh.test.js +265 -0
- package/dist/tests/clickupCommands.test.js +384 -0
- package/dist/tests/clickupPushHelper.test.js +124 -0
- package/dist/tests/configFallback.test.js +68 -0
- package/dist/tests/cred.test.js +59 -0
- package/dist/tests/discovery.test.js +67 -0
- package/dist/tests/ensureUpdateSetWarnings.test.js +218 -0
- package/dist/tests/errorLogLevels.test.js +273 -0
- package/dist/tests/example.test.js +6 -0
- package/dist/tests/fileContextSkipReason.test.js +116 -0
- package/dist/tests/globalDebounce.test.js +308 -0
- package/dist/tests/migrateCommand.test.js +113 -0
- package/dist/tests/multi-scope-watcher.test.js +558 -0
- package/dist/tests/orchestrator.test.js +23 -0
- package/dist/tests/pushFiles.test.js +248 -0
- package/dist/tests/rateLimitCoordination.test.js +271 -0
- package/dist/tests/resolveConfigReadOnly.test.js +67 -0
- package/dist/tests/retryOnHttpErr.test.js +154 -0
- package/dist/tests/scopeCaching.test.js +115 -0
- package/dist/tests/serializeUpdateSetConfig.test.js +325 -0
- package/dist/tests/snClient.fallback.test.js +120 -0
- package/dist/tests/syncManifestRefresh.test.js +287 -0
- package/dist/tests/syncManifestWhitelist.test.js +192 -0
- package/dist/tests/taskClear.test.js +170 -0
- package/dist/tests/taskStaleness.test.js +220 -0
- package/dist/tests/unwrapSNResponseError.test.js +145 -0
- package/dist/tests/v2Values.test.js +79 -0
- package/dist/tests/validateTaskId.test.js +304 -0
- package/dist/tests/verifyUpdateSetSwitch.test.js +277 -0
- package/dist/tests/writeEnvVar.test.js +119 -0
- package/dist/updateSetCommands.js +702 -0
- package/dist/wizard.js +40 -0
- package/package.json +69 -0
- package/skills/dove-configure-pipeline.md +158 -0
- package/skills/dove-create-plugin.md +174 -0
- package/skills/dove-create-record.md +135 -0
- package/skills/dove-debug-build.md +117 -0
- package/skills/dove-delete-record.md +104 -0
- package/skills/dove-manage-tables.md +174 -0
- package/skills/dove-manage-update-sets.md +88 -0
- package/skills/dove-setup-project.md +154 -0
- package/skills/dove-troubleshoot-sync.md +113 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Opt-in benchmark collector for `sinc refresh`. Wired into snClient via
|
|
4
|
+
* setBenchmarkSink — when null (the default), there is zero overhead.
|
|
5
|
+
*
|
|
6
|
+
* PR #36 changed refresh from "download files absent from disk" to "bulk-download
|
|
7
|
+
* every manifest file and compare before writing". That shifted the request
|
|
8
|
+
* profile silently. This collector surfaces the numbers so regressions are
|
|
9
|
+
* visible.
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.BenchmarkCollector = void 0;
|
|
13
|
+
class BenchmarkCollector {
|
|
14
|
+
httpSamples = [];
|
|
15
|
+
scopeSamples = [];
|
|
16
|
+
currentScope = null;
|
|
17
|
+
recordHttp(sample) {
|
|
18
|
+
this.httpSamples.push(sample);
|
|
19
|
+
}
|
|
20
|
+
startScope(scopeName) {
|
|
21
|
+
this.currentScope = {
|
|
22
|
+
name: scopeName,
|
|
23
|
+
startedAt: Date.now(),
|
|
24
|
+
httpStart: this.httpSamples.length,
|
|
25
|
+
bytesStart: this.totalBytes(),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
endScope(filesWritten, filesUnchanged) {
|
|
29
|
+
if (!this.currentScope)
|
|
30
|
+
return;
|
|
31
|
+
var scope = this.currentScope;
|
|
32
|
+
this.scopeSamples.push({
|
|
33
|
+
scopeName: scope.name,
|
|
34
|
+
wallTimeMs: Date.now() - scope.startedAt,
|
|
35
|
+
httpRequests: this.httpSamples.length - scope.httpStart,
|
|
36
|
+
filesWritten: filesWritten,
|
|
37
|
+
filesUnchanged: filesUnchanged,
|
|
38
|
+
totalResponseBytes: this.totalBytes() - scope.bytesStart,
|
|
39
|
+
});
|
|
40
|
+
this.currentScope = null;
|
|
41
|
+
}
|
|
42
|
+
getHttpSamples() {
|
|
43
|
+
return this.httpSamples.slice();
|
|
44
|
+
}
|
|
45
|
+
getScopeSamples() {
|
|
46
|
+
return this.scopeSamples.slice();
|
|
47
|
+
}
|
|
48
|
+
totalBytes() {
|
|
49
|
+
var total = 0;
|
|
50
|
+
for (var i = 0; i < this.httpSamples.length; i++) {
|
|
51
|
+
total += this.httpSamples[i].responseBytes;
|
|
52
|
+
}
|
|
53
|
+
return total;
|
|
54
|
+
}
|
|
55
|
+
formatSummary() {
|
|
56
|
+
var lines = [];
|
|
57
|
+
lines.push("");
|
|
58
|
+
lines.push("=".repeat(72));
|
|
59
|
+
lines.push("Refresh Benchmark");
|
|
60
|
+
lines.push("=".repeat(72));
|
|
61
|
+
if (this.httpSamples.length === 0) {
|
|
62
|
+
lines.push("(no samples recorded)");
|
|
63
|
+
return lines.join("\n");
|
|
64
|
+
}
|
|
65
|
+
var latencies = this.httpSamples
|
|
66
|
+
.map(function (s) { return s.durationMs; })
|
|
67
|
+
.sort(function (a, b) { return a - b; });
|
|
68
|
+
var p50 = percentile(latencies, 0.5);
|
|
69
|
+
var p95 = percentile(latencies, 0.95);
|
|
70
|
+
var max = latencies[latencies.length - 1];
|
|
71
|
+
var totalBytes = this.totalBytes();
|
|
72
|
+
lines.push("Overall: " + this.httpSamples.length + " HTTP requests, " +
|
|
73
|
+
formatBytes(totalBytes) + " received");
|
|
74
|
+
lines.push("Latency: p50 " + p50 + "ms | p95 " + p95 + "ms | max " + max + "ms");
|
|
75
|
+
if (this.scopeSamples.length > 0) {
|
|
76
|
+
lines.push("");
|
|
77
|
+
lines.push("Per-scope:");
|
|
78
|
+
for (var i = 0; i < this.scopeSamples.length; i++) {
|
|
79
|
+
var s = this.scopeSamples[i];
|
|
80
|
+
lines.push(" " + s.scopeName + ": " +
|
|
81
|
+
s.wallTimeMs + "ms wall, " +
|
|
82
|
+
s.httpRequests + " req, " +
|
|
83
|
+
formatBytes(s.totalResponseBytes) + ", " +
|
|
84
|
+
s.filesWritten + " written / " + s.filesUnchanged + " unchanged");
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
lines.push("=".repeat(72));
|
|
88
|
+
return lines.join("\n");
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
exports.BenchmarkCollector = BenchmarkCollector;
|
|
92
|
+
function percentile(sortedAsc, p) {
|
|
93
|
+
if (sortedAsc.length === 0)
|
|
94
|
+
return 0;
|
|
95
|
+
var idx = Math.min(sortedAsc.length - 1, Math.floor(sortedAsc.length * p));
|
|
96
|
+
return sortedAsc[idx];
|
|
97
|
+
}
|
|
98
|
+
function formatBytes(n) {
|
|
99
|
+
if (n < 1024)
|
|
100
|
+
return n + "B";
|
|
101
|
+
if (n < 1024 * 1024)
|
|
102
|
+
return (n / 1024).toFixed(1) + "KB";
|
|
103
|
+
return (n / (1024 * 1024)).toFixed(2) + "MB";
|
|
104
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
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.init = init;
|
|
40
|
+
const dotenv_1 = __importDefault(require("dotenv"));
|
|
41
|
+
const ConfigManager = __importStar(require("./config"));
|
|
42
|
+
const Logger_1 = require("./Logger");
|
|
43
|
+
async function init() {
|
|
44
|
+
let configLoaded = false;
|
|
45
|
+
try {
|
|
46
|
+
await ConfigManager.loadConfigs();
|
|
47
|
+
configLoaded = true;
|
|
48
|
+
}
|
|
49
|
+
catch (e) {
|
|
50
|
+
Logger_1.logger.error("Failed to load configuration: " + String(e));
|
|
51
|
+
}
|
|
52
|
+
if (configLoaded) {
|
|
53
|
+
try {
|
|
54
|
+
let path = ConfigManager.getEnvPath();
|
|
55
|
+
dotenv_1.default.config({ path });
|
|
56
|
+
}
|
|
57
|
+
catch (e) {
|
|
58
|
+
Logger_1.logger.error("Failed to load environment: " + String(e));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
(await Promise.resolve().then(() => __importStar(require("./commander")))).initCommands();
|
|
62
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.initClaudeCommand = initClaudeCommand;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const Logger_1 = require("./Logger");
|
|
10
|
+
const commands_1 = require("./commands");
|
|
11
|
+
function findSkillsDir() {
|
|
12
|
+
// Walk up from __dirname looking for a skills/ directory with .md files
|
|
13
|
+
// Handles both dist/ and dist/core/src/ output structures, plus monorepo layout
|
|
14
|
+
let dir = __dirname;
|
|
15
|
+
const root = path_1.default.parse(dir).root;
|
|
16
|
+
while (dir !== root) {
|
|
17
|
+
dir = path_1.default.dirname(dir);
|
|
18
|
+
const candidate = path_1.default.join(dir, "skills");
|
|
19
|
+
if (fs_1.default.existsSync(candidate) &&
|
|
20
|
+
fs_1.default.statSync(candidate).isDirectory() &&
|
|
21
|
+
fs_1.default.readdirSync(candidate).some(function (f) {
|
|
22
|
+
return f.endsWith(".md");
|
|
23
|
+
})) {
|
|
24
|
+
return candidate;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
async function initClaudeCommand(args) {
|
|
30
|
+
(0, commands_1.setLogLevel)(args);
|
|
31
|
+
var skillsDir = findSkillsDir();
|
|
32
|
+
if (!skillsDir) {
|
|
33
|
+
Logger_1.logger.error("Could not find Dovetail skills directory.");
|
|
34
|
+
process.exit(1);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
// Target: <cwd>/.claude/commands/
|
|
38
|
+
const targetDir = path_1.default.resolve(process.cwd(), ".claude", "commands");
|
|
39
|
+
fs_1.default.mkdirSync(targetDir, { recursive: true });
|
|
40
|
+
// Copy .md files
|
|
41
|
+
const files = fs_1.default.readdirSync(skillsDir).filter((f) => f.endsWith(".md"));
|
|
42
|
+
let copied = 0;
|
|
43
|
+
let skipped = 0;
|
|
44
|
+
for (const file of files) {
|
|
45
|
+
const dest = path_1.default.join(targetDir, file);
|
|
46
|
+
if (fs_1.default.existsSync(dest) && !args.force) {
|
|
47
|
+
Logger_1.logger.info(`Skipped (exists): ${file}`);
|
|
48
|
+
skipped++;
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
fs_1.default.copyFileSync(path_1.default.join(skillsDir, file), dest);
|
|
52
|
+
Logger_1.logger.info(`Copied: ${file}`);
|
|
53
|
+
copied++;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
Logger_1.logger.success(`Init complete! ${copied} skills copied, ${skipped} skipped. ✅`);
|
|
57
|
+
if (skipped > 0) {
|
|
58
|
+
Logger_1.logger.info("Use --force to overwrite existing files.");
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.clickupTasksCommand = clickupTasksCommand;
|
|
7
|
+
exports.clickupTaskCommand = clickupTaskCommand;
|
|
8
|
+
exports.clickupCreateCommand = clickupCreateCommand;
|
|
9
|
+
exports.clickupUpdateCommand = clickupUpdateCommand;
|
|
10
|
+
exports.clickupCommentCommand = clickupCommentCommand;
|
|
11
|
+
exports.clickupSetupCommand = clickupSetupCommand;
|
|
12
|
+
exports.clickupTeamsCommand = clickupTeamsCommand;
|
|
13
|
+
exports.clickupSpacesCommand = clickupSpacesCommand;
|
|
14
|
+
exports.clickupListsCommand = clickupListsCommand;
|
|
15
|
+
exports.refineUpdateSetName = refineUpdateSetName;
|
|
16
|
+
const dovetail_clickup_1 = require("@tenonhq/dovetail-clickup");
|
|
17
|
+
const inquirer_1 = __importDefault(require("inquirer"));
|
|
18
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
19
|
+
const Logger_1 = require("./Logger");
|
|
20
|
+
const commands_1 = require("./commands");
|
|
21
|
+
const FileUtils_1 = require("./FileUtils");
|
|
22
|
+
// --- Token & API Helpers ---
|
|
23
|
+
function getClickUpToken() {
|
|
24
|
+
var token = process.env.CLICKUP_API_TOKEN;
|
|
25
|
+
if (!token || token === "") {
|
|
26
|
+
throw new Error("CLICKUP_API_TOKEN not set. Run 'dove clickup setup' or add CLICKUP_API_TOKEN to your .env file.");
|
|
27
|
+
}
|
|
28
|
+
return token;
|
|
29
|
+
}
|
|
30
|
+
function getApi() {
|
|
31
|
+
return (0, dovetail_clickup_1.createClickUpApi)({ token: getClickUpToken() });
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* @description Resolves the team ID automatically. Uses --team flag if provided,
|
|
35
|
+
* otherwise calls getTeams() and auto-selects if there's only one.
|
|
36
|
+
* Only prompts interactively when the user belongs to multiple teams.
|
|
37
|
+
*/
|
|
38
|
+
async function resolveTeamId(api, args) {
|
|
39
|
+
// Explicit --team flag takes priority
|
|
40
|
+
if (args.team) {
|
|
41
|
+
return args.team;
|
|
42
|
+
}
|
|
43
|
+
var teams = await api.getTeams();
|
|
44
|
+
if (teams.length === 0) {
|
|
45
|
+
throw new Error("No ClickUp workspaces found. Verify your API token.");
|
|
46
|
+
}
|
|
47
|
+
if (teams.length === 1) {
|
|
48
|
+
return teams[0].id;
|
|
49
|
+
}
|
|
50
|
+
// Multiple teams — must prompt
|
|
51
|
+
var teamChoices = teams.map(function (t) {
|
|
52
|
+
return { name: t.name, value: t.id };
|
|
53
|
+
});
|
|
54
|
+
var teamAnswer = await inquirer_1.default.prompt([
|
|
55
|
+
{
|
|
56
|
+
type: "list",
|
|
57
|
+
name: "teamId",
|
|
58
|
+
message: "Select a workspace:",
|
|
59
|
+
choices: teamChoices,
|
|
60
|
+
},
|
|
61
|
+
]);
|
|
62
|
+
return teamAnswer.teamId;
|
|
63
|
+
}
|
|
64
|
+
// --- Commands ---
|
|
65
|
+
/**
|
|
66
|
+
* @description Lists tasks assigned to the authenticated user, grouped by status.
|
|
67
|
+
*/
|
|
68
|
+
async function clickupTasksCommand(args) {
|
|
69
|
+
(0, commands_1.setLogLevel)(args);
|
|
70
|
+
try {
|
|
71
|
+
var api = getApi();
|
|
72
|
+
var teamId = await resolveTeamId(api, args);
|
|
73
|
+
Logger_1.logger.info("Fetching your tasks...");
|
|
74
|
+
var statusFilters;
|
|
75
|
+
if (args.status && Array.isArray(args.status)) {
|
|
76
|
+
statusFilters = args.status;
|
|
77
|
+
}
|
|
78
|
+
var result = await api.listMyTasks({
|
|
79
|
+
teamId: teamId,
|
|
80
|
+
statuses: statusFilters,
|
|
81
|
+
});
|
|
82
|
+
if (result.total === 0) {
|
|
83
|
+
Logger_1.logger.info("No tasks assigned to you.");
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
var output = (0, dovetail_clickup_1.formatForClaude)({ tasks: result.tasks });
|
|
87
|
+
console.log(output);
|
|
88
|
+
}
|
|
89
|
+
catch (e) {
|
|
90
|
+
Logger_1.logger.error("Failed to list tasks");
|
|
91
|
+
if (e instanceof Error)
|
|
92
|
+
Logger_1.logger.error(e.message);
|
|
93
|
+
throw e;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* @description Gets details for a single ClickUp task.
|
|
98
|
+
*/
|
|
99
|
+
async function clickupTaskCommand(args) {
|
|
100
|
+
(0, commands_1.setLogLevel)(args);
|
|
101
|
+
try {
|
|
102
|
+
var api = getApi();
|
|
103
|
+
var parsed = (0, dovetail_clickup_1.parseClickUpIdentifier)(args.id);
|
|
104
|
+
Logger_1.logger.info("Fetching task " + parsed.taskId + "...");
|
|
105
|
+
var task = await api.getTask({ taskId: parsed.taskId });
|
|
106
|
+
var output = (0, dovetail_clickup_1.formatTaskDetail)({ task: task });
|
|
107
|
+
console.log(output);
|
|
108
|
+
}
|
|
109
|
+
catch (e) {
|
|
110
|
+
Logger_1.logger.error("Failed to get task");
|
|
111
|
+
if (e instanceof Error)
|
|
112
|
+
Logger_1.logger.error(e.message);
|
|
113
|
+
throw e;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* @description Creates a new task in a ClickUp list (interactive).
|
|
118
|
+
*/
|
|
119
|
+
async function clickupCreateCommand(args) {
|
|
120
|
+
(0, commands_1.setLogLevel)(args);
|
|
121
|
+
try {
|
|
122
|
+
var api = getApi();
|
|
123
|
+
var listId = args.listId;
|
|
124
|
+
var answers = await inquirer_1.default.prompt([
|
|
125
|
+
{
|
|
126
|
+
type: "input",
|
|
127
|
+
name: "name",
|
|
128
|
+
message: "Task name:",
|
|
129
|
+
validate: function (input) {
|
|
130
|
+
if (!input || input.trim() === "")
|
|
131
|
+
return "Task name is required";
|
|
132
|
+
return true;
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
type: "input",
|
|
137
|
+
name: "description",
|
|
138
|
+
message: "Description (optional):",
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
type: "input",
|
|
142
|
+
name: "status",
|
|
143
|
+
message: "Status (optional, e.g., 'to do'):",
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
type: "list",
|
|
147
|
+
name: "priority",
|
|
148
|
+
message: "Priority:",
|
|
149
|
+
choices: [
|
|
150
|
+
{ name: "None", value: undefined },
|
|
151
|
+
{ name: "Urgent", value: 1 },
|
|
152
|
+
{ name: "High", value: 2 },
|
|
153
|
+
{ name: "Normal", value: 3 },
|
|
154
|
+
{ name: "Low", value: 4 },
|
|
155
|
+
],
|
|
156
|
+
},
|
|
157
|
+
]);
|
|
158
|
+
Logger_1.logger.info("Creating task...");
|
|
159
|
+
var task = await api.createTask({
|
|
160
|
+
listId: listId,
|
|
161
|
+
name: answers.name,
|
|
162
|
+
description: answers.description || undefined,
|
|
163
|
+
status: answers.status || undefined,
|
|
164
|
+
priority: answers.priority,
|
|
165
|
+
});
|
|
166
|
+
Logger_1.logger.success(chalk_1.default.green("✓ Task created: " + task.name));
|
|
167
|
+
Logger_1.logger.info(" ID: " + task.id);
|
|
168
|
+
Logger_1.logger.info(" URL: " + task.url);
|
|
169
|
+
}
|
|
170
|
+
catch (e) {
|
|
171
|
+
Logger_1.logger.error("Failed to create task");
|
|
172
|
+
if (e instanceof Error)
|
|
173
|
+
Logger_1.logger.error(e.message);
|
|
174
|
+
throw e;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* @description Updates an existing ClickUp task (interactive).
|
|
179
|
+
*/
|
|
180
|
+
async function clickupUpdateCommand(args) {
|
|
181
|
+
(0, commands_1.setLogLevel)(args);
|
|
182
|
+
try {
|
|
183
|
+
var api = getApi();
|
|
184
|
+
var parsed = (0, dovetail_clickup_1.parseClickUpIdentifier)(args.taskId);
|
|
185
|
+
Logger_1.logger.info("Fetching current task state...");
|
|
186
|
+
var task = await api.getTask({ taskId: parsed.taskId });
|
|
187
|
+
var currentStatus = task.status && task.status.status ? task.status.status : "";
|
|
188
|
+
var answers = await inquirer_1.default.prompt([
|
|
189
|
+
{
|
|
190
|
+
type: "input",
|
|
191
|
+
name: "name",
|
|
192
|
+
message: "Name:",
|
|
193
|
+
default: task.name,
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
type: "input",
|
|
197
|
+
name: "status",
|
|
198
|
+
message: "Status:",
|
|
199
|
+
default: currentStatus,
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
type: "list",
|
|
203
|
+
name: "priority",
|
|
204
|
+
message: "Priority:",
|
|
205
|
+
choices: [
|
|
206
|
+
{ name: "No change", value: undefined },
|
|
207
|
+
{ name: "Urgent", value: 1 },
|
|
208
|
+
{ name: "High", value: 2 },
|
|
209
|
+
{ name: "Normal", value: 3 },
|
|
210
|
+
{ name: "Low", value: 4 },
|
|
211
|
+
{ name: "None", value: 0 },
|
|
212
|
+
],
|
|
213
|
+
},
|
|
214
|
+
]);
|
|
215
|
+
var updates = { taskId: parsed.taskId };
|
|
216
|
+
if (answers.name !== task.name) {
|
|
217
|
+
updates.name = answers.name;
|
|
218
|
+
}
|
|
219
|
+
if (answers.status !== currentStatus) {
|
|
220
|
+
updates.status = answers.status;
|
|
221
|
+
}
|
|
222
|
+
if (answers.priority !== undefined) {
|
|
223
|
+
updates.priority = answers.priority;
|
|
224
|
+
}
|
|
225
|
+
// Check if anything changed
|
|
226
|
+
if (Object.keys(updates).length <= 1) {
|
|
227
|
+
Logger_1.logger.info("No changes made.");
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
Logger_1.logger.info("Updating task...");
|
|
231
|
+
var updated = await api.updateTask(updates);
|
|
232
|
+
Logger_1.logger.success(chalk_1.default.green("✓ Task updated: " + updated.name));
|
|
233
|
+
}
|
|
234
|
+
catch (e) {
|
|
235
|
+
Logger_1.logger.error("Failed to update task");
|
|
236
|
+
if (e instanceof Error)
|
|
237
|
+
Logger_1.logger.error(e.message);
|
|
238
|
+
throw e;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* @description Adds a comment to a ClickUp task.
|
|
243
|
+
*/
|
|
244
|
+
async function clickupCommentCommand(args) {
|
|
245
|
+
(0, commands_1.setLogLevel)(args);
|
|
246
|
+
try {
|
|
247
|
+
var api = getApi();
|
|
248
|
+
var parsed = (0, dovetail_clickup_1.parseClickUpIdentifier)(args.taskId);
|
|
249
|
+
var msg = args.msg;
|
|
250
|
+
if (!msg || msg.trim() === "") {
|
|
251
|
+
throw new Error("Comment message is required.");
|
|
252
|
+
}
|
|
253
|
+
Logger_1.logger.info("Adding comment to task " + parsed.taskId + "...");
|
|
254
|
+
await api.addComment({ taskId: parsed.taskId, commentText: msg });
|
|
255
|
+
Logger_1.logger.success(chalk_1.default.green("✓ Comment added"));
|
|
256
|
+
}
|
|
257
|
+
catch (e) {
|
|
258
|
+
Logger_1.logger.error("Failed to add comment");
|
|
259
|
+
if (e instanceof Error)
|
|
260
|
+
Logger_1.logger.error(e.message);
|
|
261
|
+
throw e;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* @description Interactive setup wizard for ClickUp API token and team configuration.
|
|
266
|
+
*/
|
|
267
|
+
async function clickupSetupCommand(args) {
|
|
268
|
+
(0, commands_1.setLogLevel)(args);
|
|
269
|
+
try {
|
|
270
|
+
Logger_1.logger.info(chalk_1.default.bold("\nClickUp Setup"));
|
|
271
|
+
Logger_1.logger.info("─".repeat(40));
|
|
272
|
+
Logger_1.logger.info("");
|
|
273
|
+
Logger_1.logger.info("To get your ClickUp API token:");
|
|
274
|
+
Logger_1.logger.info(" 1. Go to ClickUp → Settings (bottom-left) → Apps");
|
|
275
|
+
Logger_1.logger.info(' 2. Under "API Token", click Generate');
|
|
276
|
+
Logger_1.logger.info(" 3. Copy the token and paste it below");
|
|
277
|
+
Logger_1.logger.info("");
|
|
278
|
+
var answers = await inquirer_1.default.prompt([
|
|
279
|
+
{
|
|
280
|
+
type: "password",
|
|
281
|
+
name: "token",
|
|
282
|
+
message: "ClickUp API Token:",
|
|
283
|
+
mask: "*",
|
|
284
|
+
validate: function (input) {
|
|
285
|
+
if (!input || input.trim() === "")
|
|
286
|
+
return "API token is required";
|
|
287
|
+
return true;
|
|
288
|
+
},
|
|
289
|
+
},
|
|
290
|
+
]);
|
|
291
|
+
var token = answers.token.trim();
|
|
292
|
+
// Validate the token by fetching teams
|
|
293
|
+
Logger_1.logger.info("Validating token...");
|
|
294
|
+
var api = (0, dovetail_clickup_1.createClickUpApi)({ token: token });
|
|
295
|
+
var teams = await api.getTeams();
|
|
296
|
+
if (teams.length === 0) {
|
|
297
|
+
throw new Error("Token is valid but no workspaces found. Check your ClickUp permissions.");
|
|
298
|
+
}
|
|
299
|
+
Logger_1.logger.success(chalk_1.default.green("✓ Token valid"));
|
|
300
|
+
// Show discovered workspaces
|
|
301
|
+
for (var i = 0; i < teams.length; i++) {
|
|
302
|
+
var memberCount = teams[i].members ? teams[i].members.length : 0;
|
|
303
|
+
Logger_1.logger.info(" Workspace: " + teams[i].name + " (" + memberCount + " members)");
|
|
304
|
+
}
|
|
305
|
+
// Write token to .env file
|
|
306
|
+
(0, FileUtils_1.writeEnvVar)({ key: "CLICKUP_API_TOKEN", value: token });
|
|
307
|
+
Logger_1.logger.info("");
|
|
308
|
+
Logger_1.logger.success(chalk_1.default.green("✓ ClickUp configured! Token saved to .env"));
|
|
309
|
+
Logger_1.logger.info("");
|
|
310
|
+
Logger_1.logger.info("You can now use:");
|
|
311
|
+
Logger_1.logger.info(" dove clickup tasks — List your tasks");
|
|
312
|
+
Logger_1.logger.info(" dove clickup task <id> — Get task details");
|
|
313
|
+
Logger_1.logger.info(" dove createUpdateSet -cu <task-id> — Create update set from task");
|
|
314
|
+
}
|
|
315
|
+
catch (e) {
|
|
316
|
+
Logger_1.logger.error("ClickUp setup failed");
|
|
317
|
+
if (e instanceof Error)
|
|
318
|
+
Logger_1.logger.error(e.message);
|
|
319
|
+
throw e;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* @description Lists all ClickUp workspaces/teams the token has access to.
|
|
324
|
+
*/
|
|
325
|
+
async function clickupTeamsCommand(args) {
|
|
326
|
+
(0, commands_1.setLogLevel)(args);
|
|
327
|
+
try {
|
|
328
|
+
var api = getApi();
|
|
329
|
+
Logger_1.logger.info("Fetching teams...");
|
|
330
|
+
var teams = await api.getTeams();
|
|
331
|
+
if (teams.length === 0) {
|
|
332
|
+
Logger_1.logger.info("No workspaces found for this token.");
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
Logger_1.logger.info(chalk_1.default.bold("\nWorkspaces / Teams:"));
|
|
336
|
+
Logger_1.logger.info("─".repeat(40));
|
|
337
|
+
for (var i = 0; i < teams.length; i++) {
|
|
338
|
+
var team = teams[i];
|
|
339
|
+
var memberCount = team.members ? team.members.length : 0;
|
|
340
|
+
console.log(" " + team.name + " — ID: " + team.id + " (" + memberCount + " members)");
|
|
341
|
+
}
|
|
342
|
+
console.log("");
|
|
343
|
+
}
|
|
344
|
+
catch (e) {
|
|
345
|
+
Logger_1.logger.error("Failed to list teams");
|
|
346
|
+
if (e instanceof Error)
|
|
347
|
+
Logger_1.logger.error(e.message);
|
|
348
|
+
throw e;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* @description Lists available ClickUp spaces in a workspace.
|
|
353
|
+
*/
|
|
354
|
+
async function clickupSpacesCommand(args) {
|
|
355
|
+
(0, commands_1.setLogLevel)(args);
|
|
356
|
+
try {
|
|
357
|
+
var api = getApi();
|
|
358
|
+
var teamId = await resolveTeamId(api, args);
|
|
359
|
+
Logger_1.logger.info("Fetching spaces...");
|
|
360
|
+
var spaces = await api.getSpaces({ teamId: teamId });
|
|
361
|
+
if (spaces.length === 0) {
|
|
362
|
+
Logger_1.logger.info("No spaces found in this workspace.");
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
Logger_1.logger.info(chalk_1.default.bold("\nSpaces:"));
|
|
366
|
+
Logger_1.logger.info("─".repeat(40));
|
|
367
|
+
for (var i = 0; i < spaces.length; i++) {
|
|
368
|
+
var space = spaces[i];
|
|
369
|
+
var visibility = space.private ? "(private)" : "(public)";
|
|
370
|
+
console.log(" " + space.name + " — ID: " + space.id + " " + visibility);
|
|
371
|
+
}
|
|
372
|
+
console.log("");
|
|
373
|
+
}
|
|
374
|
+
catch (e) {
|
|
375
|
+
Logger_1.logger.error("Failed to list spaces");
|
|
376
|
+
if (e instanceof Error)
|
|
377
|
+
Logger_1.logger.error(e.message);
|
|
378
|
+
throw e;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* @description Lists available ClickUp lists in a folder or folderless lists in a space.
|
|
383
|
+
*/
|
|
384
|
+
async function clickupListsCommand(args) {
|
|
385
|
+
(0, commands_1.setLogLevel)(args);
|
|
386
|
+
try {
|
|
387
|
+
var api = getApi();
|
|
388
|
+
var id = args.spaceOrFolder;
|
|
389
|
+
// Try as folder first, fall back to space
|
|
390
|
+
Logger_1.logger.info("Fetching lists...");
|
|
391
|
+
var lists;
|
|
392
|
+
try {
|
|
393
|
+
lists = await api.getLists({ folderId: id });
|
|
394
|
+
}
|
|
395
|
+
catch (folderErr) {
|
|
396
|
+
// If folder fails, try getting folders from a space
|
|
397
|
+
Logger_1.logger.debug("Not a folder ID, trying as space...");
|
|
398
|
+
var folders = await api.getFolders({ spaceId: id });
|
|
399
|
+
if (folders.length === 0) {
|
|
400
|
+
Logger_1.logger.info("No folders or lists found for ID: " + id);
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
Logger_1.logger.info(chalk_1.default.bold("\nFolders & Lists:"));
|
|
404
|
+
Logger_1.logger.info("─".repeat(50));
|
|
405
|
+
for (var f = 0; f < folders.length; f++) {
|
|
406
|
+
var folder = folders[f];
|
|
407
|
+
console.log("\n 📁 " + folder.name + " (ID: " + folder.id + ")");
|
|
408
|
+
if (folder.lists && folder.lists.length > 0) {
|
|
409
|
+
for (var l = 0; l < folder.lists.length; l++) {
|
|
410
|
+
var fList = folder.lists[l];
|
|
411
|
+
var countLabel = fList.task_count !== null ? " (" + fList.task_count + " tasks)" : "";
|
|
412
|
+
console.log(" " + fList.name + " — ID: " + fList.id + countLabel);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
console.log("");
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
if (lists.length === 0) {
|
|
420
|
+
Logger_1.logger.info("No lists found.");
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
Logger_1.logger.info(chalk_1.default.bold("\nLists:"));
|
|
424
|
+
Logger_1.logger.info("─".repeat(40));
|
|
425
|
+
for (var i = 0; i < lists.length; i++) {
|
|
426
|
+
var list = lists[i];
|
|
427
|
+
var taskCount = list.task_count !== null ? " (" + list.task_count + " tasks)" : "";
|
|
428
|
+
console.log(" " + list.name + " — ID: " + list.id + taskCount);
|
|
429
|
+
}
|
|
430
|
+
console.log("");
|
|
431
|
+
}
|
|
432
|
+
catch (e) {
|
|
433
|
+
Logger_1.logger.error("Failed to list");
|
|
434
|
+
if (e instanceof Error)
|
|
435
|
+
Logger_1.logger.error(e.message);
|
|
436
|
+
throw e;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
// --- Claude CLI Refiner ---
|
|
440
|
+
/**
|
|
441
|
+
* @description Generates a refined update set name using Claude CLI, with fallback.
|
|
442
|
+
* @param params - Object with taskName, taskId, and taskDescription.
|
|
443
|
+
* @returns The refined or convention-based update set name.
|
|
444
|
+
*/
|
|
445
|
+
function refineUpdateSetName(params) {
|
|
446
|
+
var execSync = require("child_process").execSync;
|
|
447
|
+
// Sanitize inputs for shell safety
|
|
448
|
+
var safeName = params.taskName.replace(/['"\\]/g, "");
|
|
449
|
+
var safeDesc = params.taskDescription
|
|
450
|
+
.replace(/['"\\]/g, "")
|
|
451
|
+
.substring(0, 200);
|
|
452
|
+
try {
|
|
453
|
+
var prompt = "Generate a concise ServiceNow update set name (under 80 chars) for this ClickUp task. " +
|
|
454
|
+
"The name should be descriptive and professional. " +
|
|
455
|
+
"Task: " + safeName + ". " +
|
|
456
|
+
"Description: " + safeDesc + ". " +
|
|
457
|
+
"Return ONLY the update set name, nothing else.";
|
|
458
|
+
var result = execSync('claude -p "' + prompt.replace(/"/g, '\\"') + '" --output-format text', { encoding: "utf8", timeout: 30000, stdio: ["pipe", "pipe", "pipe"] });
|
|
459
|
+
var refined = result.trim();
|
|
460
|
+
if (refined && refined.length > 0 && refined.length <= 100) {
|
|
461
|
+
return refined;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
catch (e) {
|
|
465
|
+
// Claude CLI not available or failed — fall back to convention
|
|
466
|
+
}
|
|
467
|
+
// Fallback: clean up the task name for use as an update set name
|
|
468
|
+
return params.taskName
|
|
469
|
+
.replace(/[^a-zA-Z0-9\s\-_]/g, "")
|
|
470
|
+
.trim()
|
|
471
|
+
.substring(0, 80);
|
|
472
|
+
}
|