@heart-of-gold/toolkit 0.1.29 → 0.1.30

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
@@ -39,6 +39,8 @@ When installed as a Pi package, Heart of Gold exposes Pi-native extension comman
39
39
  - `/deep-thought-brainstorm` — start a brainstorm (collaborative discovery)
40
40
  - `/deep-thought-plan` — start planning (research and produce a plan document)
41
41
  - `/deep-thought-architect` — turn brainstorm decisions into stories and architecture docs
42
+ - `/share` — publish an HTML file or static site directory through the portable `share-html` skill
43
+ - `/share-server-setup` — set up or adopt the local share server through the portable `share-server-setup` skill
42
44
  - `/marvin-work` — start executing a plan (with always-on safety guardrails)
43
45
 
44
46
  Pi package installs also include a Pi-only guided workflow enhancer for supported Heart of Gold skills. For `brainstorm`, `plan`, and `architect`, when the assistant asks a high-confidence structured question, Pi can upgrade it into a custom interactive TUI and feed the answer back into the same workflow. Shared skills remain plain-text portable in every other harness.
@@ -95,16 +97,18 @@ The unglamorous work that compounds.
95
97
 
96
98
  Execute plans task by task with tests after every change. Quick-review code with an emphasis on simplicity — catch YAGNI violations, premature abstractions, and code that solves problems that don't exist yet. Document solutions so the next person doesn't waste time re-discovering what you already figured out.
97
99
 
98
- 7 skills · 2 agents · 3 knowledge files
100
+ 9 skills · 2 agents · 3 knowledge files
99
101
 
100
102
  ```
101
- /marvin:work # execute plans — implement, test, commit, ship
102
- /marvin:quick-review # fast opinionated quality pass (simplicity, tests, correctness)
103
- /marvin:compound # document solved problems for future reference
104
- /marvin:redteam # adversarial review — find weaknesses, expose with failing tests
105
- /marvin:scaffold # prepare project structure, configs, dependencies
106
- /marvin:test-writer # write failing tests from user stories
107
- /marvin:copy-editor # two-layer copy editor (typography audit + LLM judgment)
103
+ /marvin:work # execute plans — implement, test, commit, ship
104
+ /marvin:quick-review # fast opinionated quality pass (simplicity, tests, correctness)
105
+ /marvin:compound # document solved problems for future reference
106
+ /marvin:redteam # adversarial review — find weaknesses, expose with failing tests
107
+ /marvin:scaffold # prepare project structure, configs, dependencies
108
+ /marvin:test-writer # write failing tests from user stories
109
+ /marvin:copy-editor # two-layer copy editor (typography audit + LLM judgment)
110
+ /marvin:share-server-setup # set up local artifact sharing infrastructure
111
+ /marvin:share-html # publish HTML/static output to a browser URL
108
112
  ```
109
113
 
110
114
  ### [Guide](plugins/guide/) — The Hitchhiker's Guide
@@ -113,18 +117,16 @@ Your personal content engine.
113
117
 
114
118
  Configurable sources (RSS, Gmail, HN, web search), narrative briefs, LinkedIn drafts, blog outlines, voice fidelity checking, iMessage delivery, and two-way captures.
115
119
 
116
- 9 skills · 2 agents · 5 scripts
120
+ 7 skills · 2 agents · 5 scripts
117
121
 
118
122
  ```
119
- /guide:setup # configure your sources, themes, and voice
120
- /guide:pipeline # run the full content engine
121
- /guide:capture # morning/evening thought capture
122
- /guide:write-post # guided blog writing (7 phases)
123
- /guide:share-server-setup # set up local artifact sharing infrastructure
124
- /guide:share-html # publish HTML/static output to a browser URL
125
- /guide:claude-code # Claude Code CLI guidance
126
- /guide:codex # Codex CLI guidance
127
- /guide:gemini # Gemini CLI guidance
123
+ /guide:setup # configure your sources, themes, and voice
124
+ /guide:pipeline # run the full content engine
125
+ /guide:capture # morning/evening thought capture
126
+ /guide:write-post # guided blog writing (7 phases)
127
+ /guide:claude-code # Claude Code CLI guidance
128
+ /guide:codex # Codex CLI guidance
129
+ /guide:gemini # Gemini CLI guidance
128
130
  ```
