@md2do/cli 0.4.0 → 0.5.0

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 (39) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +25 -0
  3. package/coverage/coverage-final.json +3 -2
  4. package/coverage/index.html +11 -11
  5. package/coverage/lcov-report/index.html +11 -11
  6. package/coverage/lcov-report/src/cli.ts.html +10 -4
  7. package/coverage/lcov-report/src/commands/config.ts.html +2269 -0
  8. package/coverage/lcov-report/src/commands/index.html +22 -7
  9. package/coverage/lcov-report/src/commands/index.ts.html +7 -4
  10. package/coverage/lcov-report/src/commands/list.ts.html +1 -1
  11. package/coverage/lcov-report/src/commands/stats.ts.html +1 -1
  12. package/coverage/lcov-report/src/commands/todoist.ts.html +1 -1
  13. package/coverage/lcov-report/src/formatters/index.html +1 -1
  14. package/coverage/lcov-report/src/formatters/json.ts.html +1 -1
  15. package/coverage/lcov-report/src/formatters/pretty.ts.html +1 -1
  16. package/coverage/lcov-report/src/index.html +5 -5
  17. package/coverage/lcov-report/src/index.ts.html +1 -1
  18. package/coverage/lcov-report/src/scanner.ts.html +1 -1
  19. package/coverage/lcov.info +746 -3
  20. package/coverage/src/cli.ts.html +10 -4
  21. package/coverage/src/commands/config.ts.html +2269 -0
  22. package/coverage/src/commands/index.html +22 -7
  23. package/coverage/src/commands/index.ts.html +7 -4
  24. package/coverage/src/commands/list.ts.html +1 -1
  25. package/coverage/src/commands/stats.ts.html +1 -1
  26. package/coverage/src/commands/todoist.ts.html +1 -1
  27. package/coverage/src/formatters/index.html +1 -1
  28. package/coverage/src/formatters/json.ts.html +1 -1
  29. package/coverage/src/formatters/pretty.ts.html +1 -1
  30. package/coverage/src/index.html +5 -5
  31. package/coverage/src/index.ts.html +1 -1
  32. package/coverage/src/scanner.ts.html +1 -1
  33. package/dist/cli.js +466 -6
  34. package/dist/index.d.ts +6 -1
  35. package/dist/index.js +461 -0
  36. package/package.json +5 -2
  37. package/src/cli.ts +2 -0
  38. package/src/commands/config.ts +731 -0
  39. package/src/commands/index.ts +1 -0
package/dist/index.js CHANGED
@@ -30,6 +30,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
+ createConfigCommand: () => createConfigCommand,
33
34
  createListCommand: () => createListCommand,
34
35
  createStatsCommand: () => createStatsCommand,
35
36
  createTodoistCommand: () => createTodoistCommand
@@ -1209,8 +1210,468 @@ async function todoistSyncAction(options) {
1209
1210
  );
1210
1211
  console.log("");
1211
1212
  }
