cortask 0.2.37 → 0.2.39

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 CHANGED
@@ -1,15 +1,15 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { Command as Command4 } from "commander";
4
+ import { Command as Command11 } from "commander";
5
5
  import {
6
- loadConfig as loadConfig3,
7
- getDataDir as getDataDir4,
8
- WorkspaceManager as WorkspaceManager2,
9
- SessionStore,
10
- EncryptedCredentialStore,
11
- getOrCreateSecret,
12
- createProvider,
6
+ loadConfig as loadConfig6,
7
+ getDataDir as getDataDir11,
8
+ WorkspaceManager as WorkspaceManager3,
9
+ SessionStore as SessionStore2,
10
+ EncryptedCredentialStore as EncryptedCredentialStore2,
11
+ getOrCreateSecret as getOrCreateSecret2,
12
+ createProvider as createProvider2,
13
13
  AgentRunner,
14
14
  builtinTools,
15
15
  createCronTool,
@@ -20,9 +20,12 @@ import {
20
20
  CronService,
21
21
  ArtifactStore,
22
22
  installSkillFromGit,
23
- removeSkill
23
+ removeSkill,
24
+ createSkill,
25
+ updateSkill,
26
+ readSkillFile
24
27
  } from "@cortask/core";
25
- import path4 from "path";
28
+ import path11 from "path";
26
29
  import readline from "readline";
27
30
 
28
31
  // src/terminal/theme.ts
@@ -314,11 +317,501 @@ Dashboard URL: ${url}`));
314
317
  console.log();
315
318
  });
316
319
 
320
+ // src/commands/config.ts
321
+ import { Command as Command4 } from "commander";
322
+ import path4 from "path";
323
+ import { getDataDir as getDataDir4, loadConfig as loadConfig3, saveConfig } from "@cortask/core";
324
+ var configCommand = new Command4("config").description("View and update configuration");
325
+ configCommand.command("show").description("Show current configuration").action(async () => {
326
+ const dataDir = getDataDir4();
327
+ const config = await loadConfig3(path4.join(dataDir, "config.yaml"));
328
+ console.log(theme.heading("\n Agent"));
329
+ console.log(` Max turns: ${theme.info(String(config.agent.maxTurns))}`);
330
+ console.log(` Temperature: ${theme.info(String(config.agent.temperature))}`);
331
+ console.log(` Max tokens: ${theme.info(config.agent.maxTokens ? String(config.agent.maxTokens) : "default")}`);
332
+ console.log(theme.heading("\n Server"));
333
+ console.log(` Host: ${theme.info(config.server.host)}`);
334
+ console.log(` Port: ${theme.info(String(config.server.port))}`);
335
+ console.log(theme.heading("\n Spending"));
336
+ console.log(` Enabled: ${config.spending.enabled ? theme.success("yes") : theme.muted("no")}`);
337
+ console.log(` Max tokens: ${theme.info(config.spending.maxTokens ? String(config.spending.maxTokens) : "unlimited")}`);
338
+ console.log(` Max cost: ${theme.info(config.spending.maxCostUsd ? `$${config.spending.maxCostUsd}` : "unlimited")}`);
339
+ console.log(` Period: ${theme.info(config.spending.period)}`);
340
+ console.log(theme.heading("\n Memory"));
341
+ console.log(` Embedding provider: ${theme.info(config.memory.embeddingProvider)}`);
342
+ if (config.memory.embeddingModel) {
343
+ console.log(` Embedding model: ${theme.info(config.memory.embeddingModel)}`);
344
+ }
345
+ console.log(theme.heading("\n Skills"));
346
+ if (config.skills.dirs.length > 0) {
347
+ for (const dir of config.skills.dirs) {
348
+ console.log(` ${theme.muted("\u2022")} ${theme.info(dir)}`);
349
+ }
350
+ } else {
351
+ console.log(` ${theme.muted("No custom skill directories")}`);
352
+ }
353
+ console.log();
354
+ });
355
+ configCommand.command("set").description("Set a configuration value").argument("<key>", "Config key (e.g. agent.maxTurns, server.port, spending.enabled)").argument("<value>", "Config value").action(async (key, value) => {
356
+ const dataDir = getDataDir4();
357
+ const configPath = path4.join(dataDir, "config.yaml");
358
+ const config = await loadConfig3(configPath);
359
+ const parts = key.split(".");
360
+ if (parts.length !== 2) {
361
+ console.error(theme.error("\u2717 Key must be in format section.field (e.g. agent.maxTurns)"));
362
+ process.exit(1);
363
+ }
364
+ const [section, field] = parts;
365
+ const cfg = config;
366
+ if (!(section in cfg) || typeof cfg[section] !== "object") {
367
+ console.error(theme.error(`\u2717 Unknown config section: ${section}`));
368
+ console.error(theme.muted(" Valid sections: agent, server, spending, memory, skills"));
369
+ process.exit(1);
370
+ }
371
+ let parsed = value;
372
+ if (value === "true") parsed = true;
373
+ else if (value === "false") parsed = false;
374
+ else if (/^\d+$/.test(value)) parsed = parseInt(value, 10);
375
+ else if (/^\d+\.\d+$/.test(value)) parsed = parseFloat(value);
376
+ cfg[section][field] = parsed;
377
+ await saveConfig(configPath, config);
378
+ console.log(`${theme.success("\u2713")} Set ${theme.command(key)} = ${theme.info(String(parsed))}`);
379
+ });
380
+ configCommand.command("get").description("Get a configuration value").argument("<key>", "Config key (e.g. agent.maxTurns)").action(async (key) => {
381
+ const dataDir = getDataDir4();
382
+ const config = await loadConfig3(path4.join(dataDir, "config.yaml"));
383
+ const parts = key.split(".");
384
+ let current = config;
385
+ for (const part of parts) {
386
+ if (current == null || typeof current !== "object" || !(part in current)) {
387
+ console.error(theme.error(`\u2717 Unknown config key: ${key}`));
388
+ process.exit(1);
389
+ }
390
+ current = current[part];
391
+ }
392
+ console.log(typeof current === "object" ? JSON.stringify(current, null, 2) : String(current));
393
+ });
394
+
395
+ // src/commands/providers.ts
396
+ import { Command as Command5 } from "commander";
397
+ import path5 from "path";
398
+ import {
399
+ getDataDir as getDataDir5,
400
+ loadConfig as loadConfig4,
401
+ saveConfig as saveConfig2,
402
+ EncryptedCredentialStore,
403
+ getOrCreateSecret,
404
+ createProvider,
405
+ AVAILABLE_PROVIDERS,
406
+ getModelDefinitions
407
+ } from "@cortask/core";
408
+ var providersCommand = new Command5("providers").description("Manage LLM providers");
409
+ providersCommand.command("list").description("List all available providers and their status").action(async () => {
410
+ const dataDir = getDataDir5();
411
+ const config = await loadConfig4(path5.join(dataDir, "config.yaml"));
412
+ const secret = await getOrCreateSecret(dataDir);
413
+ const credentialStore = new EncryptedCredentialStore(
414
+ path5.join(dataDir, "credentials.enc.json"),
415
+ secret
416
+ );
417
+ const defaultProvider = config.providers.default;
418
+ for (const p of AVAILABLE_PROVIDERS) {
419
+ const apiKey = await credentialStore.get(`provider.${p.id}.apiKey`);
420
+ const configured = p.requiresApiKey ? !!apiKey : true;
421
+ const isDefault = p.id === defaultProvider;
422
+ const icon = configured ? theme.success("\u2713") : theme.muted("\u25CB");
423
+ const defaultTag = isDefault ? theme.accent(" [default]") : "";
424
+ const providerConfig = config.providers[p.id];
425
+ const model = typeof providerConfig === "object" && providerConfig && "model" in providerConfig ? providerConfig.model : void 0;
426
+ const modelTag = model ? theme.muted(` (${model})`) : "";
427
+ console.log(` ${icon} ${theme.command(p.name)}${defaultTag}${modelTag}`);
428
+ }
429
+ });
430
+ providersCommand.command("default").description("Set the default provider and optionally model").argument("<provider>", "Provider ID (e.g. anthropic, openai, google)").argument("[model]", "Model ID").action(async (providerId, model) => {
431
+ const dataDir = getDataDir5();
432
+ const configPath = path5.join(dataDir, "config.yaml");
433
+ const config = await loadConfig4(configPath);
434
+ const valid = AVAILABLE_PROVIDERS.find((p) => p.id === providerId);
435
+ if (!valid) {
436
+ console.error(theme.error(`\u2717 Unknown provider: ${providerId}`));
437
+ console.error(theme.muted(" Valid providers: " + AVAILABLE_PROVIDERS.map((p) => p.id).join(", ")));
438
+ process.exit(1);
439
+ }
440
+ config.providers.default = providerId;
441
+ if (model) {
442
+ config.providers[providerId] = {
443
+ ...config.providers[providerId],
444
+ model
445
+ };
446
+ }
447
+ await saveConfig2(configPath, config);
448
+ console.log(`${theme.success("\u2713")} Default provider set to ${theme.command(valid.name)}${model ? ` with model ${theme.info(model)}` : ""}`);
449
+ });
450
+ providersCommand.command("test").description("Test provider credentials with a simple API call").argument("<provider>", "Provider ID").action(async (providerId) => {
451
+ const dataDir = getDataDir5();
452
+ const secret = await getOrCreateSecret(dataDir);
453
+ const credentialStore = new EncryptedCredentialStore(
454
+ path5.join(dataDir, "credentials.enc.json"),
455
+ secret
456
+ );
457
+ const config = await loadConfig4(path5.join(dataDir, "config.yaml"));
458
+ const valid = AVAILABLE_PROVIDERS.find((p) => p.id === providerId);
459
+ if (!valid) {
460
+ console.error(theme.error(`\u2717 Unknown provider: ${providerId}`));
461
+ process.exit(1);
462
+ }
463
+ const apiKey = await credentialStore.get(`provider.${providerId}.apiKey`);
464
+ if (!apiKey && valid.requiresApiKey) {
465
+ console.error(theme.error(`\u2717 No API key set for ${providerId}`));
466
+ console.error(theme.muted(` Set it with: cortask credentials set provider.${providerId}.apiKey YOUR_KEY`));
467
+ process.exit(1);
468
+ }
469
+ console.log(theme.muted(`Testing ${valid.name}...`));
470
+ try {
471
+ const provider = createProvider(providerId, apiKey ?? "");
472
+ const providerConfig = config.providers[providerId];
473
+ const model = typeof providerConfig === "object" && providerConfig && "model" in providerConfig ? providerConfig.model : void 0;
474
+ const defaultModel = getModelDefinitions(providerId)[0]?.id;
475
+ const result = await provider.generateText({
476
+ model: model ?? defaultModel ?? providerId,
477
+ messages: [{ role: "user", content: [{ type: "text", text: "Say hi in exactly one word." }] }],
478
+ maxTokens: 20
479
+ });
480
+ console.log(`${theme.success("\u2713")} Provider responded successfully`);
481
+ console.log(` ${theme.muted("Response:")} ${result.content}`);
482
+ if (result.usage) {
483
+ console.log(` ${theme.muted("Tokens:")} ${result.usage.inputTokens} in / ${result.usage.outputTokens} out`);
484
+ }
485
+ } catch (err) {
486
+ console.error(`${theme.error("\u2717")} Provider test failed: ${err instanceof Error ? err.message : String(err)}`);
487
+ process.exit(1);
488
+ }
489
+ });
490
+
491
+ // src/commands/sessions.ts
492
+ import { Command as Command6 } from "commander";
493
+ import path6 from "path";
494
+ import { getDataDir as getDataDir6, WorkspaceManager as WorkspaceManager2, SessionStore } from "@cortask/core";
495
+ var sessionsCommand = new Command6("sessions").description("Manage chat sessions");
496
+ sessionsCommand.command("list").description("List sessions for a workspace").requiredOption("-w, --workspace <id>", "Workspace ID").action(async (opts) => {
497
+ const dataDir = getDataDir6();
498
+ const dbPath = path6.join(dataDir, "cortask.db");
499
+ const wm = new WorkspaceManager2(dbPath);
500
+ const workspace = await wm.get(opts.workspace);
501
+ if (!workspace) {
502
+ console.error(theme.error(`\u2717 Workspace not found: ${opts.workspace}`));
503
+ wm.close();
504
+ process.exit(1);
505
+ }
506
+ const sessionStore = new SessionStore(wm.getSessionDbPath(workspace.rootPath));
507
+ const sessions = sessionStore.listSessions();
508
+ if (sessions.length === 0) {
509
+ console.log(theme.muted("No sessions found."));
510
+ } else {
511
+ for (const s of sessions) {
512
+ const date = new Date(s.updatedAt).toLocaleString();
513
+ console.log(` ${theme.muted("\u2022")} ${theme.command(s.title || "Untitled")} ${theme.muted(`(${s.id})`)}`);
514
+ console.log(` ${theme.muted("Updated:")} ${date}`);
515
+ }
516
+ }
517
+ wm.close();
518
+ });
519
+ sessionsCommand.command("show").description("Show session messages").argument("<id>", "Session ID").requiredOption("-w, --workspace <id>", "Workspace ID").option("-n, --limit <count>", "Number of messages to show", "20").action(async (id, opts) => {
520
+ const dataDir = getDataDir6();
521
+ const dbPath = path6.join(dataDir, "cortask.db");
522
+ const wm = new WorkspaceManager2(dbPath);
523
+ const workspace = await wm.get(opts.workspace);
524
+ if (!workspace) {
525
+ console.error(theme.error(`\u2717 Workspace not found: ${opts.workspace}`));
526
+ wm.close();
527
+ process.exit(1);
528
+ }
529
+ const sessionStore = new SessionStore(wm.getSessionDbPath(workspace.rootPath));
530
+ const session = sessionStore.getSession(id);
531
+ if (!session) {
532
+ console.error(theme.error(`\u2717 Session not found: ${id}`));
533
+ wm.close();
534
+ process.exit(1);
535
+ }
536
+ console.log(theme.heading(`
537
+ ${session.title || "Untitled Session"}`));
538
+ console.log(theme.muted(` ID: ${session.id}`));
539
+ console.log(theme.muted(` Created: ${new Date(session.createdAt).toLocaleString()}`));
540
+ console.log();
541
+ const limit = parseInt(opts.limit, 10);
542
+ const messages = session.messages.slice(-limit);
543
+ for (const msg of messages) {
544
+ const role = msg.role === "user" ? theme.accent("you>") : theme.accentBright("cortask>");
545
+ let text = "";
546
+ if (typeof msg.content === "string") {
547
+ text = msg.content;
548
+ } else if (Array.isArray(msg.content)) {
549
+ text = msg.content.filter((c) => c.type === "text").map((c) => c.text ?? "").join("");
550
+ }
551
+ if (text) {
552
+ console.log(`${role} ${text.slice(0, 500)}${text.length > 500 ? "..." : ""}`);
553
+ console.log();
554
+ }
555
+ }
556
+ wm.close();
557
+ });
558
+ sessionsCommand.command("delete").description("Delete a session").argument("<id>", "Session ID").requiredOption("-w, --workspace <id>", "Workspace ID").action(async (id, opts) => {
559
+ const dataDir = getDataDir6();
560
+ const dbPath = path6.join(dataDir, "cortask.db");
561
+ const wm = new WorkspaceManager2(dbPath);
562
+ const workspace = await wm.get(opts.workspace);
563
+ if (!workspace) {
564
+ console.error(theme.error(`\u2717 Workspace not found: ${opts.workspace}`));
565
+ wm.close();
566
+ process.exit(1);
567
+ }
568
+ const sessionStore = new SessionStore(wm.getSessionDbPath(workspace.rootPath));
569
+ sessionStore.deleteSession(id);
570
+ console.log(`${theme.success("\u2713")} Deleted session ${theme.muted(id)}`);
571
+ wm.close();
572
+ });
573
+
574
+ // src/commands/usage.ts
575
+ import { Command as Command7 } from "commander";
576
+ import path7 from "path";
577
+ import { getDataDir as getDataDir7, UsageStore, ModelStore } from "@cortask/core";
578
+ var usageCommand = new Command7("usage").description("View token usage and costs");
579
+ usageCommand.command("summary").description("Show usage summary for a period").option("-p, --period <period>", "Period: daily, weekly, monthly", "monthly").action(async (opts) => {
580
+ const dataDir = getDataDir7();
581
+ const dbPath = path7.join(dataDir, "cortask.db");
582
+ const modelStore = new ModelStore(dbPath);
583
+ const usageStore = new UsageStore(dbPath, modelStore);
584
+ const period = opts.period;
585
+ const summary = usageStore.getSummary(period);
586
+ console.log(theme.heading(`
587
+ Usage (${period})`));
588
+ console.log(` Requests: ${theme.info(String(summary.recordCount))}`);
589
+ console.log(` Input tokens: ${theme.info(summary.totalInputTokens.toLocaleString())}`);
590
+ console.log(` Output tokens: ${theme.info(summary.totalOutputTokens.toLocaleString())}`);
591
+ console.log(` Total tokens: ${theme.info(summary.totalTokens.toLocaleString())}`);
592
+ console.log(` Total cost: ${theme.info(`$${summary.totalCostUsd.toFixed(4)}`)}`);
593
+ console.log();
594
+ });
595
+ usageCommand.command("history").description("Show daily usage history").option("-d, --days <days>", "Number of days", "14").action(async (opts) => {
596
+ const dataDir = getDataDir7();
597
+ const dbPath = path7.join(dataDir, "cortask.db");
598
+ const modelStore = new ModelStore(dbPath);
599
+ const usageStore = new UsageStore(dbPath, modelStore);
600
+ const days = parseInt(opts.days, 10);
601
+ const history = usageStore.getHistory(days);
602
+ if (history.length === 0) {
603
+ console.log(theme.muted("No usage data found."));
604
+ return;
605
+ }
606
+ console.log(theme.heading(`
607
+ Usage History (last ${days} days)
608
+ `));
609
+ console.log(` ${theme.muted("Date".padEnd(12))} ${theme.muted("Tokens".padStart(12))} ${theme.muted("Cost".padStart(10))}`);
610
+ console.log(` ${theme.muted("\u2500".repeat(34))}`);
611
+ for (const row of history) {
612
+ const date = row.date.padEnd(12);
613
+ const tokens = row.tokens.toLocaleString().padStart(12);
614
+ const cost = `$${row.costUsd.toFixed(4)}`.padStart(10);
615
+ console.log(` ${theme.info(date)} ${tokens} ${theme.muted(cost)}`);
616
+ }
617
+ console.log();
618
+ });
619
+
620
+ // src/commands/models.ts
621
+ import { Command as Command8 } from "commander";
622
+ import path8 from "path";
623
+ import { getDataDir as getDataDir8, ModelStore as ModelStore2, getModelDefinitions as getModelDefinitions2 } from "@cortask/core";
624
+ var modelsCommand = new Command8("models").description("Manage available models");
625
+ modelsCommand.command("available").description("List available models for a provider").argument("<provider>", "Provider ID (e.g. anthropic, openai, google)").action(async (providerId) => {
626
+ const models = getModelDefinitions2(providerId);
627
+ if (models.length === 0) {
628
+ console.log(theme.muted(`No hardcoded models for provider "${providerId}".`));
629
+ console.log(theme.muted("This provider may fetch models dynamically from its API."));
630
+ return;
631
+ }
632
+ console.log(theme.heading(`
633
+ Models for ${providerId}
634
+ `));
635
+ for (const m of models) {
636
+ const pricing = m.inputPricePer1m != null ? theme.muted(` ($${m.inputPricePer1m}/$${m.outputPricePer1m} per 1M)`) : "";
637
+ console.log(` ${theme.muted("\u2022")} ${theme.command(m.id)} ${theme.info(m.name)}${pricing}`);
638
+ }
639
+ console.log();
640
+ });
641
+ modelsCommand.command("list").description("List enabled models").option("-p, --provider <provider>", "Filter by provider").action(async (opts) => {
642
+ const dataDir = getDataDir8();
643
+ const dbPath = path8.join(dataDir, "cortask.db");
644
+ const modelStore = new ModelStore2(dbPath);
645
+ const models = modelStore.list(opts.provider);
646
+ if (models.length === 0) {
647
+ console.log(theme.muted("No models enabled."));
648
+ console.log(theme.muted("Enable models with: cortask models enable <provider> <model-id> <label> <input-price> <output-price>"));
649
+ return;
650
+ }
651
+ console.log(theme.heading("\n Enabled Models\n"));
652
+ for (const m of models) {
653
+ console.log(` ${theme.success("\u2713")} ${theme.command(m.label)} ${theme.muted(`(${m.provider}/${m.modelId})`)}`);
654
+ console.log(` ${theme.muted(`$${m.inputPricePer1m}/$${m.outputPricePer1m} per 1M tokens`)}`);
655
+ }
656
+ console.log();
657
+ });
658
+ modelsCommand.command("enable").description("Enable a model").argument("<provider>", "Provider ID").argument("<model-id>", "Model ID").argument("<label>", "Display label").argument("<input-price>", "Input price per 1M tokens (USD)").argument("<output-price>", "Output price per 1M tokens (USD)").action(async (provider, modelId, label, inputPrice, outputPrice) => {
659
+ const dataDir = getDataDir8();
660
+ const dbPath = path8.join(dataDir, "cortask.db");
661
+ const modelStore = new ModelStore2(dbPath);
662
+ const model = modelStore.enable(
663
+ provider,
664
+ modelId,
665
+ label,
666
+ parseFloat(inputPrice),
667
+ parseFloat(outputPrice)
668
+ );
669
+ console.log(`${theme.success("\u2713")} Enabled model ${theme.command(model.label)} ${theme.muted(`(${provider}/${modelId})`)}`);
670
+ });
671
+ modelsCommand.command("disable").description("Disable a model").argument("<provider>", "Provider ID").argument("<model-id>", "Model ID").action(async (provider, modelId) => {
672
+ const dataDir = getDataDir8();
673
+ const dbPath = path8.join(dataDir, "cortask.db");
674
+ const modelStore = new ModelStore2(dbPath);
675
+ modelStore.disable(provider, modelId);
676
+ console.log(`${theme.success("\u2713")} Disabled model ${theme.muted(`${provider}/${modelId}`)}`);
677
+ });
678
+
679
+ // src/commands/templates.ts
680
+ import { Command as Command9 } from "commander";
681
+ import path9 from "path";
682
+ import { getDataDir as getDataDir9, TemplateStore } from "@cortask/core";
683
+ var templatesCommand = new Command9("templates").description("Manage prompt templates");
684
+ templatesCommand.command("list").description("List all templates").action(async () => {
685
+ const dataDir = getDataDir9();
686
+ const dbPath = path9.join(dataDir, "cortask.db");
687
+ const store = new TemplateStore(dbPath);
688
+ const templates = store.list();
689
+ if (templates.length === 0) {
690
+ console.log(theme.muted("No templates found."));
691
+ return;
692
+ }
693
+ let currentCategory = "";
694
+ for (const t of templates) {
695
+ if (t.category !== currentCategory) {
696
+ currentCategory = t.category;
697
+ console.log(theme.heading(`
698
+ ${currentCategory}`));
699
+ }
700
+ console.log(` ${theme.muted("\u2022")} ${theme.command(t.name)} ${theme.muted(`(${t.id.slice(0, 8)}...)`)}`);
701
+ console.log(` ${theme.muted(t.content.slice(0, 80).replace(/\n/g, " "))}${t.content.length > 80 ? "..." : ""}`);
702
+ }
703
+ console.log();
704
+ });
705
+ templatesCommand.command("create").description("Create a new template").requiredOption("-n, --name <name>", "Template name").requiredOption("-c, --content <content>", "Template content").option("--category <category>", "Category", "General").action(async (opts) => {
706
+ const dataDir = getDataDir9();
707
+ const dbPath = path9.join(dataDir, "cortask.db");
708
+ const store = new TemplateStore(dbPath);
709
+ const template = store.create(opts.name, opts.content, opts.category);
710
+ console.log(`${theme.success("\u2713")} Created template ${theme.command(template.name)} ${theme.muted(`(${template.id.slice(0, 8)}...)`)}`);
711
+ });
712
+ templatesCommand.command("update").description("Update a template").argument("<id>", "Template ID").option("-n, --name <name>", "New name").option("-c, --content <content>", "New content").option("--category <category>", "New category").action(async (id, opts) => {
713
+ const dataDir = getDataDir9();
714
+ const dbPath = path9.join(dataDir, "cortask.db");
715
+ const store = new TemplateStore(dbPath);
716
+ const updates = {};
717
+ if (opts.name) updates.name = opts.name;
718
+ if (opts.content) updates.content = opts.content;
719
+ if (opts.category) updates.category = opts.category;
720
+ if (Object.keys(updates).length === 0) {
721
+ console.error(theme.error("\u2717 No fields to update. Use --name, --content, or --category."));
722
+ process.exit(1);
723
+ }
724
+ const result = store.update(id, updates);
725
+ if (!result) {
726
+ console.error(theme.error(`\u2717 Template not found: ${id}`));
727
+ process.exit(1);
728
+ }
729
+ console.log(`${theme.success("\u2713")} Updated template ${theme.command(result.name)}`);
730
+ });
731
+ templatesCommand.command("delete").description("Delete a template").argument("<id>", "Template ID").action(async (id) => {
732
+ const dataDir = getDataDir9();
733
+ const dbPath = path9.join(dataDir, "cortask.db");
734
+ const store = new TemplateStore(dbPath);
735
+ store.delete(id);
736
+ console.log(`${theme.success("\u2713")} Deleted template ${theme.muted(id)}`);
737
+ });
738
+
739
+ // src/commands/channels.ts
740
+ import { Command as Command10 } from "commander";
741
+ import path10 from "path";
742
+ import { getDataDir as getDataDir10, loadConfig as loadConfig5 } from "@cortask/core";
743
+ async function fetchGateway(host, port, path12, method = "GET") {
744
+ const res = await fetch(`http://${host}:${port}${path12}`, { method });
745
+ if (!res.ok) {
746
+ const body = await res.text();
747
+ throw new Error(`Gateway returned ${res.status}: ${body}`);
748
+ }
749
+ return res.json();
750
+ }
751
+ var channelsCommand = new Command10("channels").description("Manage messaging channels (requires running gateway)");
752
+ channelsCommand.command("list").description("List channel statuses").action(async () => {
753
+ const dataDir = getDataDir10();
754
+ const config = await loadConfig5(path10.join(dataDir, "config.yaml"));
755
+ try {
756
+ const channels = await fetchGateway(
757
+ config.server.host,
758
+ config.server.port,
759
+ "/api/channels"
760
+ );
761
+ if (channels.length === 0) {
762
+ console.log(theme.muted("No channels available."));
763
+ return;
764
+ }
765
+ for (const ch of channels) {
766
+ const statusIcon = ch.running ? theme.success("\u25CF") : theme.muted("\u25CB");
767
+ const status = ch.running ? theme.success("running") : theme.muted("stopped");
768
+ const auth = ch.authenticated != null ? ch.authenticated ? theme.success(" (authenticated)") : theme.warn(" (not authenticated)") : "";
769
+ console.log(` ${statusIcon} ${theme.command(ch.name)} ${status}${auth}`);
770
+ }
771
+ } catch (err) {
772
+ console.error(theme.error(`\u2717 ${err instanceof Error ? err.message : String(err)}`));
773
+ console.error(theme.muted(" Is the gateway running? Start it with: cortask serve"));
774
+ process.exit(1);
775
+ }
776
+ });
777
+ channelsCommand.command("start").description("Start a channel").argument("<channel>", "Channel ID (telegram, discord, whatsapp)").action(async (channelId) => {
778
+ const dataDir = getDataDir10();
779
+ const config = await loadConfig5(path10.join(dataDir, "config.yaml"));
780
+ try {
781
+ await fetchGateway(
782
+ config.server.host,
783
+ config.server.port,
784
+ `/api/channels/${channelId}/start`,
785
+ "POST"
786
+ );
787
+ console.log(`${theme.success("\u2713")} Channel ${theme.command(channelId)} started`);
788
+ } catch (err) {
789
+ console.error(theme.error(`\u2717 ${err instanceof Error ? err.message : String(err)}`));
790
+ process.exit(1);
791
+ }
792
+ });
793
+ channelsCommand.command("stop").description("Stop a channel").argument("<channel>", "Channel ID (telegram, discord, whatsapp)").action(async (channelId) => {
794
+ const dataDir = getDataDir10();
795
+ const config = await loadConfig5(path10.join(dataDir, "config.yaml"));
796
+ try {
797
+ await fetchGateway(
798
+ config.server.host,
799
+ config.server.port,
800
+ `/api/channels/${channelId}/stop`,
801
+ "POST"
802
+ );
803
+ console.log(`${theme.success("\u2713")} Channel ${theme.command(channelId)} stopped`);
804
+ } catch (err) {
805
+ console.error(theme.error(`\u2717 ${err instanceof Error ? err.message : String(err)}`));
806
+ process.exit(1);
807
+ }
808
+ });
809
+
317
810
  // src/index.ts