129
131
 
130
132
  ### [Babel Fish](plugins/babel-fish/) — Universal Translator
@@ -4,6 +4,7 @@ import architectExtension from "./architect";
4
4
  import brainstormExtension from "./brainstorm";
5
5
  import guidedWorkflowsExtension from "./guided-workflows";
6
6
  import planExtension from "./plan";
7
+ import shareExtension from "./share";
7
8
  import workExtension from "./work";
8
9
 
9
10
  export default function heartOfGoldPiExtensions(pi: ExtensionAPI) {
@@ -11,5 +12,6 @@ export default function heartOfGoldPiExtensions(pi: ExtensionAPI) {
11
12
  brainstormExtension(pi);
12
13
  planExtension(pi);
13
14
  architectExtension(pi);
15
+ shareExtension(pi);
14
16
  workExtension(pi);
15
17
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heart-of-gold/toolkit",
3
- "version": "0.1.29",
3
+ "version": "0.1.30",
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": {
@@ -65,10 +65,10 @@
65
65
  "./plugins/guide/skills/gemini",
66
66
  "./plugins/guide/skills/pipeline",
67
67
  "./plugins/guide/skills/setup",
68
- "./plugins/guide/skills/share-html",
69
- "./plugins/guide/skills/share-server-setup",
70
68
  "./plugins/guide/skills/write-post",
71
69
  "./plugins/marvin/skills/compound",
70
+ "./plugins/marvin/skills/share-html",
71
+ "./plugins/marvin/skills/share-server-setup",
72
72
  "./plugins/marvin/skills/quick-review",
73
73
  "./plugins/marvin/skills/redteam",
74
74
  "./plugins/marvin/skills/scaffold",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "guide",
3
- "version": "0.3.1",
3
+ "version": "0.3.0",
4
4
  "description": "The Hitchhiker's Guide — content creation suite with automated pipeline, daily briefs, and blog writing",
5
5
  "author": {
6
6
  "name": "ondrej-svec",
@@ -73,26 +73,6 @@ Guided blog writing in seven phases:
73
73
 
74
74
  Can pick up blog outlines generated by the pipeline (`needs_write_post: true` in frontmatter).
75
75
 
76
- ### /guide:share-server-setup
77
-
78
- Set up or adopt a local share server for browser-viewable coding-agent artifacts.
79
-
80
- Use it when you want a reusable local service that can host generated HTML reports or static previews, optionally exposed privately over Tailscale. The setup flow supports:
81
- - configuring an already-running compatible server
82
- - installing the reference Heart of Gold share server
83
- - setting up macOS LaunchAgent persistence for long-running use
84
- - optionally wiring only the viewer surface through `tailscale serve`
85
-
86
- ### /guide:share-html
87
-
88
- Publish a local HTML file or static site directory to the configured share server and return a browser URL.
89
-
90
- Supports:
91
- - a single `.html` file
92
- - a directory containing `index.html`
93
-
94
- If the server is not configured yet, run `/guide:share-server-setup` first.
95
-
96
76
  ### /guide:claude-code
97
77
 
98
78
  Headless Claude Code handoff for second opinions, code review, targeted analysis, or follow-up on a bounded task.
@@ -82,11 +82,15 @@ print(fetch_body)
82
82
  PY
83
83
  }
84
84
 
85
- if mapfile -t GMAIL_SETTINGS < <(read_gmail_settings "$CONFIG_PATH" 2>/dev/null); then
86
- GMAIL_LABEL="${GMAIL_SETTINGS[0]:-Content-Feed}"
87
- MAX_ITEMS="${GMAIL_SETTINGS[1]:-20}"
88
- GMAIL_LABEL_ID="${GMAIL_SETTINGS[2]:-}"
89
- FETCH_BODY="${GMAIL_SETTINGS[3]:-false}"
85
+ GMAIL_SETTINGS_RAW=$(read_gmail_settings "$CONFIG_PATH" 2>/dev/null) || GMAIL_SETTINGS_RAW=""
86
+ if [[ -n "$GMAIL_SETTINGS_RAW" ]]; then
87
+ GMAIL_LABEL=$(echo "$GMAIL_SETTINGS_RAW" | sed -n '1p')
88
+ MAX_ITEMS=$(echo "$GMAIL_SETTINGS_RAW" | sed -n '2p')
89
+ GMAIL_LABEL_ID=$(echo "$GMAIL_SETTINGS_RAW" | sed -n '3p')
90
+ FETCH_BODY=$(echo "$GMAIL_SETTINGS_RAW" | sed -n '4p')
91
+ GMAIL_LABEL="${GMAIL_LABEL:-Content-Feed}"
92
+ MAX_ITEMS="${MAX_ITEMS:-20}"
93
+ FETCH_BODY="${FETCH_BODY:-false}"
90
94
  else
91
95
  GMAIL_LABEL="Content-Feed"
92
96
  MAX_ITEMS="20"
@@ -94,14 +98,61 @@ else
94
98
  FETCH_BODY="false"
95
99
  fi
96
100
 
101
+ # Collect seen Gmail message IDs from previous days to avoid duplicates
102
+ PIPELINE_DIR=$(python3 -c "
103
+ import yaml, sys
104
+ with open(sys.argv[1]) as f:
105
+ c = yaml.safe_load(f)
106
+ print(c.get('output', {}).get('pipeline_dir', 'content/pipeline'))
107
+ " "$CONFIG_PATH" 2>/dev/null || echo "content/pipeline")
108
+
109
+ SEEN_IDS=$(python3 -c "
110
+ import json, glob, os, sys
111
+
112
+ pipeline_dir = sys.argv[1]
113
+ config_path = sys.argv[2]
114
+
115
+ # Resolve relative pipeline_dir against project root (config's grandparent,
116
+ # since config lives in content/ which is one level below project root)
117
+ if not os.path.isabs(pipeline_dir):
118
+ config_dir = os.path.dirname(os.path.abspath(config_path))
119
+ # Walk up until we find .git or use config's parent's parent as fallback
120
+ project_root = config_dir
121
+ while project_root != '/':
122
+ if os.path.isdir(os.path.join(project_root, '.git')):
123
+ break
124
+ project_root = os.path.dirname(project_root)
125
+ pipeline_dir = os.path.join(project_root, pipeline_dir)
126
+
127
+ seen = set()
128
+ # Check last 7 days of signals files
129
+ for path in sorted(glob.glob(os.path.join(pipeline_dir, '*/signals.json')))[-7:]:
130
+ try:
131
+ signals = json.load(open(path))
132
+ for s in signals:
133
+ if s.get('source') == 'gmail':
134
+ mid = s.get('metadata', {}).get('message_id', '')
135
+ if mid:
136
+ seen.add(mid)
137
+ except (json.JSONDecodeError, KeyError):
138
+ pass
139
+ # Output as comma-separated IDs
140
+ print(','.join(seen))
141
+ " "$PIPELINE_DIR" "$CONFIG_PATH" 2>/dev/null || echo "")
142
+
143
+ if [[ -n "$SEEN_IDS" ]]; then
144
+ SEEN_COUNT=$(echo "$SEEN_IDS" | tr ',' '\n' | wc -l | tr -d ' ')
145
+ echo " · $SEEN_COUNT previously seen Gmail message IDs loaded" >&2
146
+ fi
147
+
97
148
  # Fetch emails using gws +triage helper (more reliable than raw API calls —
98
149
  # the helper handles auth scopes internally, avoiding insufficientPermissions errors)
99
150
  # Fetch recent emails with labels, then filter by label ID in Python
100
151
  GWS_ERR=$(mktemp)
101
152
  trap "rm -f $GWS_ERR" EXIT
102
153
  EMAILS_JSON=$(gws gmail +triage \
103
- --query "newer_than:3d" \
104
- --max 50 \
154
+ --query "newer_than:7d" \
155
+ --max 200 \
105
156
  --format json \
106
157
  --labels \
107
158
  2>"$GWS_ERR") || {
@@ -122,17 +173,21 @@ EMAILS_JSON=$(gws gmail +triage \
122
173
  # Determine the scripts directory (where this script lives)
123
174
  SCRIPTS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
124
175
 
176
+ export _GMAIL_LABEL_ID="$GMAIL_LABEL_ID"
177
+ export _GMAIL_MAX_ITEMS="$MAX_ITEMS"
178
+ export _GMAIL_CONFIG_PATH="$CONFIG_PATH"
179
+ export _GMAIL_SEEN_IDS="$SEEN_IDS"
180
+
125
181
  if [[ "$FETCH_BODY" == "true" ]]; then
126
182
  # Deep mode: filter messages first, then pass to fetch-gmail-deep.py for
127
183
  # full body fetching, link extraction, and article following
128
- echo "$EMAILS_JSON" | python3 - "$GMAIL_LABEL_ID" "$MAX_ITEMS" "$CONFIG_PATH" <<'PY' 2>/dev/null | python3 "$SCRIPTS_DIR/fetch-gmail-deep.py" || {
129
- import json
130
- import sys
131
- import yaml
184
+ echo "$EMAILS_JSON" | python3 -c "
185
+ import json, sys, yaml, os
132
186
 
133
- label_id = sys.argv[1]
134
- max_items = int(sys.argv[2])
135
- config_path = sys.argv[3]
187
+ label_id = os.environ['_GMAIL_LABEL_ID']
188
+ max_items = int(os.environ['_GMAIL_MAX_ITEMS'])
189
+ config_path = os.environ['_GMAIL_CONFIG_PATH']
190
+ seen_ids = set(os.environ.get('_GMAIL_SEEN_IDS', '').split(',')) - {''}
136
191
 
137
192
  data = json.load(sys.stdin)
138
193
  messages = data.get('messages', []) if isinstance(data, dict) else data
@@ -140,6 +195,12 @@ messages = data.get('messages', []) if isinstance(data, dict) else data
140
195
  if label_id:
141
196
  messages = [m for m in messages if label_id in m.get('labels', [])]
142
197
 
198
+ before = len(messages)
199
+ messages = [m for m in messages if m.get('id', '') not in seen_ids]
200
+ skipped = before - len(messages)
201
+ if skipped:
202
+ print(f' Dedup: skipped {skipped} already-seen messages', file=sys.stderr)
203
+
143
204
  messages = messages[:max_items]
144
205
 
145
206
  with open(config_path) as f:
@@ -147,19 +208,19 @@ with open(config_path) as f:
147
208
  gmail_config = config.get('sources', {}).get('gmail', {}) or {}
148
209
 
149
210
  json.dump({'messages': messages, 'config': gmail_config}, sys.stdout)
150
- PY
211
+ " | python3 "$SCRIPTS_DIR/fetch-gmail-deep.py" || {
151
212
  echo "Error: Deep Gmail processing failed" >&2
152
213
  exit 1
153
214
  }
154
215
  else
155
216
  # Shallow mode: subject-only signals (original behavior)
156
- echo "$EMAILS_JSON" | python3 - "$GMAIL_LABEL_ID" "$MAX_ITEMS" <<'PY' 2>/dev/null || {
157
- import json
158
- import sys
217
+ echo "$EMAILS_JSON" | python3 -c "
218
+ import json, sys, os
159
219
  from datetime import datetime, timezone
160
220
 
161
- label_id = sys.argv[1]
162
- max_items = int(sys.argv[2])
221
+ label_id = os.environ.get('_GMAIL_LABEL_ID', '')
222
+ max_items = int(os.environ.get('_GMAIL_MAX_ITEMS', '20'))
223
+ seen_ids = set(os.environ.get('_GMAIL_SEEN_IDS', '').split(',')) - {''}
163
224
 
164
225
  data = json.load(sys.stdin)
165
226
  messages = data.get('messages', []) if isinstance(data, dict) else data
@@ -167,6 +228,12 @@ messages = data.get('messages', []) if isinstance(data, dict) else data
167
228
  if label_id:
168
229
  messages = [m for m in messages if label_id in m.get('labels', [])]
169
230
 
231
+ before = len(messages)
232
+ messages = [m for m in messages if m.get('id', '') not in seen_ids]
233
+ skipped = before - len(messages)
234
+ if skipped:
235
+ print(f' Dedup: skipped {skipped} already-seen messages', file=sys.stderr)
236
+
170
237
  messages = messages[:max_items]
171
238
 
172
239
  signals = []
@@ -203,7 +270,7 @@ for email in messages:
203
270
  })
204
271
 
205
272
  json.dump(signals, sys.stdout, indent=2)
206
- PY
273
+ " 2>/dev/null || {
207
274
  echo "Error: Failed to parse gws output" >&2
208
275
  exit 1
209
276
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "marvin",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "The Paranoid Android — quality tools for code review, knowledge compounding, and work execution",
5
5
  "author": {
6
6
  "name": "ondrej-svec",
@@ -2,7 +2,7 @@
2
2
 
3
3
  > "Here I am, brain the size of a planet, and they ask me to review your code. Call that job satisfaction? 'Cause I don't."
4
4
 
5
- A quality plugin for Claude Code. Seven skills for the unglamorous work that compounds: executing plans, reviewing code, documenting solutions, scaffolding projects, writing failing tests, adversarial review, and keeping copy clean.
5
+ A quality plugin for Claude Code. Nine skills for the unglamorous work that compounds: executing plans, reviewing code, documenting solutions, scaffolding projects, writing failing tests, adversarial review, keeping copy clean, and publishing browser-viewable artifacts.
6
6
 
7
7
  ## Skills
8
8
 
@@ -29,6 +29,12 @@ Write failing tests from user stories and architecture docs. Behavioral tests ve
29
29
  ### `/marvin:copy-editor`
30
30
  Two-layer copy editor. Layer 1 is a deterministic typography audit (regex-level, auto-closeable). Layer 2 is LLM judgment — reject-list hits, nominal-style detection, clarity/ambiguity pass for participant-facing content, voice/register check, and spoken-readability read. Loads repo-local `.copy-editor.yaml` to compose the baked-in language profile with the repo's style guide.
31
31
 
32
+ ### `/marvin:share-server-setup`
33
+ Set up or adopt a local share server for browser-viewable coding-agent artifacts. Supports configuring an existing compatible server, installing the Heart of Gold reference share server, adding macOS LaunchAgent persistence, and optionally exposing only the viewer surface over `tailscale serve`.
34
+
35
+ ### `/marvin:share-html`
36
+ Publish a local HTML file or static site directory to the configured share server and return a browser URL. Supports a single `.html` file or a directory containing `index.html`.
37
+
32
38
  ## Agents
33
39
 
34
40
  | Agent | Focus |
package/src/index.ts CHANGED
@@ -8,7 +8,7 @@ import { shareServerCommand } from "./commands/share-server";
8
8
  const main = defineCommand({
9
9
  meta: {
10
10
  name: "heart-of-gold",
11
- version: "0.1.29",
11
+ version: "0.1.30",
12
12
  description:
13
13
  "Cross-platform installer for Heart of Gold skills — Codex, OpenCode, Pi, Claude Code, and more",
14
14
  },