1213
+
1214
+ // src/commands/config.ts
1215
+ var import_commander4 = require("commander");
1216
+ var import_config3 = require("@md2do/config");
1217
+ var import_fs = require("fs");
1218
+ var import_path = require("path");
1219
+ var import_os = require("os");
1220
+ var import_child_process = require("child_process");
1221
+ var import_js_yaml = __toESM(require("js-yaml"));
1222
+ function createConfigCommand() {
1223
+ const command = new import_commander4.Command("config");
1224
+ command.description("Manage md2do configuration");
1225
+ command.addCommand(createConfigInitCommand());
1226
+ command.addCommand(createConfigSetCommand());
1227
+ command.addCommand(createConfigGetCommand());
1228
+ command.addCommand(createConfigListCommand());
1229
+ command.addCommand(createConfigEditCommand());
1230
+ command.addCommand(createConfigValidateCommand());
1231
+ return command;
1232
+ }
1233
+ function createConfigInitCommand() {
1234
+ const command = new import_commander4.Command("init");
1235
+ command.description("Initialize md2do configuration with interactive prompts").option("-g, --global", "Create global configuration in home directory").option(
1236
+ "--format <type>",
1237
+ "Config file format (json|yaml|js)",
1238
+ "json"
1239
+ ).option("--default-assignee <username>", "Default assignee username").option("--workday-start <time>", "Work day start time (HH:MM)").option("--workday-end <time>", "Work day end time (HH:MM)").option(
1240
+ "--default-due-time <when>",
1241
+ "Default due time (start|end)",
1242
+ "end"
1243
+ ).option(
1244
+ "--output-format <type>",
1245
+ "Output format (pretty|table|json)",
1246
+ "pretty"
1247
+ ).option("--no-colors", "Disable colored output").option(
1248
+ "--warnings <level>",
1249
+ "Warning level (recommended|strict|off)",
1250
+ "recommended"
1251
+ ).action(async (options) => {
1252
+ try {
1253
+ await configInitAction(options);
1254
+ } catch (error) {
1255
+ if (error instanceof Error && error.message === "canceled") {
1256
+ const p = await import("@clack/prompts");
1257
+ p.cancel("Configuration canceled");
1258
+ process.exit(0);
1259
+ }
1260
+ console.error(
1261
+ "Error:",
1262
+ error instanceof Error ? error.message : String(error)
1263
+ );
1264
+ process.exit(1);
1265
+ }
1266
+ });
1267
+ return command;
1268
+ }
1269
+ async function configInitAction(options) {
1270
+ const p = await import("@clack/prompts");
1271
+ p.intro("Welcome to md2do configuration!");
1272
+ const hasAnyOption = options.defaultAssignee !== void 0 || options.workdayStart !== void 0 || options.workdayEnd !== void 0 || options.defaultDueTime !== void 0 || options.outputFormat !== void 0 || options.colors !== void 0 || options.warnings !== void 0;
1273
+ const interactive = !hasAnyOption;
1274
+ const config = {};
1275
+ if (interactive) {
1276
+ const defaultAssignee = await p.text({
1277
+ message: "Your username (for filtering tasks):",
1278
+ placeholder: "Leave empty to skip",
1279
+ validate: (value) => {
1280
+ if (value && !/^[a-zA-Z0-9_-]+$/.test(value)) {
1281
+ return "Username should only contain letters, numbers, dashes, and underscores";
1282
+ }
1283
+ }
1284
+ });
1285
+ const workdayStart = await p.text({
1286
+ message: "Work day start time (HH:MM):",
1287
+ initialValue: "08:00",
1288
+ validate: (value) => {
1289
+ if (typeof value === "string" && !/^\d{2}:\d{2}$/.test(value)) {
1290
+ return "Time must be in HH:MM format";
1291
+ }
1292
+ }
1293
+ });
1294
+ const workdayEnd = await p.text({
1295
+ message: "Work day end time (HH:MM):",
1296
+ initialValue: "17:00",
1297
+ validate: (value) => {
1298
+ if (typeof value === "string" && !/^\d{2}:\d{2}$/.test(value)) {
1299
+ return "Time must be in HH:MM format";
1300
+ }
1301
+ }
1302
+ });
1303
+ const defaultDueTime = await p.select({
1304
+ message: "Default due time:",
1305
+ options: [
1306
+ { value: "end", label: "End of day" },
1307
+ { value: "start", label: "Start of day" }
1308
+ ],
1309
+ initialValue: "end"
1310
+ });
1311
+ const outputFormat = await p.select({
1312
+ message: "Output format:",
1313
+ options: [
1314
+ { value: "pretty", label: "Pretty (human-readable)" },
1315
+ { value: "table", label: "Table (structured)" },
1316
+ { value: "json", label: "JSON (machine-readable)" }
1317
+ ],
1318
+ initialValue: "pretty"
1319
+ });
1320
+ const colors = await p.confirm({
1321
+ message: "Enable colored output?",
1322
+ initialValue: true
1323
+ });
1324
+ const warnings = await p.select({
1325
+ message: "Warning level:",
1326
+ options: [
1327
+ {
1328
+ value: "recommended",
1329
+ label: "Recommended (validates format, metadata optional)"
1330
+ },
1331
+ {
1332
+ value: "strict",
1333
+ label: "Strict (enforces complete metadata)"
1334
+ },
1335
+ { value: "off", label: "Off (no warnings)" }
1336
+ ],
1337
+ initialValue: "recommended"
1338
+ });
1339
+ if (defaultAssignee && typeof defaultAssignee === "string") {
1340
+ config.defaultAssignee = defaultAssignee;
1341
+ }
1342
+ config.workday = {
1343
+ startTime: typeof workdayStart === "string" ? workdayStart : "08:00",
1344
+ endTime: typeof workdayEnd === "string" ? workdayEnd : "17:00",
1345
+ defaultDueTime: typeof defaultDueTime === "string" ? defaultDueTime : "end"
1346
+ };
1347
+ config.output = {
1348
+ format: typeof outputFormat === "string" ? outputFormat : "pretty",
1349
+ colors: typeof colors === "boolean" ? colors : true,
1350
+ paths: true
1351
+ };
1352
+ if (typeof warnings === "string" && warnings === "off") {
1353
+ config.warnings = { enabled: false };
1354
+ } else if (typeof warnings === "string" && warnings === "strict") {
1355
+ config.warnings = {
1356
+ enabled: true,
1357
+ rules: {
1358
+ "unsupported-bullet": "warn",
1359
+ "malformed-checkbox": "warn",
1360
+ "missing-space-after": "warn",
1361
+ "missing-space-before": "warn",
1362
+ "relative-date-no-context": "warn",
1363
+ "missing-due-date": "warn",
1364
+ "missing-completed-date": "warn",
1365
+ "duplicate-todoist-id": "error",
1366
+ "file-read-error": "error"
1367
+ }
1368
+ };
1369
+ }
1370
+ } else {
1371
+ if (options.defaultAssignee) {
1372
+ config.defaultAssignee = options.defaultAssignee;
1373
+ }
1374
+ config.workday = {
1375
+ startTime: options.workdayStart || "08:00",
1376
+ endTime: options.workdayEnd || "17:00",
1377
+ defaultDueTime: options.defaultDueTime || "end"
1378
+ };
1379
+ config.output = {
1380
+ format: options.outputFormat || "pretty",
1381
+ colors: options.colors ?? true,
1382
+ paths: true
1383
+ };
1384
+ if (options.warnings === "off") {
1385
+ config.warnings = { enabled: false };
1386
+ } else if (options.warnings === "strict") {
1387
+ config.warnings = {
1388
+ enabled: true,
1389
+ rules: {
1390
+ "unsupported-bullet": "warn",
1391
+ "malformed-checkbox": "warn",
1392
+ "missing-space-after": "warn",
1393
+ "missing-space-before": "warn",
1394
+ "relative-date-no-context": "warn",
1395
+ "missing-due-date": "warn",
1396
+ "missing-completed-date": "warn",
1397
+ "duplicate-todoist-id": "error",
1398
+ "file-read-error": "error"
1399
+ }
1400
+ };
1401
+ }
1402
+ }
1403
+ try {
1404
+ (0, import_config3.validateConfig)(config);
1405
+ } catch (error) {
1406
+ p.cancel(
1407
+ `Invalid configuration: ${error instanceof Error ? error.message : String(error)}`
1408
+ );
1409
+ process.exit(1);
1410
+ }
1411
+ const format = options.format || "json";
1412
+ const configPath = getConfigPath(options.global || false, format);
1413
+ if ((0, import_fs.existsSync)(configPath)) {
1414
+ if (interactive) {
1415
+ const overwrite = await p.confirm({
1416
+ message: `Configuration file already exists at ${configPath}. Overwrite?`,
1417
+ initialValue: false
1418
+ });
1419
+ if (!overwrite) {
1420
+ p.cancel("Configuration canceled");
1421
+ process.exit(0);
1422
+ }
1423
+ }
1424
+ }
1425
+ writeConfigFile(configPath, config, format);
1426
+ p.outro(`\u2713 Configuration saved to ${configPath}`);
1427
+ }
1428
+ function createConfigSetCommand() {
1429
+ const command = new import_commander4.Command("set");
1430
+ command.description("Set a configuration value").argument("<key>", "Configuration key (e.g., workday.startTime)").argument("<value>", "Configuration value").option("-g, --global", "Set in global configuration").action((key, value, options) => {
1431
+ try {
1432
+ configSetAction(key, value, options);
1433
+ } catch (error) {
1434
+ console.error(
1435
+ "Error:",
1436
+ error instanceof Error ? error.message : String(error)
1437
+ );
1438
+ process.exit(1);
1439
+ }
1440
+ });
1441
+ return command;
1442
+ }
1443
+ function configSetAction(key, value, options) {
1444
+ const isGlobal = options.global || false;
1445
+ const configPath = findExistingConfigPath(isGlobal);
1446
+ let config = {};
1447
+ let format = "json";
1448
+ if (configPath) {
1449
+ format = getConfigFormat(configPath);
1450
+ const content = (0, import_fs.readFileSync)(configPath, "utf-8");
1451
+ config = parseConfigFile(content, format);
1452
+ } else {
1453
+ format = "json";
1454
+ }
1455
+ const parsedValue = parseValue(value);
1456
+ setNestedValue(config, key, parsedValue);
1457
+ try {
1458
+ (0, import_config3.validateConfig)(config);
1459
+ } catch (error) {
1460
+ console.error(
1461
+ `Invalid configuration: ${error instanceof Error ? error.message : String(error)}`
1462
+ );
1463
+ process.exit(1);
1464
+ }
1465
+ const finalPath = configPath || getConfigPath(isGlobal, format);
1466
+ writeConfigFile(finalPath, config, format);
1467
+ console.log(`\u2713 Set ${key} = ${value} in ${finalPath}`);
1468
+ }
1469
+ function createConfigGetCommand() {
1470
+ const command = new import_commander4.Command("get");
1471
+ command.description("Get a configuration value").argument("<key>", "Configuration key (e.g., workday.startTime)").option("-g, --global", "Get from global configuration only").action(async (key, options) => {
1472
+ try {
1473
+ await configGetAction(key, options);
1474
+ } catch (error) {
1475
+ console.error(
1476
+ "Error:",
1477
+ error instanceof Error ? error.message : String(error)
1478
+ );
1479
+ process.exit(1);
1480
+ }
1481
+ });
1482
+ return command;
1483
+ }
1484
+ async function configGetAction(key, options) {
1485
+ const config = options.global ? await (0, import_config3.loadConfig)({ loadGlobal: true, loadEnv: false, cwd: (0, import_os.homedir)() }) : await (0, import_config3.loadConfig)();
1486
+ const value = getNestedValue(config, key);
1487
+ if (value === void 0) {
1488
+ console.log("(not set)");
1489
+ } else if (typeof value === "object") {
1490
+ console.log(JSON.stringify(value, null, 2));
1491
+ } else {
1492
+ console.log(value);
1493
+ }
1494
+ }
1495
+ function createConfigListCommand() {
1496
+ const command = new import_commander4.Command("list");
1497
+ command.description("Show all configuration values").option("--show-origin", "Show where each value comes from").action(async (options) => {
1498
+ try {
1499
+ await configListAction(options);
1500
+ } catch (error) {
1501
+ console.error(
1502
+ "Error:",
1503
+ error instanceof Error ? error.message : String(error)
1504
+ );
1505
+ process.exit(1);
1506
+ }
1507
+ });
1508
+ return command;
1509
+ }
1510
+ async function configListAction(options) {
1511
+ const config = await (0, import_config3.loadConfig)();
1512
+ console.log("");
1513
+ console.log("Current configuration:");
1514
+ console.log("");
1515
+ console.log(JSON.stringify(config, null, 2));
1516
+ console.log("");
1517
+ if (options.showOrigin) {
1518
+ console.log("Configuration sources:");
1519
+ console.log(" 1. Default values (built-in)");
1520
+ console.log(` 2. Global config (${getConfigPath(true, "json")})`);
1521
+ console.log(` 3. Project config (${getConfigPath(false, "json")})`);
1522
+ console.log(" 4. Environment variables");
1523
+ console.log("");
1524
+ }
1525
+ }
1526
+ function createConfigEditCommand() {
1527
+ const command = new import_commander4.Command("edit");
1528
+ command.description("Open configuration file in your default editor").option("-g, --global", "Edit global configuration").action((options) => {
1529
+ try {
1530
+ configEditAction(options);
1531
+ } catch (error) {
1532
+ console.error(
1533
+ "Error:",
1534
+ error instanceof Error ? error.message : String(error)
1535
+ );
1536
+ process.exit(1);
1537
+ }
1538
+ });
1539
+ return command;
1540
+ }
1541
+ function configEditAction(options) {
1542
+ const isGlobal = options.global || false;
1543
+ let configPath = findExistingConfigPath(isGlobal);
1544
+ if (!configPath) {
1545
+ configPath = getConfigPath(isGlobal, "json");
1546
+ writeConfigFile(configPath, {}, "json");
1547
+ console.log(`Created new configuration file: ${configPath}`);
1548
+ }
1549
+ const editor = process.env.VISUAL || process.env.EDITOR || (process.platform === "win32" ? "notepad" : "vi");
1550
+ console.log(`Opening ${configPath} in ${editor}...`);
1551
+ try {
1552
+ (0, import_child_process.execSync)(`${editor} '${configPath}'`, { stdio: "inherit" });
1553
+ } catch (error) {
1554
+ console.error("Failed to open editor");
1555
+ console.error(`You can manually edit the file at: ${configPath}`);
1556
+ process.exit(1);
1557
+ }
1558
+ }
1559
+ function createConfigValidateCommand() {
1560
+ const command = new import_commander4.Command("validate");
1561
+ command.description("Validate current configuration").action(async () => {
1562
+ try {
1563
+ await configValidateAction();
1564
+ } catch (error) {
1565
+ console.error(
1566
+ "Error:",
1567
+ error instanceof Error ? error.message : String(error)
1568
+ );
1569
+ process.exit(1);
1570
+ }
1571
+ });
1572
+ return command;
1573
+ }
1574
+ async function configValidateAction() {
1575
+ try {
1576
+ const config = await (0, import_config3.loadConfig)();
1577
+ (0, import_config3.validateConfig)(config);
1578
+ console.log("\u2713 Configuration is valid");
1579
+ } catch (error) {
1580
+ console.error(
1581
+ `Invalid configuration: ${error instanceof Error ? error.message : String(error)}`
1582
+ );
1583
+ process.exit(1);
1584
+ }
1585
+ }
1586
+ function getConfigPath(isGlobal, format) {
1587
+ const base = isGlobal ? (0, import_os.homedir)() : process.cwd();
1588
+ const fileName = format === "json" ? ".md2do.json" : format === "yaml" ? ".md2do.yaml" : ".md2do.js";
1589
+ return (0, import_path.join)(base, fileName);
1590
+ }
1591
+ function findExistingConfigPath(isGlobal) {
1592
+ const base = isGlobal ? (0, import_os.homedir)() : process.cwd();
1593
+ const possibleFiles = [
1594
+ ".md2do.json",
1595
+ ".md2do.yaml",
1596
+ ".md2do.yml",
1597
+ ".md2do.js",
1598
+ ".md2do.cjs",
1599
+ "md2do.config.js",
1600
+ "md2do.config.cjs"
1601
+ ];
1602
+ for (const file of possibleFiles) {
1603
+ const path = (0, import_path.join)(base, file);
1604
+ if ((0, import_fs.existsSync)(path)) {
1605
+ return path;
1606
+ }
1607
+ }
1608
+ return null;
1609
+ }
1610
+ function getConfigFormat(path) {
1611
+ if (path.endsWith(".json")) return "json";
1612
+ if (path.endsWith(".yaml") || path.endsWith(".yml")) return "yaml";
1613
+ return "js";
1614
+ }
1615
+ function parseConfigFile(content, format) {
1616
+ if (format === "json") {
1617
+ return JSON.parse(content);
1618
+ } else if (format === "yaml") {
1619
+ const loaded = import_js_yaml.default.load(content);
1620
+ return loaded || {};
1621
+ } else {
1622
+ return JSON.parse(content);
1623
+ }
1624
+ }
1625
+ function writeConfigFile(path, config, format) {
1626
+ let content;
1627
+ if (format === "json") {
1628
+ content = JSON.stringify(config, null, 2) + "\n";
1629
+ } else if (format === "yaml") {
1630
+ content = import_js_yaml.default.dump(config);
1631
+ } else {
1632
+ content = `module.exports = ${JSON.stringify(config, null, 2)};
1633
+ `;
1634
+ }
1635
+ (0, import_fs.writeFileSync)(path, content, "utf-8");
1636
+ }
1637
+ function parseValue(value) {
1638
+ if (value === "true") return true;
1639
+ if (value === "false") return false;
1640
+ const num = Number(value);
1641
+ if (!isNaN(num)) return num;
1642
+ try {
1643
+ return JSON.parse(value);
1644
+ } catch {
1645
+ return value;
1646
+ }
1647
+ }
1648
+ function getNestedValue(obj, path) {
1649
+ const keys = path.split(".");
1650
+ let current = obj;
1651
+ for (const key of keys) {
1652
+ if (current === null || current === void 0 || typeof current !== "object") {
1653
+ return void 0;
1654
+ }
1655
+ current = current[key];
1656
+ }
1657
+ return current;
1658
+ }
1659
+ function setNestedValue(obj, path, value) {
1660
+ const keys = path.split(".");
1661
+ const lastKey = keys.pop();
1662
+ if (!lastKey) return;
1663
+ let current = obj;
1664
+ for (const key of keys) {
1665
+ if (!(key in current) || typeof current[key] !== "object") {
1666
+ current[key] = {};
1667
+ }
1668
+ current = current[key];
1669
+ }
1670
+ current[lastKey] = value;
1671
+ }
1212
1672
  // Annotate the CommonJS export names for ESM import in node:
