@heart-of-gold/toolkit 0.1.8 → 0.1.9

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/README.md CHANGED
@@ -36,12 +36,13 @@ pi install npm:@heart-of-gold/toolkit
36
36
  Pi also discovers skills from the shared `~/.agents/skills/` location, so installs done with the OpenCode target are usable from Pi too.
37
37
 
38
38
  When installed as a Pi package, Heart of Gold exposes Pi-native extension commands for the flagship workflows:
39
- - `/deep-thought-brainstorm` — guided brainstorm intake for the shared `brainstorm` skill
40
- - `/deep-thought-plan` — planning mode entrypoint with pi-friendly tool defaults
41
- - `/marvin-work` — execution mode entrypoint with stronger work guardrails
42
- - `/marvin-execute` — leave planning mode and begin execution
39
+ - `/deep-thought-brainstorm` — start a brainstorm (collaborative discovery)
40
+ - `/deep-thought-plan` — start planning (research and produce a plan document)
41
+ - `/marvin-work` — start executing a plan (with always-on safety guardrails)
43
42
 
44
- For Pi, these extension commands replace the direct shared-skill entries for `brainstorm`, `plan`, and `work` so the command palette stays clean and there is only one obvious command per flagship workflow.
43
+ The skills themselves enforce their own boundaries (read-only for brainstorm/plan, safe commands for work) via `allowed-tools` and prompt constraints no manual mode switching needed.
44
+
45
+ The work extension also provides always-on guardrails that protect `.env`, `.git/`, and `node_modules/` from edits, block `git add .` and destructive `rm`, and require confirmation for `git push` / `npm publish`.
45
46
 
46
47
  ### List available skills
