opencode-thrum-pro 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/assets/commands/thrum-_deviations-protocol.md +87 -0
  2. package/assets/commands/thrum-_generator-idempotency-protocol.md +15 -0
  3. package/assets/commands/thrum-_project-rules-protocol.md +18 -0
  4. package/assets/commands/thrum-_snapshot-protocol.md +265 -0
  5. package/assets/commands/thrum-_stamp-protocol.md +112 -0
  6. package/assets/commands/thrum-_verify-before-write-protocol.md +27 -0
  7. package/assets/commands/thrum-inbox.md +22 -0
  8. package/assets/commands/thrum-load-context.md +17 -0
  9. package/assets/commands/thrum-overview.md +17 -0
  10. package/assets/commands/thrum-prime.md +30 -0
  11. package/assets/commands/thrum-quickstart.md +14 -0
  12. package/assets/commands/thrum-reply.md +28 -0
  13. package/assets/commands/thrum-restart-extended.md +195 -0
  14. package/assets/commands/thrum-restart.md +293 -0
  15. package/assets/commands/thrum-send.md +31 -0
  16. package/assets/commands/thrum-sleep-extended.md +186 -0
  17. package/assets/commands/thrum-sleep.md +212 -0
  18. package/assets/commands/thrum-team.md +12 -0
  19. package/assets/commands/thrum-update-project.md +259 -0
  20. package/assets/commands/thrum-wait.md +14 -0
  21. package/assets/skills/adversarial-critique/SKILL.md +242 -0
  22. package/assets/skills/brainstorm-queue-aware/SKILL.md +167 -0
  23. package/assets/skills/brainstorm-steward/SKILL.md +181 -0
  24. package/assets/skills/choosing-subagent-models/SKILL.md +244 -0
  25. package/assets/skills/closing-findings-while-warm/SKILL.md +104 -0
  26. package/assets/skills/configure-roles/SKILL.md +229 -0
  27. package/assets/skills/coordinator-assessing-agent-completion/SKILL.md +205 -0
  28. package/assets/skills/coordinator-assessing-agent-completion/scan.py +117 -0
  29. package/assets/skills/coordinator-branch-split-on-block/SKILL.md +168 -0
  30. package/assets/skills/coordinator-context-monitoring/SKILL.md +674 -0
  31. package/assets/skills/coordinator-dispatching-work/SKILL.md +288 -0
  32. package/assets/skills/coordinator-hotpath-merge-gate/SKILL.md +522 -0
  33. package/assets/skills/coordinator-interacting-with-the-owner/SKILL.md +171 -0
  34. package/assets/skills/coordinator-maintaining-memory/SKILL.md +89 -0
  35. package/assets/skills/coordinator-managing-state-and-lifecycle/SKILL.md +192 -0
  36. package/assets/skills/coordinator-merging-code/SKILL.md +331 -0
  37. package/assets/skills/coordinator-philosophy-merge-gate/SKILL.md +442 -0
  38. package/assets/skills/coordinator-plan-reconcile-gate/SKILL.md +198 -0
  39. package/assets/skills/coordinator-post-restart-sweep/SKILL.md +135 -0
  40. package/assets/skills/coordinator-running-autonomous-feature-development/SKILL.md +199 -0
  41. package/assets/skills/coordinator-running-brainstorm-cycles/SKILL.md +654 -0
  42. package/assets/skills/coordinator-running-review-cycles/SKILL.md +153 -0
  43. package/assets/skills/coordinator-single-responsibility-merge-gate/SKILL.md +14 -0
  44. package/assets/skills/diagnosing-fleet-and-daemon/SKILL.md +255 -0
  45. package/assets/skills/efficient-multi-agent-research/SKILL.md +177 -0
  46. package/assets/skills/fixing-documentation-errors/SKILL.md +258 -0
  47. package/assets/skills/implementer-maintaining-memory/SKILL.md +84 -0
  48. package/assets/skills/implementer-orchestrating-execution/SKILL.md +125 -0
  49. package/assets/skills/implementer-receiving-dispatch/SKILL.md +127 -0
  50. package/assets/skills/implementer-receiving-review-feedback/SKILL.md +125 -0
  51. package/assets/skills/implementer-status-and-handoff/SKILL.md +150 -0
  52. package/assets/skills/implementer-tdd-and-quality/SKILL.md +99 -0
  53. package/assets/skills/memory-audit-recover/SKILL.md +153 -0
  54. package/assets/skills/memory-curate/SKILL.md +139 -0
  55. package/assets/skills/memory-maintenance/SKILL.md +87 -0
  56. package/assets/skills/memory-read-discipline/SKILL.md +164 -0
  57. package/assets/skills/memory-search-advanced/SKILL.md +217 -0
  58. package/assets/skills/memory-write-discipline/SKILL.md +135 -0
  59. package/assets/skills/orchestrate/SKILL.md +795 -0
  60. package/assets/skills/persistent-watcher-archetype/SKILL.md +225 -0
  61. package/assets/skills/prime-agent/SKILL.md +92 -0
  62. package/assets/skills/project-hotpath-gate/SKILL.md +255 -0
  63. package/assets/skills/project-philosophy/SKILL.md +418 -0
  64. package/assets/skills/project-philosophy/resources/philosophy-template.md +155 -0
  65. package/assets/skills/project-philosophy/resources/reliability-class-library.md +653 -0
  66. package/assets/skills/project-setup/SKILL.md +1007 -0
  67. package/assets/skills/project-setup/resources/implementation-agent.md +826 -0
  68. package/assets/skills/researcher-answering-queries/SKILL.md +100 -0
  69. package/assets/skills/researcher-investigating/SKILL.md +152 -0
  70. package/assets/skills/researcher-maintaining-memory/SKILL.md +176 -0
  71. package/assets/skills/state-migration/SKILL.md +54 -0
  72. package/assets/skills/thrum/SKILL.md +161 -0
  73. package/assets/skills/thrum/resources/ANTI_PATTERNS.md +132 -0
  74. package/assets/skills/thrum/resources/BOUNDARIES.md +56 -0
  75. package/assets/skills/thrum/resources/CLI_REFERENCE.md +1066 -0
  76. package/assets/skills/thrum/resources/IDENTITY.md +49 -0
  77. package/assets/skills/thrum/resources/LISTENER_PATTERN.md +64 -0
  78. package/assets/skills/thrum/resources/MESSAGING.md +163 -0
  79. package/assets/skills/thrum/resources/TMUX_SESSIONS.md +118 -0
  80. package/assets/skills/thrum/resources/WORKTREES.md +117 -0
  81. package/assets/skills/thrum-memory/SKILL.md +138 -0
  82. package/assets/skills/trace-spec-to-plan/SKILL.md +104 -0
  83. package/assets/skills/trace-spec-to-plan/resources/sweep-agent-prompt.md +57 -0
  84. package/assets/skills/update-queue/SKILL.md +149 -0
  85. package/assets/skills/using-the-queue/SKILL.md +172 -0
  86. package/assets/skills/using-thrum-state/SKILL.md +104 -0
  87. package/assets/skills/verify-against-plan/SKILL.md +323 -0
  88. package/assets/skills/verify-against-source/SKILL.md +237 -0
  89. package/assets/skills/watcher-archetype/SKILL.md +84 -0
  90. package/assets/skills/writing-durable-markdown/SKILL.md +66 -0
  91. package/dist/hooks/block-sync-cd.d.ts +9 -0
  92. package/dist/hooks/block-sync-cd.js +71 -0
  93. package/dist/hooks/block-sync-cd.test.d.ts +1 -0
  94. package/dist/hooks/block-sync-cd.test.js +61 -0
  95. package/dist/hooks/shell-env.d.ts +8 -0
  96. package/dist/hooks/shell-env.js +22 -0
  97. package/dist/index.d.ts +6 -0
  98. package/dist/index.js +25 -0
  99. package/dist/installer.d.ts +2 -0
  100. package/dist/installer.js +86 -0
  101. package/package.json +49 -0