318
811
  import { createRequire } from "module";
319
812
  var require2 = createRequire(import.meta.url);
320
813
  var VERSION = require2("../package.json").version;
321
- var program = new Command4();
814
+ var program = new Command11();
322
815
  program.name("cortask").description("Cortask \u2014 Local AI agent with skills, workspaces, and cron").version(VERSION);
323
816
  configureHelp(program, VERSION);
324
817
  program.command("serve").description("Start the gateway server").option("-p, --port <port>", "Port to listen on", "3777").option("--host <host>", "Host to bind to", "127.0.0.1").action(async (opts) => {
@@ -331,35 +824,35 @@ program.command("serve").description("Start the gateway server").option("-p, --p
331
824
  });
332
825
  program.command("chat").description("Interactive chat REPL").option("-w, --workspace <path>", "Workspace directory path", ".").action(async (opts) => {
333
826
  emitBanner(VERSION);
334
- const dataDir = getDataDir4();
335
- const configPath = path4.join(dataDir, "config.yaml");
336
- const config = await loadConfig3(configPath);
337
- const dbPath = path4.join(dataDir, "cortask.db");
338
- const workspaceManager = new WorkspaceManager2(dbPath);
339
- const secret = await getOrCreateSecret(dataDir);
340
- const credentialStore = new EncryptedCredentialStore(
341
- path4.join(dataDir, "credentials.enc.json"),
827
+ const dataDir = getDataDir11();
828
+ const configPath = path11.join(dataDir, "config.yaml");
829
+ const config = await loadConfig6(configPath);
830
+ const dbPath = path11.join(dataDir, "cortask.db");
831
+ const workspaceManager = new WorkspaceManager3(dbPath);
832
+ const secret = await getOrCreateSecret2(dataDir);
833
+ const credentialStore = new EncryptedCredentialStore2(
834
+ path11.join(dataDir, "credentials.enc.json"),
342
835
  secret
343
836
  );
344
837
  const cronService = new CronService(dbPath);
345
838
  const artifactStore = new ArtifactStore();
346
- const workspacePath = path4.resolve(opts.workspace);
839
+ const workspacePath = path11.resolve(opts.workspace);
347
840
  const workspaces = await workspaceManager.list();
348
841
  let workspace = workspaces.find((w) => w.rootPath === workspacePath);
349
842
  if (!workspace) {
350
843
  workspace = await workspaceManager.create(
351
- path4.basename(workspacePath),
844
+ path11.basename(workspacePath),
352
845
  workspacePath
353
846
  );
354
847
  console.log(
355
848
  `${theme.success("\u2713")} Registered workspace: ${theme.command(workspace.name)}`
356
849
  );
357
850
  }
358
- const sessionStore = new SessionStore(
851
+ const sessionStore = new SessionStore2(
359
852
  workspaceManager.getSessionDbPath(workspacePath)
360
853
  );
361
- const bundledSkillsDir = path4.resolve("skills");
362
- const userSkillsDir = path4.join(dataDir, "skills");
854
+ const bundledSkillsDir = path11.resolve("skills");
855
+ const userSkillsDir = path11.join(dataDir, "skills");
363
856
  const allSkills = await loadSkills(
364
857
  bundledSkillsDir,
365
858
  userSkillsDir,
@@ -392,7 +885,7 @@ program.command("chat").description("Interactive chat REPL").option("-w, --works
392
885
  console.log();
393
886
  process.exit(1);
394
887
  }
395
- const provider = createProvider(providerId, apiKey);
888
+ const provider = createProvider2(providerId, apiKey);
396
889
  const model = providerConfig?.model ?? "claude-sonnet-4-5-20250929";
397
890
  const sessionId = `cli_${Date.now()}`;
398
891
  const runner = new AgentRunner({
@@ -494,28 +987,28 @@ ${theme.muted("[")}${theme.warn("tool")}${theme.muted(":")} ${theme.command(even
494
987
  });
495
988
  });
496
989
  program.command("run").description("Run a one-shot prompt").argument("<prompt>", "The prompt to execute").option("-w, --workspace <path>", "Workspace directory path", ".").action(async (prompt, opts) => {
497
- const dataDir = getDataDir4();
498
- const configPath = path4.join(dataDir, "config.yaml");
499
- const config = await loadConfig3(configPath);
500
- const dbPath = path4.join(dataDir, "cortask.db");
501
- const workspaceManager = new WorkspaceManager2(dbPath);
502
- const secret = await getOrCreateSecret(dataDir);
503
- const credentialStore = new EncryptedCredentialStore(
504
- path4.join(dataDir, "credentials.enc.json"),
990
+ const dataDir = getDataDir11();
991
+ const configPath = path11.join(dataDir, "config.yaml");
992
+ const config = await loadConfig6(configPath);
993
+ const dbPath = path11.join(dataDir, "cortask.db");
994
+ const workspaceManager = new WorkspaceManager3(dbPath);
995
+ const secret = await getOrCreateSecret2(dataDir);
996
+ const credentialStore = new EncryptedCredentialStore2(
997
+ path11.join(dataDir, "credentials.enc.json"),
505
998
  secret
506
999
  );
507
1000
  const cronService = new CronService(dbPath);
508
1001
  const artifactStore = new ArtifactStore();
509
- const workspacePath = path4.resolve(opts.workspace);
1002
+ const workspacePath = path11.resolve(opts.workspace);
510
1003
  const workspaces = await workspaceManager.list();
511
1004
  let workspace = workspaces.find((w) => w.rootPath === workspacePath);
512
1005
  if (!workspace) {
513
1006
  workspace = await workspaceManager.create(
514
- path4.basename(workspacePath),
1007
+ path11.basename(workspacePath),
515
1008
  workspacePath
516
1009
  );
517
1010
  }
518
- const sessionStore = new SessionStore(
1011
+ const sessionStore = new SessionStore2(
519
1012
  workspaceManager.getSessionDbPath(workspacePath)
520
1013
  );
521
1014
  const providerId = config.providers.default || "anthropic";
@@ -531,7 +1024,7 @@ program.command("run").description("Run a one-shot prompt").argument("<prompt>",
531
1024
  );
532
1025
  process.exit(1);
533
1026
  }
534
- const provider = createProvider(providerId, apiKey);
1027
+ const provider = createProvider2(providerId, apiKey);
535
1028
  const providerConfig = config.providers[providerId];
536
1029
  const model = providerConfig?.model ?? "claude-sonnet-4-5-20250929";
537
1030
  const runner = new AgentRunner({
@@ -562,11 +1055,18 @@ program.command("run").description("Run a one-shot prompt").argument("<prompt>",
562
1055
  program.addCommand(setupCommand);
563
1056
  program.addCommand(statusCommand);
564
1057
  program.addCommand(dashboardCommand);
1058
+ program.addCommand(configCommand);
1059
+ program.addCommand(providersCommand);
1060
+ program.addCommand(sessionsCommand);
1061
+ program.addCommand(usageCommand);
1062
+ program.addCommand(modelsCommand);
1063
+ program.addCommand(templatesCommand);
1064
+ program.addCommand(channelsCommand);
565
1065
  var ws = program.command("workspaces").description("Manage workspaces");
566
1066
  ws.command("list").action(async () => {
567
- const dataDir = getDataDir4();
568
- const dbPath = path4.join(dataDir, "cortask.db");
569
- const wm = new WorkspaceManager2(dbPath);
1067
+ const dataDir = getDataDir11();
1068
+ const dbPath = path11.join(dataDir, "cortask.db");
1069
+ const wm = new WorkspaceManager3(dbPath);
570
1070
  const list = await wm.list();
571
1071
  if (list.length === 0) {
572
1072
  console.log(theme.muted("No workspaces registered."));
@@ -580,9 +1080,9 @@ ws.command("list").action(async () => {
580
1080
  wm.close();
581
1081
  });
582
1082
  ws.command("add").argument("<name>", "Workspace name").argument("<path>", "Workspace directory path").action(async (name, dirPath) => {
583
- const dataDir = getDataDir4();
584
- const dbPath = path4.join(dataDir, "cortask.db");
585
- const wm = new WorkspaceManager2(dbPath);
1083
+ const dataDir = getDataDir11();
1084
+ const dbPath = path11.join(dataDir, "cortask.db");
1085
+ const wm = new WorkspaceManager3(dbPath);
586
1086
  const workspace = await wm.create(name, dirPath);
587
1087
  console.log(
588
1088
  `${theme.success("\u2713")} Created workspace ${theme.command(workspace.name)} ${theme.muted(`(${workspace.id})`)}`
@@ -591,19 +1091,19 @@ ws.command("add").argument("<name>", "Workspace name").argument("<path>", "Works
591
1091
  wm.close();
592
1092
  });
593
1093
  ws.command("remove").argument("<id>", "Workspace ID").action(async (id) => {
594
- const dataDir = getDataDir4();
595
- const dbPath = path4.join(dataDir, "cortask.db");
596
- const wm = new WorkspaceManager2(dbPath);
1094
+ const dataDir = getDataDir11();
1095
+ const dbPath = path11.join(dataDir, "cortask.db");
1096
+ const wm = new WorkspaceManager3(dbPath);
597
1097
  await wm.delete(id);
598
1098
  console.log(`${theme.success("\u2713")} Removed workspace ${theme.muted(id)}`);
599
1099
  wm.close();
600
1100
  });
601
1101
  var creds = program.command("credentials").description("Manage credentials");
602
1102
  creds.command("list").action(async () => {
603
- const dataDir = getDataDir4();
604
- const secret = await getOrCreateSecret(dataDir);
605
- const store = new EncryptedCredentialStore(
606
- path4.join(dataDir, "credentials.enc.json"),
1103
+ const dataDir = getDataDir11();
1104
+ const secret = await getOrCreateSecret2(dataDir);
1105
+ const store = new EncryptedCredentialStore2(
1106
+ path11.join(dataDir, "credentials.enc.json"),
607
1107
  secret
608
1108
  );
609
1109
  const keys = await store.list();
@@ -616,20 +1116,20 @@ creds.command("list").action(async () => {
616
1116
  }
617
1117
  });
618
1118
  creds.command("set").argument("<key>", "Credential key (e.g. provider.anthropic.apiKey)").argument("<value>", "Credential value").action(async (key, value) => {
619
- const dataDir = getDataDir4();
620
- const secret = await getOrCreateSecret(dataDir);
621
- const store = new EncryptedCredentialStore(
622
- path4.join(dataDir, "credentials.enc.json"),
1119
+ const dataDir = getDataDir11();
1120
+ const secret = await getOrCreateSecret2(dataDir);
1121
+ const store = new EncryptedCredentialStore2(
1122
+ path11.join(dataDir, "credentials.enc.json"),
623
1123
  secret
624
1124
  );
625
1125
  await store.set(key, value);
626
1126
  console.log(`${theme.success("\u2713")} Credential ${theme.command(key)} saved.`);
627
1127
  });
628
1128
  creds.command("remove").argument("<key>", "Credential key").action(async (key) => {
629
- const dataDir = getDataDir4();
630
- const secret = await getOrCreateSecret(dataDir);
631
- const store = new EncryptedCredentialStore(
632
- path4.join(dataDir, "credentials.enc.json"),
1129
+ const dataDir = getDataDir11();
1130
+ const secret = await getOrCreateSecret2(dataDir);
1131
+ const store = new EncryptedCredentialStore2(
1132
+ path11.join(dataDir, "credentials.enc.json"),
633
1133
  secret
634
1134
  );
635
1135
  await store.delete(key);
@@ -637,16 +1137,16 @@ creds.command("remove").argument("<key>", "Credential key").action(async (key) =
637
1137
  });
638
1138
  var sk = program.command("skills").description("Manage skills");
639
1139
  sk.command("list").action(async () => {
640
- const dataDir = getDataDir4();
641
- const secret = await getOrCreateSecret(dataDir);
642
- const credentialStore = new EncryptedCredentialStore(
643
- path4.join(dataDir, "credentials.enc.json"),
1140
+ const dataDir = getDataDir11();
1141
+ const secret = await getOrCreateSecret2(dataDir);
1142
+ const credentialStore = new EncryptedCredentialStore2(
1143
+ path11.join(dataDir, "credentials.enc.json"),
644
1144
  secret
645
1145
  );
646
- const bundledSkillsDir = path4.resolve("skills");
647
- const userSkillsDir = path4.join(dataDir, "skills");
648
- const configPath = path4.join(dataDir, "config.yaml");
649
- const config = await loadConfig3(configPath);
1146
+ const bundledSkillsDir = path11.resolve("skills");
1147
+ const userSkillsDir = path11.join(dataDir, "skills");
1148
+ const configPath = path11.join(dataDir, "config.yaml");
1149
+ const config = await loadConfig6(configPath);
650
1150
  const allSkills = await loadSkills(
651
1151
  bundledSkillsDir,
652
1152
  userSkillsDir,
@@ -665,23 +1165,45 @@ sk.command("list").action(async () => {
665
1165
  }
666
1166
  });
667
1167
  sk.command("install").argument("<git-url>", "Git URL of the skill repository").action(async (gitUrl) => {
668
- const dataDir = getDataDir4();
669
- const userSkillsDir = path4.join(dataDir, "skills");
1168
+ const dataDir = getDataDir11();
1169
+ const userSkillsDir = path11.join(dataDir, "skills");
670
1170
  const result = await installSkillFromGit(gitUrl, userSkillsDir);
671
1171
  console.log(
672
1172
  `${theme.success("\u2713")} Installed skill ${theme.command(result.name)}`
673
1173
  );
674
1174
  });
675
1175
  sk.command("remove").argument("<name>", "Skill name").action(async (name) => {
676
- const dataDir = getDataDir4();
677
- const userSkillsDir = path4.join(dataDir, "skills");
1176
+ const dataDir = getDataDir11();
1177
+ const userSkillsDir = path11.join(dataDir, "skills");
678
1178
  await removeSkill(name, userSkillsDir);
679
1179
  console.log(`${theme.success("\u2713")} Removed skill ${theme.command(name)}`);
680
1180
  });
1181
+ sk.command("create").description("Create a new custom skill").argument("<name>", "Skill name (kebab-case)").requiredOption("-c, --content <content>", "SKILL.md content (or use --file)").action(async (name, opts) => {
1182
+ const dataDir = getDataDir11();
1183
+ const userSkillsDir = path11.join(dataDir, "skills");
1184
+ await createSkill(userSkillsDir, name, opts.content);
1185
+ console.log(`${theme.success("\u2713")} Created skill ${theme.command(name)}`);
1186
+ });
1187
+ sk.command("update").description("Update a custom skill").argument("<name>", "Skill name").requiredOption("-c, --content <content>", "New SKILL.md content").action(async (name, opts) => {
1188
+ const dataDir = getDataDir11();
1189
+ const userSkillsDir = path11.join(dataDir, "skills");
1190
+ await updateSkill(userSkillsDir, name, opts.content);
1191
+ console.log(`${theme.success("\u2713")} Updated skill ${theme.command(name)}`);
1192
+ });
1193
+ sk.command("show").description("Show skill content").argument("<name>", "Skill name").action(async (name) => {
1194
+ const dataDir = getDataDir11();
1195
+ const userSkillsDir = path11.join(dataDir, "skills");
1196
+ const content = await readSkillFile(userSkillsDir, name);
1197
+ if (!content) {
1198
+ console.error(theme.error(`\u2717 Skill not found: ${name}`));
1199
+ process.exit(1);
1200
+ }
1201
+ console.log(content);
1202
+ });
681
1203
  var cr = program.command("cron").description("Manage cron jobs");
682
1204
  cr.command("list").action(async () => {
683
- const dataDir = getDataDir4();
684
- const dbPath = path4.join(dataDir, "cortask.db");
1205
+ const dataDir = getDataDir11();
1206
+ const dbPath = path11.join(dataDir, "cortask.db");
685
1207
  const cronService = new CronService(dbPath);
686
1208
  const jobs = cronService.list();
687
1209
  if (jobs.length === 0) {
@@ -697,8 +1219,8 @@ cr.command("list").action(async () => {
697
1219
  cronService.stop();
698
1220
  });
699
1221
  cr.command("add").requiredOption("-n, --name <name>", "Job name").requiredOption("-s, --schedule <cron>", "Cron expression").requiredOption("-p, --prompt <prompt>", "Prompt to execute").option("-w, --workspace <id>", "Workspace ID").action(async (opts) => {
700
- const dataDir = getDataDir4();
701
- const dbPath = path4.join(dataDir, "cortask.db");
1222
+ const dataDir = getDataDir11();
1223
+ const dbPath = path11.join(dataDir, "cortask.db");
702
1224
  const cronService = new CronService(dbPath);
703
1225
  const job = cronService.add({
704
1226
  name: opts.name,
@@ -713,13 +1235,56 @@ cr.command("add").requiredOption("-n, --name <name>", "Job name").requiredOption
713
1235
  cronService.stop();
714
1236
  });
715
1237
  cr.command("remove").argument("<id>", "Job ID").action(async (id) => {
716
- const dataDir = getDataDir4();
717
- const dbPath = path4.join(dataDir, "cortask.db");
1238
+ const dataDir = getDataDir11();
1239
+ const dbPath = path11.join(dataDir, "cortask.db");
718
1240
  const cronService = new CronService(dbPath);
719
1241
  cronService.remove(id);
720
1242
  console.log(`${theme.success("\u2713")} Deleted cron job ${theme.muted(id)}`);
721
1243
  cronService.stop();
722
1244
  });
1245
+ cr.command("update").description("Update a cron job").argument("<id>", "Job ID").option("-n, --name <name>", "New job name").option("-s, --schedule <cron>", "New cron expression").option("-p, --prompt <prompt>", "New prompt").option("--enable", "Enable the job").option("--disable", "Disable the job").action(async (id, opts) => {
1246
+ const dataDir = getDataDir11();
1247
+ const dbPath = path11.join(dataDir, "cortask.db");
1248
+ const cronService = new CronService(dbPath);
1249
+ const updates = {};
1250
+ if (opts.name) updates.name = opts.name;
1251
+ if (opts.schedule) updates.schedule = { type: "cron", expression: opts.schedule };
1252
+ if (opts.prompt) updates.prompt = opts.prompt;
1253
+ if (opts.enable) updates.enabled = true;
1254
+ if (opts.disable) updates.enabled = false;
1255
+ if (Object.keys(updates).length === 0) {
1256
+ console.error(theme.error("\u2717 No fields to update. Use --name, --schedule, --prompt, --enable, or --disable."));
1257
+ cronService.stop();
1258
+ process.exit(1);
1259
+ }
1260
+ const job = cronService.update(id, updates);
1261
+ if (!job) {
1262
+ console.error(theme.error(`\u2717 Cron job not found: ${id}`));
1263
+ cronService.stop();
1264
+ process.exit(1);
1265
+ }
1266
+ console.log(`${theme.success("\u2713")} Updated cron job ${theme.command(job.name)}`);
1267
+ cronService.stop();
1268
+ });
1269
+ cr.command("run").description("Execute a cron job immediately").argument("<id>", "Job ID").action(async (id) => {
1270
+ const dataDir = getDataDir11();
1271
+ const dbPath = path11.join(dataDir, "cortask.db");
1272
+ const cronService = new CronService(dbPath);
1273
+ const job = cronService.getJob(id);
1274
+ if (!job) {
1275
+ console.error(theme.error(`\u2717 Cron job not found: ${id}`));
1276
+ cronService.stop();
1277
+ process.exit(1);
1278
+ }
1279
+ console.log(theme.muted(`Executing job "${job.name}"...`));
1280
+ try {
1281
+ await cronService.runNow(id);
1282
+ console.log(`${theme.success("\u2713")} Job executed successfully`);
1283
+ } catch (err) {
1284
+ console.error(theme.error(`\u2717 ${err instanceof Error ? err.message : String(err)}`));
1285
+ }
1286
+ cronService.stop();
1287
+ });
723
1288
  program.command("update").description("Check for updates and optionally install them").option("--check", "Only check, do not install").action(async (opts) => {
724
1289
  try {
725
1290
  const res = await fetch("https://registry.npmjs.org/cortask/latest");