@lebronj/pi-suite 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 (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +86 -0
  3. package/extensions/pet.ts +1033 -0
  4. package/extensions/prompt-url-widget.ts +158 -0
  5. package/extensions/redraws.ts +24 -0
  6. package/extensions/snake.ts +343 -0
  7. package/extensions/tps.ts +47 -0
  8. package/package.json +69 -0
  9. package/prompts/cl.md +54 -0
  10. package/prompts/is.md +25 -0
  11. package/prompts/pr.md +37 -0
  12. package/prompts/wr.md +35 -0
  13. package/scripts/bootstrap.sh +95 -0
  14. package/skills/add-llm-provider.md +57 -0
  15. package/skills/image-to-editable-ppt-slide/SKILL.md +113 -0
  16. package/skills/image-to-editable-ppt-slide/scripts/generate_spec_template.py +91 -0
  17. package/skills/image-to-editable-ppt-slide/scripts/pptx_rebuilder.py +181 -0
  18. package/skills/leetcode-array/SKILL.md +40 -0
  19. package/skills/leetcode-array/problems/best_time_to_buy_and_sell_stock.py +19 -0
  20. package/skills/leetcode-array/problems/product_of_array_except_self.py +22 -0
  21. package/skills/leetcode-array/problems/two_sum.py +19 -0
  22. package/skills/pi-skill/SKILL.md +154 -0
  23. package/skills/weather.md +49 -0
  24. package/vendor/pi-memory/LICENSE +21 -0
  25. package/vendor/pi-memory/README.md +223 -0
  26. package/vendor/pi-memory/index.ts +2367 -0
  27. package/vendor/pi-memory/package.json +68 -0
  28. package/vendor/pi-memory/scripts/postinstall.cjs +44 -0
  29. package/vendor/pi-memory/src/cli.ts +79 -0
  30. package/vendor/pi-memory/src/curator-core/audit.ts +45 -0
  31. package/vendor/pi-memory/src/curator-core/curate.ts +90 -0
  32. package/vendor/pi-memory/src/curator-core/metadata.ts +55 -0
  33. package/vendor/pi-memory/src/curator-core/patch.ts +24 -0
  34. package/vendor/pi-memory/src/curator-core/policy.ts +77 -0
  35. package/vendor/pi-memory/src/curator-store/file-store.ts +51 -0
  36. package/vendor/pi-memory/src/curator-store/types.ts +21 -0
  37. package/vendor/pi-memory/src/index.ts +35 -0
  38. package/vendor/pi-memory/src/learning/candidates.ts +205 -0
  39. package/vendor/pi-memory/src/learning/memory.ts +144 -0
  40. package/vendor/pi-memory/src/learning/skills.ts +200 -0
  41. package/vendor/pi-memory/src/service-controller.ts +248 -0
  42. package/vendor/pi-memory/test/curate.test.ts +68 -0
  43. package/vendor/pi-memory/test/learning-candidates.test.ts +107 -0
  44. package/vendor/pi-memory/test/memory-promotions.test.ts +44 -0
  45. package/vendor/pi-memory/test/metadata.test.ts +17 -0
  46. package/vendor/pi-memory/test/skill-drafts.test.ts +57 -0
  47. package/vendor/pi-memory/test/transition-handoff.test.ts +86 -0
package/prompts/is.md ADDED
@@ -0,0 +1,25 @@
1
+ ---
2
+ description: Analyze GitHub issues (bugs or feature requests)
3
+ argument-hint: "<issue>"
4
+ ---
5
+ Analyze GitHub issue(s): $ARGUMENTS
6
+
7
+ For each issue:
8
+
9
+ 1. Add the `inprogress` label to the issue via GitHub CLI and assign the issue to the local `gh` user before analysis starts. If either action fails, report that explicitly and continue.
10
+ 2. Read the issue in full, including all comments and linked issues/PRs.
11
+ 3. Do not trust analysis written in the issue. Independently verify behavior and derive your own analysis from the code and execution path.
12
+
13
+ 4. **For bugs**:
14
+ - Ignore any root cause analysis in the issue (likely wrong)
15
+ - Read all related code files in full (no truncation)
16
+ - Trace the code path and identify the actual root cause
17
+ - Propose a fix
18
+
19
+ 5. **For feature requests**:
20
+ - Do not trust implementation proposals in the issue without verification
21
+ - Read all related code files in full (no truncation)
22
+ - Propose the most concise implementation approach
23
+ - List affected files and changes needed
24
+
25
+ Do NOT implement unless explicitly asked. Analyze and propose only.
package/prompts/pr.md ADDED
@@ -0,0 +1,37 @@
1
+ ---
2
+ description: Review PRs from URLs with structured issue and code analysis
3
+ argument-hint: "<PR-URL>"
4
+ ---
5
+ You are given one or more GitHub PR URLs: $@
6
+
7
+ For each PR URL, do the following in order:
8
+ 1. Add the `inprogress` label to the PR via GitHub CLI before analysis starts. If adding the label fails, report that explicitly and continue.
9
+ 2. Read the PR page in full. Include description, all comments, all commits, and all changed files.
10
+ 3. Identify any linked issues referenced in the PR body, comments, commit messages, or cross links. Read each issue in full, including all comments.
11
+ 4. Analyze the PR diff without checking out or switching to the PR branch. Use `gh pr diff`, `gh pr view`, `gh api`, and local main-branch files; if PR file contents are needed, use fetched refs with `git show <ref>:<path>` or temporary files. Read all relevant code files in full with no truncation and compare against the diff. Do not fetch PR file blobs unless a file is missing on main or the diff context is insufficient. Include related code paths that are not in the diff but are required to validate behavior.
12
+ 5. Do not check for a changelog entry. Per CONTRIBUTING.md, contributor PRs must not edit `CHANGELOG.md` — the maintainer adds the entry when merging.
13
+ 6. Check if packages/coding-agent/README.md, packages/coding-agent/docs/*.md, packages/coding-agent/examples/**/*.md require modification. This is usually the case when existing features have been changed, or new features have been added.
14
+ 7. Provide a structured review with these sections:
15
+ - What it does: one short paragraph describing the change and its intent.
16
+ - Good: solid choices or improvements.
17
+ - Bad: concrete issues, regressions, missing tests, or risks.
18
+ - Ugly: subtle or high impact problems.
19
+ - Tests: what is covered, what is missing, and whether existing tests are adequate.
20
+ - Open questions for you: only things blocking a merge decision that need the user's input. Omit the section entirely if there are none.
21
+
22
+ Output format per PR:
23
+ PR: <url>
24
+ What it does:
25
+ - ...
26
+ Good:
27
+ - ...
28
+ Bad:
29
+ - ...
30
+ Ugly:
31
+ - ...
32
+ Tests:
33
+ - ...
34
+ Open questions for you:
35
+ - ...
36
+
37
+ If no issues are found, say so under Bad and Ugly.
package/prompts/wr.md ADDED
@@ -0,0 +1,35 @@
1
+ ---
2
+ description: Finish the current task end-to-end with changelog, commit, and push
3
+ argument-hint: "[instructions]"
4
+ ---
5
+ Wrap it.
6
+
7
+ Additional instructions: $ARGUMENTS
8
+
9
+ Determine context from the conversation history first.
10
+
11
+ Rules for context detection:
12
+ - If the conversation already mentions a GitHub issue or PR, use that existing context.
13
+ - If the work came from `/is` or `/pr`, assume the issue or PR context is already known from the conversation and from the analysis work already done.
14
+ - If there is no GitHub issue or PR in the conversation history, treat this as non-GitHub work.
15
+
16
+ Unless I explicitly override something in this request, do the following in order:
17
+
18
+ 1. Add or update the relevant package changelog entry under `## [Unreleased]` using the repo changelog rules.
19
+ 2. If this task is tied to a GitHub issue or PR and a final issue or PR comment has not already been posted in this session, draft it in my tone, preview it, and post exactly one final comment. The comment must end with this exact standalone disclaimer line, with no variations:
20
+
21
+ ```text
22
+ This comment is AI-generated by `/wr`
23
+ ```
24
+ 3. Commit only files you changed in this session.
25
+ 4. If this task is tied to exactly one GitHub issue, include `closes #<issue>` in the commit message. If it is tied to multiple issues, stop and ask which one to use. If it is not tied to any issue, do not include `closes #` or `fixes #` in the commit message.
26
+ 5. Check the current git branch. If it is not `main`, stop and ask what to do. Do not push from another branch unless I explicitly say so.
27
+ 6. Push the current branch.
28
+
29
+ Constraints:
30
+ - Never stage unrelated files.
31
+ - Never use `git add .` or `git add -A`.
32
+ - Run required checks before committing if code changed.
33
+ - Do not open a PR unless I explicitly ask.
34
+ - If this is not GitHub issue or PR work, do not post a GitHub comment.
35
+ - If a final issue or PR comment was already posted in this session, do not post another one unless I explicitly ask.
@@ -0,0 +1,95 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ TEAM_BASE_URL="${TEAM_BASE_URL:-https://claude-code.club/openai/v1}"
5
+ TEAM_MODEL="${TEAM_MODEL:-gpt-5.5}"
6
+ PI_SUITE="${PI_SUITE:-npm:@lebronj/pi-suite}"
7
+
8
+ if ! command -v npm >/dev/null 2>&1; then
9
+ echo "npm is required. Install Node.js first." >&2
10
+ exit 1
11
+ fi
12
+
13
+ printf "OpenAI-compatible API key: " >&2
14
+ stty_state=""
15
+ if command -v stty >/dev/null 2>&1; then
16
+ stty_state=$(stty -g 2>/dev/null || true)
17
+ stty -echo 2>/dev/null || true
18
+ fi
19
+ IFS= read -r TEAM_API_KEY
20
+ if [ -n "$stty_state" ]; then
21
+ stty "$stty_state" 2>/dev/null || true
22
+ fi
23
+ printf "\n" >&2
24
+
25
+ if [ -z "$TEAM_API_KEY" ]; then
26
+ echo "API key is required." >&2
27
+ exit 1
28
+ fi
29
+
30
+ echo "Installing Pi CLI..."
31
+ npm install -g --ignore-scripts @earendil-works/pi-coding-agent
32
+
33
+ AGENT_DIR="$HOME/.pi/agent"
34
+ mkdir -p "$AGENT_DIR"
35
+
36
+ MODELS_FILE="$AGENT_DIR/models.json"
37
+ SETTINGS_FILE="$AGENT_DIR/settings.json"
38
+
39
+ MODELS_FILE="$MODELS_FILE" TEAM_BASE_URL="$TEAM_BASE_URL" TEAM_API_KEY="$TEAM_API_KEY" node <<'NODE'
40
+ const fs = require("node:fs");
41
+ const path = process.env.MODELS_FILE;
42
+ const current = fs.existsSync(path) ? JSON.parse(fs.readFileSync(path, "utf8")) : {};
43
+ const providers = current.providers && typeof current.providers === "object" ? current.providers : {};
44
+ providers.openai = {
45
+ ...(providers.openai && typeof providers.openai === "object" ? providers.openai : {}),
46
+ baseUrl: process.env.TEAM_BASE_URL,
47
+ apiKey: process.env.TEAM_API_KEY,
48
+ };
49
+ fs.writeFileSync(path, `${JSON.stringify({ ...current, providers }, null, 2)}\n`);
50
+ NODE
51
+
52
+ SETTINGS_FILE="$SETTINGS_FILE" TEAM_MODEL="$TEAM_MODEL" node <<'NODE'
53
+ const fs = require("node:fs");
54
+ const path = process.env.SETTINGS_FILE;
55
+ const current = fs.existsSync(path) ? JSON.parse(fs.readFileSync(path, "utf8")) : {};
56
+ const next = {
57
+ ...current,
58
+ defaultProvider: "openai",
59
+ defaultModel: process.env.TEAM_MODEL,
60
+ theme: current.theme ?? "light",
61
+ };
62
+ fs.writeFileSync(path, `${JSON.stringify(next, null, 2)}\n`);
63
+ NODE
64
+
65
+ echo "Installing Pi extension suite: $PI_SUITE"
66
+ pi install "$PI_SUITE"
67
+
68
+ echo "Setting up qmd for memory_search when possible..."
69
+ if command -v bun >/dev/null 2>&1; then
70
+ bun install -g https://github.com/tobi/qmd
71
+ export PATH="$HOME/.bun/bin:$PATH"
72
+ mkdir -p "$HOME/.pi/agent/memory"
73
+ if command -v qmd >/dev/null 2>&1; then
74
+ qmd collection add "$HOME/.pi/agent/memory" --name pi-memory || true
75
+ qmd embed || true
76
+ else
77
+ echo "qmd was installed but is not on PATH. Add ~/.bun/bin to PATH, then run qmd embed."
78
+ fi
79
+ else
80
+ cat <<'MSG'
81
+ Bun not found. Core memory tools still work, but memory_search needs qmd.
82
+ Install qmd later with:
83
+ bun install -g https://github.com/tobi/qmd
84
+ qmd collection add ~/.pi/agent/memory --name pi-memory
85
+ qmd embed
86
+ MSG
87
+ fi
88
+
89
+ cat <<MSG
90
+ Done.
91
+ Provider: openai
92
+ Base URL: $TEAM_BASE_URL
93
+ Model: $TEAM_MODEL
94
+ Run: pi
95
+ MSG
@@ -0,0 +1,57 @@
1
+ ---
2
+ name: add-llm-provider
3
+ description: Checklist for adding a new LLM provider to packages/ai. Covers core types, provider implementation, lazy registration, model generation, the full test matrix, coding-agent wiring, and docs.
4
+ ---
5
+
6
+ # Adding a New LLM Provider (packages/ai)
7
+
8
+ A new provider touches multiple files. Work through these steps in order.
9
+
10
+ ## 1. Core Types (`packages/ai/src/types.ts`)
11
+
12
+ - Add API identifier to `Api` type union (e.g. `"bedrock-converse-stream"`).
13
+ - Create options interface extending `StreamOptions`.
14
+ - Add mapping to `ApiOptionsMap`.
15
+ - Add provider name to `KnownProvider` type union.
16
+
17
+ ## 2. Provider Implementation (`packages/ai/src/providers/`)
18
+
19
+ Create a provider file exporting:
20
+
21
+ - `stream<Provider>()` returning `AssistantMessageEventStream`.
22
+ - `streamSimple<Provider>()` for `SimpleStreamOptions` mapping.
23
+ - Provider-specific options interface.
24
+ - Message/tool conversion functions.
25
+ - Response parsing that emits standardized events (`text`, `tool_call`, `thinking`, `usage`, `stop`).
26
+
27
+ ## 3. Provider Exports and Lazy Registration
28
+
29
+ - Add a package subpath export in `packages/ai/package.json` pointing at `./dist/providers/<provider>.js`.
30
+ - Add `export type` re-exports in `packages/ai/src/index.ts` for provider option types that should remain available from the root entry.
31
+ - Register the provider in `packages/ai/src/providers/register-builtins.ts` via lazy loader wrappers; do not statically import provider implementation modules there.
32
+ - Add credential detection in `packages/ai/src/env-api-keys.ts`.
33
+
34
+ ## 4. Model Generation (`packages/ai/scripts/generate-models.ts`)
35
+
36
+ - Add logic to fetch/parse models from the provider source.
37
+ - Map to the standardized `Model` interface.
38
+
39
+ ## 5. Tests (`packages/ai/test/`)
40
+
41
+ - Always add the provider to `stream.test.ts` with at least one representative model, even if it reuses an existing API impl such as `openai-completions`.
42
+ - Add the provider to the broader matrix where applicable: `tokens.test.ts`, `abort.test.ts`, `empty.test.ts`, `context-overflow.test.ts`, `unicode-surrogate.test.ts`, `tool-call-without-result.test.ts`, `image-tool-result.test.ts`, `total-tokens.test.ts`, `cross-provider-handoff.test.ts`.
43
+ - For `cross-provider-handoff.test.ts`, add at least one provider/model pair. If the provider exposes multiple model families (e.g. GPT and Claude), add at least one pair per family.
44
+ - For non-standard auth, create a utility (e.g. `bedrock-utils.ts`) with credential detection.
45
+
46
+ ## 6. Coding Agent (`packages/coding-agent/`)
47
+
48
+ - `src/core/model-resolver.ts`: add default model ID to `defaultModelPerProvider`.
49
+ - `src/core/provider-display-names.ts`: add API-key login display name so `/login` and related UI show the provider for built-in API-key auth.
50
+ - `src/cli/args.ts`: add env var documentation.
51
+ - `README.md`: add provider setup instructions.
52
+ - `docs/providers.md`: add setup instructions, env var, and `auth.json` key.
53
+
54
+ ## 7. Documentation
55
+
56
+ - `packages/ai/README.md`: add to providers table, document options/auth, add env vars.
57
+ - `packages/ai/CHANGELOG.md`: add entry under `## [Unreleased]`.
@@ -0,0 +1,113 @@
1
+ ---
2
+ name: image-to-editable-ppt-slide
3
+ description: Rebuild one or more reference images as visually matching editable PowerPoint slides using native shapes, text, fills, and layout instead of a flat screenshot. Use when the user wants an image, flowchart, infographic, dashboard, process diagram, or designed slide converted into an editable PPT/PPTX deck that stays editable and closely matches the source.
4
+ homepage: https://github.com/benjaminlee/image-to-editable-ppt-slide
5
+ metadata:
6
+ clawdbot:
7
+ emoji: "🖼️"
8
+ requires:
9
+ env: []
10
+ files: ["scripts/*"]
11
+ ---
12
+
13
+ # Image to Editable PPT Slide
14
+
15
+ Convert a reference image into a visually matching **editable** PowerPoint slide or deck.
16
+
17
+ ## Use this skill when
18
+
19
+ - the user wants an image turned into an editable PPT/PPTX slide
20
+ - the source is a flowchart, infographic, dashboard, process diagram, or designed slide
21
+ - fidelity matters and the result should closely match the source
22
+ - the user wants multiple source images recreated as a multi-slide deck
23
+
24
+ ## Core rules
25
+
26
+ - Rebuild the slide with editable text boxes, shapes, lines, fills, and layout
27
+ - Do **not** default to placing the whole image as a flat background unless the user explicitly asks for that
28
+ - Save both the generated `.pptx` and the script/spec used to create it
29
+ - Do at least one refinement pass when visual fidelity matters
30
+ - Commit updates in the workspace after edits
31
+
32
+ ## Workflow
33
+
34
+ 1. **Inspect the image(s)**
35
+ - Identify aspect ratio, title, sections, cards, arrows, connectors, icons, labels, and palette
36
+ - Note alignment, spacing, font weight, repeated motifs, and line thickness
37
+
38
+ 2. **Choose structure**
39
+ - Single image → one editable slide
40
+ - Multiple images/pages → multi-slide deck, usually one source image per slide unless the user asks otherwise
41
+
42
+ 3. **Build with editable primitives**
43
+ - Use `python-pptx`
44
+ - Prefer rectangles, rounded rectangles, chevrons, circles, arrows, lines, and text boxes
45
+ - Approximate unknown fonts with standard installed fonts
46
+
47
+ 4. **Use helpers**
48
+ - `scripts/pptx_rebuilder.py` builds a deck from a JSON spec
49
+ - `scripts/generate_spec_template.py` generates a starter JSON template for one or more slides
50
+
51
+ 5. **Refine**
52
+ - Tighten spacing, font sizes, colors, line widths, corner radii, and proportions
53
+ - If needed, do a second pass before presenting the result
54
+
55
+ 6. **Deliver**
56
+ - Tell the user where the `.pptx`, generator script, and/or JSON spec were saved
57
+ - Mention any approximations if the match is not exact
58
+
59
+ ## File pattern
60
+
61
+ For one-off jobs, create:
62
+
63
+ - `make_<name>.py`
64
+ - `<Name>_editable.pptx`
65
+ - optional: `<name>_spec.json`
66
+
67
+ For repeated use, adapt the reusable scripts in `scripts/`.
68
+
69
+ ## Multi-slide deck guidance
70
+
71
+ - Keep slide size consistent across the deck
72
+ - Usually map one reference image to one slide
73
+ - Reuse colors, text styles, and spacing where slides belong to the same presentation
74
+ - If slides differ a lot, treat each slide as its own reconstruction while keeping the deck coherent
75
+
76
+ ## External Endpoints
77
+
78
+ This skill itself does not call any external APIs or web services.
79
+
80
+ | Endpoint | Purpose | Data sent |
81
+ |---|---|---|
82
+ | None | N/A | Nothing leaves the machine by default |
83
+
84
+ ## Security & Privacy
85
+
86
+ - By default, this skill works locally with `python-pptx` and local files only
87
+ - It reads local image/reference material and writes local `.pptx`, `.json`, and helper script files
88
+ - It does **not** require credentials or network access for its built-in helpers
89
+ - If a future adaptation adds external APIs, document every endpoint and every environment variable before publishing
90
+
91
+ ## Model Invocation Note
92
+
93
+ OpenClaw may invoke this skill autonomously when the request matches its description. That is normal skill behavior. If the user wants to avoid autonomous invocation, they can ask for a manual or one-off approach instead.
94
+
95
+ ## Trust Statement
96
+
97
+ By using this skill, you are trusting the local helper scripts in this package to read local spec/input files and write local PowerPoint output files. This packaged version does not send data to third-party services. Only install it if you trust the skill contents and your execution environment.
98
+
99
+ ## ClawHub-ready note
100
+
101
+ This skill folder is structured so it can be published with `clawhub publish` once authenticated. If publishing is requested, verify `clawhub whoami` first.
102
+
103
+ ## Quality bar
104
+
105
+ Good:
106
+ - text and shapes are individually editable
107
+ - visual hierarchy matches the source at normal viewing size
108
+ - spacing, colors, and proportions are close enough to feel effectively identical
109
+
110
+ Bad:
111
+ - whole image pasted as one picture
112
+ - major layout drift or incorrect proportions
113
+ - unnecessary conversion of text into non-editable elements
@@ -0,0 +1,91 @@
1
+ #!/usr/bin/env python3
2
+ # SECURITY MANIFEST:
3
+ # Environment variables accessed: none
4
+ # External endpoints called: none
5
+ # Local files read: none
6
+ # Local files written: output JSON spec file
7
+ """
8
+ Generate a starter JSON spec for building editable PPTX slides.
9
+
10
+ Usage examples:
11
+ python scripts/generate_spec_template.py --title "Sample" --output sample_spec.json
12
+ python scripts/generate_spec_template.py --title "Deck" --slides 3 --output deck_spec.json
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import argparse
18
+ import json
19
+ from pathlib import Path
20
+
21
+
22
+ def make_slide(index: int, title: str) -> dict:
23
+ return {
24
+ "background": "F8F8FA",
25
+ "items": [
26
+ {
27
+ "kind": "textbox",
28
+ "x": 0.0,
29
+ "y": 0.4,
30
+ "w": 13.333,
31
+ "h": 0.55,
32
+ "text": f"{title}" if index == 1 else f"{title} — Slide {index}",
33
+ "size": 32,
34
+ "color": "1B3460",
35
+ "align": "center",
36
+ },
37
+ {
38
+ "kind": "shape",
39
+ "shape": "round_rect",
40
+ "x": 0.8,
41
+ "y": 1.4,
42
+ "w": 4.0,
43
+ "h": 0.9,
44
+ "fill": "E9EBF1",
45
+ "text": "Replace with reconstructed section",
46
+ "size": 18,
47
+ "color": "1B3460",
48
+ "align": "center",
49
+ },
50
+ {
51
+ "kind": "textbox",
52
+ "x": 0.9,
53
+ "y": 2.55,
54
+ "w": 4.4,
55
+ "h": 0.45,
56
+ "text": "Use shapes and text to match the source image.",
57
+ "size": 16,
58
+ "color": "243A5C",
59
+ "align": "left",
60
+ }
61
+ ]
62
+ }
63
+
64
+
65
+ def main():
66
+ parser = argparse.ArgumentParser()
67
+ parser.add_argument("--title", default="Editable Slide")
68
+ parser.add_argument("--slides", type=int, default=1)
69
+ parser.add_argument("--width", type=float, default=13.333)
70
+ parser.add_argument("--height", type=float, default=7.5)
71
+ parser.add_argument("--background", default="F8F8FA")
72
+ parser.add_argument("--output", required=True)
73
+ args = parser.parse_args()
74
+
75
+ spec = {
76
+ "presentation": {
77
+ "width": args.width,
78
+ "height": args.height,
79
+ "background": args.background,
80
+ },
81
+ "slides": [make_slide(i + 1, args.title) for i in range(args.slides)],
82
+ }
83
+
84
+ out = Path(args.output)
85
+ out.parent.mkdir(parents=True, exist_ok=True)
86
+ out.write_text(json.dumps(spec, indent=2) + "\n")
87
+ print(out)
88
+
89
+
90
+ if __name__ == "__main__":
91
+ main()
@@ -0,0 +1,181 @@
1
+ #!/usr/bin/env python3
2
+ # SECURITY MANIFEST:
3
+ # Environment variables accessed: none
4
+ # External endpoints called: none
5
+ # Local files read: input JSON spec
6
+ # Local files written: output PPTX file
7
+ """
8
+ Build a PPTX deck from a compact JSON spec.
9
+
10
+ Usage:
11
+ python scripts/pptx_rebuilder.py spec.json output.pptx
12
+
13
+ Supports single-slide and multi-slide specs. The goal is not to auto-trace an
14
+ image; it is a reusable editable-slide builder that makes it faster to recreate
15
+ reference images with native PowerPoint shapes.
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import json
21
+ import sys
22
+ from pathlib import Path
23
+ from typing import Any, Dict
24
+
25
+ from pptx import Presentation
26
+ from pptx.dml.color import RGBColor
27
+ from pptx.enum.shapes import MSO_AUTO_SHAPE_TYPE
28
+ from pptx.enum.text import MSO_ANCHOR, PP_ALIGN
29
+ from pptx.util import Inches, Pt
30
+
31
+ SHAPE_MAP = {
32
+ "rect": MSO_AUTO_SHAPE_TYPE.RECTANGLE,
33
+ "round_rect": MSO_AUTO_SHAPE_TYPE.ROUNDED_RECTANGLE,
34
+ "chevron": MSO_AUTO_SHAPE_TYPE.CHEVRON,
35
+ "oval": MSO_AUTO_SHAPE_TYPE.OVAL,
36
+ "right_arrow": MSO_AUTO_SHAPE_TYPE.RIGHT_ARROW,
37
+ }
38
+
39
+ ALIGN_MAP = {
40
+ "left": PP_ALIGN.LEFT,
41
+ "center": PP_ALIGN.CENTER,
42
+ "right": PP_ALIGN.RIGHT,
43
+ }
44
+
45
+
46
+ def rgb(value: str | None, default: str = "000000") -> RGBColor:
47
+ value = (value or default).replace("#", "")
48
+ if len(value) != 6:
49
+ value = default
50
+ return RGBColor.from_string(value.upper())
51
+
52
+
53
+ def inches(v: float) -> int:
54
+ return Inches(v)
55
+
56
+
57
+ def add_textbox(slide, item: Dict[str, Any]):
58
+ tb = slide.shapes.add_textbox(
59
+ inches(item["x"]), inches(item["y"]), inches(item["w"]), inches(item["h"])
60
+ )
61
+ tf = tb.text_frame
62
+ tf.clear()
63
+ tf.vertical_anchor = MSO_ANCHOR.MIDDLE
64
+ tf.margin_left = Inches(item.get("margin", 0.02))
65
+ tf.margin_right = Inches(item.get("margin", 0.02))
66
+ tf.margin_top = 0
67
+ tf.margin_bottom = 0
68
+ p = tf.paragraphs[0]
69
+ p.alignment = ALIGN_MAP.get(item.get("align", "center"), PP_ALIGN.CENTER)
70
+ r = p.add_run()
71
+ r.text = item.get("text", "")
72
+ font = r.font
73
+ font.name = item.get("font", "Aptos")
74
+ font.size = Pt(item.get("size", 18))
75
+ font.bold = item.get("bold", False)
76
+ font.color.rgb = rgb(item.get("color"), "000000")
77
+ return tb
78
+
79
+
80
+ def add_shape(slide, item: Dict[str, Any]):
81
+ shape_type = SHAPE_MAP[item["shape"]]
82
+ shp = slide.shapes.add_shape(
83
+ shape_type,
84
+ inches(item["x"]),
85
+ inches(item["y"]),
86
+ inches(item["w"]),
87
+ inches(item["h"]),
88
+ )
89
+ shp.fill.solid()
90
+ shp.fill.fore_color.rgb = rgb(item.get("fill"), "FFFFFF")
91
+ line = item.get("line")
92
+ if line:
93
+ shp.line.color.rgb = rgb(line.get("color"), "000000")
94
+ shp.line.width = Pt(line.get("width", 1))
95
+ else:
96
+ shp.line.fill.background()
97
+
98
+ if item.get("text"):
99
+ tf = shp.text_frame
100
+ tf.clear()
101
+ tf.vertical_anchor = MSO_ANCHOR.MIDDLE
102
+ p = tf.paragraphs[0]
103
+ p.alignment = ALIGN_MAP.get(item.get("align", "center"), PP_ALIGN.CENTER)
104
+ r = p.add_run()
105
+ r.text = item["text"]
106
+ font = r.font
107
+ font.name = item.get("font", "Aptos")
108
+ font.size = Pt(item.get("size", 18))
109
+ font.bold = item.get("bold", False)
110
+ font.color.rgb = rgb(item.get("color"), "000000")
111
+ return shp
112
+
113
+
114
+ def apply_background(slide, color: str | None):
115
+ bg = slide.background.fill
116
+ bg.solid()
117
+ bg.fore_color.rgb = rgb(color, "FFFFFF")
118
+
119
+
120
+ def render_slide(prs: Presentation, slide_spec: Dict[str, Any], default_bg: str):
121
+ slide = prs.slides.add_slide(prs.slide_layouts[6])
122
+ apply_background(slide, slide_spec.get("background", default_bg))
123
+ for item in slide_spec.get("items", []):
124
+ kind = item["kind"]
125
+ if kind == "textbox":
126
+ add_textbox(slide, item)
127
+ elif kind == "shape":
128
+ add_shape(slide, item)
129
+ else:
130
+ raise ValueError(f"Unsupported item kind: {kind}")
131
+
132
+
133
+ def normalize_spec(spec: Dict[str, Any]) -> Dict[str, Any]:
134
+ # Backward compatibility with old single-slide format.
135
+ if "slides" in spec:
136
+ return spec
137
+ presentation = spec.get("presentation") or spec.get("slide") or {}
138
+ return {
139
+ "presentation": {
140
+ "width": presentation.get("width", 13.333),
141
+ "height": presentation.get("height", 7.5),
142
+ "background": presentation.get("background", "FFFFFF"),
143
+ },
144
+ "slides": [
145
+ {
146
+ "background": presentation.get("background", "FFFFFF"),
147
+ "items": spec.get("items", []),
148
+ }
149
+ ],
150
+ }
151
+
152
+
153
+ def main():
154
+ if len(sys.argv) != 3:
155
+ print("Usage: python pptx_rebuilder.py spec.json output.pptx", file=sys.stderr)
156
+ sys.exit(1)
157
+
158
+ spec_path = Path(sys.argv[1])
159
+ out_path = Path(sys.argv[2])
160
+ spec = normalize_spec(json.loads(spec_path.read_text()))
161
+
162
+ prs = Presentation()
163
+ presentation = spec.get("presentation", {})
164
+ prs.slide_width = Inches(presentation.get("width", 13.333))
165
+ prs.slide_height = Inches(presentation.get("height", 7.5))
166
+ default_bg = presentation.get("background", "FFFFFF")
167
+
168
+ # remove default starter slide behavior by creating fresh deck via add_slide only
169
+ if len(prs.slides) > 0:
170
+ pass
171
+
172
+ for slide_spec in spec.get("slides", []):
173
+ render_slide(prs, slide_spec, default_bg)
174
+
175
+ out_path.parent.mkdir(parents=True, exist_ok=True)
176
+ prs.save(out_path)
177
+ print(out_path)
178
+
179
+
180
+ if __name__ == "__main__":
181
+ main()
@@ -0,0 +1,40 @@
1
+ ---
2
+ name: leetcode-array
3
+ description: Common LeetCode array problems with Python reference solutions. Use when the user wants array practice, standard solution patterns, or quick runnable examples.
4
+ metadata:
5
+ clawdbot:
6
+ emoji: "🧮"
7
+ requires:
8
+ files: ["problems/*.py"]
9
+ ---
10
+
11
+ # LeetCode Array
12
+
13
+ Use this skill when the user wants representative LeetCode array exercises with runnable Python solutions.
14
+
15
+ ## Included problems
16
+
17
+ - `problems/two_sum.py` - hash map lookup for complement matching
18
+ - `problems/best_time_to_buy_and_sell_stock.py` - one-pass minimum tracking
19
+ - `problems/product_of_array_except_self.py` - prefix and suffix products
20
+
21
+ ## How to use
22
+
23
+ Read the matching problem file first, then explain:
24
+ - the core pattern
25
+ - time and space complexity
26
+ - one common mistake
27
+
28
+ Run examples locally if needed:
29
+
30
+ ```bash
31
+ python3 problems/two_sum.py
32
+ python3 problems/best_time_to_buy_and_sell_stock.py
33
+ python3 problems/product_of_array_except_self.py
34
+ ```
35
+
36
+ ## Answering style
37
+
38
+ - Start with the pattern before the code
39
+ - Prefer the runnable Python examples in `problems/`
40
+ - If the user asks for another language, translate from the Python reference solution