@mindtnv/todoist-cli 0.3.0 → 0.4.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.
- package/dist/index.js +543 -184
- package/marketplace.json +16 -0
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -657,66 +657,59 @@ function createPaletteRegistry() {
|
|
|
657
657
|
}
|
|
658
658
|
|
|
659
659
|
// src/plugins/storage.ts
|
|
660
|
-
import {
|
|
661
|
-
import { mkdirSync as mkdirSync3, existsSync as existsSync3 } from "fs";
|
|
660
|
+
import { mkdirSync as mkdirSync3, existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
662
661
|
import { join as join3 } from "path";
|
|
662
|
+
function loadJson(path, fallback) {
|
|
663
|
+
if (!existsSync3(path))
|
|
664
|
+
return fallback;
|
|
665
|
+
try {
|
|
666
|
+
return JSON.parse(readFileSync3(path, "utf-8"));
|
|
667
|
+
} catch {
|
|
668
|
+
return fallback;
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
function saveJson(path, data) {
|
|
672
|
+
writeFileSync3(path, JSON.stringify(data, null, 2) + `
|
|
673
|
+
`);
|
|
674
|
+
}
|
|
663
675
|
function createPluginStorage(dataDir) {
|
|
664
676
|
if (!existsSync3(dataDir)) {
|
|
665
677
|
mkdirSync3(dataDir, { recursive: true });
|
|
666
678
|
}
|
|
667
|
-
const
|
|
668
|
-
const
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
const getStmt = db.prepare("SELECT value FROM kv WHERE key = ?");
|
|
672
|
-
const setStmt = db.prepare("INSERT OR REPLACE INTO kv (key, value) VALUES (?, ?)");
|
|
673
|
-
const delStmt = db.prepare("DELETE FROM kv WHERE key = ?");
|
|
674
|
-
const listStmt = db.prepare("SELECT key FROM kv WHERE key LIKE ? ESCAPE '\\'");
|
|
675
|
-
const getTaskStmt = db.prepare("SELECT value FROM task_data WHERE task_id = ? AND key = ?");
|
|
676
|
-
const setTaskStmt = db.prepare("INSERT OR REPLACE INTO task_data (task_id, key, value) VALUES (?, ?, ?)");
|
|
679
|
+
const kvPath = join3(dataDir, "kv.json");
|
|
680
|
+
const taskDataPath = join3(dataDir, "task-data.json");
|
|
681
|
+
let kv = loadJson(kvPath, {});
|
|
682
|
+
let taskData = loadJson(taskDataPath, {});
|
|
677
683
|
return {
|
|
678
684
|
async get(key) {
|
|
679
|
-
const
|
|
680
|
-
|
|
681
|
-
return null;
|
|
682
|
-
try {
|
|
683
|
-
return JSON.parse(row.value);
|
|
684
|
-
} catch {
|
|
685
|
-
console.warn("[plugin-storage] Corrupted data for key:", key);
|
|
686
|
-
return null;
|
|
687
|
-
}
|
|
685
|
+
const value = kv[key];
|
|
686
|
+
return value !== undefined ? value : null;
|
|
688
687
|
},
|
|
689
688
|
async set(key, value) {
|
|
690
|
-
|
|
691
|
-
|
|
689
|
+
kv[key] = value;
|
|
690
|
+
saveJson(kvPath, kv);
|
|
692
691
|
},
|
|
693
692
|
async delete(key) {
|
|
694
|
-
|
|
693
|
+
delete kv[key];
|
|
694
|
+
saveJson(kvPath, kv);
|
|
695
695
|
},
|
|
696
696
|
async list(prefix) {
|
|
697
|
-
const
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
return
|
|
697
|
+
const keys = Object.keys(kv);
|
|
698
|
+
if (!prefix)
|
|
699
|
+
return keys;
|
|
700
|
+
return keys.filter((k) => k.startsWith(prefix));
|
|
701
701
|
},
|
|
702
702
|
async getTaskData(taskId, key) {
|
|
703
|
-
const
|
|
704
|
-
|
|
705
|
-
return null;
|
|
706
|
-
try {
|
|
707
|
-
return JSON.parse(row.value);
|
|
708
|
-
} catch {
|
|
709
|
-
console.warn("[plugin-storage] Corrupted data for key:", `${taskId}:${key}`);
|
|
710
|
-
return null;
|
|
711
|
-
}
|
|
703
|
+
const value = taskData[taskId]?.[key];
|
|
704
|
+
return value !== undefined ? value : null;
|
|
712
705
|
},
|
|
713
706
|
async setTaskData(taskId, key, value) {
|
|
714
|
-
|
|
715
|
-
|
|
707
|
+
if (!taskData[taskId])
|
|
708
|
+
taskData[taskId] = {};
|
|
709
|
+
taskData[taskId][key] = value;
|
|
710
|
+
saveJson(taskDataPath, taskData);
|
|
716
711
|
},
|
|
717
|
-
close() {
|
|
718
|
-
db.close();
|
|
719
|
-
}
|
|
712
|
+
close() {}
|
|
720
713
|
};
|
|
721
714
|
}
|
|
722
715
|
var init_storage = () => {};
|
|
@@ -795,7 +788,7 @@ var init_api_proxy = __esm(() => {
|
|
|
795
788
|
|
|
796
789
|
// src/plugins/loader.ts
|
|
797
790
|
import { join as join4 } from "path";
|
|
798
|
-
import { existsSync as existsSync4, readFileSync as
|
|
791
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
|
|
799
792
|
import { homedir as homedir3 } from "os";
|
|
800
793
|
function createLogger(pluginName) {
|
|
801
794
|
return {
|
|
@@ -843,7 +836,7 @@ async function loadPlugins(hooks, views, extensions, palette) {
|
|
|
843
836
|
let manifest = null;
|
|
844
837
|
if (existsSync4(manifestPath)) {
|
|
845
838
|
try {
|
|
846
|
-
manifest = JSON.parse(
|
|
839
|
+
manifest = JSON.parse(readFileSync4(manifestPath, "utf-8"));
|
|
847
840
|
} catch (parseErr) {
|
|
848
841
|
console.warn(`[plugins] Invalid plugin.json for "${name}":`, parseErr instanceof Error ? parseErr.message : parseErr);
|
|
849
842
|
}
|
|
@@ -8482,13 +8475,13 @@ Examples:
|
|
|
8482
8475
|
}
|
|
8483
8476
|
let description = opts.description;
|
|
8484
8477
|
if (opts.editor) {
|
|
8485
|
-
const { writeFileSync:
|
|
8478
|
+
const { writeFileSync: writeFileSync4, readFileSync: readFileSync5 } = await import("node:fs");
|
|
8486
8479
|
const { spawnSync } = await import("node:child_process");
|
|
8487
8480
|
const tmpFile = `/tmp/todoist-desc-${Date.now()}.md`;
|
|
8488
|
-
|
|
8481
|
+
writeFileSync4(tmpFile, description ?? "");
|
|
8489
8482
|
const editor = process.env.EDITOR || process.env.VISUAL || "vi";
|
|
8490
8483
|
spawnSync(editor, [tmpFile], { stdio: "inherit" });
|
|
8491
|
-
description =
|
|
8484
|
+
description = readFileSync5(tmpFile, "utf-8");
|
|
8492
8485
|
if (description.trim() === "")
|
|
8493
8486
|
description = undefined;
|
|
8494
8487
|
}
|
|
@@ -9465,12 +9458,12 @@ function registerSectionCommand(program) {
|
|
|
9465
9458
|
|
|
9466
9459
|
// src/cli/completion.ts
|
|
9467
9460
|
init_config();
|
|
9468
|
-
import { existsSync as existsSync5, readFileSync as
|
|
9461
|
+
import { existsSync as existsSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
9469
9462
|
import { join as join5 } from "path";
|
|
9470
9463
|
import chalk18 from "chalk";
|
|
9471
9464
|
var COMPLETION_CACHE_PATH = join5(CONFIG_DIR, ".completion-cache.json");
|
|
9472
9465
|
function saveCompletionCache(cache) {
|
|
9473
|
-
|
|
9466
|
+
writeFileSync4(COMPLETION_CACHE_PATH, JSON.stringify(cache, null, 2), "utf-8");
|
|
9474
9467
|
}
|
|
9475
9468
|
var BASH_COMPLETION = `#!/usr/bin/env bash
|
|
9476
9469
|
# todoist CLI bash completion
|
|
@@ -10223,223 +10216,589 @@ function registerFilterCommand(program) {
|
|
|
10223
10216
|
// src/cli/plugin.ts
|
|
10224
10217
|
import chalk25 from "chalk";
|
|
10225
10218
|
|
|
10226
|
-
// src/plugins/
|
|
10219
|
+
// src/plugins/marketplace.ts
|
|
10227
10220
|
init_config();
|
|
10228
|
-
import { join as join6
|
|
10229
|
-
import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync5, rmSync, symlinkSync, lstatSync } from "fs";
|
|
10221
|
+
import { join as join6 } from "path";
|
|
10230
10222
|
import { homedir as homedir4 } from "os";
|
|
10223
|
+
import {
|
|
10224
|
+
existsSync as existsSync6,
|
|
10225
|
+
mkdirSync as mkdirSync4,
|
|
10226
|
+
readFileSync as readFileSync6,
|
|
10227
|
+
rmSync,
|
|
10228
|
+
cpSync
|
|
10229
|
+
} from "fs";
|
|
10231
10230
|
import { execSync } from "child_process";
|
|
10232
|
-
var
|
|
10233
|
-
|
|
10234
|
-
|
|
10235
|
-
|
|
10236
|
-
|
|
10231
|
+
var CONFIG_DIR3 = join6(homedir4(), ".config", "todoist-cli");
|
|
10232
|
+
var MARKETPLACE_CACHE_DIR = join6(CONFIG_DIR3, "marketplace-cache");
|
|
10233
|
+
var PLUGINS_DIR2 = join6(CONFIG_DIR3, "plugins");
|
|
10234
|
+
var DEFAULT_MARKETPLACE = "github:mindtnv/todoist-cli";
|
|
10235
|
+
var DEFAULT_MARKETPLACE_NAME = "todoist-cli-official";
|
|
10236
|
+
function ensureDir(dir) {
|
|
10237
|
+
if (!existsSync6(dir)) {
|
|
10238
|
+
mkdirSync4(dir, { recursive: true });
|
|
10239
|
+
}
|
|
10240
|
+
}
|
|
10241
|
+
function parseGitHubSource(source) {
|
|
10242
|
+
if (!source.startsWith("github:"))
|
|
10243
|
+
return null;
|
|
10244
|
+
const parts = source.replace("github:", "").split("/");
|
|
10245
|
+
if (parts.length < 2 || !parts[0] || !parts[1])
|
|
10246
|
+
return null;
|
|
10247
|
+
return { user: parts[0], repo: parts[1] };
|
|
10237
10248
|
}
|
|
10238
|
-
function
|
|
10249
|
+
function deriveNameFromSource(source) {
|
|
10239
10250
|
if (source.startsWith("github:")) {
|
|
10240
10251
|
const parts = source.replace("github:", "").split("/");
|
|
10241
10252
|
return parts[parts.length - 1] ?? source;
|
|
10242
10253
|
}
|
|
10243
|
-
|
|
10244
|
-
|
|
10254
|
+
try {
|
|
10255
|
+
const url = new URL(source);
|
|
10256
|
+
const segments = url.pathname.split("/").filter(Boolean);
|
|
10257
|
+
return segments[segments.length - 1] ?? source;
|
|
10258
|
+
} catch {
|
|
10259
|
+
return source.split("/").pop() ?? source;
|
|
10245
10260
|
}
|
|
10246
|
-
return source.split("/").pop() ?? source;
|
|
10247
10261
|
}
|
|
10248
|
-
|
|
10249
|
-
|
|
10250
|
-
|
|
10251
|
-
|
|
10252
|
-
|
|
10253
|
-
|
|
10262
|
+
function isExternalSource(source) {
|
|
10263
|
+
return typeof source === "object" && source !== null && "type" in source;
|
|
10264
|
+
}
|
|
10265
|
+
function getRegisteredMarketplaces() {
|
|
10266
|
+
const config = getConfig();
|
|
10267
|
+
const marketplaces = [];
|
|
10268
|
+
marketplaces.push({
|
|
10269
|
+
name: DEFAULT_MARKETPLACE_NAME,
|
|
10270
|
+
source: DEFAULT_MARKETPLACE,
|
|
10271
|
+
autoUpdate: true
|
|
10272
|
+
});
|
|
10273
|
+
const configMarketplaces = config.marketplaces;
|
|
10274
|
+
if (configMarketplaces) {
|
|
10275
|
+
for (const [name, entry] of Object.entries(configMarketplaces)) {
|
|
10276
|
+
if (name === DEFAULT_MARKETPLACE_NAME)
|
|
10277
|
+
continue;
|
|
10278
|
+
marketplaces.push({
|
|
10279
|
+
name,
|
|
10280
|
+
source: entry.source ?? "",
|
|
10281
|
+
autoUpdate: entry.autoUpdate ?? true
|
|
10282
|
+
});
|
|
10283
|
+
}
|
|
10254
10284
|
}
|
|
10255
|
-
|
|
10256
|
-
|
|
10257
|
-
|
|
10258
|
-
|
|
10259
|
-
|
|
10260
|
-
|
|
10261
|
-
|
|
10285
|
+
return marketplaces;
|
|
10286
|
+
}
|
|
10287
|
+
function addMarketplace(source) {
|
|
10288
|
+
const name = deriveNameFromSource(source);
|
|
10289
|
+
if (name === DEFAULT_MARKETPLACE_NAME) {
|
|
10290
|
+
throw new Error(`Cannot add marketplace with reserved name "${DEFAULT_MARKETPLACE_NAME}".`);
|
|
10291
|
+
}
|
|
10292
|
+
const config = getConfig();
|
|
10293
|
+
const rawConfig = config;
|
|
10294
|
+
if (!rawConfig.marketplaces) {
|
|
10295
|
+
rawConfig.marketplaces = {};
|
|
10296
|
+
}
|
|
10297
|
+
const marketplaces = rawConfig.marketplaces;
|
|
10298
|
+
marketplaces[name] = { source, autoUpdate: true };
|
|
10299
|
+
saveConfig(config);
|
|
10300
|
+
return name;
|
|
10301
|
+
}
|
|
10302
|
+
function removeMarketplace(name) {
|
|
10303
|
+
if (name === DEFAULT_MARKETPLACE_NAME) {
|
|
10304
|
+
throw new Error(`Cannot remove the default marketplace "${DEFAULT_MARKETPLACE_NAME}".`);
|
|
10305
|
+
}
|
|
10306
|
+
const config = getConfig();
|
|
10307
|
+
const rawConfig = config;
|
|
10308
|
+
const marketplaces = rawConfig.marketplaces;
|
|
10309
|
+
if (!marketplaces || !(name in marketplaces)) {
|
|
10310
|
+
throw new Error(`Marketplace "${name}" is not registered.`);
|
|
10311
|
+
}
|
|
10312
|
+
delete marketplaces[name];
|
|
10313
|
+
saveConfig(config);
|
|
10314
|
+
const cacheDir = join6(MARKETPLACE_CACHE_DIR, name);
|
|
10315
|
+
if (existsSync6(cacheDir)) {
|
|
10316
|
+
rmSync(cacheDir, { recursive: true, force: true });
|
|
10317
|
+
}
|
|
10318
|
+
}
|
|
10319
|
+
async function fetchMarketplaceManifest(config) {
|
|
10320
|
+
ensureDir(MARKETPLACE_CACHE_DIR);
|
|
10321
|
+
const github = parseGitHubSource(config.source);
|
|
10322
|
+
if (github) {
|
|
10323
|
+
const cacheDir = join6(MARKETPLACE_CACHE_DIR, config.name);
|
|
10324
|
+
if (existsSync6(cacheDir)) {
|
|
10325
|
+
try {
|
|
10326
|
+
execSync(`git -C "${cacheDir}" pull`, { stdio: "pipe" });
|
|
10327
|
+
} catch {}
|
|
10328
|
+
} else {
|
|
10329
|
+
execSync(`git clone https://github.com/${github.user}/${github.repo}.git "${cacheDir}"`, { stdio: "pipe" });
|
|
10330
|
+
}
|
|
10331
|
+
const manifestPath2 = join6(cacheDir, "marketplace.json");
|
|
10332
|
+
if (!existsSync6(manifestPath2)) {
|
|
10333
|
+
throw new Error(`Marketplace "${config.name}" does not contain a marketplace.json file.`);
|
|
10334
|
+
}
|
|
10335
|
+
const raw2 = readFileSync6(manifestPath2, "utf-8");
|
|
10336
|
+
return JSON.parse(raw2);
|
|
10337
|
+
}
|
|
10338
|
+
if (config.source.startsWith("http://") || config.source.startsWith("https://")) {
|
|
10339
|
+
const response = await fetch(config.source);
|
|
10340
|
+
if (!response.ok) {
|
|
10341
|
+
throw new Error(`Failed to fetch marketplace manifest from ${config.source}: ${response.statusText}`);
|
|
10342
|
+
}
|
|
10343
|
+
return await response.json();
|
|
10344
|
+
}
|
|
10345
|
+
const manifestPath = join6(config.source, "marketplace.json");
|
|
10346
|
+
if (!existsSync6(manifestPath)) {
|
|
10347
|
+
throw new Error(`Marketplace at "${config.source}" does not contain a marketplace.json file.`);
|
|
10348
|
+
}
|
|
10349
|
+
const raw = readFileSync6(manifestPath, "utf-8");
|
|
10350
|
+
return JSON.parse(raw);
|
|
10351
|
+
}
|
|
10352
|
+
async function discoverPlugins() {
|
|
10353
|
+
const marketplaces = getRegisteredMarketplaces();
|
|
10354
|
+
const discovered = [];
|
|
10355
|
+
const config = getConfig();
|
|
10356
|
+
const installedPlugins = config.plugins ?? {};
|
|
10357
|
+
for (const marketplace of marketplaces) {
|
|
10358
|
+
try {
|
|
10359
|
+
const manifest = await fetchMarketplaceManifest(marketplace);
|
|
10360
|
+
for (const plugin of manifest.plugins) {
|
|
10361
|
+
const isInstalled = plugin.name in installedPlugins;
|
|
10362
|
+
const pluginConfig = installedPlugins[plugin.name];
|
|
10363
|
+
const isEnabled = isInstalled ? pluginConfig?.enabled !== false : false;
|
|
10364
|
+
discovered.push({
|
|
10365
|
+
...plugin,
|
|
10366
|
+
marketplace: marketplace.name,
|
|
10367
|
+
installed: isInstalled,
|
|
10368
|
+
enabled: isEnabled
|
|
10369
|
+
});
|
|
10370
|
+
}
|
|
10371
|
+
} catch {
|
|
10372
|
+
continue;
|
|
10373
|
+
}
|
|
10374
|
+
}
|
|
10375
|
+
return discovered;
|
|
10376
|
+
}
|
|
10377
|
+
async function installPlugin(pluginName, marketplaceName) {
|
|
10378
|
+
ensureDir(PLUGINS_DIR2);
|
|
10379
|
+
const allPlugins = await discoverPlugins();
|
|
10380
|
+
let candidates = allPlugins.filter((p) => p.name === pluginName);
|
|
10381
|
+
if (marketplaceName) {
|
|
10382
|
+
candidates = candidates.filter((p) => p.marketplace === marketplaceName);
|
|
10383
|
+
}
|
|
10384
|
+
if (candidates.length === 0) {
|
|
10385
|
+
throw new Error(`Plugin "${pluginName}" not found${marketplaceName ? ` in marketplace "${marketplaceName}"` : ""}.`);
|
|
10386
|
+
}
|
|
10387
|
+
const plugin = candidates[0];
|
|
10388
|
+
if (plugin.installed) {
|
|
10389
|
+
throw new Error(`Plugin "${pluginName}" is already installed. Use "todoist plugin remove ${pluginName}" first.`);
|
|
10390
|
+
}
|
|
10391
|
+
const targetDir = join6(PLUGINS_DIR2, pluginName);
|
|
10392
|
+
if (isExternalSource(plugin.source)) {
|
|
10393
|
+
resolveExternalSource(plugin.source, targetDir);
|
|
10262
10394
|
} else {
|
|
10263
|
-
const
|
|
10264
|
-
if (
|
|
10265
|
-
|
|
10266
|
-
|
|
10395
|
+
const source = plugin.source;
|
|
10396
|
+
if (source.startsWith("./") || source.startsWith("../")) {
|
|
10397
|
+
const cacheDir = join6(MARKETPLACE_CACHE_DIR, plugin.marketplace);
|
|
10398
|
+
const sourcePath = join6(cacheDir, source);
|
|
10399
|
+
if (!existsSync6(sourcePath)) {
|
|
10400
|
+
throw new Error(`Plugin source path not found: ${sourcePath}`);
|
|
10401
|
+
}
|
|
10402
|
+
ensureDir(targetDir);
|
|
10403
|
+
cpSync(sourcePath, targetDir, { recursive: true });
|
|
10404
|
+
} else if (source.startsWith("github:")) {
|
|
10405
|
+
const github = parseGitHubSource(source);
|
|
10406
|
+
if (!github)
|
|
10407
|
+
throw new Error(`Invalid GitHub source: ${source}`);
|
|
10408
|
+
execSync(`git clone https://github.com/${github.user}/${github.repo}.git "${targetDir}"`, { stdio: "pipe" });
|
|
10409
|
+
} else {
|
|
10410
|
+
if (!existsSync6(source)) {
|
|
10411
|
+
throw new Error(`Plugin source path not found: ${source}`);
|
|
10412
|
+
}
|
|
10413
|
+
ensureDir(targetDir);
|
|
10414
|
+
cpSync(source, targetDir, { recursive: true });
|
|
10415
|
+
}
|
|
10267
10416
|
}
|
|
10268
|
-
|
|
10269
|
-
if (!isSymlink && existsSync6(join6(targetDir, "package.json"))) {
|
|
10417
|
+
if (existsSync6(join6(targetDir, "package.json"))) {
|
|
10270
10418
|
try {
|
|
10271
10419
|
execSync(`cd "${targetDir}" && bun install`, { stdio: "pipe" });
|
|
10272
10420
|
} catch {
|
|
10273
|
-
|
|
10421
|
+
try {
|
|
10422
|
+
execSync(`cd "${targetDir}" && npm install`, { stdio: "pipe" });
|
|
10423
|
+
} catch {}
|
|
10274
10424
|
}
|
|
10275
10425
|
}
|
|
10276
|
-
|
|
10277
|
-
|
|
10278
|
-
|
|
10279
|
-
|
|
10280
|
-
name = manifest.name;
|
|
10281
|
-
}
|
|
10282
|
-
setPluginEntry(name, { source });
|
|
10426
|
+
setPluginEntry(pluginName, {
|
|
10427
|
+
source: `${pluginName}@${plugin.marketplace}`,
|
|
10428
|
+
enabled: true
|
|
10429
|
+
});
|
|
10283
10430
|
return {
|
|
10284
|
-
name,
|
|
10285
|
-
version:
|
|
10286
|
-
|
|
10287
|
-
|
|
10431
|
+
name: pluginName,
|
|
10432
|
+
version: plugin.version ?? "unknown",
|
|
10433
|
+
marketplace: plugin.marketplace,
|
|
10434
|
+
description: plugin.description
|
|
10288
10435
|
};
|
|
10289
10436
|
}
|
|
10437
|
+
function resolveExternalSource(source, targetDir) {
|
|
10438
|
+
switch (source.type) {
|
|
10439
|
+
case "github": {
|
|
10440
|
+
if (!source.repo)
|
|
10441
|
+
throw new Error("GitHub source requires a 'repo' field.");
|
|
10442
|
+
const ref = source.ref ? ` --branch ${source.ref}` : "";
|
|
10443
|
+
execSync(`git clone https://github.com/${source.repo}.git${ref} "${targetDir}"`, { stdio: "pipe" });
|
|
10444
|
+
if (source.sha) {
|
|
10445
|
+
execSync(`git -C "${targetDir}" checkout ${source.sha}`, {
|
|
10446
|
+
stdio: "pipe"
|
|
10447
|
+
});
|
|
10448
|
+
}
|
|
10449
|
+
break;
|
|
10450
|
+
}
|
|
10451
|
+
case "git": {
|
|
10452
|
+
if (!source.url)
|
|
10453
|
+
throw new Error("Git source requires a 'url' field.");
|
|
10454
|
+
const ref = source.ref ? ` --branch ${source.ref}` : "";
|
|
10455
|
+
execSync(`git clone ${source.url}${ref} "${targetDir}"`, {
|
|
10456
|
+
stdio: "pipe"
|
|
10457
|
+
});
|
|
10458
|
+
if (source.sha) {
|
|
10459
|
+
execSync(`git -C "${targetDir}" checkout ${source.sha}`, {
|
|
10460
|
+
stdio: "pipe"
|
|
10461
|
+
});
|
|
10462
|
+
}
|
|
10463
|
+
break;
|
|
10464
|
+
}
|
|
10465
|
+
case "npm": {
|
|
10466
|
+
if (!source.package)
|
|
10467
|
+
throw new Error("npm source requires a 'package' field.");
|
|
10468
|
+
mkdirSync4(targetDir, { recursive: true });
|
|
10469
|
+
execSync(`cd "${targetDir}" && npm init -y && npm install ${source.package}`, { stdio: "pipe" });
|
|
10470
|
+
break;
|
|
10471
|
+
}
|
|
10472
|
+
default:
|
|
10473
|
+
throw new Error(`Unknown source type: ${source.type}`);
|
|
10474
|
+
}
|
|
10475
|
+
}
|
|
10290
10476
|
function removePlugin(name) {
|
|
10291
10477
|
const targetDir = join6(PLUGINS_DIR2, name);
|
|
10292
|
-
if (
|
|
10293
|
-
throw new Error(`Plugin "${name}" is not installed.`);
|
|
10294
|
-
}
|
|
10295
|
-
if (lstatSync(targetDir).isSymbolicLink()) {
|
|
10296
|
-
rmSync(targetDir);
|
|
10297
|
-
} else {
|
|
10478
|
+
if (existsSync6(targetDir)) {
|
|
10298
10479
|
rmSync(targetDir, { recursive: true, force: true });
|
|
10299
10480
|
}
|
|
10300
10481
|
removePluginEntry(name);
|
|
10301
10482
|
}
|
|
10302
|
-
function
|
|
10483
|
+
async function updatePlugin(name) {
|
|
10303
10484
|
const config = getConfig();
|
|
10304
10485
|
const plugins = config.plugins;
|
|
10305
10486
|
if (!plugins?.[name]) {
|
|
10306
10487
|
throw new Error(`Plugin "${name}" is not installed.`);
|
|
10307
10488
|
}
|
|
10308
|
-
const
|
|
10309
|
-
|
|
10310
|
-
|
|
10311
|
-
|
|
10312
|
-
const config = getConfig();
|
|
10313
|
-
const plugins = config.plugins;
|
|
10314
|
-
if (!plugins?.[name]) {
|
|
10315
|
-
throw new Error(`Plugin "${name}" is not installed.`);
|
|
10489
|
+
const pluginEntry = plugins[name];
|
|
10490
|
+
const sourceStr = pluginEntry.source;
|
|
10491
|
+
if (!sourceStr) {
|
|
10492
|
+
return { name, updated: false, message: "No source information found" };
|
|
10316
10493
|
}
|
|
10317
|
-
|
|
10318
|
-
|
|
10319
|
-
|
|
10320
|
-
|
|
10321
|
-
const plugins = config.plugins;
|
|
10322
|
-
if (!plugins?.[name]) {
|
|
10323
|
-
throw new Error(`Plugin "${name}" is not installed.`);
|
|
10494
|
+
const atIndex = sourceStr.lastIndexOf("@");
|
|
10495
|
+
const marketplaceName = atIndex > 0 ? sourceStr.substring(atIndex + 1) : undefined;
|
|
10496
|
+
if (!marketplaceName) {
|
|
10497
|
+
return { name, updated: false, message: "Cannot determine marketplace for plugin" };
|
|
10324
10498
|
}
|
|
10325
|
-
|
|
10326
|
-
|
|
10327
|
-
|
|
10499
|
+
await refreshMarketplaceCache(marketplaceName);
|
|
10500
|
+
const marketplaces = getRegisteredMarketplaces();
|
|
10501
|
+
const marketplace = marketplaces.find((m) => m.name === marketplaceName);
|
|
10502
|
+
if (!marketplace) {
|
|
10503
|
+
return { name, updated: false, message: `Marketplace "${marketplaceName}" not found` };
|
|
10328
10504
|
}
|
|
10329
|
-
|
|
10330
|
-
|
|
10331
|
-
|
|
10332
|
-
|
|
10505
|
+
let manifest;
|
|
10506
|
+
try {
|
|
10507
|
+
manifest = await fetchMarketplaceManifest(marketplace);
|
|
10508
|
+
} catch {
|
|
10509
|
+
return { name, updated: false, message: `Failed to fetch marketplace "${marketplaceName}"` };
|
|
10333
10510
|
}
|
|
10334
|
-
|
|
10335
|
-
|
|
10336
|
-
|
|
10337
|
-
|
|
10338
|
-
|
|
10339
|
-
|
|
10340
|
-
|
|
10511
|
+
const pluginManifest = manifest.plugins.find((p) => p.name === name);
|
|
10512
|
+
if (!pluginManifest) {
|
|
10513
|
+
return { name, updated: false, message: `Plugin "${name}" no longer in marketplace` };
|
|
10514
|
+
}
|
|
10515
|
+
const oldVersion = pluginEntry.version ?? "unknown";
|
|
10516
|
+
const newVersion = pluginManifest.version ?? "unknown";
|
|
10517
|
+
const targetDir = join6(PLUGINS_DIR2, name);
|
|
10518
|
+
if (isExternalSource(pluginManifest.source)) {
|
|
10519
|
+
if (pluginManifest.source.type === "github" && existsSync6(join6(targetDir, ".git"))) {
|
|
10520
|
+
execSync(`git -C "${targetDir}" pull`, { stdio: "pipe" });
|
|
10521
|
+
} else {
|
|
10522
|
+
if (existsSync6(targetDir)) {
|
|
10523
|
+
rmSync(targetDir, { recursive: true, force: true });
|
|
10524
|
+
}
|
|
10525
|
+
resolveExternalSource(pluginManifest.source, targetDir);
|
|
10526
|
+
}
|
|
10527
|
+
} else {
|
|
10528
|
+
const source = pluginManifest.source;
|
|
10529
|
+
if (source.startsWith("./") || source.startsWith("../")) {
|
|
10530
|
+
const cacheDir = join6(MARKETPLACE_CACHE_DIR, marketplaceName);
|
|
10531
|
+
const sourcePath = join6(cacheDir, source);
|
|
10532
|
+
if (existsSync6(targetDir)) {
|
|
10533
|
+
rmSync(targetDir, { recursive: true, force: true });
|
|
10341
10534
|
}
|
|
10535
|
+
ensureDir(targetDir);
|
|
10536
|
+
cpSync(sourcePath, targetDir, { recursive: true });
|
|
10537
|
+
} else if (existsSync6(join6(targetDir, ".git"))) {
|
|
10538
|
+
execSync(`git -C "${targetDir}" pull`, { stdio: "pipe" });
|
|
10342
10539
|
}
|
|
10343
|
-
return { name, updated: true, message: "Pulled latest from git" };
|
|
10344
10540
|
}
|
|
10345
|
-
if (
|
|
10346
|
-
|
|
10347
|
-
|
|
10348
|
-
|
|
10541
|
+
if (existsSync6(join6(targetDir, "package.json"))) {
|
|
10542
|
+
try {
|
|
10543
|
+
execSync(`cd "${targetDir}" && bun install`, { stdio: "pipe" });
|
|
10544
|
+
} catch {
|
|
10545
|
+
try {
|
|
10546
|
+
execSync(`cd "${targetDir}" && npm install`, { stdio: "pipe" });
|
|
10547
|
+
} catch {}
|
|
10548
|
+
}
|
|
10349
10549
|
}
|
|
10350
|
-
|
|
10550
|
+
setPluginEntry(name, {
|
|
10551
|
+
...pluginEntry,
|
|
10552
|
+
version: newVersion
|
|
10553
|
+
});
|
|
10554
|
+
const updated = oldVersion !== newVersion;
|
|
10555
|
+
return {
|
|
10556
|
+
name,
|
|
10557
|
+
updated,
|
|
10558
|
+
oldVersion,
|
|
10559
|
+
newVersion,
|
|
10560
|
+
message: updated ? `Updated from ${oldVersion} to ${newVersion}` : "Already at latest version"
|
|
10561
|
+
};
|
|
10351
10562
|
}
|
|
10352
|
-
function
|
|
10563
|
+
async function updateAllPlugins() {
|
|
10353
10564
|
const config = getConfig();
|
|
10354
10565
|
const plugins = config.plugins;
|
|
10355
10566
|
if (!plugins)
|
|
10356
10567
|
return [];
|
|
10357
|
-
|
|
10358
|
-
|
|
10359
|
-
|
|
10360
|
-
|
|
10361
|
-
|
|
10568
|
+
const results = [];
|
|
10569
|
+
for (const name of Object.keys(plugins)) {
|
|
10570
|
+
try {
|
|
10571
|
+
const result = await updatePlugin(name);
|
|
10572
|
+
results.push(result);
|
|
10573
|
+
} catch (err) {
|
|
10574
|
+
results.push({
|
|
10575
|
+
name,
|
|
10576
|
+
updated: false,
|
|
10577
|
+
message: err instanceof Error ? err.message : "Unknown error"
|
|
10578
|
+
});
|
|
10579
|
+
}
|
|
10580
|
+
}
|
|
10581
|
+
return results;
|
|
10582
|
+
}
|
|
10583
|
+
async function refreshMarketplaceCache(name) {
|
|
10584
|
+
const marketplaces = getRegisteredMarketplaces();
|
|
10585
|
+
const targets = name ? marketplaces.filter((m) => m.name === name) : marketplaces;
|
|
10586
|
+
for (const marketplace of targets) {
|
|
10587
|
+
const github = parseGitHubSource(marketplace.source);
|
|
10588
|
+
if (!github)
|
|
10589
|
+
continue;
|
|
10590
|
+
const cacheDir = join6(MARKETPLACE_CACHE_DIR, marketplace.name);
|
|
10591
|
+
if (existsSync6(cacheDir)) {
|
|
10592
|
+
try {
|
|
10593
|
+
execSync(`git -C "${cacheDir}" pull`, { stdio: "pipe" });
|
|
10594
|
+
} catch {}
|
|
10595
|
+
} else {
|
|
10596
|
+
try {
|
|
10597
|
+
execSync(`git clone https://github.com/${github.user}/${github.repo}.git "${cacheDir}"`, { stdio: "pipe" });
|
|
10598
|
+
} catch {}
|
|
10599
|
+
}
|
|
10600
|
+
}
|
|
10601
|
+
}
|
|
10602
|
+
function enablePlugin(name) {
|
|
10603
|
+
const config = getConfig();
|
|
10604
|
+
const plugins = config.plugins;
|
|
10605
|
+
if (!plugins?.[name]) {
|
|
10606
|
+
throw new Error(`Plugin "${name}" is not installed.`);
|
|
10607
|
+
}
|
|
10608
|
+
const entry = plugins[name];
|
|
10609
|
+
const { enabled: _enabled, ...rest } = entry;
|
|
10610
|
+
setPluginEntry(name, rest);
|
|
10611
|
+
}
|
|
10612
|
+
function disablePlugin(name) {
|
|
10613
|
+
const config = getConfig();
|
|
10614
|
+
const plugins = config.plugins;
|
|
10615
|
+
if (!plugins?.[name]) {
|
|
10616
|
+
throw new Error(`Plugin "${name}" is not installed.`);
|
|
10617
|
+
}
|
|
10618
|
+
setPluginEntry(name, { ...plugins[name], enabled: false });
|
|
10362
10619
|
}
|
|
10363
10620
|
|
|
10364
10621
|
// src/cli/plugin.ts
|
|
10622
|
+
init_config();
|
|
10365
10623
|
function registerPluginCommand(program) {
|
|
10366
|
-
const plugin = program.command("plugin").description("Manage plugins");
|
|
10367
|
-
plugin.command("
|
|
10624
|
+
const plugin = program.command("plugin").description("Manage plugins and marketplaces");
|
|
10625
|
+
plugin.command("list").description("List installed plugins").action(() => {
|
|
10626
|
+
try {
|
|
10627
|
+
const config = getConfig();
|
|
10628
|
+
const plugins = config.plugins;
|
|
10629
|
+
if (!plugins || Object.keys(plugins).length === 0) {
|
|
10630
|
+
console.log(chalk25.dim("No plugins installed."));
|
|
10631
|
+
console.log(chalk25.dim("Discover plugins with: todoist plugin discover"));
|
|
10632
|
+
return;
|
|
10633
|
+
}
|
|
10634
|
+
console.log(chalk25.bold("Installed Plugins"));
|
|
10635
|
+
console.log("");
|
|
10636
|
+
for (const [name, entry] of Object.entries(plugins)) {
|
|
10637
|
+
const isEnabled = entry.enabled !== false;
|
|
10638
|
+
const status = isEnabled ? chalk25.green("●") : chalk25.yellow("○");
|
|
10639
|
+
const statusLabel = isEnabled ? chalk25.green("enabled") : chalk25.yellow("disabled");
|
|
10640
|
+
const version = entry.version ?? "unknown";
|
|
10641
|
+
const source = entry.source ?? "";
|
|
10642
|
+
console.log(` ${status} ${chalk25.bold(name)} ${chalk25.cyan("v" + version)} ${statusLabel} ${chalk25.dim(source)}`);
|
|
10643
|
+
}
|
|
10644
|
+
} catch (err) {
|
|
10645
|
+
console.error(chalk25.red(`Error: ${err instanceof Error ? err.message : String(err)}`));
|
|
10646
|
+
process.exit(1);
|
|
10647
|
+
}
|
|
10648
|
+
});
|
|
10649
|
+
plugin.command("discover").description("Browse available plugins from all marketplaces").action(async () => {
|
|
10368
10650
|
try {
|
|
10369
|
-
console.log(chalk25.dim(
|
|
10370
|
-
const
|
|
10371
|
-
|
|
10651
|
+
console.log(chalk25.dim("Fetching plugins from marketplaces..."));
|
|
10652
|
+
const discovered = await discoverPlugins();
|
|
10653
|
+
if (discovered.length === 0) {
|
|
10654
|
+
console.log(chalk25.dim("No plugins found in any marketplace."));
|
|
10655
|
+
return;
|
|
10656
|
+
}
|
|
10657
|
+
const grouped = new Map;
|
|
10658
|
+
for (const plugin2 of discovered) {
|
|
10659
|
+
const group = grouped.get(plugin2.marketplace) ?? [];
|
|
10660
|
+
group.push(plugin2);
|
|
10661
|
+
grouped.set(plugin2.marketplace, group);
|
|
10662
|
+
}
|
|
10663
|
+
for (const [marketplace2, plugins] of grouped) {
|
|
10664
|
+
console.log("");
|
|
10665
|
+
console.log(chalk25.bold.underline(marketplace2));
|
|
10666
|
+
console.log("");
|
|
10667
|
+
for (const p of plugins) {
|
|
10668
|
+
let indicator;
|
|
10669
|
+
if (p.installed && p.enabled) {
|
|
10670
|
+
indicator = chalk25.green("●");
|
|
10671
|
+
} else if (p.installed && !p.enabled) {
|
|
10672
|
+
indicator = chalk25.yellow("◐");
|
|
10673
|
+
} else {
|
|
10674
|
+
indicator = chalk25.dim("○");
|
|
10675
|
+
}
|
|
10676
|
+
const version = p.version ? chalk25.cyan("v" + p.version) : "";
|
|
10677
|
+
const description = p.description ? chalk25.dim(p.description) : "";
|
|
10678
|
+
console.log(` ${indicator} ${chalk25.bold(p.name)} ${version} ${description}`);
|
|
10679
|
+
}
|
|
10680
|
+
}
|
|
10681
|
+
} catch (err) {
|
|
10682
|
+
console.error(chalk25.red(`Error: ${err instanceof Error ? err.message : String(err)}`));
|
|
10683
|
+
process.exit(1);
|
|
10684
|
+
}
|
|
10685
|
+
});
|
|
10686
|
+
plugin.command("install").description("Install a plugin (optionally name@marketplace)").argument("<name>", "Plugin name (or name@marketplace)").action(async (nameArg) => {
|
|
10687
|
+
try {
|
|
10688
|
+
let pluginName = nameArg;
|
|
10689
|
+
let marketplaceName;
|
|
10690
|
+
const atIndex = nameArg.lastIndexOf("@");
|
|
10691
|
+
if (atIndex > 0) {
|
|
10692
|
+
pluginName = nameArg.substring(0, atIndex);
|
|
10693
|
+
marketplaceName = nameArg.substring(atIndex + 1);
|
|
10694
|
+
}
|
|
10695
|
+
console.log(chalk25.dim(`Installing plugin "${pluginName}"...`));
|
|
10696
|
+
const result = await installPlugin(pluginName, marketplaceName);
|
|
10697
|
+
console.log(chalk25.green(`Installed ${result.name} v${result.version} from ${result.marketplace}`));
|
|
10372
10698
|
if (result.description) {
|
|
10373
10699
|
console.log(chalk25.dim(` ${result.description}`));
|
|
10374
10700
|
}
|
|
10375
|
-
if (result.permissions?.length) {
|
|
10376
|
-
console.log(chalk25.dim(` Permissions: ${result.permissions.join(", ")}`));
|
|
10377
|
-
}
|
|
10378
10701
|
} catch (err) {
|
|
10379
|
-
console.error(chalk25.red(`Failed to install: ${err instanceof Error ? err.message : err}`));
|
|
10702
|
+
console.error(chalk25.red(`Failed to install: ${err instanceof Error ? err.message : String(err)}`));
|
|
10380
10703
|
process.exit(1);
|
|
10381
10704
|
}
|
|
10382
10705
|
});
|
|
10383
10706
|
plugin.command("remove").description("Remove an installed plugin").argument("<name>", "Plugin name").action((name) => {
|
|
10384
10707
|
try {
|
|
10385
10708
|
removePlugin(name);
|
|
10386
|
-
console.log(chalk25.green(
|
|
10709
|
+
console.log(chalk25.green(`Removed ${name}`));
|
|
10387
10710
|
} catch (err) {
|
|
10388
|
-
console.error(chalk25.red(err instanceof Error ? err.message : String(err)));
|
|
10711
|
+
console.error(chalk25.red(`Error: ${err instanceof Error ? err.message : String(err)}`));
|
|
10389
10712
|
process.exit(1);
|
|
10390
10713
|
}
|
|
10391
10714
|
});
|
|
10392
|
-
plugin.command("
|
|
10393
|
-
|
|
10394
|
-
|
|
10395
|
-
|
|
10396
|
-
|
|
10397
|
-
|
|
10398
|
-
|
|
10399
|
-
|
|
10400
|
-
|
|
10401
|
-
|
|
10715
|
+
plugin.command("update").description("Update a specific plugin or all plugins").argument("[name]", "Plugin name (omit to update all)").action(async (name) => {
|
|
10716
|
+
try {
|
|
10717
|
+
if (name) {
|
|
10718
|
+
const result = await updatePlugin(name);
|
|
10719
|
+
if (result.updated) {
|
|
10720
|
+
console.log(chalk25.green(`${result.name}: ${result.message}`));
|
|
10721
|
+
} else {
|
|
10722
|
+
console.log(chalk25.dim(`${result.name}: ${result.message}`));
|
|
10723
|
+
}
|
|
10724
|
+
} else {
|
|
10725
|
+
console.log(chalk25.dim("Updating all plugins..."));
|
|
10726
|
+
const results = await updateAllPlugins();
|
|
10727
|
+
if (results.length === 0) {
|
|
10728
|
+
console.log(chalk25.dim("No plugins installed."));
|
|
10729
|
+
return;
|
|
10730
|
+
}
|
|
10731
|
+
for (const result of results) {
|
|
10732
|
+
if (result.updated) {
|
|
10733
|
+
console.log(chalk25.green(` ${result.name}: ${result.message}`));
|
|
10734
|
+
} else {
|
|
10735
|
+
console.log(chalk25.dim(` ${result.name}: ${result.message}`));
|
|
10736
|
+
}
|
|
10737
|
+
}
|
|
10738
|
+
}
|
|
10739
|
+
} catch (err) {
|
|
10740
|
+
console.error(chalk25.red(`Error: ${err instanceof Error ? err.message : String(err)}`));
|
|
10741
|
+
process.exit(1);
|
|
10402
10742
|
}
|
|
10403
10743
|
});
|
|
10404
10744
|
plugin.command("enable").description("Enable a disabled plugin").argument("<name>", "Plugin name").action((name) => {
|
|
10405
10745
|
try {
|
|
10406
10746
|
enablePlugin(name);
|
|
10407
|
-
console.log(chalk25.green(
|
|
10747
|
+
console.log(chalk25.green(`Enabled ${name}`));
|
|
10408
10748
|
} catch (err) {
|
|
10409
|
-
console.error(chalk25.red(err instanceof Error ? err.message : String(err)));
|
|
10749
|
+
console.error(chalk25.red(`Error: ${err instanceof Error ? err.message : String(err)}`));
|
|
10410
10750
|
process.exit(1);
|
|
10411
10751
|
}
|
|
10412
10752
|
});
|
|
10413
10753
|
plugin.command("disable").description("Disable a plugin without removing it").argument("<name>", "Plugin name").action((name) => {
|
|
10414
10754
|
try {
|
|
10415
10755
|
disablePlugin(name);
|
|
10416
|
-
console.log(chalk25.yellow(
|
|
10756
|
+
console.log(chalk25.yellow(`Disabled ${name}`));
|
|
10417
10757
|
} catch (err) {
|
|
10418
|
-
console.error(chalk25.red(err instanceof Error ? err.message : String(err)));
|
|
10758
|
+
console.error(chalk25.red(`Error: ${err instanceof Error ? err.message : String(err)}`));
|
|
10419
10759
|
process.exit(1);
|
|
10420
10760
|
}
|
|
10421
10761
|
});
|
|
10422
|
-
plugin.command("
|
|
10762
|
+
const marketplace = plugin.command("marketplace").description("Manage plugin marketplaces");
|
|
10763
|
+
marketplace.command("list").description("List registered marketplaces").action(() => {
|
|
10423
10764
|
try {
|
|
10424
|
-
const
|
|
10425
|
-
|
|
10426
|
-
|
|
10427
|
-
|
|
10428
|
-
|
|
10429
|
-
|
|
10430
|
-
try {
|
|
10431
|
-
const result = updatePlugin(p.name);
|
|
10432
|
-
if (result.updated) {
|
|
10433
|
-
console.log(chalk25.green(`✓ ${result.name}: ${result.message}`));
|
|
10434
|
-
} else {
|
|
10435
|
-
console.log(chalk25.dim(` ${result.name}: ${result.message}`));
|
|
10436
|
-
}
|
|
10437
|
-
} catch (err) {
|
|
10438
|
-
console.error(chalk25.red(`✗ ${p.name}: ${err instanceof Error ? err.message : err}`));
|
|
10439
|
-
}
|
|
10765
|
+
const marketplaces = getRegisteredMarketplaces();
|
|
10766
|
+
console.log(chalk25.bold("Registered Marketplaces"));
|
|
10767
|
+
console.log("");
|
|
10768
|
+
for (const m of marketplaces) {
|
|
10769
|
+
const autoUpdate = m.autoUpdate ? chalk25.green("auto-update") : chalk25.dim("manual");
|
|
10770
|
+
console.log(` ${chalk25.bold(m.name)} ${chalk25.dim(m.source)} ${autoUpdate}`);
|
|
10440
10771
|
}
|
|
10441
10772
|
} catch (err) {
|
|
10442
|
-
console.error(chalk25.red(err instanceof Error ? err.message : String(err)));
|
|
10773
|
+
console.error(chalk25.red(`Error: ${err instanceof Error ? err.message : String(err)}`));
|
|
10774
|
+
process.exit(1);
|
|
10775
|
+
}
|
|
10776
|
+
});
|
|
10777
|
+
marketplace.command("add").description("Add a marketplace (e.g. github:user/repo)").argument("<source>", "Marketplace source (github:user/repo)").action((source) => {
|
|
10778
|
+
try {
|
|
10779
|
+
const name = addMarketplace(source);
|
|
10780
|
+
console.log(chalk25.green(`Added marketplace "${name}" from ${source}`));
|
|
10781
|
+
} catch (err) {
|
|
10782
|
+
console.error(chalk25.red(`Error: ${err instanceof Error ? err.message : String(err)}`));
|
|
10783
|
+
process.exit(1);
|
|
10784
|
+
}
|
|
10785
|
+
});
|
|
10786
|
+
marketplace.command("remove").description("Remove a registered marketplace").argument("<name>", "Marketplace name").action((name) => {
|
|
10787
|
+
try {
|
|
10788
|
+
removeMarketplace(name);
|
|
10789
|
+
console.log(chalk25.green(`Removed marketplace "${name}"`));
|
|
10790
|
+
} catch (err) {
|
|
10791
|
+
console.error(chalk25.red(`Error: ${err instanceof Error ? err.message : String(err)}`));
|
|
10792
|
+
process.exit(1);
|
|
10793
|
+
}
|
|
10794
|
+
});
|
|
10795
|
+
marketplace.command("refresh").description("Refresh marketplace cache").argument("[name]", "Marketplace name (omit to refresh all)").action(async (name) => {
|
|
10796
|
+
try {
|
|
10797
|
+
console.log(chalk25.dim(`Refreshing ${name ? `"${name}"` : "all marketplaces"}...`));
|
|
10798
|
+
await refreshMarketplaceCache(name);
|
|
10799
|
+
console.log(chalk25.green(`Marketplace cache refreshed.`));
|
|
10800
|
+
} catch (err) {
|
|
10801
|
+
console.error(chalk25.red(`Error: ${err instanceof Error ? err.message : String(err)}`));
|
|
10443
10802
|
process.exit(1);
|
|
10444
10803
|
}
|
|
10445
10804
|
});
|
|
@@ -10524,7 +10883,7 @@ async function runWithWatch(interval, fn) {
|
|
|
10524
10883
|
await new Promise((r) => setTimeout(r, interval * 1000));
|
|
10525
10884
|
}
|
|
10526
10885
|
}
|
|
10527
|
-
program.name("todoist").description("CLI tool for managing Todoist tasks").version("0.
|
|
10886
|
+
program.name("todoist").description("CLI tool for managing Todoist tasks").version("0.4.0").option("--debug", "Enable debug output").hook("preAction", () => {
|
|
10528
10887
|
if (program.opts().debug) {
|
|
10529
10888
|
setDebug(true);
|
|
10530
10889
|
debug("Debug mode enabled");
|
package/marketplace.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "todoist-cli-official",
|
|
3
|
+
"description": "Official todoist-cli plugin marketplace",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"plugins": [
|
|
6
|
+
{
|
|
7
|
+
"name": "time-tracking",
|
|
8
|
+
"source": "./plugins/time-tracking",
|
|
9
|
+
"version": "1.0.0",
|
|
10
|
+
"description": "Track time spent on tasks",
|
|
11
|
+
"author": "todoist-cli",
|
|
12
|
+
"category": "productivity",
|
|
13
|
+
"keywords": ["time", "tracking", "timer", "report"]
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
}
|
package/package.json
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mindtnv/todoist-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "A fast, keyboard-driven Todoist client for the terminal — interactive TUI and scriptable CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"todoist": "dist/index.js"
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
|
-
"dist"
|
|
10
|
+
"dist",
|
|
11
|
+
"marketplace.json"
|
|
11
12
|
],
|
|
12
13
|
"scripts": {
|
|
13
14
|
"dev": "bun run src/cli/index.ts",
|