47
48
  ```bash
@@ -1,67 +1,23 @@
1
1
  import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
2
2
 
3
- function sendPrompt(pi: ExtensionAPI, prompt: string, ctx: Parameters<NonNullable<Parameters<ExtensionAPI["registerCommand"]>[1]["handler"]>>[1]) {
4
- if (ctx.isIdle()) {
5
- pi.sendUserMessage(prompt);
6
- } else {
7
- pi.sendUserMessage(prompt, { deliverAs: "followUp" });
8
- ctx.ui.notify("Brainstorm prompt queued as follow-up", "info");
9
- }
10
- }
11
-
12
3
  export default function brainstormExtension(pi: ExtensionAPI) {
13
4
  pi.registerCommand("deep-thought-brainstorm", {
14
- description: "Interactive pi-first intake for the shared brainstorm skill",
5
+ description: "Start a brainstorm collaborative discovery before planning",
15
6
  handler: async (args, ctx) => {
16
- if (!ctx.hasUI) {
17
- ctx.ui.notify("/deep-thought-brainstorm requires interactive mode", "warning");
18
- return;
19
- }
20
-
21
- const topic = args.trim() || (await ctx.ui.editor("Brainstorm topic", ""))?.trim();
7
+ const topic = args.trim() || (ctx.hasUI ? (await ctx.ui.editor("Brainstorm topic", ""))?.trim() : undefined);
22
8
  if (!topic) {
23
- ctx.ui.notify("Cancelled", "info");
9
+ ctx.ui.notify("Usage: /deep-thought-brainstorm <topic>", "info");
24
10
  return;
25
11
  }
26
12
 
27
- const clarity = await ctx.ui.select("How clear are the requirements right now?", [
28
- "Unclear — need discovery",
29
- "Partly clear — explore tradeoffs",
30
- "Quite clear — validate before planning",
31
- ]);
32
- if (!clarity) {
33
- ctx.ui.notify("Cancelled", "info");
34
- return;
35
- }
13
+ const prompt = `/skill:brainstorm ${topic}`;
36
14
 
37
- const goal = await ctx.ui.select("What should the brainstorm optimize for first?", [
38
- "Find the right problem framing",
39
- "Compare 2-3 viable approaches",
40
- "Pressure-test the chosen direction",
41
- ]);
42
- if (!goal) {
43
- ctx.ui.notify("Cancelled", "info");
44
- return;
15
+ if (ctx.isIdle()) {
16
+ pi.sendUserMessage(prompt);
17
+ } else {
18
+ pi.sendUserMessage(prompt, { deliverAs: "followUp" });
19
+ ctx.ui.notify("Brainstorm queued as follow-up", "info");
45
20
  }
46
-
47
- const prompt = [
48
- `/skill:brainstorm ${topic}`,
49
- "",
50
- "Use a pi-friendly interactive flow:",
51
- "- Ask one question at a time.",
52
- "- Use explicit option lists whenever there are natural choices.",
53
- "- Keep momentum high and avoid dumping questionnaires.",
54
- "",
55
- `Current clarity: ${clarity}`,
56
- `Primary goal: ${goal}`,
57
- ].join("\n");
58
-
59
- const theme = ctx.ui.theme;
60
- ctx.ui.setStatus(
61
- "deep-thought-brainstorm",
62
- theme.fg("accent", "◉") + theme.fg("dim", " Brainstorm flow active"),
63
- );
64
- sendPrompt(pi, prompt, ctx);
65
21
  },
66
22
  });
67
23
  }
@@ -1,93 +1,22 @@
1
1
  import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
2
2
 
3
- const PLAN_SAFE_TOOLS = ["read", "bash", "grep", "find", "ls"];
4
-
5
3
  export default function planExtension(pi: ExtensionAPI) {
6
- let previousTools: string[] | null = null;
7
-
8
- const applyPlanTools = () => {
9
- const available = new Set(pi.getAllTools().map((tool) => tool.name));
10
- const nextTools = PLAN_SAFE_TOOLS.filter((tool) => available.has(tool));
11
- if (nextTools.length > 0) {
12
- pi.setActiveTools(nextTools);
13
- }
14
- };
15
-
16
4
  pi.registerCommand("deep-thought-plan", {
17
- description: "Interactive pi-first entrypoint for planning mode and the shared plan skill",
5
+ description: "Start planning research and produce a plan document",
18
6
  handler: async (args, ctx) => {
19
- if (!ctx.hasUI) {
20
- ctx.ui.notify("/deep-thought-plan requires interactive mode", "warning");
21
- return;
22
- }
23
-
24
- const source = args.trim() || (await ctx.ui.editor("Plan topic or brainstorm path", ""))?.trim();
7
+ const source = args.trim() || (ctx.hasUI ? (await ctx.ui.editor("Plan topic or brainstorm path", ""))?.trim() : undefined);
25
8
  if (!source) {
26
- ctx.ui.notify("Cancelled", "info");
27
- return;
28
- }
29
-
30
- const mode = await ctx.ui.select("How should planning mode behave?", [
31
- "Read-only planning (recommended)",
32
- "Allow normal tools, but stay in planning mode",
33
- ]);
34
- if (!mode) {
35
- ctx.ui.notify("Cancelled", "info");
9
+ ctx.ui.notify("Usage: /deep-thought-plan <topic or brainstorm path>", "info");
36
10
  return;
37
11
  }
38
12
 
39
- if (previousTools === null) {
40
- previousTools = pi.getActiveTools();
41
- }
42
- if (mode.startsWith("Read-only")) {
43
- applyPlanTools();
44
- }
45
-
46
- const prompt = [
47
- `/skill:plan ${source}`,
48
- "",
49
- "Use a pi-friendly planning flow:",
50
- "- Keep planning read-only unless the user explicitly exits planning mode.",
51
- "- Ask one question at a time when clarification is needed.",
52
- "- Use concise option lists when choosing between paths or handoffs.",
53
- ].join("\n");
54
-
55
- const theme = ctx.ui.theme;
56
- ctx.ui.setStatus(
57
- "deep-thought-plan",
58
- theme.fg("accent", "◉") + theme.fg("dim", " Plan mode active"),
59
- );
60
-
61
- if (ctx.isIdle()) {
62
- pi.sendUserMessage(prompt);
63
- } else {
64
- pi.sendUserMessage(prompt, { deliverAs: "followUp" });
65
- ctx.ui.notify("Plan prompt queued as follow-up", "info");
66
- }
67
- },
68
- });
69
-
70
- pi.registerCommand("marvin-execute", {
71
- description: "Exit planning mode, restore tools, and optionally start work",
72
- handler: async (args, ctx) => {
73
- if (previousTools) {
74
- pi.setActiveTools(previousTools);
75
- previousTools = null;
76
- }
77
- ctx.ui.setStatus("deep-thought-plan", "");
78
-
79
- const target = args.trim();
80
- if (!target) {
81
- ctx.ui.notify("Planning mode cleared. Run /marvin-work <plan-path> when ready.", "info");
82
- return;
83
- }
13
+ const prompt = `/skill:plan ${source}`;
84
14
 
85
- const prompt = `/skill:work ${target}`;
86
15
  if (ctx.isIdle()) {
87
16
  pi.sendUserMessage(prompt);
88
17
  } else {
89
18
  pi.sendUserMessage(prompt, { deliverAs: "followUp" });
90
- ctx.ui.notify("Work prompt queued as follow-up", "info");
19
+ ctx.ui.notify("Plan queued as follow-up", "info");
91
20
  }
92
21
  },
93
22
  });
@@ -5,76 +5,15 @@ const CONFIRM_COMMANDS = [/\bgit\s+push\b/i, /\bnpm\s+publish\b/i, /\bbun\s+publ
5
5
  const BLOCKED_COMMANDS = [/\bgit\s+add\s+\.\b/i, /\brm\s+(-rf?|--recursive)/i];
6
6
 
7
7
  export default function workExtension(pi: ExtensionAPI) {
8
- let workMode = false;
9
-
10
- pi.registerCommand("marvin-work", {
11
- description: "Interactive pi-first entrypoint for the shared work skill",
12
- handler: async (args, ctx) => {
13
- if (!ctx.hasUI) {
14
- ctx.ui.notify("/marvin-work requires interactive mode", "warning");
15
- return;
16
- }
17
-
18
- const planPath = args.trim() || (await ctx.ui.editor("Plan path", ""))?.trim();
19
- if (!planPath) {
20
- ctx.ui.notify("Cancelled", "info");
21
- return;
22
- }
23
-
24
- const runMode = await ctx.ui.select("How strict should work mode be?", [
25
- "Normal guardrails (recommended)",
26
- "Strict guardrails for shipping work",
27
- ]);
28
- if (!runMode) {
29
- ctx.ui.notify("Cancelled", "info");
30
- return;
31
- }
32
-
33
- workMode = true;
34
- const theme = ctx.ui.theme;
35
- ctx.ui.setStatus(
36
- "marvin-work",
37
- theme.fg("accent", "◉") + theme.fg("dim", ` Work mode active — ${runMode}`),
38
- );
39
-
40
- const prompt = [
41
- `/skill:work ${planPath}`,
42
- "",
43
- "Use pi guardrails while executing:",
44
- "- keep progress visible as tasks move from in-progress to complete",
45
- "- do not use `git add .`",
46
- "- confirm push/publish actions deliberately",
47
- "- protect .env, .git/, and node_modules/ from accidental edits",
48
- ].join("\n");
49
-
50
- if (ctx.isIdle()) {
51
- pi.sendUserMessage(prompt);
52
- } else {
53
- pi.sendUserMessage(prompt, { deliverAs: "followUp" });
54
- ctx.ui.notify("Work prompt queued as follow-up", "info");
55
- }
56
- },
57
- });
58
-
59
- pi.registerCommand("marvin-work-off", {
60
- description: "Disable Heart of Gold work-mode guardrails",
61
- handler: async (_args, ctx) => {
62
- workMode = false;
63
- ctx.ui.setStatus("marvin-work", "");
64
- ctx.ui.notify("Work mode disabled", "info");
65
- },
66
- });
67
-
8
+ // Always-on guardrails — no mode toggle needed
68
9
  pi.on("tool_call", async (event, ctx) => {
69
- if (!workMode) return undefined;
70
-
71
10
  if (event.toolName === "write" || event.toolName === "edit") {
72
11
  const path = String(event.input.path ?? "");
73
12
  if (PROTECTED_PATHS.some((segment) => path.includes(segment))) {
74
13
  if (ctx.hasUI) {
75
- ctx.ui.notify(`Blocked protected path in work mode: ${path}`, "warning");
14
+ ctx.ui.notify(`⛔ Protected path: ${path}`, "warning");
76
15
  }
77
- return { block: true, reason: `Protected path in work mode: ${path}` };
16
+ return { block: true, reason: `Protected path: ${path}` };
78
17
  }
79
18
  }
80
19
 
@@ -82,7 +21,7 @@ export default function workExtension(pi: ExtensionAPI) {
82
21
  const command = String(event.input.command ?? "");
83
22
 
84
23
  if (BLOCKED_COMMANDS.some((pattern) => pattern.test(command))) {
85
- return { block: true, reason: `Blocked unsafe command in work mode: ${command}` };
24
+ return { block: true, reason: `Blocked unsafe command: ${command}` };
86
25
  }
87
26
 
88
27
  if (!CONFIRM_COMMANDS.some((pattern) => pattern.test(command))) {
@@ -93,11 +32,31 @@ export default function workExtension(pi: ExtensionAPI) {
93
32
  return { block: true, reason: `Interactive confirmation required for: ${command}` };
94
33
  }
95
34
 
96
- const choice = await ctx.ui.select(`Confirm work-mode command:\n\n${command}`, ["Allow", "Block"]);
35
+ const choice = await ctx.ui.select(`Confirm: ${command}`, ["Allow", "Block"]);
97
36
  if (choice !== "Allow") {
98
37
  return { block: true, reason: `Blocked by user: ${command}` };
99
38
  }
100
39
 
101
40
  return undefined;
102
41
  });
42
+
43
+ pi.registerCommand("marvin-work", {
44
+ description: "Start executing a plan — implement with guardrails",
45
+ handler: async (args, ctx) => {
46
+ const planPath = args.trim() || (ctx.hasUI ? (await ctx.ui.editor("Plan path", ""))?.trim() : undefined);
47
+ if (!planPath) {
48
+ ctx.ui.notify("Usage: /marvin-work <plan path>", "info");
49
+ return;
50
+ }
51
+
52
+ const prompt = `/skill:work ${planPath}`;
53
+
54
+ if (ctx.isIdle()) {
55
+ pi.sendUserMessage(prompt);
56
+ } else {
57
+ pi.sendUserMessage(prompt, { deliverAs: "followUp" });
58
+ ctx.ui.notify("Work queued as follow-up", "info");
59
+ }
60
+ },
61
+ });
103
62
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heart-of-gold/toolkit",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
4
4
  "type": "module",
5
5
  "description": "Cross-platform installer for Heart of Gold skills — works with Codex, OpenCode, Pi, Claude Code, and more",
6
6
  "bin": {