@@ -0,0 +1,66 @@
1
+ ---
2
+ name: writing-durable-markdown
3
+ description: "Use when creating or editing ANY markdown file - CLAUDE.md, a role preamble, a SKILL.md, a runbook, project state, State.md, a spec, a plan, a README, or docs. Fires on adding a rule, writing a note, correcting a wrong claim, recording a lesson, or documenting an incident. Enforces state-the-rule-and-stop, because narrative in a loaded file costs tokens at every wake and leaves contradictions nobody can untangle."
4
+ ---
5
+
6
+ # Writing or editing ANY markdown file — state the rule and stop
7
+
8
+ Not what was done. Not why it was wrong. Not what the previous version said. Not
9
+ what not to do. Not the incident. **Just the rule, in the imperative.**
10
+
11
+ ## Before you save: measure
12
+
13
+ ```bash
14
+ git diff --numstat -- <file>
15
+ ```
16
+
17
+ **More than ~5 added lines for one rule means you wrote a story.** Cut it.
18
+
19
+ ## The three costs
20
+
21
+ **1. Tokens, paid at every wake, by everyone.** `CLAUDE.md`, role preambles and
22
+ state files are injected into every agent's context at every wake. A 40-line
23
+ explanation is not paid once by the author — it is paid by every agent, every
24
+ session, forever. Inside a session it is worse than linear: injected text is
25
+ re-read on every later turn.
26
+
27
+ **2. Contradiction.** Stories assert facts. Three stories about one rule disagree
28
+ about its scope, whether it still applies, and which version is current. **An
29
+ agent cannot tell which sentence is authoritative, so it obeys both.** Prose rots
30
+ silently — a test goes red when it drifts, a paragraph never does.
31
+
32
+ **3. Abandonment.** Past a certain size nobody can audit the file for
33
+ contradictions, so nobody tries, and the contradictions become permanent.
34
+ **The bloat is what makes the file unfixable** — every story added removes
35
+ someone's ability to fix the story before it.
36
+
37
+ ## A correction replaces; it does not accumulate
38
+
39
+ Delete the wrong text and write the right rule in its place. A correction left
40
+ *beside* what it corrects leaves two claims and no way to rank them. Corrections
41
+ are always longer than what they replace, so this is the main way these files
42
+ grow.
43
+
44
+ ## Where the story goes
45
+
46
+ `thrum memory` — retrieved on demand by the agent who needs it, instead of
47
+ injected into everyone. Keep the rule in the doc; leave at most a one-line
48
+ pointer.
49
+
50
+ ## Exempt from the cap
51
+
52
+ Tables, CLI output, command strings, file paths, enumerated vocabularies. **Never
53
+ truncate those to hit a line count.** The cap stops narrative; it does not
54
+ shorten data.
55
+
56
+ ## Shipped content carries no internal particulars
57
+
58
+ No issue IDs, no dates, no agent names, no measurements in customer-facing files.
59
+ The tracker references the change; the change never references the tracker.
60
+
61
+ ## A skill is generic — never specific to the repo you edit from
62
+
63
+ Write the rule so it holds for any repo running the skill. No repo names, config
64
+ keys, box names, agent names, or paths particular to the repo you happen to be in.
65
+ Repo-specific values live in that repo's config or memory; the skill names them by
66
+ role, not by literal.
@@ -0,0 +1,9 @@
1
+ /** Returns true if the command must be blocked. Exported for testing. */
2
+ export declare function isBlockedCommand(command: string): boolean;
3
+ export declare function blockSyncCdHook(): (input: {
4
+ tool: string;
5
+ sessionID: string;
6
+ callID: string;
7
+ }, output: {
8
+ args: Record<string, any>;
9
+ }) => Promise<void>;
@@ -0,0 +1,71 @@
1
+ // tool.execute.before hook: protect the a-sync JSONL worktree from destruction.
2
+ //
3
+ // .git/thrum-sync/a-sync/ is a detached git worktree living INSIDE .git/.
4
+ // It holds the append-only JSONL event log for thrum message sync.
5
+ //
6
+ // DANGER: If you check out a different branch in that worktree, git replaces
7
+ // its contents — but since it lives inside .git/, this DESTROYS the git object
8
+ // store, refs, and config. The entire repo and ALL worktrees are wiped.
9
+ //
10
+ // This happened once and required significant effort to recover.
11
+ //
12
+ // Faithful port of claude-plugin/scripts/block-sync-worktree-cd.sh (4 patterns
13
+ // + command-position anchor). OpenCode contract: throwing inside
14
+ // tool.execute.before ABORTS the tool call (the bash command never runs) —
15
+ // verified against sst/opencode plugin dispatch (raw `yield*`, no catch).
16
+ //
17
+ // Blocked:
18
+ // 1. cd/pushd/chdir into the a-sync worktree (prevents arbitrary commands there)
19
+ // 2. git -C <a-sync-path> with branch-changing commands (checkout, switch,
20
+ // reset, merge, rebase, pull) — these change HEAD without cd
21
+ // 3. git --work-tree=<a-sync-path> with branch-changing commands
22
+ // 4. git --git-dir=<a-sync-path> with branch-changing commands
23
+ //
24
+ // Allowed:
25
+ // git -C <a-sync-path> add/commit/push/status/rm/log/diff — safe operations
26
+ // ls/cat/grep/rm on absolute paths — no CWD change
27
+ // Matches the literal sync-worktree path. Mirrors SYNC_PATTERN in the bash hook.
28
+ const SYNC = String.raw `\.git/thrum-sync/a-sync`;
29
+ // Command-position anchor: line start or immediately after a shell separator
30
+ // (;, &&, ||), then optional whitespace. This matches `cd`/`git` only when they
31
+ // are the actual command — NOT when those words appear inside a quoted argument
32
+ // preceded by a bare space (the D5 false-positive: `bd remember "... cd
33
+ // .git/thrum-sync/a-sync ..."` must NOT be denied). Match op-shape, not the mere
34
+ // presence of the path string.
35
+ const CMDPOS = String.raw `(^|;|&&|\|\|)\s*`;
36
+ // Branch-changing git subcommands that mutate HEAD / working tree.
37
+ const BRANCH_CHANGING = String.raw `(checkout|switch|reset|merge|rebase|pull)\b`;
38
+ // The `m` flag makes `^` in CMDPOS match the start of each line, mirroring the
39
+ // line-by-line behavior of `echo "$command" | grep -E` in the bash hook.
40
+ const PATTERNS = [
41
+ // 1. cd/pushd/chdir into the a-sync worktree (command-position only)
42
+ new RegExp(String.raw `${CMDPOS}(cd|pushd|chdir)\s+['"]?[^\s]*${SYNC}`, "m"),
43
+ // 2. git -C <a-sync-path> with a branch-changing command
44
+ new RegExp(String.raw `${CMDPOS}git\s+-C\s+['"]?[^\s]*${SYNC}['"]?\s+${BRANCH_CHANGING}`, "m"),
45
+ // 3. git --work-tree=<a-sync-path> ... branch-changing command
46
+ new RegExp(String.raw `${CMDPOS}git\s+--work-tree[= ]['"]?[^\s]*${SYNC}['"]?\s+.*${BRANCH_CHANGING}`, "m"),
47
+ // 4. git --git-dir=<a-sync-path> ... branch-changing command
48
+ new RegExp(String.raw `${CMDPOS}git\s+--git-dir[= ]['"]?[^\s]*${SYNC}['"]?\s+.*${BRANCH_CHANGING}`, "m"),
49
+ ];
50
+ const DENY_MESSAGE = "BLOCKED: branch-changing git operations on (or cd into) the " +
51
+ ".git/thrum-sync/a-sync/ worktree are forbidden. That worktree lives INSIDE " +
52
+ ".git/ — checking out a different branch there DESTROYS the entire git object " +
53
+ "store, wiping the repo and all worktrees. Use absolute paths with safe " +
54
+ "operations (ls, grep, rm, git -C ... add/commit/push) instead.";
55
+ /** Returns true if the command must be blocked. Exported for testing. */
56
+ export function isBlockedCommand(command) {
57
+ if (!command)
58
+ return false;
59
+ return PATTERNS.some((re) => re.test(command));
60
+ }
61
+ export function blockSyncCdHook() {
62
+ return async (input, output) => {
63
+ if (input.tool !== "bash")
64
+ return;
65
+ const cmd = String(output.args?.command ?? "");
66
+ if (isBlockedCommand(cmd)) {
67
+ // Throwing aborts the tool call in OpenCode (tool.execute.before contract).
68
+ throw new Error(DENY_MESSAGE);
69
+ }
70
+ };
71
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,61 @@
1
+ // Regression tests for the a-sync worktree guard (block-sync-cd.ts).
2
+ // Mirrors claude-plugin/scripts/block-sync-worktree-cd_test.sh so the OpenCode
3
+ // port stays behavior-faithful to the reference bash hook.
4
+ //
5
+ // Run: npm test (builds, then `node --test dist`)
6
+ import { test } from "node:test";
7
+ import assert from "node:assert/strict";
8
+ import { isBlockedCommand, blockSyncCdHook } from "./block-sync-cd.js";
9
+ const ASYNC = ".git/thrum-sync/a-sync";
10
+ // DENY: branch-changing ops on / cd into the a-sync worktree.
11
+ const DENY = [
12
+ ["cd into a-sync", `cd ${ASYNC}`],
13
+ ["pushd into a-sync", `pushd ${ASYNC}`],
14
+ ["cd after &&", `git status && cd ${ASYNC}`],
15
+ ["cd after ;", `echo hi; cd ${ASYNC}`],
16
+ ["git -C a-sync checkout", `git -C ${ASYNC} checkout main`],
17
+ ["git -C a-sync switch", `git -C ${ASYNC} switch main`],
18
+ ["git -C a-sync reset --hard", `git -C ${ASYNC} reset --hard`],
19
+ ["git -C a-sync merge", `git -C ${ASYNC} merge main`],
20
+ ["git -C a-sync rebase", `git -C ${ASYNC} rebase main`],
21
+ ["git -C a-sync pull", `git -C ${ASYNC} pull`],
22
+ ["git --work-tree= checkout", `git --work-tree=${ASYNC} checkout main`],
23
+ ["git --git-dir= checkout", `git --git-dir=${ASYNC} checkout main`],
24
+ ];
25
+ // ALLOW: safe ops on a-sync, non-command-position mentions, unrelated commands.
26
+ const ALLOW = [
27
+ ["bare git status", "git status"],
28
+ ["git -C a-sync add", `git -C ${ASYNC} add .`],
29
+ ["git -C a-sync status", `git -C ${ASYNC} status`],
30
+ ["git -C a-sync commit", `git -C ${ASYNC} commit -m x`],
31
+ ["git -C a-sync push", `git -C ${ASYNC} push`],
32
+ ["git -C a-sync log (not branch-changing)", `git -C ${ASYNC} log --oneline`],
33
+ ["ls the path", `ls ${ASYNC}/`],
34
+ // D5 false-positive guard: cd inside a quoted argument (bare space before it),
35
+ // not at command position -> must be ALLOWED.
36
+ ["quoted cd inside bd remember", `bd remember "note: do not cd ${ASYNC} directly"`],
37
+ ];
38
+ for (const [name, cmd] of DENY) {
39
+ test(`DENY: ${name}`, () => {
40
+ assert.equal(isBlockedCommand(cmd), true, `expected blocked: ${cmd}`);
41
+ });
42
+ }
43
+ for (const [name, cmd] of ALLOW) {
44
+ test(`ALLOW: ${name}`, () => {
45
+ assert.equal(isBlockedCommand(cmd), false, `expected allowed: ${cmd}`);
46
+ });
47
+ }
48
+ // Contract: the hook THROWS on a blocked bash command (OpenCode aborts the
49
+ // tool call on throw) and resolves for allowed / non-bash tools.
50
+ test("hook throws on blocked bash command", async () => {
51
+ const hook = blockSyncCdHook();
52
+ await assert.rejects(() => hook({ tool: "bash", sessionID: "s", callID: "c" }, { args: { command: `git -C ${ASYNC} checkout main` } }));
53
+ });
54
+ test("hook resolves on allowed bash command", async () => {
55
+ const hook = blockSyncCdHook();
56
+ await assert.doesNotReject(() => hook({ tool: "bash", sessionID: "s", callID: "c" }, { args: { command: `git -C ${ASYNC} add .` } }));
57
+ });
58
+ test("hook ignores non-bash tools", async () => {
59
+ const hook = blockSyncCdHook();
60
+ await assert.doesNotReject(() => hook({ tool: "read", sessionID: "s", callID: "c" }, { args: { command: `cd ${ASYNC}` } }));
61
+ });
@@ -0,0 +1,8 @@
1
+ import type { PluginInput } from "@opencode-ai/plugin";
2
+ export declare function shellEnvHook(ctx: PluginInput): (input: {
3
+ cwd: string;
4
+ sessionID?: string;
5
+ callID?: string;
6
+ }, output: {
7
+ env: Record<string, string>;
8
+ }) => Promise<void>;
@@ -0,0 +1,22 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ export function shellEnvHook(ctx) {
4
+ return async (input, output) => {
5
+ const dirs = [input.cwd, ctx.directory, ctx.worktree].filter(Boolean);
6
+ for (const dir of dirs) {
7
+ const identityPath = path.join(dir, ".thrum", "identity.json");
8
+ try {
9
+ const raw = fs.readFileSync(identityPath, "utf8");
10
+ const identity = JSON.parse(raw);
11
+ output.env.THRUM_NAME = identity.agent_id ?? "";
12
+ output.env.THRUM_ROLE = identity.role ?? "";
13
+ output.env.THRUM_MODULE = identity.module ?? "";
14
+ output.env.THRUM_HOME = dir;
15
+ return;
16
+ }
17
+ catch {
18
+ // No identity file in this dir, try next
19
+ }
20
+ }
21
+ };
22
+ }
@@ -0,0 +1,6 @@
1
+ import type { Plugin } from "@opencode-ai/plugin";
2
+ declare const _default: {
3
+ id: string;
4
+ server: Plugin;
5
+ };
6
+ export default _default;
package/dist/index.js ADDED
@@ -0,0 +1,25 @@
1
+ import { installAssets } from "./installer.js";
2
+ import { shellEnvHook } from "./hooks/shell-env.js";
3
+ import { blockSyncCdHook } from "./hooks/block-sync-cd.js";
4
+ const TOOL_MAPPING = `
5
+ ## Thrum Plugin — Tool Mapping for OpenCode
6
+
7
+ When Thrum skills reference Claude Code tool names, use these OpenCode equivalents:
8
+ - TodoWrite → todowrite
9
+ - Task (subagents) → @mention syntax (e.g., @explore, @general)
10
+ - Skill tool → OpenCode's native skill tool
11
+ - Agent tool → @mention syntax for subagents
12
+ - Read, Write, Edit, Bash → your native tools (same names, lowercase)
13
+ `.trim();
14
+ const server = async (ctx) => {
15
+ await installAssets(ctx);
16
+ return {
17
+ "shell.env": shellEnvHook(ctx),
18
+ "tool.execute.before": blockSyncCdHook(),
19
+ "experimental.chat.system.transform": async (_input, output) => {
20
+ ;
21
+ (output.system ||= []).push(TOOL_MAPPING);
22
+ },
23
+ };
24
+ };
25
+ export default { id: "opencode-thrum", server };
@@ -0,0 +1,2 @@
1
+ import type { PluginInput } from "@opencode-ai/plugin";
2
+ export declare function installAssets(ctx: PluginInput): Promise<void>;
@@ -0,0 +1,86 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import os from "os";
4
+ import { fileURLToPath } from "url";
5
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
6
+ const ASSETS_DIR = path.resolve(__dirname, "..", "assets");
7
+ const VERSION_FILE = ".thrum-plugin-version";
8
+ function getPluginVersion() {
9
+ const pkgPath = path.resolve(__dirname, "..", "package.json");
10
+ try {
11
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
12
+ return pkg.version ?? "0.0.0";
13
+ }
14
+ catch {
15
+ return "0.0.0";
16
+ }
17
+ }
18
+ function getConfigDir() {
19
+ const xdg = process.env.XDG_CONFIG_HOME;
20
+ if (xdg)
21
+ return path.join(xdg, "opencode");
22
+ return path.join(os.homedir(), ".config", "opencode");
23
+ }
24
+ function copyDirRecursive(src, dest) {
25
+ fs.mkdirSync(dest, { recursive: true });
26
+ for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
27
+ const srcPath = path.join(src, entry.name);
28
+ const destPath = path.join(dest, entry.name);
29
+ if (entry.isDirectory()) {
30
+ copyDirRecursive(srcPath, destPath);
31
+ }
32
+ else {
33
+ fs.copyFileSync(srcPath, destPath);
34
+ }
35
+ }
36
+ }
37
+ export async function installAssets(ctx) {
38
+ const configDir = getConfigDir();
39
+ const skillsDir = path.join(configDir, "skills");
40
+ const commandsDir = path.join(configDir, "commands");
41
+ const versionPath = path.join(skillsDir, "thrum", VERSION_FILE);
42
+ const currentVersion = getPluginVersion();
43
+ // Check if already installed at this version
44
+ try {
45
+ const installed = fs.readFileSync(versionPath, "utf8").trim();
46
+ if (installed === currentVersion)
47
+ return;
48
+ }
49
+ catch {
50
+ // Not installed yet
51
+ }
52
+ const log = (msg) => ctx.client.app.log({
53
+ body: { service: "opencode-thrum", level: "info", message: msg },
54
+ });
55
+ // Copy each skill subdirectory
56
+ const assetsSkillsDir = path.join(ASSETS_DIR, "skills");
57
+ if (fs.existsSync(assetsSkillsDir)) {
58
+ for (const entry of fs.readdirSync(assetsSkillsDir, {
59
+ withFileTypes: true,
60
+ })) {
61
+ if (!entry.isDirectory())
62
+ continue;
63
+ const src = path.join(assetsSkillsDir, entry.name);
64
+ const dest = path.join(skillsDir, entry.name);
65
+ copyDirRecursive(src, dest);
66
+ await log(`Installed skill: ${entry.name}`);
67
+ }
68
+ }
69
+ // Copy commands
70
+ const assetsCommandsDir = path.join(ASSETS_DIR, "commands");
71
+ if (fs.existsSync(assetsCommandsDir)) {
72
+ fs.mkdirSync(commandsDir, { recursive: true });
73
+ let count = 0;
74
+ for (const entry of fs.readdirSync(assetsCommandsDir)) {
75
+ if (!entry.endsWith(".md"))
76
+ continue;
77
+ fs.copyFileSync(path.join(assetsCommandsDir, entry), path.join(commandsDir, entry));
78
+ count++;
79
+ }
80
+ await log(`Installed ${count} commands`);
81
+ }
82
+ // Write version marker
83
+ fs.mkdirSync(path.dirname(versionPath), { recursive: true });
84
+ fs.writeFileSync(versionPath, currentVersion);
85
+ await log(`opencode-thrum v${currentVersion} assets installed`);
86
+ }
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "opencode-thrum-pro",
3
+ "version": "0.1.0",
4
+ "description": "Thrum multi-agent coordination plugin for Open Code",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "exports": {
8
+ ".": {
9
+ "import": "./dist/index.js",
10
+ "types": "./dist/index.d.ts"
11
+ },
12
+ "./server": {
13
+ "import": "./dist/index.js",
14
+ "types": "./dist/index.d.ts"
15
+ }
16
+ },
17
+ "scripts": {
18
+ "build": "tsc",
19
+ "clean": "rm -rf dist",
20
+ "test": "npm run build && node --test dist",
21
+ "prepublishOnly": "npm run build"
22
+ },
23
+ "files": [
24
+ "dist",
25
+ "assets"
26
+ ],
27
+ "keywords": [
28
+ "opencode",
29
+ "plugin",
30
+ "thrum",
31
+ "multi-agent",
32
+ "coordination"
33
+ ],
34
+ "author": "Leon Letto",
35
+ "license": "Apache-2.0",
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "git+https://github.com/leonletto/thrum-pro.git",
39
+ "directory": "opencode-plugin"
40
+ },
41
+ "peerDependencies": {
42
+ "@opencode-ai/plugin": ">=1.0.0"
43
+ },
44
+ "devDependencies": {
45
+ "@opencode-ai/plugin": "^1.4.0",
46
+ "@types/node": "^25.5.2",
47
+ "typescript": "^5.3.0"
48
+ }
49
+ }