1213
1673
  0 && (module.exports = {
1674
+ createConfigCommand,
1214
1675
  createListCommand,
1215
1676
  createStatsCommand,
1216
1677
  createTodoistCommand
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@md2do/cli",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "CLI interface for md2do task manager",
5
5
  "keywords": [
6
6
  "markdown",
@@ -39,6 +39,7 @@
39
39
  "access": "public"
40
40
  },
41
41
  "dependencies": {
42
+ "@clack/prompts": "^1.0.0",
42
43
  "@doist/todoist-api-typescript": "^3.0.3",
43
44
  "chalk": "4.1.2",
44
45
  "cli-table3": "^0.6.5",
@@ -46,12 +47,14 @@
46
47
  "cosmiconfig": "^9.0.0",
47
48
  "date-fns": "^3.0.6",
48
49
  "fast-glob": "^3.3.3",
50
+ "js-yaml": "^4.1.1",
49
51
  "zod": "^3.22.4",
50
- "@md2do/config": "0.4.0",
51
52
  "@md2do/core": "0.4.0",
53
+ "@md2do/config": "0.4.0",
52
54
  "@md2do/todoist": "0.4.0"
53
55
  },
54
56
  "devDependencies": {
57
+ "@types/js-yaml": "^4.0.9",
55
58
  "tsup": "^8.0.1"
56
59
  },
57
60
  "scripts": {
package/src/cli.ts CHANGED
@@ -6,6 +6,7 @@ import {
6
6
  createListCommand,
7
7
  createStatsCommand,
8
8
  createTodoistCommand,
9
+ createConfigCommand,
9
10
  } from './commands/index.js';
10
11
 
11
12
  // Read version from package.json
@@ -33,6 +34,7 @@ program
33
34
  program.addCommand(createListCommand());
34
35
  program.addCommand(createStatsCommand());
35
36
  program.addCommand(createTodoistCommand());
37
+ program.addCommand(createConfigCommand());
36
38
 
37
39
  // Show help if no command specified
38
40
  if (process.argv.length === 2) {