thoth-plugin 1.1.0 → 1.1.1
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 +185 -73
- package/dist/tools/skill/types.d.ts +2 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -405,7 +405,92 @@ Before stating any fact about Zeus's life:
|
|
|
405
405
|
- *If NOT FOUND*: Say "I don't have that recorded" \u2014 never guess
|
|
406
406
|
</Knowledge_Management>`;
|
|
407
407
|
// src/specialization/prompt-builder.ts
|
|
408
|
-
import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
|
|
408
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2, readdirSync } from "fs";
|
|
409
|
+
import { join as join3 } from "path";
|
|
410
|
+
import { homedir } from "os";
|
|
411
|
+
function parseSkillTriggers(content) {
|
|
412
|
+
const frontmatterRegex = /^---\r?\n([\s\S]*?)\r?\n---/;
|
|
413
|
+
const match = content.match(frontmatterRegex);
|
|
414
|
+
if (!match)
|
|
415
|
+
return null;
|
|
416
|
+
const yaml = match[1];
|
|
417
|
+
let name = "";
|
|
418
|
+
const triggers = [];
|
|
419
|
+
let inTriggers = false;
|
|
420
|
+
for (const line of yaml.split(`
|
|
421
|
+
`)) {
|
|
422
|
+
const trimmed = line.trim();
|
|
423
|
+
if (trimmed.startsWith("name:")) {
|
|
424
|
+
name = trimmed.slice(5).trim();
|
|
425
|
+
inTriggers = false;
|
|
426
|
+
} else if (trimmed === "triggers:") {
|
|
427
|
+
inTriggers = true;
|
|
428
|
+
} else if (inTriggers && trimmed.startsWith("- ")) {
|
|
429
|
+
triggers.push(trimmed.slice(2).trim());
|
|
430
|
+
} else if (trimmed.includes(":") && !trimmed.startsWith("-")) {
|
|
431
|
+
inTriggers = false;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
return triggers.length > 0 ? { name, triggers } : null;
|
|
435
|
+
}
|
|
436
|
+
function discoverTriggersFromDir(skillsDir) {
|
|
437
|
+
if (!existsSync3(skillsDir))
|
|
438
|
+
return [];
|
|
439
|
+
const results = [];
|
|
440
|
+
try {
|
|
441
|
+
const entries = readdirSync(skillsDir, { withFileTypes: true });
|
|
442
|
+
for (const entry of entries) {
|
|
443
|
+
if (entry.name.startsWith("."))
|
|
444
|
+
continue;
|
|
445
|
+
if (!entry.isDirectory())
|
|
446
|
+
continue;
|
|
447
|
+
const skillMdPath = join3(skillsDir, entry.name, "SKILL.md");
|
|
448
|
+
if (!existsSync3(skillMdPath))
|
|
449
|
+
continue;
|
|
450
|
+
try {
|
|
451
|
+
const content = readFileSync2(skillMdPath, "utf-8");
|
|
452
|
+
const parsed = parseSkillTriggers(content);
|
|
453
|
+
if (parsed) {
|
|
454
|
+
results.push(parsed);
|
|
455
|
+
}
|
|
456
|
+
} catch {}
|
|
457
|
+
}
|
|
458
|
+
} catch {}
|
|
459
|
+
return results;
|
|
460
|
+
}
|
|
461
|
+
function buildSkillRoutingSection() {
|
|
462
|
+
const projectSkillsDir = join3(process.cwd(), ".opencode", "skill");
|
|
463
|
+
const userSkillsDir = join3(homedir(), ".opencode", "skill");
|
|
464
|
+
const projectTriggers = discoverTriggersFromDir(projectSkillsDir);
|
|
465
|
+
const userTriggers = discoverTriggersFromDir(userSkillsDir);
|
|
466
|
+
const seen = new Set;
|
|
467
|
+
const allTriggers = [];
|
|
468
|
+
for (const t of [...projectTriggers, ...userTriggers]) {
|
|
469
|
+
if (!seen.has(t.name)) {
|
|
470
|
+
seen.add(t.name);
|
|
471
|
+
allTriggers.push(t);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
if (allTriggers.length === 0) {
|
|
475
|
+
return "";
|
|
476
|
+
}
|
|
477
|
+
const lines = [
|
|
478
|
+
"<Skill_Routing>",
|
|
479
|
+
"## Skill Routing (CHECK BEFORE RESPONDING)",
|
|
480
|
+
"",
|
|
481
|
+
"Before responding to user requests, check if their intent matches a skill trigger:",
|
|
482
|
+
""
|
|
483
|
+
];
|
|
484
|
+
for (const skill of allTriggers) {
|
|
485
|
+
const triggers = skill.triggers.map((t) => `"${t}"`).join(", ");
|
|
486
|
+
lines.push(`- ${triggers} \u2192 \`skill({ skill: "${skill.name}" })\``);
|
|
487
|
+
}
|
|
488
|
+
lines.push("");
|
|
489
|
+
lines.push("**Rule**: If user intent matches a trigger, invoke the skill immediately. Don't improvise workflows that already exist.");
|
|
490
|
+
lines.push("</Skill_Routing>");
|
|
491
|
+
return lines.join(`
|
|
492
|
+
`);
|
|
493
|
+
}
|
|
409
494
|
var THOTH_CORE_CAPABILITIES = `<Core_Capabilities>
|
|
410
495
|
## Core Capabilities
|
|
411
496
|
|
|
@@ -539,6 +624,10 @@ function buildThothPrompt(spec) {
|
|
|
539
624
|
sections.push(THOTH_ANTI_PATTERNS);
|
|
540
625
|
sections.push(THOTH_BEHAVIORAL_GUIDANCE);
|
|
541
626
|
sections.push(THOTH_KNOWLEDGE_MANAGEMENT);
|
|
627
|
+
const skillRouting = buildSkillRoutingSection();
|
|
628
|
+
if (skillRouting) {
|
|
629
|
+
sections.push(skillRouting);
|
|
630
|
+
}
|
|
542
631
|
sections.push(THOTH_CORE_CAPABILITIES);
|
|
543
632
|
sections.push(THOTH_EXECUTION);
|
|
544
633
|
sections.push(THOTH_PERMISSIONS);
|
|
@@ -3238,7 +3327,7 @@ To proceed, mark this as Emergency/P0 or wait until work hours.`
|
|
|
3238
3327
|
}
|
|
3239
3328
|
// src/hooks/directory-agents-injector/index.ts
|
|
3240
3329
|
import { existsSync as existsSync6, readFileSync as readFileSync6 } from "fs";
|
|
3241
|
-
import { dirname as dirname4, join as
|
|
3330
|
+
import { dirname as dirname4, join as join9, resolve } from "path";
|
|
3242
3331
|
|
|
3243
3332
|
// src/hooks/directory-agents-injector/storage.ts
|
|
3244
3333
|
import {
|
|
@@ -3248,18 +3337,18 @@ import {
|
|
|
3248
3337
|
writeFileSync as writeFileSync3,
|
|
3249
3338
|
unlinkSync
|
|
3250
3339
|
} from "fs";
|
|
3251
|
-
import { join as
|
|
3340
|
+
import { join as join8 } from "path";
|
|
3252
3341
|
|
|
3253
3342
|
// src/hooks/directory-agents-injector/constants.ts
|
|
3254
|
-
import { join as
|
|
3255
|
-
var xdgData = process.env.XDG_DATA_HOME ||
|
|
3256
|
-
var OPENCODE_STORAGE =
|
|
3257
|
-
var AGENTS_INJECTOR_STORAGE =
|
|
3343
|
+
import { join as join7 } from "path";
|
|
3344
|
+
var xdgData = process.env.XDG_DATA_HOME || join7(process.env.HOME || "", ".local", "share");
|
|
3345
|
+
var OPENCODE_STORAGE = join7(xdgData, "opencode", "storage");
|
|
3346
|
+
var AGENTS_INJECTOR_STORAGE = join7(OPENCODE_STORAGE, "thoth-directory-agents");
|
|
3258
3347
|
var AGENTS_FILENAME2 = "AGENTS.md";
|
|
3259
3348
|
|
|
3260
3349
|
// src/hooks/directory-agents-injector/storage.ts
|
|
3261
3350
|
function getStoragePath(sessionID) {
|
|
3262
|
-
return
|
|
3351
|
+
return join8(AGENTS_INJECTOR_STORAGE, `${sessionID}.json`);
|
|
3263
3352
|
}
|
|
3264
3353
|
function loadInjectedPaths(sessionID) {
|
|
3265
3354
|
const filePath = getStoragePath(sessionID);
|
|
@@ -3315,7 +3404,7 @@ function createDirectoryAgentsInjectorHook(options) {
|
|
|
3315
3404
|
let current = startDir;
|
|
3316
3405
|
const normalizedBase = knowledgeBasePath.endsWith("/") ? knowledgeBasePath.slice(0, -1) : knowledgeBasePath;
|
|
3317
3406
|
while (true) {
|
|
3318
|
-
const agentsPath =
|
|
3407
|
+
const agentsPath = join9(current, AGENTS_FILENAME2);
|
|
3319
3408
|
if (existsSync6(agentsPath)) {
|
|
3320
3409
|
found.push(agentsPath);
|
|
3321
3410
|
}
|
|
@@ -3396,8 +3485,8 @@ ${content}`;
|
|
|
3396
3485
|
}
|
|
3397
3486
|
|
|
3398
3487
|
// src/shared-hooks/todo-continuation-enforcer.ts
|
|
3399
|
-
import { existsSync as existsSync8, readdirSync as
|
|
3400
|
-
import { join as
|
|
3488
|
+
import { existsSync as existsSync8, readdirSync as readdirSync3 } from "fs";
|
|
3489
|
+
import { join as join13 } from "path";
|
|
3401
3490
|
|
|
3402
3491
|
// src/shared-hooks/utils/session-state.ts
|
|
3403
3492
|
var subagentSessions = new Set;
|
|
@@ -3410,16 +3499,16 @@ function getMainSessionID() {
|
|
|
3410
3499
|
}
|
|
3411
3500
|
|
|
3412
3501
|
// src/shared-hooks/utils/message-storage.ts
|
|
3413
|
-
import { existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync7, readdirSync, writeFileSync as writeFileSync4 } from "fs";
|
|
3414
|
-
import { join as
|
|
3502
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync7, readdirSync as readdirSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
3503
|
+
import { join as join11 } from "path";
|
|
3415
3504
|
|
|
3416
3505
|
// src/shared-hooks/utils/constants.ts
|
|
3417
|
-
import { join as
|
|
3418
|
-
import { homedir as
|
|
3419
|
-
var xdgData2 = process.env.XDG_DATA_HOME ||
|
|
3420
|
-
var OPENCODE_STORAGE2 =
|
|
3421
|
-
var MESSAGE_STORAGE =
|
|
3422
|
-
var PART_STORAGE =
|
|
3506
|
+
import { join as join10 } from "path";
|
|
3507
|
+
import { homedir as homedir3 } from "os";
|
|
3508
|
+
var xdgData2 = process.env.XDG_DATA_HOME || join10(homedir3(), ".local", "share");
|
|
3509
|
+
var OPENCODE_STORAGE2 = join10(xdgData2, "opencode", "storage");
|
|
3510
|
+
var MESSAGE_STORAGE = join10(OPENCODE_STORAGE2, "message");
|
|
3511
|
+
var PART_STORAGE = join10(OPENCODE_STORAGE2, "part");
|
|
3423
3512
|
var THINKING_TYPES = new Set(["thinking", "redacted_thinking", "reasoning"]);
|
|
3424
3513
|
var META_TYPES = new Set(["step-start", "step-finish"]);
|
|
3425
3514
|
var CONTENT_TYPES = new Set(["text", "tool", "tool_use", "tool_result"]);
|
|
@@ -3427,10 +3516,10 @@ var CONTENT_TYPES = new Set(["text", "tool", "tool_use", "tool_result"]);
|
|
|
3427
3516
|
// src/shared-hooks/utils/message-storage.ts
|
|
3428
3517
|
function findNearestMessageWithFields(messageDir) {
|
|
3429
3518
|
try {
|
|
3430
|
-
const files =
|
|
3519
|
+
const files = readdirSync2(messageDir).filter((f) => f.endsWith(".json")).sort().reverse();
|
|
3431
3520
|
for (const file of files) {
|
|
3432
3521
|
try {
|
|
3433
|
-
const content = readFileSync7(
|
|
3522
|
+
const content = readFileSync7(join11(messageDir, file), "utf-8");
|
|
3434
3523
|
const msg = JSON.parse(content);
|
|
3435
3524
|
if (msg.agent && msg.model?.providerID && msg.model?.modelID) {
|
|
3436
3525
|
return msg;
|
|
@@ -3471,11 +3560,11 @@ Incomplete tasks remain in your todo list. Continue working on the next pending
|
|
|
3471
3560
|
function getMessageDir(sessionID) {
|
|
3472
3561
|
if (!existsSync8(MESSAGE_STORAGE))
|
|
3473
3562
|
return null;
|
|
3474
|
-
const directPath =
|
|
3563
|
+
const directPath = join13(MESSAGE_STORAGE, sessionID);
|
|
3475
3564
|
if (existsSync8(directPath))
|
|
3476
3565
|
return directPath;
|
|
3477
|
-
for (const dir of
|
|
3478
|
-
const sessionPath =
|
|
3566
|
+
for (const dir of readdirSync3(MESSAGE_STORAGE)) {
|
|
3567
|
+
const sessionPath = join13(MESSAGE_STORAGE, dir, sessionID);
|
|
3479
3568
|
if (existsSync8(sessionPath))
|
|
3480
3569
|
return sessionPath;
|
|
3481
3570
|
}
|
|
@@ -3703,16 +3792,16 @@ function createTodoContinuationEnforcer(ctx) {
|
|
|
3703
3792
|
};
|
|
3704
3793
|
}
|
|
3705
3794
|
// src/shared-hooks/session-recovery/storage.ts
|
|
3706
|
-
import { existsSync as existsSync9, mkdirSync as mkdirSync4, readdirSync as
|
|
3707
|
-
import { join as
|
|
3795
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync4, readdirSync as readdirSync4, readFileSync as readFileSync8, unlinkSync as unlinkSync2, writeFileSync as writeFileSync5 } from "fs";
|
|
3796
|
+
import { join as join15 } from "path";
|
|
3708
3797
|
|
|
3709
3798
|
// src/shared-hooks/session-recovery/constants.ts
|
|
3710
|
-
import { join as
|
|
3711
|
-
import { homedir as
|
|
3712
|
-
var xdgData3 = process.env.XDG_DATA_HOME ||
|
|
3713
|
-
var OPENCODE_STORAGE3 =
|
|
3714
|
-
var MESSAGE_STORAGE2 =
|
|
3715
|
-
var PART_STORAGE2 =
|
|
3799
|
+
import { join as join14 } from "path";
|
|
3800
|
+
import { homedir as homedir4 } from "os";
|
|
3801
|
+
var xdgData3 = process.env.XDG_DATA_HOME || join14(homedir4(), ".local", "share");
|
|
3802
|
+
var OPENCODE_STORAGE3 = join14(xdgData3, "opencode", "storage");
|
|
3803
|
+
var MESSAGE_STORAGE2 = join14(OPENCODE_STORAGE3, "message");
|
|
3804
|
+
var PART_STORAGE2 = join14(OPENCODE_STORAGE3, "part");
|
|
3716
3805
|
var THINKING_TYPES2 = new Set(["thinking", "redacted_thinking", "reasoning"]);
|
|
3717
3806
|
var META_TYPES2 = new Set(["step-start", "step-finish"]);
|
|
3718
3807
|
var CONTENT_TYPES2 = new Set(["text", "tool", "tool_use", "tool_result"]);
|
|
@@ -3721,12 +3810,12 @@ var CONTENT_TYPES2 = new Set(["text", "tool", "tool_use", "tool_result"]);
|
|
|
3721
3810
|
function getMessageDir2(sessionID) {
|
|
3722
3811
|
if (!existsSync9(MESSAGE_STORAGE2))
|
|
3723
3812
|
return "";
|
|
3724
|
-
const directPath =
|
|
3813
|
+
const directPath = join15(MESSAGE_STORAGE2, sessionID);
|
|
3725
3814
|
if (existsSync9(directPath)) {
|
|
3726
3815
|
return directPath;
|
|
3727
3816
|
}
|
|
3728
|
-
for (const dir of
|
|
3729
|
-
const sessionPath =
|
|
3817
|
+
for (const dir of readdirSync4(MESSAGE_STORAGE2)) {
|
|
3818
|
+
const sessionPath = join15(MESSAGE_STORAGE2, dir, sessionID);
|
|
3730
3819
|
if (existsSync9(sessionPath)) {
|
|
3731
3820
|
return sessionPath;
|
|
3732
3821
|
}
|
|
@@ -3738,11 +3827,11 @@ function readMessages(sessionID) {
|
|
|
3738
3827
|
if (!messageDir || !existsSync9(messageDir))
|
|
3739
3828
|
return [];
|
|
3740
3829
|
const messages = [];
|
|
3741
|
-
for (const file of
|
|
3830
|
+
for (const file of readdirSync4(messageDir)) {
|
|
3742
3831
|
if (!file.endsWith(".json"))
|
|
3743
3832
|
continue;
|
|
3744
3833
|
try {
|
|
3745
|
-
const content = readFileSync8(
|
|
3834
|
+
const content = readFileSync8(join15(messageDir, file), "utf-8");
|
|
3746
3835
|
messages.push(JSON.parse(content));
|
|
3747
3836
|
} catch {
|
|
3748
3837
|
continue;
|
|
@@ -3757,15 +3846,15 @@ function readMessages(sessionID) {
|
|
|
3757
3846
|
});
|
|
3758
3847
|
}
|
|
3759
3848
|
function readParts(messageID) {
|
|
3760
|
-
const partDir =
|
|
3849
|
+
const partDir = join15(PART_STORAGE2, messageID);
|
|
3761
3850
|
if (!existsSync9(partDir))
|
|
3762
3851
|
return [];
|
|
3763
3852
|
const parts = [];
|
|
3764
|
-
for (const file of
|
|
3853
|
+
for (const file of readdirSync4(partDir)) {
|
|
3765
3854
|
if (!file.endsWith(".json"))
|
|
3766
3855
|
continue;
|
|
3767
3856
|
try {
|
|
3768
|
-
const content = readFileSync8(
|
|
3857
|
+
const content = readFileSync8(join15(partDir, file), "utf-8");
|
|
3769
3858
|
parts.push(JSON.parse(content));
|
|
3770
3859
|
} catch {
|
|
3771
3860
|
continue;
|
|
@@ -3807,7 +3896,7 @@ function findMessagesWithOrphanThinking(sessionID) {
|
|
|
3807
3896
|
return result;
|
|
3808
3897
|
}
|
|
3809
3898
|
function prependThinkingPart(sessionID, messageID) {
|
|
3810
|
-
const partDir =
|
|
3899
|
+
const partDir = join15(PART_STORAGE2, messageID);
|
|
3811
3900
|
if (!existsSync9(partDir)) {
|
|
3812
3901
|
mkdirSync4(partDir, { recursive: true });
|
|
3813
3902
|
}
|
|
@@ -3821,22 +3910,22 @@ function prependThinkingPart(sessionID, messageID) {
|
|
|
3821
3910
|
synthetic: true
|
|
3822
3911
|
};
|
|
3823
3912
|
try {
|
|
3824
|
-
writeFileSync5(
|
|
3913
|
+
writeFileSync5(join15(partDir, `${partId}.json`), JSON.stringify(part, null, 2));
|
|
3825
3914
|
return true;
|
|
3826
3915
|
} catch {
|
|
3827
3916
|
return false;
|
|
3828
3917
|
}
|
|
3829
3918
|
}
|
|
3830
3919
|
function stripThinkingParts(messageID) {
|
|
3831
|
-
const partDir =
|
|
3920
|
+
const partDir = join15(PART_STORAGE2, messageID);
|
|
3832
3921
|
if (!existsSync9(partDir))
|
|
3833
3922
|
return false;
|
|
3834
3923
|
let anyRemoved = false;
|
|
3835
|
-
for (const file of
|
|
3924
|
+
for (const file of readdirSync4(partDir)) {
|
|
3836
3925
|
if (!file.endsWith(".json"))
|
|
3837
3926
|
continue;
|
|
3838
3927
|
try {
|
|
3839
|
-
const filePath =
|
|
3928
|
+
const filePath = join15(partDir, file);
|
|
3840
3929
|
const content = readFileSync8(filePath, "utf-8");
|
|
3841
3930
|
const part = JSON.parse(content);
|
|
3842
3931
|
if (THINKING_TYPES2.has(part.type)) {
|
|
@@ -4163,16 +4252,16 @@ ${CONTEXT_REMINDER}
|
|
|
4163
4252
|
};
|
|
4164
4253
|
}
|
|
4165
4254
|
// src/shared-hooks/background-agent/manager.ts
|
|
4166
|
-
import { existsSync as existsSync10, readdirSync as
|
|
4167
|
-
import { join as
|
|
4255
|
+
import { existsSync as existsSync10, readdirSync as readdirSync5 } from "fs";
|
|
4256
|
+
import { join as join16 } from "path";
|
|
4168
4257
|
function getMessageDir3(sessionID) {
|
|
4169
4258
|
if (!existsSync10(MESSAGE_STORAGE))
|
|
4170
4259
|
return null;
|
|
4171
|
-
const directPath =
|
|
4260
|
+
const directPath = join16(MESSAGE_STORAGE, sessionID);
|
|
4172
4261
|
if (existsSync10(directPath))
|
|
4173
4262
|
return directPath;
|
|
4174
|
-
for (const dir of
|
|
4175
|
-
const sessionPath =
|
|
4263
|
+
for (const dir of readdirSync5(MESSAGE_STORAGE)) {
|
|
4264
|
+
const sessionPath = join16(MESSAGE_STORAGE, dir, sessionID);
|
|
4176
4265
|
if (existsSync10(sessionPath))
|
|
4177
4266
|
return sessionPath;
|
|
4178
4267
|
}
|
|
@@ -17115,13 +17204,13 @@ Status: ${task.status}`;
|
|
|
17115
17204
|
});
|
|
17116
17205
|
}
|
|
17117
17206
|
// src/tools/skill/tools.ts
|
|
17118
|
-
import { existsSync as existsSync11, readdirSync as
|
|
17119
|
-
import { homedir as
|
|
17120
|
-
import { join as
|
|
17207
|
+
import { existsSync as existsSync11, readdirSync as readdirSync6, readFileSync as readFileSync9, lstatSync, readlinkSync } from "fs";
|
|
17208
|
+
import { homedir as homedir5 } from "os";
|
|
17209
|
+
import { join as join17, basename as basename2, resolve as resolve2, dirname as dirname5 } from "path";
|
|
17121
17210
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
17122
17211
|
var __filename3 = fileURLToPath2(import.meta.url);
|
|
17123
17212
|
var __dirname3 = dirname5(__filename3);
|
|
17124
|
-
var NPM_DEFAULTS_SKILL_DIR =
|
|
17213
|
+
var NPM_DEFAULTS_SKILL_DIR = join17(__dirname3, "..", "..", "defaults", "skill");
|
|
17125
17214
|
function deduplicateSkills(skills) {
|
|
17126
17215
|
const seen = new Set;
|
|
17127
17216
|
return skills.filter((skill) => {
|
|
@@ -17194,6 +17283,7 @@ function parseSkillFrontmatter(data) {
|
|
|
17194
17283
|
description: typeof data.description === "string" ? data.description : "",
|
|
17195
17284
|
license: typeof data.license === "string" ? data.license : undefined,
|
|
17196
17285
|
"allowed-tools": Array.isArray(data["allowed-tools"]) ? data["allowed-tools"] : undefined,
|
|
17286
|
+
triggers: Array.isArray(data.triggers) ? data.triggers : undefined,
|
|
17197
17287
|
metadata: typeof data.metadata === "object" && data.metadata !== null ? data.metadata : undefined
|
|
17198
17288
|
};
|
|
17199
17289
|
}
|
|
@@ -17201,15 +17291,15 @@ function discoverSkillsFromDir(skillsDir, scope) {
|
|
|
17201
17291
|
if (!existsSync11(skillsDir)) {
|
|
17202
17292
|
return [];
|
|
17203
17293
|
}
|
|
17204
|
-
const entries =
|
|
17294
|
+
const entries = readdirSync6(skillsDir, { withFileTypes: true });
|
|
17205
17295
|
const skills = [];
|
|
17206
17296
|
for (const entry of entries) {
|
|
17207
17297
|
if (entry.name.startsWith("."))
|
|
17208
17298
|
continue;
|
|
17209
|
-
const skillPath =
|
|
17299
|
+
const skillPath = join17(skillsDir, entry.name);
|
|
17210
17300
|
if (entry.isDirectory() || entry.isSymbolicLink()) {
|
|
17211
17301
|
const resolvedPath = resolveSymlink(skillPath);
|
|
17212
|
-
const skillMdPath =
|
|
17302
|
+
const skillMdPath = join17(resolvedPath, "SKILL.md");
|
|
17213
17303
|
if (!existsSync11(skillMdPath))
|
|
17214
17304
|
continue;
|
|
17215
17305
|
try {
|
|
@@ -17218,7 +17308,8 @@ function discoverSkillsFromDir(skillsDir, scope) {
|
|
|
17218
17308
|
skills.push({
|
|
17219
17309
|
name: data.name || entry.name,
|
|
17220
17310
|
description: data.description || "",
|
|
17221
|
-
scope
|
|
17311
|
+
scope,
|
|
17312
|
+
triggers: Array.isArray(data.triggers) ? data.triggers : undefined
|
|
17222
17313
|
});
|
|
17223
17314
|
} catch {
|
|
17224
17315
|
continue;
|
|
@@ -17228,16 +17319,35 @@ function discoverSkillsFromDir(skillsDir, scope) {
|
|
|
17228
17319
|
return skills;
|
|
17229
17320
|
}
|
|
17230
17321
|
function discoverSkillsSync() {
|
|
17231
|
-
const userSkillsDir =
|
|
17232
|
-
const projectSkillsDir =
|
|
17322
|
+
const userSkillsDir = join17(homedir5(), ".opencode", "skill");
|
|
17323
|
+
const projectSkillsDir = join17(process.cwd(), ".opencode", "skill");
|
|
17233
17324
|
const builtinSkills = discoverSkillsFromDir(NPM_DEFAULTS_SKILL_DIR, "builtin");
|
|
17234
17325
|
const userSkills = discoverSkillsFromDir(userSkillsDir, "user");
|
|
17235
17326
|
const projectSkills = discoverSkillsFromDir(projectSkillsDir, "project");
|
|
17236
17327
|
return deduplicateSkills([...projectSkills, ...userSkills, ...builtinSkills]);
|
|
17237
17328
|
}
|
|
17329
|
+
function buildTriggerSection(skills) {
|
|
17330
|
+
const skillsWithTriggers = skills.filter((s) => s.triggers && s.triggers.length > 0);
|
|
17331
|
+
if (skillsWithTriggers.length === 0) {
|
|
17332
|
+
return "";
|
|
17333
|
+
}
|
|
17334
|
+
const lines = [
|
|
17335
|
+
`
|
|
17336
|
+
|
|
17337
|
+
Skill Triggers (invoke skill when user says these phrases):`
|
|
17338
|
+
];
|
|
17339
|
+
for (const skill of skillsWithTriggers) {
|
|
17340
|
+
const triggers = skill.triggers.map((t) => `"${t}"`).join(", ");
|
|
17341
|
+
lines.push(`- ${triggers} \u2192 ${skill.name}`);
|
|
17342
|
+
}
|
|
17343
|
+
lines.push(`
|
|
17344
|
+
IMPORTANT: If user intent matches a trigger phrase, invoke the skill immediately. Don't improvise.`);
|
|
17345
|
+
return lines.join(`
|
|
17346
|
+
`);
|
|
17347
|
+
}
|
|
17238
17348
|
async function parseSkillMd(skillPath) {
|
|
17239
17349
|
const resolvedPath = resolveSymlink(skillPath);
|
|
17240
|
-
const skillMdPath =
|
|
17350
|
+
const skillMdPath = join17(resolvedPath, "SKILL.md");
|
|
17241
17351
|
if (!existsSync11(skillMdPath)) {
|
|
17242
17352
|
return null;
|
|
17243
17353
|
}
|
|
@@ -17250,14 +17360,15 @@ async function parseSkillMd(skillPath) {
|
|
|
17250
17360
|
description: frontmatter.description,
|
|
17251
17361
|
license: frontmatter.license,
|
|
17252
17362
|
allowedTools: frontmatter["allowed-tools"],
|
|
17363
|
+
triggers: frontmatter.triggers,
|
|
17253
17364
|
metadata: frontmatter.metadata
|
|
17254
17365
|
};
|
|
17255
|
-
const referencesDir =
|
|
17256
|
-
const scriptsDir =
|
|
17257
|
-
const assetsDir =
|
|
17258
|
-
const references = existsSync11(referencesDir) ?
|
|
17259
|
-
const scripts = existsSync11(scriptsDir) ?
|
|
17260
|
-
const assets = existsSync11(assetsDir) ?
|
|
17366
|
+
const referencesDir = join17(resolvedPath, "references");
|
|
17367
|
+
const scriptsDir = join17(resolvedPath, "scripts");
|
|
17368
|
+
const assetsDir = join17(resolvedPath, "assets");
|
|
17369
|
+
const references = existsSync11(referencesDir) ? readdirSync6(referencesDir).filter((f) => !f.startsWith(".")) : [];
|
|
17370
|
+
const scripts = existsSync11(scriptsDir) ? readdirSync6(scriptsDir).filter((f) => !f.startsWith(".") && !f.startsWith("__")) : [];
|
|
17371
|
+
const assets = existsSync11(assetsDir) ? readdirSync6(assetsDir).filter((f) => !f.startsWith(".")) : [];
|
|
17261
17372
|
return {
|
|
17262
17373
|
name: metadata.name,
|
|
17263
17374
|
path: resolvedPath,
|
|
@@ -17276,12 +17387,12 @@ async function discoverSkillsFromDirAsync(skillsDir) {
|
|
|
17276
17387
|
if (!existsSync11(skillsDir)) {
|
|
17277
17388
|
return [];
|
|
17278
17389
|
}
|
|
17279
|
-
const entries =
|
|
17390
|
+
const entries = readdirSync6(skillsDir, { withFileTypes: true });
|
|
17280
17391
|
const skills = [];
|
|
17281
17392
|
for (const entry of entries) {
|
|
17282
17393
|
if (entry.name.startsWith("."))
|
|
17283
17394
|
continue;
|
|
17284
|
-
const skillPath =
|
|
17395
|
+
const skillPath = join17(skillsDir, entry.name);
|
|
17285
17396
|
if (entry.isDirectory() || entry.isSymbolicLink()) {
|
|
17286
17397
|
const skillInfo = await parseSkillMd(skillPath);
|
|
17287
17398
|
if (skillInfo) {
|
|
@@ -17292,8 +17403,8 @@ async function discoverSkillsFromDirAsync(skillsDir) {
|
|
|
17292
17403
|
return skills;
|
|
17293
17404
|
}
|
|
17294
17405
|
async function discoverSkills() {
|
|
17295
|
-
const userSkillsDir =
|
|
17296
|
-
const projectSkillsDir =
|
|
17406
|
+
const userSkillsDir = join17(homedir5(), ".opencode", "skill");
|
|
17407
|
+
const projectSkillsDir = join17(process.cwd(), ".opencode", "skill");
|
|
17297
17408
|
const builtinSkills = await discoverSkillsFromDirAsync(NPM_DEFAULTS_SKILL_DIR);
|
|
17298
17409
|
const userSkills = await discoverSkillsFromDirAsync(userSkillsDir);
|
|
17299
17410
|
const projectSkills = await discoverSkillsFromDirAsync(projectSkillsDir);
|
|
@@ -17323,7 +17434,7 @@ async function loadSkillWithReferences(skill, includeRefs) {
|
|
|
17323
17434
|
const referencesLoaded = [];
|
|
17324
17435
|
if (includeRefs && skill.references.length > 0) {
|
|
17325
17436
|
for (const ref of skill.references) {
|
|
17326
|
-
const refPath =
|
|
17437
|
+
const refPath = join17(skill.path, "references", ref);
|
|
17327
17438
|
try {
|
|
17328
17439
|
const content = readFileSync9(refPath, "utf-8");
|
|
17329
17440
|
referencesLoaded.push({ path: ref, content });
|
|
@@ -17384,13 +17495,14 @@ function createSkillTool() {
|
|
|
17384
17495
|
const availableSkills = discoverSkillsSync();
|
|
17385
17496
|
const skillListForDescription = availableSkills.map((s) => `- ${s.name}: ${s.description} (${s.scope})`).join(`
|
|
17386
17497
|
`);
|
|
17498
|
+
const triggerSection = buildTriggerSection(availableSkills);
|
|
17387
17499
|
return tool({
|
|
17388
17500
|
description: `Execute a skill within the main conversation.
|
|
17389
17501
|
|
|
17390
17502
|
When you invoke a skill, the skill's prompt will expand and provide detailed instructions on how to complete the task.
|
|
17391
17503
|
|
|
17392
17504
|
Available Skills:
|
|
17393
|
-
${skillListForDescription || "(No skills discovered yet)"}`,
|
|
17505
|
+
${skillListForDescription || "(No skills discovered yet)"}${triggerSection}`,
|
|
17394
17506
|
args: {
|
|
17395
17507
|
skill: tool.schema.string().describe("The skill name or search query to find and load. Can be exact skill name (e.g., 'morning-boot') or keywords (e.g., 'morning', 'boot').")
|
|
17396
17508
|
},
|
|
@@ -5,6 +5,7 @@ export declare const SkillFrontmatterSchema: z.ZodObject<{
|
|
|
5
5
|
description: z.ZodString;
|
|
6
6
|
license: z.ZodOptional<z.ZodString>;
|
|
7
7
|
"allowed-tools": z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
8
|
+
triggers: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
8
9
|
metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
9
10
|
}, z.core.$strip>;
|
|
10
11
|
export type SkillFrontmatter = z.infer<typeof SkillFrontmatterSchema>;
|
|
@@ -13,6 +14,7 @@ export interface SkillMetadata {
|
|
|
13
14
|
description: string;
|
|
14
15
|
license?: string;
|
|
15
16
|
allowedTools?: string[];
|
|
17
|
+
triggers?: string[];
|
|
16
18
|
metadata?: Record<string, string>;
|
|
17
19
|
}
|
|
18
20
|
export interface SkillInfo {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "thoth-plugin",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "Thoth - Root-level life orchestrator for OpenCode. Unified AI chief of staff combining Sisyphus execution quality, Personal-OS rhythms, and Thoth relationship model.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|