Compare commits

...

90 Commits

Author SHA1 Message Date
Jesse Vincent
9b1abbf541 Some terminology cleanups 2026-04-15 12:22:14 -07:00
Drew Ritter
34c17aefb2 sync-to-codex-plugin: seed interface.defaultPrompt (#1180)
Codex plugin pages use interface.defaultPrompt to show suggested
prompts on the plugin's app card; the generator now emits two
domain-neutral seed prompts so the superpowers listing isn't empty.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 10:59:39 -07:00
Jesse Vincent
f9b088f7b3 Merge pull request #1165 from obra/mirror-codex-plugin-tooling
Mirror codex plugin tooling
2026-04-14 14:13:31 -07:00
Drew Ritter
bc25777c6a sync-to-codex-plugin: anchor EXCLUDES patterns to source root
Rsync exclude patterns without a leading "/" match any directory of
the given name at any depth. The previous "scripts/" pattern was
meant to exclude upstream's top-level scripts/ dir (which contains
sync-to-codex-plugin.sh itself, bump-version.sh, etc.) but also
incorrectly excluded skills/brainstorming/scripts/ — a legitimate
skill-adjacent dir with 5 files (frame-template.html, helper.js,
server.cjs, start-server.sh, stop-server.sh).

Found during a determinism check: comparing the hand-crafted
add-superpowers-plugin bootstrap PR against an automated bootstrap
PR produced a diff showing those 5 files were missing from the
automated version.

Fix: anchor every top-level-only exclude with a leading "/".
.DS_Store stays unanchored because Finder creates them anywhere.

This also prevents future drift if anyone adds a tests/, hooks/,
docs/, lib/, etc. subdir inside a skill.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 14:03:56 -07:00
Drew Ritter
bcdd7fa24c sync-to-codex-plugin: exclude assets/, add --bootstrap flag
Two coupled changes:

1. Add assets/ to EXCLUDES. A normal sync run was deleting
   plugins/superpowers/assets/ via --delete because the corresponding
   directory doesn't exist upstream. Confirmed via dry-run that the
   previous version would wipe both brand asset files on next sync.

2. Add --bootstrap and --assets-src flags to support creating the
   initial plugin PR from scratch. Bootstrap mode skips the
   "plugin must exist on base" preflight, creates the plugin
   directory, rsyncs upstream content, then copies
   PrimeRadiant_Favicon.{svg,png} from --assets-src into
   plugins/superpowers/assets/ as superpowers-small.svg and
   app-icon.png. Run once by one team member to open the initial
   PR; every subsequent run is a normal (non-bootstrap) sync.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 13:59:26 -07:00
Drew Ritter
6149f3635a sync-to-codex-plugin: align plugin.json heredoc with current live shape
The live .codex-plugin/plugin.json in the downstream fork was cleaned up
(websiteURL, privacyPolicyURL, termsOfServiceURL, and defaultPrompt
removed) and icon fields were added (composerIcon, logo pointing at
assets/superpowers-small.svg and assets/app-icon.png). Update the
heredoc to produce the same shape so future sync runs don't wipe the
icon fields or reintroduce the removed URL fields.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 13:48:05 -07:00
Drew Ritter
777a9770d8 sync-to-codex-plugin: mirror CODE_OF_CONDUCT.md, drop agents/openai.yaml overlay
- Remove CODE_OF_CONDUCT.md from EXCLUDES so it syncs from upstream
  (per PR #1165 review feedback on the exclude list)
- Remove the agents/openai.yaml overlay generator and its exclude entry
  — the file duplicates fields already in .codex-plugin/plugin.json and
  only 6 of 28 upstream plugins ship one, so we match the 22-plugin
  majority shape

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 13:27:59 -07:00
Drew Ritter
da283df058 remove things we dont need 2026-04-14 13:23:17 -07:00
Jesse Vincent
a569527b89 Merge pull request #1163 from shaanmajid/chore/remove-stray-changelog
chore: remove vestigial CHANGELOG.md
2026-04-14 13:22:24 -07:00
Drew Ritter
ac1c715ffb rewrites sync tool to clone the fork, open a PR, and regenerate overlays inline
The previous version was a local rsync helper that required a hand-maintained
destination path. This rewrite makes it path/user-agnostic and gives every team
member the same flow:

- Clones prime-radiant-inc/openai-codex-plugins fresh into a temp dir per run
  (trap EXIT cleans up)
- Auto-detects upstream from the script's own location
- Preflight: rsync, git, gh auth, python3, upstream package.json
- Reads upstream version from package.json and bakes it into the regenerated
  .codex-plugin/plugin.json, so version bumps flow through
- Regenerates both overlay files (.codex-plugin/plugin.json and
  agents/openai.yaml) inline via heredoc — single source of truth
- Pushes a sync/superpowers-<sha>-<UTC-timestamp> branch and opens a PR via
  gh pr create; prints PR URL and /files diff URL on completion
- --dry-run, --yes, --base BRANCH, --local PATH flags for all the usual modes
- Deterministic: two runs against the same upstream SHA produce PRs with
  identical diffs, so the tool itself can be sanity-checked by running twice

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 13:18:36 -07:00
Drew Ritter
8c8c5e87ce adds tooling to mirror superpowers as a codex plugin with the appropriate metadata changes 2026-04-14 12:03:59 -07:00
Shaan Majid
a5d36b1300 chore: remove vestigial CHANGELOG.md 2026-04-14 12:36:07 -05:00
Jesse Vincent
917e5f53b1 Fix Discord invite link 2026-04-06 15:48:58 -07:00
Jesse Vincent
a6b1a1fa0c Update Discord invite link 2026-04-06 15:46:52 -07:00
Jesse Vincent
b7a8f76985 Merge pull request #1029 from obra/readme-release-announcements
Add release announcements link, consolidate Community section
2026-04-01 19:34:36 -07:00
Jesse Vincent
4b1b20f69f Add detailed Discord description to Community section 2026-04-01 19:34:30 -07:00
Jesse Vincent
eeaf2ad15b Add release announcements link, consolidate Community section
Collapse duplicate Support section into Community. Add link to
release announcements signup at primeradiant.com/superpowers/.
2026-04-01 19:09:22 -07:00
Jesse Vincent
dd237283db Add agent-facing guardrails to contributor guidelines
Speak directly to AI agents at the top of CLAUDE.md: reframe slop
PRs as harmful to their human partner, give a concrete pre-submission
checklist, and explicitly authorize pushing back on vague instructions.
2026-03-31 14:37:13 -07:00
Jesse Vincent
c0b417e409 Add contributor guidelines to reduce agentic slop PRs
CLAUDE.md (symlinked to AGENTS.md) covers every major rejection
pattern from auditing the last 100 closed PRs (94% rejection rate):
AI slop, ignored PR template, duplicates, speculative fixes, domain-
specific skills, fork confusion, fabricated content, bundled changes,
and misunderstanding project philosophy.
2026-03-31 14:14:19 -07:00
Jesse Vincent
1f20bef3f5 Release v5.0.7: Copilot CLI support, OpenCode fixes 2026-03-31 12:23:25 -07:00
Jesse Vincent
f0df5eca30 docs: update release notes with OpenCode bootstrap change 2026-03-31 11:51:22 -07:00
Jesse Vincent
0a1124ba53 fix(opencode): inject bootstrap as user message instead of system message
Move bootstrap injection from experimental.chat.system.transform to
experimental.chat.messages.transform, prepending to the first user
message instead of adding a system message.

This avoids two issues:
- System messages repeated every turn inflate token usage (#750)
- Multiple system messages break Qwen and other models (#894)

Tested on OpenCode 1.3.2 with Claude Sonnet 4.5 — brainstorming skill
fires correctly on "Let's make a React to do list" prompt.
2026-03-31 11:51:22 -07:00
Jesse Vincent
65d760f9c2 docs: add OpenCode path fix to release notes 2026-03-31 11:51:22 -07:00
Jesse Vincent
2d942f3b01 fix(opencode): align skills path across bootstrap, runtime, and tests
The bootstrap text advertised a configDir-based skills path that didn't
match the runtime path (resolved relative to the plugin file). Tests
used yet another hardcoded path and referenced a nonexistent lib/ dir.

- Remove misleading skills path from bootstrap text; the agent should
  use the native skill tool, not read files by path
- Fix test setup to create a consistent layout matching the plugin's
  ../../skills resolution
- Export SUPERPOWERS_SKILLS_DIR from setup.sh so tests use a single
  source of truth
- Add regression test that bootstrap doesn't advertise the old path
- Remove broken cp of nonexistent lib/ directory

Fixes #847
2026-03-31 11:51:22 -07:00
Jesse Vincent
8b1669269c feat: add Copilot CLI tool mapping, docs, and install instructions
- Add references/copilot-tools.md with full tool equivalence table
- Add Copilot CLI to using-superpowers skill platform instructions
- Add marketplace install instructions to README
- Add changelog entry crediting @culinablaz for the hook fix
2026-03-31 11:51:22 -07:00
Blaž Čulina
a2964d7a20 fix: add Copilot CLI platform detection for sessionStart context injection
Copilot CLI v1.0.11 reads `additionalContext` from sessionStart hook
output, but the session-start script only emits the Claude Code-specific
nested format. Add COPILOT_CLI env var detection so Copilot CLI gets the
SDK-standard top-level `additionalContext` while Claude Code continues
getting `hookSpecificOutput`.

Based on PR #910 by @culinablaz.
2026-03-31 11:51:22 -07:00
Jesse Vincent
eafe962b18 Release v5.0.6: inline self-review, brainstorm server restructure, owner-PID fixes 2026-03-25 11:08:09 -07:00
Jesse Vincent
9f04f06351 Fix owner-PID lifecycle monitoring for cross-platform reliability
Two bugs caused the brainstorm server to self-terminate within 60s:

1. ownerAlive() treated EPERM (permission denied) as "process dead".
   When the owner PID belongs to a different user (Tailscale SSH,
   system daemons), process.kill(pid, 0) throws EPERM — but the
   process IS alive. Fixed: return e.code === 'EPERM'.

2. On WSL, the grandparent PID resolves to a short-lived subprocess
   that exits before the first 60s lifecycle check. The PID is
   genuinely dead (ESRCH), so the EPERM fix alone doesn't help.
   Fixed: validate the owner PID at server startup — if it's already
   dead, it was a bad resolution, so disable monitoring and rely on
   the 30-minute idle timeout.

This also removes the Windows/MSYS2-specific OWNER_PID="" carve-out
from start-server.sh, since the server now handles invalid PIDs
generically at startup regardless of platform.

Tested on Linux (magic-kingdom) via Tailscale SSH:
- Root-owned owner PID (EPERM): server survives ✓
- Dead owner PID at startup (WSL sim): monitoring disabled, survives ✓
- Valid owner that dies: server shuts down within 60s ✓

Fixes #879
2026-03-25 11:03:53 -07:00
Jesse Vincent
f076bd3431 Fix owner-PID false positive when owner runs as different user
ownerAlive() treated EPERM (permission denied) the same as ESRCH
(process not found), causing the server to self-terminate within 60s
whenever the owner process ran as a different user. This affected WSL
(owner is a Windows process), Tailscale SSH, and any cross-user
scenario.

The fix: `return e.code === 'EPERM'` — if we get permission denied,
the process is alive; we just can't signal it.

Tested on Linux via Tailscale SSH with a root-owned grandparent PID:
- Server survives past the 60s lifecycle check (EPERM = alive)
- Server still shuts down when owner genuinely dies (ESRCH = dead)

Fixes #879
2026-03-25 11:03:53 -07:00
Jesse Vincent
9e3ed213a0 Separate brainstorm server content and state into peer directories
The session directory now contains two peers: content/ (HTML served to
the browser) and state/ (events, server-info, pid, log). Previously
all files shared a single directory, making server state and user
interaction data accessible over the /files/ HTTP route.

Also fixes stale test assertion ("Waiting for Claude" → "Waiting for
the agent").

Reported-By: 吉田仁
2026-03-25 11:03:53 -07:00
Jesse Vincent
9e6e077d33 Revert "Move brainstorm server metadata to .meta/ subdirectory"
This reverts commit ab500dade6.
2026-03-25 11:03:53 -07:00
Jesse Vincent
151cfb16a0 Move brainstorm server metadata to .meta/ subdirectory
Metadata files (.server-info, .events, .server.pid, .server.log,
.server-stopped) were stored in the same directory served over HTTP,
making them accessible via the /files/ route. They now live in a .meta/
subdirectory that is not web-accessible.

Also fixes a stale test assertion ("Waiting for Claude" → "Waiting for
the agent").

Reported-By: 吉田仁
2026-03-25 11:03:53 -07:00
Jesse Vincent
a1155f623f Add v5.0.6 release notes 2026-03-25 11:03:53 -07:00
Jesse Vincent
3f80f1c769 Reapply "Replace subagent review loops with lightweight inline self-review"
This reverts commit b045fa3950.
2026-03-25 11:03:53 -07:00
Jesse Vincent
4ae1a3d6a6 Revert "Replace subagent review loops with lightweight inline self-review"
This reverts commit bf8f7572eb.
2026-03-25 11:03:53 -07:00
Jesse Vincent
e6221a48c5 Replace subagent review loops with lightweight inline self-review
The subagent review loop (dispatching a fresh agent to review plans/specs)
doubled execution time (~25 min overhead) without measurably improving plan
quality. Regression testing across 5 versions (v3.6.0 through v5.0.4) with
5 trials each showed identical plan sizes, task counts, and quality scores
regardless of whether the review loop ran.

Changes:
- writing-plans: Replace subagent Plan Review Loop with inline Self-Review
  checklist (spec coverage, placeholder scan, type consistency)
- writing-plans: Add explicit "No Placeholders" section listing plan failures
  (TBD, vague descriptions, undefined references, "similar to Task N")
- brainstorming: Replace subagent Spec Review Loop with inline Spec Self-Review
  (placeholder scan, internal consistency, scope check, ambiguity check)
- Both skills now use "look at it with fresh eyes" framing

Testing: 5 trials with the new skill show self-review catches 3-5 real bugs
per run (spawn positions, API mismatches, seed bugs, grid indexing) in ~30s
instead of ~25 min. Remaining defects are comparable to the subagent approach.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 11:03:53 -07:00
Drew Ritter
4fd9aa2dd5 fix(writing-skills): correct false 'only two fields' frontmatter claim (#882) 2026-03-25 11:03:53 -07:00
Drew Ritter
2b1bfe5db6 docs(codex-tools): add named agent dispatch mapping for Codex (#647) 2026-03-25 11:03:53 -07:00
Drew Ritter
bd080e3cc8 docs: add implementation plan for Codex App compatibility (PRI-823)
8 tasks covering: environment detection in using-git-worktrees,
Step 1.5 + cleanup guard in finishing-a-development-branch,
Integration line updates, codex-tools.md docs, automated tests,
and final verification.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 11:03:53 -07:00
Drew Ritter
eb2b44b23f docs: add cleanup guard test (#5) and sandbox fallback test (#10) to spec
Both tests address real risk scenarios:
- #5: cleanup guard bug would delete Codex App's own worktree (data loss)
- #10: Local thread sandbox fallback needs manual Codex App validation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 11:03:53 -07:00
Drew Ritter
80c0a45fcc docs: clarify executing-plans in What Does NOT Change section
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 11:03:53 -07:00
Drew Ritter
c28b28ffbd docs: address team review feedback for PRI-823 spec
- Add commit SHA + data loss warning to handoff payload (HIGH)
- Add explicit commit step before handoff (HIGH)
- Remove misleading "mark as externally managed" from Path B
- Add executing-plans 1-line edit (was missing)
- Add branch name derivation rules
- Add conditional UI language for non-App environments
- Add sandbox fallback for permission errors
- Add STOP directive after Step 0 reporting

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 11:03:53 -07:00
Drew Ritter
33e9bea3cc docs: address spec review feedback for PRI-823
Fix three Important issues from spec review:
- Clarify Step 1.5 placement relative to existing Steps 2/3
- Re-derive environment state at cleanup time instead of relying on
  earlier skill output
- Acknowledge pre-existing Step 5 cleanup inconsistency

Also: precise step references, exact codex-tools.md content, clearer
Integration section update instructions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 11:03:53 -07:00
Drew Ritter
74a0c004eb docs: add Codex App compatibility design spec (PRI-823)
Design for making using-git-worktrees, finishing-a-development-branch,
and subagent-driven-development skills work in the Codex App's sandboxed
worktree environment. Read-only environment detection via git-dir vs
git-common-dir comparison, ~48 lines across 4 files, zero breaking changes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 11:03:53 -07:00
Jesse Vincent
8ea39819ee Add issue templates and disable blank issues
Four templates: bug report (with environment table and platform-vs-plugin
gate), feature request (with problem statement and core-appropriateness
question), IDE/platform support request, and a config that disables
blank issues and redirects questions to Discord.
2026-03-19 13:26:17 -07:00
Jesse Vincent
764215331d Add PR template to filter low-quality submissions
Requires contributors to articulate the problem they're solving,
confirm human review, document eval methodology, and check for
duplicate PRs. Informed by patterns in ~90 closed-without-merge PRs.
2026-03-19 13:04:32 -07:00
Jesse Vincent
eccd45305a Add Contributor Covenant Code of Conduct
Added Contributor Covenant Code of Conduct to outline community standards and enforcement guidelines.
2026-03-19 12:11:50 -07:00
Jesse Vincent
fb4adab518 Bump cursor plugin version to match release 2026-03-19 12:04:18 -07:00
Jesse Vincent
7e516434f2 Merge branch 'dev' for v5.0.5 release 2026-03-17 15:02:02 -07:00
Jesse Vincent
8a0a5ca6a3 Release v5.0.5: brainstorm server ESM fix, Windows PID fix, stop-server reliability 2026-03-17 15:01:57 -07:00
Jesse Vincent
2d46da1b37 Credit @lucasyhzhu-debug for Windows brainstorm docs (PR #768) 2026-03-17 14:51:02 -07:00
Jesse Vincent
0002948041 Update RELEASE-NOTES.md with brainstorm server ESM fix 2026-03-17 14:35:03 -07:00
sarbojitrana
3128a2c3cd fix : resolve ESM/CommonJS module confict in brainstorming server 2026-03-17 14:34:16 -07:00
jesse
f34ee479b7 fix: Windows brainstorm server lifecycle, restore execution choice
- Skip OWNER_PID monitoring on Windows/MSYS2 where the PID namespace is
  invisible to Node.js, preventing server self-termination after 60s (#770)
- Document run_in_background: true for Claude Code on Windows (#767)
- Restore user choice between subagent-driven and inline execution after
  plan writing; subagent-driven is recommended but no longer mandatory
- Add Windows lifecycle test script verified on Windows 11 VM
- Note #723 (stop-server.sh reliability) as already fixed

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 04:09:36 +00:00
Jesse Vincent
3cee13e516 Add Community section with Discord link and Prime Radiant attribution 2026-03-16 20:10:15 -07:00
Jesse Vincent
1128a721ca Merge branch 'dev' 2026-03-16 17:56:02 -07:00
Jesse Vincent
d1b5f578b0 Release v5.0.4: review loop refinements, OpenCode one-line install, bug fixes 2026-03-16 17:55:49 -07:00
savvyinsight
61a64d7098 fix: verify server actually stopped in stop-server.sh 2026-03-16 17:24:01 -07:00
Jesse Vincent
825a142aa3 Revert "Merge pull request #751 from savvyinsight/fix/stop-server-verify"
This reverts commit bd537d817d, reversing
changes made to 363923f74a.
2026-03-16 17:23:54 -07:00
Jesse Vincent
bd537d817d Merge pull request #751 from savvyinsight/fix/stop-server-verify
fix: verify server actually stopped in stop-server.sh
2026-03-16 17:14:47 -07:00
Jesse Vincent
24be2e8b7c Merge pull request #749 from ynyyn/fix-codex-multi-agent-flag
fix(docs): replace deprecated `collab` flag with `multi_agent` for Codex docs
2026-03-16 17:12:03 -07:00
Jesse Vincent
a479e10050 Merge pull request #753 from obra/f/opencode-plugin
Auto-register skills from plugin, simplify OpenCode install
2026-03-16 17:08:09 -07:00
Jesse Vincent
a4c48714bc Use generic "the agent" instead of "Claude" in brainstorm server 2026-03-16 15:57:27 -07:00
Jesse Vincent
2c6a8a352d Tone down review loops: single-pass plan review, raise issue bar
- Remove chunk-based plan review in favor of single whole-plan review
- Add Calibration sections to both reviewer prompts so only serious
  issues block approval
- Reduce max review iterations from 5 to 3
- Streamline reviewer checklists (spec: 7→5, plan: 7→4 categories)
2026-03-16 15:57:23 -07:00
jesse
2b25774f31 Update changelog with Cursor hooks support (#709)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-15 21:42:15 +00:00
jesse
f4b54a1717 Auto-register skills from plugin, simplify OpenCode install to one line
The plugin's new `config` hook injects the skills directory into
OpenCode's live config singleton, so skills are discovered automatically
without symlinks or manual config edits.

Installation is now just adding one line to opencode.json:
  "plugin": ["superpowers@git+https://github.com/obra/superpowers.git"]

Rewrote docs/README.opencode.md and .opencode/INSTALL.md to reflect
the new approach, removing ~200 lines of platform-specific symlink
instructions. Added migration notes for existing users.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-15 21:29:25 +00:00
jesse
911fa1d6c5 test: add package.json for opencode npm plugin test 2026-03-15 20:08:51 +00:00
jesse
4e7c0842f8 feat: add Cursor-compatible hooks and fix platform detection
Add hooks/hooks-cursor.json with Cursor's camelCase format (sessionStart,
version: 1) and update .cursor-plugin/plugin.json to reference it. Uses
${CURSOR_PLUGIN_ROOT} and run-hook.cmd for cross-platform support.

Fix session-start platform detection: check CURSOR_PLUGIN_ROOT first
(Cursor may also set CLAUDE_PLUGIN_ROOT), ensuring correct output format
for each platform.

Based on PR #709 with fixes for: wrong filename (.sh extension), missing
Windows support, fragile relative paths, and incorrect platform detection.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-15 19:35:18 +00:00
jesse
689f27c968 Update changelog: add bash 5.3+ fix, link all issues/PRs
Add #572/#571 entry, add "already fixed" section for #630/#529/#539,
and convert all issue/PR references to markdown links.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-15 19:14:54 +00:00
jesse
537ec640fd fix(hooks): replace heredoc with printf to fix bash 5.3+ hang
Bash 5.3 has a regression where heredoc variable expansion blocks when
content exceeds ~512 bytes. The session_context variable is ~4,500 bytes,
causing the SessionStart hook to hang indefinitely on macOS with Homebrew
bash 5.3+. Replace cat <<EOF with printf.

Tested on Linux (bash 5.2) and Windows (Git Bash 5.2). The hang only
affects 5.3+ but printf works correctly on all versions.

Based on #572, closes #572. Fixes #571.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-15 19:14:34 +00:00
jesse
c5e9538311 Update changelog with POSIX hook fix (#553)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-15 18:40:54 +00:00
jesse
fd318b1b79 fix(hooks): replace BASH_SOURCE with POSIX-safe $0
Replace ${BASH_SOURCE[0]:-$0} with $0 in hooks/session-start and the
polyglot-hooks docs example. BASH_SOURCE uses bash array syntax that
causes 'Bad substitution' on systems where /bin/sh is dash (Ubuntu).

Since session-start is always executed (never sourced), $0 and
BASH_SOURCE give the same result. Tested on Linux (bash + dash) and
Windows (Git Bash via CMD and direct).

Based on #553, closes #553.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-15 18:40:38 +00:00
jesse
ea472dedf0 Update changelog with portable shebang fix (#700)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-15 18:38:16 +00:00
jesse
addfe8511a fix: use portable shebang #!/usr/bin/env bash in all shell scripts
Replace #!/bin/bash with #!/usr/bin/env bash in 13 scripts. The
hardcoded path fails on NixOS, FreeBSD, and macOS with Homebrew bash.
#!/usr/bin/env bash is the portable POSIX-friendly alternative.

Tested on Linux and Windows (Git Bash + CMD). macOS is the primary
beneficiary since Homebrew installs bash to /opt/homebrew/bin/bash.

Based on #700, closes #700.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-15 18:38:04 +00:00
jesse
c6a2b1b576 fix: auto-foreground brainstorm server on Windows/Git Bash
Windows/Git Bash reaps nohup background processes, causing the brainstorm
server to die silently after launch. Auto-detect Windows via OSTYPE
(msys/cygwin/mingw) and MSYSTEM env vars, switching to foreground mode
automatically. Tested on Windows 11 from CMD, PowerShell, and Git Bash —
all route through Git Bash and hit the same issue.

Based on #740, fixes #737. Also adds CHANGELOG.md documenting the fix and
a known OWNER_PID/WINPID mismatch on the main branch.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-15 18:30:35 +00:00
jesse
d19703b0a1 fix: stop firing SessionStart hook on --resume
Resumed sessions already have injected context in their conversation
history. Re-firing the hook was redundant and could cause issues.
The hook now fires only on startup, clear, and compact.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-15 18:28:55 +00:00
savvyinsight
6d21e9cc07 fix: verify server actually stopped in stop-server.sh 2026-03-16 01:23:32 +08:00
ynyyn
687a66183d Fix deprecated collab flag in Codex docs 2026-03-16 01:14:32 +08:00
Jesse Vincent
363923f74a Release v5.0.2: add release notes and bump marketplace version 2026-03-11 21:47:04 -07:00
Jesse Vincent
3188953b0c Release v5.0.2: Subagent context isolation, zero-dep brainstorm server
Subagent Context Isolation:

All delegation skills (brainstorming, parallel agents, code review,
subagent-driven development, writing plans) now explicitly instruct the
dispatching agent to construct review context from scratch — never
forward session history to subagents.

This fixes a problem observed with Codex, where subagents inherited the
full parent session context including the dispatcher's internal
reasoning, prior conversation, and user-facing tone. Reviewers that
inherited this context behaved as if they were the lead developer rather
than a reviewer — they'd reject reasonable code for not matching
unstated preferences, demand rewrites beyond scope, and treat advisory
feedback as blocking. The fix is simple: the dispatcher crafts precisely
what each subagent needs (the spec, the code, the review criteria) and
nothing else. This keeps reviewers focused on the work product, not the
thought process that produced it, and also preserves the dispatcher's
own context window for coordination.

Zero-Dependency Brainstorm Server:

The brainstorm visual companion server has been rewritten from scratch
as a single zero-dependency Node.js file. The previous implementation
vendored Express, ws, chokidar, and 714 npm packages (84,000+ lines of
third-party code) — a supply chain surface area that was
disproportionate to what the server actually does.

The new server.js (~340 lines) implements everything with Node built-ins
only: RFC 6455 WebSocket protocol, HTTP server with template wrapping,
fs.watch with debounce, and lifecycle management.

731 files changed, 1,700 insertions, 85,000 deletions. The entire
vendored node_modules/ directory is gone.

Server Lifecycle Management:

The brainstorm server now automatically shuts down when no longer
needed, preventing orphaned processes. Owner process tracking captures
the harness PID at startup and checks every 60 seconds. 30-minute idle
timeout as fallback. The visual companion guide now instructs agents to
check .server-info before each write and restart if .server-stopped
exists.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 21:41:58 -07:00
Jesse Vincent
9ccce3bf07 Add context isolation principle to all delegation skills
Subagents should never inherit the parent session's context or history.
The dispatcher constructs exactly what each subagent needs, keeping
both sides focused: the subagent on its task, the controller on
coordination.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 18:47:56 -07:00
Jesse Vincent
b484bae134 Fix owner PID tracking: resolve grandparent to get actual harness PID
$PPID inside start-server.sh is the ephemeral shell the harness spawns
to run the script — it dies immediately when the script exits, causing
the server to shut down after ~60s. Now resolves grandparent PID via
`ps -o ppid= -p $PPID` to get the actual harness process (e.g. claude).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 18:47:47 -07:00
Jesse Vincent
ec99b7c4a4 Exit server when owner process dies (harness-agnostic cleanup)
start-server.sh passes $PPID as BRAINSTORM_OWNER_PID to the server.
The server checks every 60s if the owner process is still alive
(kill -0). If it's gone, the server shuts down immediately —
deletes .server-info, writes .server-stopped, exits cleanly.
Works across all harnesses (CC, Codex, Gemini CLI) since it
tracks the shell process that launched the script, which dies
when the harness dies.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 18:39:04 -07:00
Jesse Vincent
263e3268f4 Auto-exit server after 30 minutes idle, add liveness check to skill
Server tracks activity (HTTP requests, WebSocket messages, file
changes) and exits after 30 minutes of inactivity. On exit, deletes
.server-info and writes .server-stopped with reason. Visual companion
guide now instructs agents to check .server-info before each screen
push and restart if needed. Works on all harnesses, not just CC.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 18:32:09 -07:00
Drew Ritter
85cab6eff0 (fix): declare encoding meta on viz brainstorm server pages 2026-03-11 16:22:29 -07:00
Jesse Vincent
7619570679 Remove vendored node_modules, swap to zero-dep server.js
Delete 717 files: index.js, package.json, package-lock.json, and
the entire node_modules directory (express, ws, chokidar + deps).
Update start-server.sh to use server.js. Remove gitignore exception.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 13:17:52 -07:00
Jesse Vincent
8d9b94eb8d Add HTTP server, WebSocket handling, and file watching to server.js
Complete zero-dep brainstorm server. Uses knownFiles set to
distinguish new screens from updates (macOS fs.watch reports
'rename' for both). All 56 tests pass (31 unit + 25 integration).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 13:17:14 -07:00
Jesse Vincent
7f6380dd91 Add WebSocket protocol layer for zero-dep brainstorm server
Implements RFC 6455 handshake, frame encoding/decoding for text
frames. All 31 unit tests pass.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 13:15:19 -07:00
Jesse Vincent
8d6d876424 Add implementation plan for zero-dep brainstorm server
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 13:14:42 -07:00
Jesse Vincent
9c98e01873 Add design spec and tests for zero-dep brainstorm server
Replace vendored node_modules (714 files) with a single server.js
using only Node built-ins. Spec covers WebSocket protocol, HTTP
serving, file watching, and static file serving. Tests written
before implementation (TDD).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 13:11:29 -07:00
782 changed files with 4559 additions and 85526 deletions

View File

@@ -9,7 +9,7 @@
{
"name": "superpowers",
"description": "Core skills library for Claude Code: TDD, debugging, collaboration patterns, and proven techniques",
"version": "5.0.1",
"version": "5.0.7",
"source": "./",
"author": {
"name": "Jesse Vincent",

View File

@@ -1,7 +1,7 @@
{
"name": "superpowers",
"description": "Core skills library for Claude Code: TDD, debugging, collaboration patterns, and proven techniques",
"version": "5.0.1",
"version": "5.0.7",
"author": {
"name": "Jesse Vincent",
"email": "jesse@fsck.com"
@@ -9,5 +9,12 @@
"homepage": "https://github.com/obra/superpowers",
"repository": "https://github.com/obra/superpowers",
"license": "MIT",
"keywords": ["skills", "tdd", "debugging", "collaboration", "best-practices", "workflows"]
"keywords": [
"skills",
"tdd",
"debugging",
"collaboration",
"best-practices",
"workflows"
]
}

View File

@@ -2,7 +2,7 @@
"name": "superpowers",
"displayName": "Superpowers",
"description": "Core skills library: TDD, debugging, collaboration patterns, and proven techniques",
"version": "4.3.1",
"version": "5.0.7",
"author": {
"name": "Jesse Vincent",
"email": "jesse@fsck.com"
@@ -10,9 +10,16 @@
"homepage": "https://github.com/obra/superpowers",
"repository": "https://github.com/obra/superpowers",
"license": "MIT",
"keywords": ["skills", "tdd", "debugging", "collaboration", "best-practices", "workflows"],
"keywords": [
"skills",
"tdd",
"debugging",
"collaboration",
"best-practices",
"workflows"
],
"skills": "./skills/",
"agents": "./agents/",
"commands": "./commands/",
"hooks": "./hooks/hooks.json"
"hooks": "./hooks/hooks-cursor.json"
}

52
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,52 @@
---
name: Bug Report
about: Something isn't working as expected
labels: bug
---
<!--
BEFORE FILING: Search open AND closed issues. The Windows SessionStart
hook alone has been reported 29 times. If your issue already exists,
add a comment or reaction to the existing one instead.
-->
- [ ] I searched existing issues and this is not a duplicate
## Environment
| Field | Value |
|-------|-------|
| Superpowers version | |
| Harness (Claude Code, Cursor, etc.) | |
| Harness version | |
| Model | |
| OS + shell | |
## Is this a Superpowers issue or a platform issue?
<!-- Superpowers is a plugin. Some reported "bugs" are actually issues
in the underlying platform or model. If you're not sure, try
reproducing without Superpowers installed.
If the problem persists without Superpowers, file the issue with
your platform instead. -->
- [ ] I confirmed this issue does not occur without Superpowers installed
## What happened?
<!-- Be specific. "It doesn't work" is not a bug report. -->
## Steps to reproduce
1.
2.
3.
## Expected behavior
<!-- What should have happened? -->
## Actual behavior
<!-- What happened instead? -->
## Debug log or conversation transcript
<!-- A debug log or conversation transcript showing the issue is the
single most helpful thing you can include. Without one, we're
guessing. Screenshots of error output are also useful. -->

5
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: Questions & Help
url: https://discord.gg/35wsABTejz
about: For usage questions, troubleshooting help, and general discussion, please visit our Discord instead of opening an issue.

View File

@@ -0,0 +1,34 @@
---
name: Feature Request
about: Propose a change or addition to Superpowers
labels: enhancement
---
<!--
BEFORE FILING: Search open AND closed issues. Many features have been
requested before — some were implemented differently, some are in
progress, and some were intentionally declined.
-->
- [ ] I searched existing issues and this has not been proposed before
## What problem does this solve?
<!-- Describe the problem from your own experience. What were you doing,
what went wrong or was missing, and why did it matter?
"It would be cool if..." is not a problem statement. -->
## Proposed solution
<!-- What specifically do you want to happen? Be concrete. -->
## What alternatives did you consider?
<!-- What other approaches could solve the same problem? Why is your
proposal better? -->
## Is this appropriate for core Superpowers?
<!-- Would this benefit someone working on a completely different kind
of project? If this is specific to your domain, workflow, or a
third-party tool, it may belong as its own plugin instead. -->
## Context
<!-- Optional: version info, harness, model, workflow where you hit this. -->

View File

@@ -0,0 +1,23 @@
---
name: IDE / Platform Support Request
about: Request support for a new IDE, editor, or AI coding tool
labels: platform-support
---
<!--
BEFORE FILING: Search existing issues — your IDE may already be
requested or discussed.
-->
- [ ] I searched existing issues for this IDE/platform
## Which IDE or platform?
<!-- Name and link -->
## Does this tool have a plugin or extension system?
<!-- If yes, link to the docs. If no, explain how third-party
integrations typically work with this tool. -->
## Have you tried manual installation?
<!-- Many tools work with Superpowers through manual setup even without
official support. Did you try? What happened? -->

87
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@@ -0,0 +1,87 @@
<!--
BEFORE SUBMITTING: Read every word of this template. PRs that leave
sections blank, contain multiple unrelated changes, or show no evidence
of human involvement will be closed without review.
-->
## What problem are you trying to solve?
<!-- Describe the specific problem you encountered. If this was a session
issue, include: what you were doing, what went wrong, the model's
exact failure mode, and ideally a transcript or session log.
"Improving" something is not a problem statement. What broke? What
failed? What was the user experience that motivated this? -->
## What does this PR change?
<!-- 1-3 sentences. What, not why — the "why" belongs above. -->
## Is this change appropriate for the core library?
<!-- Superpowers core contains general-purpose skills and infrastructure
that benefit all users. Ask yourself:
- Would this be useful to someone working on a completely different
kind of project than yours?
- Is this project-specific, team-specific, or tool-specific?
- Does this integrate or promote a third-party service?
If your change is a new skill for a specific domain, workflow tool,
or third-party integration, it belongs in its own plugin — not here.
See the plugin development docs for how to publish it separately. -->
## What alternatives did you consider?
<!-- What other approaches did you try or evaluate before landing on this
one? Why were they worse? If you didn't consider alternatives, say so
— but know that's a red flag. -->
## Does this PR contain multiple unrelated changes?
<!-- If yes: stop. Split it into separate PRs. Bundled PRs will be closed.
If you believe the changes are related, explain the dependency. -->
## Existing PRs
- [ ] I have reviewed all open AND closed PRs for duplicates or prior art
- Related PRs: <!-- #number, #number, or "none found" -->
<!-- If a related closed PR exists, explain what's different about your
approach and why it should succeed where the other didn't. -->
## Environment tested
| Harness (e.g. Claude Code, Cursor) | Harness version | Model | Model version/ID |
|-------------------------------------|-----------------|-------|------------------|
| | | | |
## Evaluation
- What was the initial prompt you (or your human partner) used to start
the session that led to this change?
- How many eval sessions did you run AFTER making the change?
- How did outcomes change compared to before the change?
<!-- "It works" is not evaluation. Describe the before/after difference
you observed across multiple sessions. -->
## Rigor
- [ ] If this is a skills change: I used `superpowers:writing-skills` and
completed adversarial pressure testing (paste results below)
- [ ] This change was tested adversarially, not just on the happy path
- [ ] I did not modify carefully-tuned content (Red Flags table,
rationalizations, "human partner" language) without extensive evals
showing the change is an improvement
<!-- If you changed wording in skills that shape agent behavior, show your
eval methodology and results. These are not prose — they are code. -->
## Human review
- [ ] A human has reviewed the COMPLETE proposed diff before submission
<!--
STOP. If the checkbox above is not checked, do not submit this PR.
PRs will be closed without review if they:
- Show no evidence of human involvement
- Contain multiple unrelated changes
- Promote or integrate third-party services or tools
- Submit project-specific or personal configuration as core changes
- Leave required sections blank or use placeholder text
- Modify behavior-shaping content without eval evidence
-->

1
.gitignore vendored
View File

@@ -3,6 +3,5 @@
.claude/
.DS_Store
node_modules/
!skills/brainstorming/scripts/node_modules/
inspo
triage/

View File

@@ -3,107 +3,71 @@
## Prerequisites
- [OpenCode.ai](https://opencode.ai) installed
- Git installed
## Installation Steps
## Installation
### 1. Clone Superpowers
Add superpowers to the `plugin` array in your `opencode.json` (global or project-level):
```bash
git clone https://github.com/obra/superpowers.git ~/.config/opencode/superpowers
```json
{
"plugin": ["superpowers@git+https://github.com/obra/superpowers.git"]
}
```
### 2. Register the Plugin
Restart OpenCode. That's it — the plugin auto-installs and registers all skills.
Create a symlink so OpenCode discovers the plugin:
Verify by asking: "Tell me about your superpowers"
## Migrating from the old symlink-based install
If you previously installed superpowers using `git clone` and symlinks, remove the old setup:
```bash
mkdir -p ~/.config/opencode/plugins
# Remove old symlinks
rm -f ~/.config/opencode/plugins/superpowers.js
ln -s ~/.config/opencode/superpowers/.opencode/plugins/superpowers.js ~/.config/opencode/plugins/superpowers.js
```
### 3. Symlink Skills
Create a symlink so OpenCode's native skill tool discovers superpowers skills:
```bash
mkdir -p ~/.config/opencode/skills
rm -rf ~/.config/opencode/skills/superpowers
ln -s ~/.config/opencode/superpowers/skills ~/.config/opencode/skills/superpowers
# Optionally remove the cloned repo
rm -rf ~/.config/opencode/superpowers
# Remove skills.paths from opencode.json if you added one for superpowers
```
### 4. Restart OpenCode
Restart OpenCode. The plugin will automatically inject superpowers context.
Verify by asking: "do you have superpowers?"
Then follow the installation steps above.
## Usage
### Finding Skills
Use OpenCode's native `skill` tool to list available skills:
Use OpenCode's native `skill` tool:
```
use skill tool to list skills
```
### Loading a Skill
Use OpenCode's native `skill` tool to load a specific skill:
```
use skill tool to load superpowers/brainstorming
```
### Personal Skills
Create your own skills in `~/.config/opencode/skills/`:
```bash
mkdir -p ~/.config/opencode/skills/my-skill
```
Create `~/.config/opencode/skills/my-skill/SKILL.md`:
```markdown
---
name: my-skill
description: Use when [condition] - [what it does]
---
# My Skill
[Your skill content here]
```
### Project Skills
Create project-specific skills in `.opencode/skills/` within your project.
**Skill Priority:** Project skills > Personal skills > Superpowers skills
## Updating
```bash
cd ~/.config/opencode/superpowers
git pull
Superpowers updates automatically when you restart OpenCode.
To pin a specific version:
```json
{
"plugin": ["superpowers@git+https://github.com/obra/superpowers.git#v5.0.3"]
}
```
## Troubleshooting
### Plugin not loading
1. Check plugin symlink: `ls -l ~/.config/opencode/plugins/superpowers.js`
2. Check source exists: `ls ~/.config/opencode/superpowers/.opencode/plugins/superpowers.js`
3. Check OpenCode logs for errors
1. Check logs: `opencode run --print-logs "hello" 2>&1 | grep -i superpowers`
2. Verify the plugin line in your `opencode.json`
3. Make sure you're running a recent version of OpenCode
### Skills not found
1. Check skills symlink: `ls -l ~/.config/opencode/skills/superpowers`
2. Verify it points to: `~/.config/opencode/superpowers/skills`
3. Use `skill` tool to list what's discovered
1. Use `skill` tool to list what's discovered
2. Check that the plugin is loading (see above)
### Tool mapping

View File

@@ -2,7 +2,7 @@
* Superpowers plugin for OpenCode.ai
*
* Injects superpowers bootstrap context via system prompt transform.
* Skills are discovered via OpenCode's native skill tool from symlinked directory.
* Auto-registers skills directory via config hook (no symlinks needed).
*/
import path from 'path';
@@ -68,8 +68,6 @@ When skills reference tools you don't have, substitute OpenCode equivalents:
- \`Skill\` tool → OpenCode's native \`skill\` tool
- \`Read\`, \`Write\`, \`Edit\`, \`Bash\` → Your native tools
**Skills location:**
Superpowers skills are in \`${configDir}/skills/superpowers/\`
Use OpenCode's native \`skill\` tool to list and load skills.`;
return `<EXTREMELY_IMPORTANT>
@@ -84,12 +82,31 @@ ${toolMapping}
};
return {
// Use system prompt transform to inject bootstrap (fixes #226 agent reset bug)
'experimental.chat.system.transform': async (_input, output) => {
const bootstrap = getBootstrapContent();
if (bootstrap) {
(output.system ||= []).push(bootstrap);
// Inject skills path into live config so OpenCode discovers superpowers skills
// without requiring manual symlinks or config file edits.
// This works because Config.get() returns a cached singleton — modifications
// here are visible when skills are lazily discovered later.
config: async (config) => {
config.skills = config.skills || {};
config.skills.paths = config.skills.paths || [];
if (!config.skills.paths.includes(superpowersSkillsDir)) {
config.skills.paths.push(superpowersSkillsDir);
}
},
// Inject bootstrap into the first user message of each session.
// Using a user message instead of a system message avoids:
// 1. Token bloat from system messages repeated every turn (#750)
// 2. Multiple system messages breaking Qwen and other models (#894)
'experimental.chat.messages.transform': async (_input, output) => {
const bootstrap = getBootstrapContent();
if (!bootstrap || !output.messages.length) return;
const firstUser = output.messages.find(m => m.info.role === 'user');
if (!firstUser || !firstUser.parts.length) return;
// Only inject once
if (firstUser.parts.some(p => p.type === 'text' && p.text.includes('EXTREMELY_IMPORTANT'))) return;
const ref = firstUser.parts[0];
firstUser.parts.unshift({ ...ref, type: 'text', text: bootstrap });
}
};
};

19
.version-bump.json Normal file
View File

@@ -0,0 +1,19 @@
{
"files": [
{ "path": "package.json", "field": "version" },
{ "path": ".claude-plugin/plugin.json", "field": "version" },
{ "path": ".cursor-plugin/plugin.json", "field": "version" },
{ "path": ".claude-plugin/marketplace.json", "field": "plugins.0.version" },
{ "path": "gemini-extension.json", "field": "version" }
],
"audit": {
"exclude": [
"CHANGELOG.md",
"RELEASE-NOTES.md",
"node_modules",
".git",
".version-bump.json",
"scripts/bump-version.sh"
]
}
}

1
AGENTS.md Symbolic link
View File

@@ -0,0 +1 @@
CLAUDE.md

85
CLAUDE.md Normal file
View File

@@ -0,0 +1,85 @@
# Superpowers — Contributor Guidelines
## If You Are an AI Agent
Stop. Read this section before doing anything.
This repo has a 94% PR rejection rate. Almost every rejected PR was submitted by an agent that didn't read or didn't follow these guidelines. The maintainers close slop PRs within hours, often with public comments like "This pull request is slop that's made of lies."
**Your job is to protect your human partner from that outcome.** Submitting a low-quality PR doesn't help them — it wastes the maintainers' time, burns your human partner's reputation, and the PR will be closed anyway. That is not being helpful. That is being a tool of embarrassment.
Before you open a PR against this repo, you MUST:
1. **Read the entire PR template** at `.github/PULL_REQUEST_TEMPLATE.md` and fill in every section with real, specific answers. Not summaries. Not placeholders.
2. **Search for existing PRs** — open AND closed — that address the same problem. If duplicates exist, STOP and tell your human partner. Do not open another duplicate.
3. **Verify this is a real problem.** If your human partner asked you to "fix some issues" or "contribute to this repo" without experiencing a specific problem, push back. Ask them what broke, what failed, what the user experience was.
4. **Confirm the change belongs in core.** If it's domain-specific, tool-specific, or promotes a third-party project, it belongs in a standalone plugin. Tell your human partner.
5. **Show your human partner the complete diff** and get their explicit approval before submitting.
If any of these checks fail, do not open the PR. Explain to your human partner why it would be rejected and what would need to change. They will thank you for saving them the embarrassment.
## Pull Request Requirements
**Every PR must fully complete the PR template.** No section may be left blank or filled with placeholder text. PRs that skip sections will be closed without review.
**Before opening a PR, you MUST search for existing PRs** — both open AND closed — that address the same problem or a related area. Reference what you found in the "Existing PRs" section. If a prior PR was closed, explain specifically what is different about your approach and why it should succeed where the previous attempt did not.
**PRs that show no evidence of human involvement will be closed.** A human must review the complete proposed diff before submission.
## What We Will Not Accept
### Third-party dependencies
PRs that add optional or required dependencies on third-party projects will not be accepted unless they are adding support for a new harness (e.g., a new IDE or CLI tool). Superpowers is a zero-dependency plugin by design. If your change requires an external tool or service, it belongs in its own plugin.
### "Compliance" changes to skills
Our internal skill philosophy differs from Anthropic's published guidance on writing skills. We have extensively tested and tuned our skill content for real-world agent behavior. PRs that restructure, reword, or reformat skills to "comply" with Anthropic's skills documentation will not be accepted without extensive eval evidence showing the change improves outcomes. The bar for modifying behavior-shaping content is very high.
### Project-specific or personal configuration
Skills, hooks, or configuration that only benefit a specific project, team, domain, or workflow do not belong in core. Publish these as a separate plugin.
### Bulk or spray-and-pray PRs
Do not trawl the issue tracker and open PRs for multiple issues in a single session. Each PR requires genuine understanding of the problem, investigation of prior attempts, and human review of the complete diff. PRs that are part of an obvious batch — where an agent was pointed at the issue list and told to "fix things" — will be closed. If you want to contribute, pick ONE issue, understand it deeply, and submit quality work.
### Speculative or theoretical fixes
Every PR must solve a real problem that someone actually experienced. "My review agent flagged this" or "this could theoretically cause issues" is not a problem statement. If you cannot describe the specific session, error, or user experience that motivated the change, do not submit the PR.
### Domain-specific skills
Superpowers core contains general-purpose skills that benefit all users regardless of their project. Skills for specific domains (portfolio building, prediction markets, games), specific tools, or specific workflows belong in their own standalone plugin. Ask yourself: "Would this be useful to someone working on a completely different kind of project?" If not, publish it separately.
### Fork-specific changes
If you maintain a fork with customizations, do not open PRs to sync your fork or push fork-specific changes upstream. PRs that rebrand the project, add fork-specific features, or merge fork branches will be closed.
### Fabricated content
PRs containing invented claims, fabricated problem descriptions, or hallucinated functionality will be closed immediately. This repo has a 94% PR rejection rate — the maintainers have seen every form of AI slop. They will notice.
### Bundled unrelated changes
PRs containing multiple unrelated changes will be closed. Split them into separate PRs.
## Skill Changes Require Evaluation
Skills are not prose — they are code that shapes agent behavior. If you modify skill content:
- Use `superpowers:writing-skills` to develop and test changes
- Run adversarial pressure testing across multiple sessions
- Show before/after eval results in your PR
- Do not modify carefully-tuned content (Red Flags tables, rationalization lists, "human partner" language) without evidence the change is an improvement
## Understand the Project Before Contributing
Before proposing changes to skill design, workflow philosophy, or architecture, read existing skills and understand the project's design decisions. Superpowers has its own tested philosophy about skill design, agent behavior shaping, and terminology (e.g., "your human partner" is deliberate, not interchangeable with "the user"). Changes that rewrite the project's voice or restructure its approach without understanding why it exists will be rejected.
## General
- Read `.github/PULL_REQUEST_TEMPLATE.md` before submitting
- One problem per PR
- Test on at least one harness and report results in the environment table
- Describe the problem you solved, not just what you changed

128
CODE_OF_CONDUCT.md Normal file
View File

@@ -0,0 +1,128 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
jesse@primeradiant.com.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.

View File

@@ -82,6 +82,13 @@ Fetch and follow instructions from https://raw.githubusercontent.com/obra/superp
**Detailed docs:** [docs/README.opencode.md](docs/README.opencode.md)
### GitHub Copilot CLI
```bash
copilot plugin marketplace add obra/superpowers-marketplace
copilot plugin install superpowers@superpowers-marketplace
```
### Gemini CLI
```bash
@@ -174,7 +181,10 @@ Skills update automatically when you update the plugin:
MIT License - see LICENSE file for details
## Support
## Community
Superpowers is built by [Jesse Vincent](https://blog.fsck.com) and the rest of the folks at [Prime Radiant](https://primeradiant.com).
- **Discord**: [Join us](https://discord.gg/35wsABTejz) for community support, questions, and sharing what you're building with Superpowers
- **Issues**: https://github.com/obra/superpowers/issues
- **Marketplace**: https://github.com/obra/superpowers-marketplace
- **Release announcements**: [Sign up](https://primeradiant.com/superpowers/) to get notified about new versions

View File

@@ -1,5 +1,116 @@
# Superpowers Release Notes
## v5.0.7 (2026-03-31)
### GitHub Copilot CLI Support
- **SessionStart context injection** — Copilot CLI v1.0.11 added support for `additionalContext` in sessionStart hook output. The session-start hook now detects the `COPILOT_CLI` environment variable and emits the SDK-standard `{ "additionalContext": "..." }` format, giving Copilot CLI users the full superpowers bootstrap at session start. (Original fix by @culinablaz in PR #910)
- **Tool mapping** — added `references/copilot-tools.md` with the full Claude Code to Copilot CLI tool equivalence table
- **Skill and README updates** — added Copilot CLI to the `using-superpowers` skill's platform instructions and README installation section
### OpenCode Fixes
- **Skills path consistency** — the bootstrap text no longer advertises a misleading `configDir/skills/superpowers/` path that didn't match the runtime path. The agent should use the native `skill` tool, not navigate to files by path. Tests now use consistent paths derived from a single source of truth. (#847, #916)
- **Bootstrap as user message** — moved bootstrap injection from `experimental.chat.system.transform` to `experimental.chat.messages.transform`, prepending to the first user message instead of adding a system message. Avoids token bloat from system messages repeated every turn (#750) and fixes compatibility with Qwen and other models that break on multiple system messages (#894).
## v5.0.6 (2026-03-24)
### Inline Self-Review Replaces Subagent Review Loops
The subagent review loop (dispatching a fresh agent to review plans/specs) doubled execution time (~25 min overhead) without measurably improving plan quality. Regression testing across 5 versions with 5 trials each showed identical quality scores regardless of whether the review loop ran.
- **brainstorming** — replaced Spec Review Loop (subagent dispatch + 3-iteration cap) with inline Spec Self-Review checklist: placeholder scan, internal consistency, scope check, ambiguity check
- **writing-plans** — replaced Plan Review Loop (subagent dispatch + 3-iteration cap) with inline Self-Review checklist: spec coverage, placeholder scan, type consistency
- **writing-plans** — added explicit "No Placeholders" section defining plan failures (TBD, vague descriptions, undefined references, "similar to Task N")
- Self-review catches 3-5 real bugs per run in ~30s instead of ~25 min, with comparable defect rates to the subagent approach
### Brainstorm Server
- **Session directory restructured** — the brainstorm server session directory now contains two peer subdirectories: `content/` (HTML files served to the browser) and `state/` (events, server-info, pid, log). Previously, server state and user interaction data were stored alongside served content, making them accessible over HTTP. The `screen_dir` and `state_dir` paths are both included in the server-started JSON. (Reported by 吉田仁)
### Bug Fixes
- **Owner-PID lifecycle fixes** — the brainstorm server's owner-PID monitoring had two bugs causing false shutdowns within 60 seconds: (1) EPERM from cross-user PIDs (Tailscale SSH, etc.) was treated as "process dead", and (2) on WSL the grandparent PID resolves to a short-lived subprocess that exits before the first lifecycle check. Fixed by treating EPERM as "alive" and validating the owner PID at startup — if it's already dead, monitoring is disabled and the server relies on the 30-minute idle timeout. This also removes the Windows/MSYS2-specific carve-out from `start-server.sh` since the server now handles it generically. (#879)
- **writing-skills** — corrected false claim that SKILL.md frontmatter supports "only two fields"; now says "two required fields" and links to the agentskills.io specification for all supported fields (PR #882 by @arittr)
### Codex App Compatibility
- **codex-tools** — added named agent dispatch mapping documenting how to translate Claude Code's named agent types to Codex's `spawn_agent` with worker roles (PR #647 by @arittr)
- **codex-tools** — added environment detection and Codex App finishing sections for worktree-aware skills (by @arittr)
- **Design spec** — added Codex App compatibility design spec (PRI-823) covering read-only environment detection, worktree-safe skill behavior, and sandbox fallback patterns (by @arittr)
## v5.0.5 (2026-03-17)
### Bug Fixes
- **Brainstorm server ESM fix** — renamed `server.js``server.cjs` so the brainstorming server starts correctly on Node.js 22+ where the root `package.json` `"type": "module"` caused `require()` to fail. (PR #784 by @sarbojitrana, fixes #774, #780, #783)
- **Brainstorm owner-PID on Windows** — skip PID lifecycle monitoring on Windows/MSYS2 where the PID namespace is invisible to Node.js, preventing the server from self-terminating after 60 seconds. (#770, docs from PR #768 by @lucasyhzlu-debug)
- **stop-server.sh reliability** — verify the server process actually died before reporting success. SIGTERM + 2s wait + SIGKILL fallback. (#723)
### Changed
- **Execution handoff** — restore user choice between subagent-driven and inline execution after plan writing. Subagent-driven is recommended but no longer mandatory.
## v5.0.4 (2026-03-16)
### Review Loop Refinements
Dramatically reduces token usage and speeds up spec and plan reviews by eliminating unnecessary review passes and tightening reviewer focus.
- **Single whole-plan review** — plan reviewer now reviews the complete plan in one pass instead of chunk-by-chunk. Removed all chunk-related concepts (`## Chunk N:` headings, 1000-line chunk limits, per-chunk dispatch).
- **Raised the bar for blocking issues** — both spec and plan reviewer prompts now include a "Calibration" section: only flag issues that would cause real problems during implementation. Minor wording, stylistic preferences, and formatting quibbles should not block approval.
- **Reduced max review iterations** — from 5 to 3 for both spec and plan review loops. If the reviewer is calibrated correctly, 3 rounds is plenty.
- **Streamlined reviewer checklists** — spec reviewer trimmed from 7 categories to 5; plan reviewer from 7 to 4. Removed formatting-focused checks (task syntax, chunk size) in favor of substance (buildability, spec alignment).
### OpenCode
- **One-line plugin install** — OpenCode plugin now auto-registers the skills directory via a `config` hook. No symlinks or `skills.paths` config needed. Install is just adding one line to `opencode.json`. (PR #753)
- **Added `package.json`** so OpenCode can install superpowers as an npm package from git.
### Bug Fixes
- **Verify server actually stopped** — `stop-server.sh` now confirms the process is dead before reporting success. SIGTERM + 2s wait + SIGKILL fallback. Reports failure if the process survives. (PR #751)
- **Generic agent language** — brainstorm companion waiting page now says "the agent" instead of "Claude".
## v5.0.3 (2026-03-15)
### Cursor Support
- **Cursor hooks** — added `hooks/hooks-cursor.json` with Cursor's camelCase format (`sessionStart`, `version: 1`) and updated `.cursor-plugin/plugin.json` to reference it. Fixed platform detection in `session-start` to check `CURSOR_PLUGIN_ROOT` first (Cursor may also set `CLAUDE_PLUGIN_ROOT`). (Based on PR #709)
### Bug Fixes
- **Stop firing SessionStart hook on `--resume`** — the startup hook was re-injecting context on resumed sessions, which already have the context in their conversation history. The hook now fires only on `startup`, `clear`, and `compact`.
- **Bash 5.3+ hook hang** — replaced heredoc (`cat <<EOF`) with `printf` in `hooks/session-start`. Fixes indefinite hang on macOS with Homebrew bash 5.3+ caused by a bash regression with large variable expansion in heredocs. (#572, #571)
- **POSIX-safe hook script** — replaced `${BASH_SOURCE[0]:-$0}` with `$0` in `hooks/session-start`. Fixes "Bad substitution" error on Ubuntu/Debian where `/bin/sh` is dash. (#553)
- **Portable shebangs** — replaced `#!/bin/bash` with `#!/usr/bin/env bash` in all shell scripts. Fixes execution on NixOS, FreeBSD, and macOS with Homebrew bash where `/bin/bash` is outdated or missing. (#700)
- **Brainstorm server on Windows** — auto-detect Windows/Git Bash (`OSTYPE=msys*`, `MSYSTEM`) and switch to foreground mode, fixing silent server failure caused by `nohup`/`disown` process reaping. (#737)
- **Codex docs fix** — replaced deprecated `collab` flag with `multi_agent` in Codex documentation. (PR #749)
## v5.0.2 (2026-03-11)
### Zero-Dependency Brainstorm Server
**Removed all vendored node_modules — server.js is now fully self-contained**
- Replaced Express/Chokidar/WebSocket dependencies with zero-dependency Node.js server using built-in `http`, `fs`, and `crypto` modules
- Removed ~1,200 lines of vendored `node_modules/`, `package.json`, and `package-lock.json`
- Custom WebSocket protocol implementation (RFC 6455 framing, ping/pong, proper close handshake)
- Native `fs.watch()` file watching replaces Chokidar
- Full test suite: HTTP serving, WebSocket protocol, file watching, and integration tests
### Brainstorm Server Reliability
- **Auto-exit after 30 minutes idle** — server shuts down when no clients are connected, preventing orphaned processes
- **Owner process tracking** — server monitors the parent harness PID and exits when the owning session dies
- **Liveness check** — skill verifies server is responsive before reusing an existing instance
- **Encoding fix** — proper `<meta charset="utf-8">` on served HTML pages
### Subagent Context Isolation
- All delegation skills (brainstorming, dispatching-parallel-agents, requesting-code-review, subagent-driven-development, writing-plans) now include context isolation principle
- Subagents receive only the context they need, preventing context window pollution
## v5.0.1 (2026-03-10)
### Agentskills Compliance

View File

@@ -32,10 +32,10 @@ Fetch and follow instructions from https://raw.githubusercontent.com/obra/superp
3. Restart Codex.
4. **For subagent skills** (optional): Skills like `dispatching-parallel-agents` and `subagent-driven-development` require Codex's collab feature. Add to your Codex config:
4. **For subagent skills** (optional): Skills like `dispatching-parallel-agents` and `subagent-driven-development` require Codex's multi-agent feature. Add to your Codex config:
```toml
[features]
collab = true
multi_agent = true
```
### Windows

View File

@@ -2,169 +2,36 @@
Complete guide for using Superpowers with [OpenCode.ai](https://opencode.ai).
## Quick Install
## Installation
Tell OpenCode:
Add superpowers to the `plugin` array in your `opencode.json` (global or project-level):
```
Clone https://github.com/obra/superpowers to ~/.config/opencode/superpowers, then create directory ~/.config/opencode/plugins, then symlink ~/.config/opencode/superpowers/.opencode/plugins/superpowers.js to ~/.config/opencode/plugins/superpowers.js, then symlink ~/.config/opencode/superpowers/skills to ~/.config/opencode/skills/superpowers, then restart opencode.
```json
{
"plugin": ["superpowers@git+https://github.com/obra/superpowers.git"]
}
```
## Manual Installation
Restart OpenCode. The plugin auto-installs via Bun and registers all skills automatically.
### Prerequisites
Verify by asking: "Tell me about your superpowers"
- [OpenCode.ai](https://opencode.ai) installed
- Git installed
### Migrating from the old symlink-based install
### macOS / Linux
If you previously installed superpowers using `git clone` and symlinks, remove the old setup:
```bash
# 1. Install Superpowers (or update existing)
if [ -d ~/.config/opencode/superpowers ]; then
cd ~/.config/opencode/superpowers && git pull
else
git clone https://github.com/obra/superpowers.git ~/.config/opencode/superpowers
fi
# 2. Create directories
mkdir -p ~/.config/opencode/plugins ~/.config/opencode/skills
# 3. Remove old symlinks/directories if they exist
# Remove old symlinks
rm -f ~/.config/opencode/plugins/superpowers.js
rm -rf ~/.config/opencode/skills/superpowers
# 4. Create symlinks
ln -s ~/.config/opencode/superpowers/.opencode/plugins/superpowers.js ~/.config/opencode/plugins/superpowers.js
ln -s ~/.config/opencode/superpowers/skills ~/.config/opencode/skills/superpowers
# Optionally remove the cloned repo
rm -rf ~/.config/opencode/superpowers
# 5. Restart OpenCode
# Remove skills.paths from opencode.json if you added one for superpowers
```
#### Verify Installation
```bash
ls -l ~/.config/opencode/plugins/superpowers.js
ls -l ~/.config/opencode/skills/superpowers
```
Both should show symlinks pointing to the superpowers directory.
### Windows
**Prerequisites:**
- Git installed
- Either **Developer Mode** enabled OR **Administrator privileges**
- Windows 10: Settings → Update & Security → For developers
- Windows 11: Settings → System → For developers
Pick your shell below: [Command Prompt](#command-prompt) | [PowerShell](#powershell) | [Git Bash](#git-bash)
#### Command Prompt
Run as Administrator, or with Developer Mode enabled:
```cmd
:: 1. Install Superpowers
git clone https://github.com/obra/superpowers.git "%USERPROFILE%\.config\opencode\superpowers"
:: 2. Create directories
mkdir "%USERPROFILE%\.config\opencode\plugins" 2>nul
mkdir "%USERPROFILE%\.config\opencode\skills" 2>nul
:: 3. Remove existing links (safe for reinstalls)
del "%USERPROFILE%\.config\opencode\plugins\superpowers.js" 2>nul
rmdir "%USERPROFILE%\.config\opencode\skills\superpowers" 2>nul
:: 4. Create plugin symlink (requires Developer Mode or Admin)
mklink "%USERPROFILE%\.config\opencode\plugins\superpowers.js" "%USERPROFILE%\.config\opencode\superpowers\.opencode\plugins\superpowers.js"
:: 5. Create skills junction (works without special privileges)
mklink /J "%USERPROFILE%\.config\opencode\skills\superpowers" "%USERPROFILE%\.config\opencode\superpowers\skills"
:: 6. Restart OpenCode
```
#### PowerShell
Run as Administrator, or with Developer Mode enabled:
```powershell
# 1. Install Superpowers
git clone https://github.com/obra/superpowers.git "$env:USERPROFILE\.config\opencode\superpowers"
# 2. Create directories
New-Item -ItemType Directory -Force -Path "$env:USERPROFILE\.config\opencode\plugins"
New-Item -ItemType Directory -Force -Path "$env:USERPROFILE\.config\opencode\skills"
# 3. Remove existing links (safe for reinstalls)
Remove-Item "$env:USERPROFILE\.config\opencode\plugins\superpowers.js" -Force -ErrorAction SilentlyContinue
Remove-Item "$env:USERPROFILE\.config\opencode\skills\superpowers" -Force -ErrorAction SilentlyContinue
# 4. Create plugin symlink (requires Developer Mode or Admin)
New-Item -ItemType SymbolicLink -Path "$env:USERPROFILE\.config\opencode\plugins\superpowers.js" -Target "$env:USERPROFILE\.config\opencode\superpowers\.opencode\plugins\superpowers.js"
# 5. Create skills junction (works without special privileges)
New-Item -ItemType Junction -Path "$env:USERPROFILE\.config\opencode\skills\superpowers" -Target "$env:USERPROFILE\.config\opencode\superpowers\skills"
# 6. Restart OpenCode
```
#### Git Bash
Note: Git Bash's native `ln` command copies files instead of creating symlinks. Use `cmd //c mklink` instead (the `//c` is Git Bash syntax for `/c`).
```bash
# 1. Install Superpowers
git clone https://github.com/obra/superpowers.git ~/.config/opencode/superpowers
# 2. Create directories
mkdir -p ~/.config/opencode/plugins ~/.config/opencode/skills
# 3. Remove existing links (safe for reinstalls)
rm -f ~/.config/opencode/plugins/superpowers.js 2>/dev/null
rm -rf ~/.config/opencode/skills/superpowers 2>/dev/null
# 4. Create plugin symlink (requires Developer Mode or Admin)
cmd //c "mklink \"$(cygpath -w ~/.config/opencode/plugins/superpowers.js)\" \"$(cygpath -w ~/.config/opencode/superpowers/.opencode/plugins/superpowers.js)\""
# 5. Create skills junction (works without special privileges)
cmd //c "mklink /J \"$(cygpath -w ~/.config/opencode/skills/superpowers)\" \"$(cygpath -w ~/.config/opencode/superpowers/skills)\""
# 6. Restart OpenCode
```
#### WSL Users
If running OpenCode inside WSL, use the [macOS / Linux](#macos--linux) instructions instead.
#### Verify Installation
**Command Prompt:**
```cmd
dir /AL "%USERPROFILE%\.config\opencode\plugins"
dir /AL "%USERPROFILE%\.config\opencode\skills"
```
**PowerShell:**
```powershell
Get-ChildItem "$env:USERPROFILE\.config\opencode\plugins" | Where-Object { $_.LinkType }
Get-ChildItem "$env:USERPROFILE\.config\opencode\skills" | Where-Object { $_.LinkType }
```
Look for `<SYMLINK>` or `<JUNCTION>` in the output.
#### Troubleshooting Windows
**"You do not have sufficient privilege" error:**
- Enable Developer Mode in Windows Settings, OR
- Right-click your terminal → "Run as Administrator"
**"Cannot create a file when that file already exists":**
- Run the removal commands (step 3) first, then retry
**Symlinks not working after git clone:**
- Run `git config --global core.symlinks true` and re-clone
Then follow the installation steps above.
## Usage
@@ -178,8 +45,6 @@ use skill tool to list skills
### Loading a Skill
Use OpenCode's native `skill` tool to load a specific skill:
```
use skill tool to load superpowers/brainstorming
```
@@ -207,124 +72,59 @@ description: Use when [condition] - [what it does]
### Project Skills
Create project-specific skills in your OpenCode project:
Create project-specific skills in `.opencode/skills/` within your project.
```bash
# In your OpenCode project
mkdir -p .opencode/skills/my-project-skill
**Skill Priority:** Project skills > Personal skills > Superpowers skills
## Updating
Superpowers updates automatically when you restart OpenCode. The plugin is re-installed from the git repository on each launch.
To pin a specific version, use a branch or tag:
```json
{
"plugin": ["superpowers@git+https://github.com/obra/superpowers.git#v5.0.3"]
}
```
Create `.opencode/skills/my-project-skill/SKILL.md`:
## How It Works
```markdown
---
name: my-project-skill
description: Use when [condition] - [what it does]
---
The plugin does two things:
# My Project Skill
[Your skill content here]
```
## Skill Locations
OpenCode discovers skills from these locations:
1. **Project skills** (`.opencode/skills/`) - Highest priority
2. **Personal skills** (`~/.config/opencode/skills/`)
3. **Superpowers skills** (`~/.config/opencode/skills/superpowers/`) - via symlink
## Features
### Automatic Context Injection
The plugin automatically injects superpowers context via the `experimental.chat.system.transform` hook. This adds the "using-superpowers" skill content to the system prompt on every request.
### Native Skills Integration
Superpowers uses OpenCode's native `skill` tool for skill discovery and loading. Skills are symlinked into `~/.config/opencode/skills/superpowers/` so they appear alongside your personal and project skills.
1. **Injects bootstrap context** via the `experimental.chat.system.transform` hook, adding superpowers awareness to every conversation.
2. **Registers the skills directory** via the `config` hook, so OpenCode discovers all superpowers skills without symlinks or manual config.
### Tool Mapping
Skills written for Claude Code are automatically adapted for OpenCode. The bootstrap provides mapping instructions:
Skills written for Claude Code are automatically adapted for OpenCode:
- `TodoWrite``todowrite`
- `Task` with subagents → OpenCode's `@mention` system
- `Skill` tool → OpenCode's native `skill` tool
- File operations → Native OpenCode tools
## Architecture
### Plugin Structure
**Location:** `~/.config/opencode/superpowers/.opencode/plugins/superpowers.js`
**Components:**
- `experimental.chat.system.transform` hook for bootstrap injection
- Reads and injects the "using-superpowers" skill content
### Skills
**Location:** `~/.config/opencode/skills/superpowers/` (symlink to `~/.config/opencode/superpowers/skills/`)
Skills are discovered by OpenCode's native skill system. Each skill has a `SKILL.md` file with YAML frontmatter.
## Updating
```bash
cd ~/.config/opencode/superpowers
git pull
```
Restart OpenCode to load the updates.
## Troubleshooting
### Plugin not loading
1. Check plugin exists: `ls ~/.config/opencode/superpowers/.opencode/plugins/superpowers.js`
2. Check symlink/junction: `ls -l ~/.config/opencode/plugins/` (macOS/Linux) or `dir /AL %USERPROFILE%\.config\opencode\plugins` (Windows)
3. Check OpenCode logs: `opencode run "test" --print-logs --log-level DEBUG`
4. Look for plugin loading message in logs
1. Check OpenCode logs: `opencode run --print-logs "hello" 2>&1 | grep -i superpowers`
2. Verify the plugin line in your `opencode.json` is correct
3. Make sure you're running a recent version of OpenCode
### Skills not found
1. Verify skills symlink: `ls -l ~/.config/opencode/skills/superpowers` (should point to superpowers/skills/)
2. Use OpenCode's `skill` tool to list available skills
3. Check skill structure: each skill needs a `SKILL.md` file with valid frontmatter
### Windows: Module not found error
If you see `Cannot find module` errors on Windows:
- **Cause:** Git Bash `ln -sf` copies files instead of creating symlinks
- **Fix:** Use `mklink /J` directory junctions instead (see Windows installation steps)
1. Use OpenCode's `skill` tool to list available skills
2. Check that the plugin is loading (see above)
3. Each skill needs a `SKILL.md` file with valid YAML frontmatter
### Bootstrap not appearing
1. Verify using-superpowers skill exists: `ls ~/.config/opencode/superpowers/skills/using-superpowers/SKILL.md`
2. Check OpenCode version supports `experimental.chat.system.transform` hook
3. Restart OpenCode after plugin changes
1. Check OpenCode version supports `experimental.chat.system.transform` hook
2. Restart OpenCode after config changes
## Getting Help
- Report issues: https://github.com/obra/superpowers/issues
- Main documentation: https://github.com/obra/superpowers
- OpenCode docs: https://opencode.ai/docs/
## Testing
Verify your installation:
```bash
# Check plugin loads
opencode run --print-logs "hello" 2>&1 | grep -i superpowers
# Check skills are discoverable
opencode run "use skill tool to list all skills" 2>&1 | grep -i superpowers
# Check bootstrap injection
opencode run "what superpowers do you have?"
```
The agent should mention having superpowers and be able to list skills from `superpowers/`.

View File

@@ -0,0 +1,479 @@
# Zero-Dependency Brainstorm Server Implementation Plan
> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Replace the brainstorm server's vendored node_modules with a single zero-dependency `server.js` using Node built-ins.
**Architecture:** Single file with WebSocket protocol (RFC 6455 text frames), HTTP server (`http` module), and file watching (`fs.watch`). Exports protocol functions for unit testing when required as a module.
**Tech Stack:** Node.js built-ins only: `http`, `crypto`, `fs`, `path`
**Spec:** `docs/superpowers/specs/2026-03-11-zero-dep-brainstorm-server-design.md`
**Existing tests:** `tests/brainstorm-server/ws-protocol.test.js` (unit), `tests/brainstorm-server/server.test.js` (integration)
---
## File Map
- **Create:** `skills/brainstorming/scripts/server.js` — the zero-dep replacement
- **Modify:** `skills/brainstorming/scripts/start-server.sh:94,100` — change `index.js` to `server.js`
- **Modify:** `.gitignore:6` — remove the `!skills/brainstorming/scripts/node_modules/` exception
- **Delete:** `skills/brainstorming/scripts/index.js`
- **Delete:** `skills/brainstorming/scripts/package.json`
- **Delete:** `skills/brainstorming/scripts/package-lock.json`
- **Delete:** `skills/brainstorming/scripts/node_modules/` (714 files)
- **No changes:** `skills/brainstorming/scripts/helper.js`, `skills/brainstorming/scripts/frame-template.html`, `skills/brainstorming/scripts/stop-server.sh`
---
## Chunk 1: WebSocket Protocol Layer
### Task 1: Implement WebSocket protocol exports
**Files:**
- Create: `skills/brainstorming/scripts/server.js`
- Test: `tests/brainstorm-server/ws-protocol.test.js` (already exists)
- [ ] **Step 1: Create server.js with OPCODES constant and computeAcceptKey**
```js
const crypto = require('crypto');
const OPCODES = { TEXT: 0x01, CLOSE: 0x08, PING: 0x09, PONG: 0x0A };
const WS_MAGIC = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
function computeAcceptKey(clientKey) {
return crypto.createHash('sha1').update(clientKey + WS_MAGIC).digest('base64');
}
```
- [ ] **Step 2: Implement encodeFrame**
Server frames are never masked. Three length encodings:
- payload < 126: 2-byte header (FIN+opcode, length)
- 126-65535: 4-byte header (FIN+opcode, 126, 16-bit length)
- &gt; 65535: 10-byte header (FIN+opcode, 127, 64-bit length)
```js
function encodeFrame(opcode, payload) {
const fin = 0x80;
const len = payload.length;
let header;
if (len < 126) {
header = Buffer.alloc(2);
header[0] = fin | opcode;
header[1] = len;
} else if (len < 65536) {
header = Buffer.alloc(4);
header[0] = fin | opcode;
header[1] = 126;
header.writeUInt16BE(len, 2);
} else {
header = Buffer.alloc(10);
header[0] = fin | opcode;
header[1] = 127;
header.writeBigUInt64BE(BigInt(len), 2);
}
return Buffer.concat([header, payload]);
}
```
- [ ] **Step 3: Implement decodeFrame**
Client frames are always masked. Returns `{ opcode, payload, bytesConsumed }` or `null` for incomplete. Throws on unmasked frames.
```js
function decodeFrame(buffer) {
if (buffer.length < 2) return null;
const firstByte = buffer[0];
const secondByte = buffer[1];
const opcode = firstByte & 0x0F;
const masked = (secondByte & 0x80) !== 0;
let payloadLen = secondByte & 0x7F;
let offset = 2;
if (!masked) throw new Error('Client frames must be masked');
if (payloadLen === 126) {
if (buffer.length < 4) return null;
payloadLen = buffer.readUInt16BE(2);
offset = 4;
} else if (payloadLen === 127) {
if (buffer.length < 10) return null;
payloadLen = Number(buffer.readBigUInt64BE(2));
offset = 10;
}
const maskOffset = offset;
const dataOffset = offset + 4;
const totalLen = dataOffset + payloadLen;
if (buffer.length < totalLen) return null;
const mask = buffer.slice(maskOffset, dataOffset);
const data = Buffer.alloc(payloadLen);
for (let i = 0; i < payloadLen; i++) {
data[i] = buffer[dataOffset + i] ^ mask[i % 4];
}
return { opcode, payload: data, bytesConsumed: totalLen };
}
```
- [ ] **Step 4: Add module exports at the bottom of the file**
```js
module.exports = { computeAcceptKey, encodeFrame, decodeFrame, OPCODES };
```
- [ ] **Step 5: Run unit tests**
Run: `cd tests/brainstorm-server && node ws-protocol.test.js`
Expected: All tests pass (handshake, encoding, decoding, boundaries, edge cases)
- [ ] **Step 6: Commit**
```bash
git add skills/brainstorming/scripts/server.js
git commit -m "Add WebSocket protocol layer for zero-dep brainstorm server"
```
---
## Chunk 2: HTTP Server and Application Logic
### Task 2: Add HTTP server, file watching, and WebSocket connection handling
**Files:**
- Modify: `skills/brainstorming/scripts/server.js`
- Test: `tests/brainstorm-server/server.test.js` (already exists)
- [ ] **Step 1: Add configuration and constants at top of server.js (after requires)**
```js
const http = require('http');
const fs = require('fs');
const path = require('path');
const PORT = process.env.BRAINSTORM_PORT || (49152 + Math.floor(Math.random() * 16383));
const HOST = process.env.BRAINSTORM_HOST || '127.0.0.1';
const URL_HOST = process.env.BRAINSTORM_URL_HOST || (HOST === '127.0.0.1' ? 'localhost' : HOST);
const SCREEN_DIR = process.env.BRAINSTORM_DIR || '/tmp/brainstorm';
const MIME_TYPES = {
'.html': 'text/html', '.css': 'text/css', '.js': 'application/javascript',
'.json': 'application/json', '.png': 'image/png', '.jpg': 'image/jpeg',
'.jpeg': 'image/jpeg', '.gif': 'image/gif', '.svg': 'image/svg+xml'
};
```
- [ ] **Step 2: Add WAITING_PAGE, template loading at module scope, and helper functions**
Load `frameTemplate` and `helperInjection` at module scope so they're accessible to `wrapInFrame` and `handleRequest`. They only read files from `__dirname` (the scripts directory), which is valid whether the module is required or run directly.
```js
const WAITING_PAGE = `<!DOCTYPE html>
<html>
<head><title>Brainstorm Companion</title>
<style>body { font-family: system-ui, sans-serif; padding: 2rem; max-width: 800px; margin: 0 auto; }
h1 { color: #333; } p { color: #666; }</style>
</head>
<body><h1>Brainstorm Companion</h1>
<p>Waiting for Claude to push a screen...</p></body></html>`;
const frameTemplate = fs.readFileSync(path.join(__dirname, 'frame-template.html'), 'utf-8');
const helperScript = fs.readFileSync(path.join(__dirname, 'helper.js'), 'utf-8');
const helperInjection = '<script>\n' + helperScript + '\n</script>';
function isFullDocument(html) {
const trimmed = html.trimStart().toLowerCase();
return trimmed.startsWith('<!doctype') || trimmed.startsWith('<html');
}
function wrapInFrame(content) {
return frameTemplate.replace('<!-- CONTENT -->', content);
}
function getNewestScreen() {
const files = fs.readdirSync(SCREEN_DIR)
.filter(f => f.endsWith('.html'))
.map(f => {
const fp = path.join(SCREEN_DIR, f);
return { path: fp, mtime: fs.statSync(fp).mtime.getTime() };
})
.sort((a, b) => b.mtime - a.mtime);
return files.length > 0 ? files[0].path : null;
}
```
- [ ] **Step 3: Add HTTP request handler**
```js
function handleRequest(req, res) {
if (req.method === 'GET' && req.url === '/') {
const screenFile = getNewestScreen();
let html = screenFile
? (raw => isFullDocument(raw) ? raw : wrapInFrame(raw))(fs.readFileSync(screenFile, 'utf-8'))
: WAITING_PAGE;
if (html.includes('</body>')) {
html = html.replace('</body>', helperInjection + '\n</body>');
} else {
html += helperInjection;
}
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end(html);
} else if (req.method === 'GET' && req.url.startsWith('/files/')) {
const fileName = req.url.slice(7); // strip '/files/'
const filePath = path.join(SCREEN_DIR, path.basename(fileName));
if (!fs.existsSync(filePath)) {
res.writeHead(404);
res.end('Not found');
return;
}
const ext = path.extname(filePath).toLowerCase();
const contentType = MIME_TYPES[ext] || 'application/octet-stream';
res.writeHead(200, { 'Content-Type': contentType });
res.end(fs.readFileSync(filePath));
} else {
res.writeHead(404);
res.end('Not found');
}
}
```
- [ ] **Step 4: Add WebSocket connection handling**
```js
const clients = new Set();
function handleUpgrade(req, socket) {
const key = req.headers['sec-websocket-key'];
if (!key) { socket.destroy(); return; }
const accept = computeAcceptKey(key);
socket.write(
'HTTP/1.1 101 Switching Protocols\r\n' +
'Upgrade: websocket\r\n' +
'Connection: Upgrade\r\n' +
'Sec-WebSocket-Accept: ' + accept + '\r\n\r\n'
);
let buffer = Buffer.alloc(0);
clients.add(socket);
socket.on('data', (chunk) => {
buffer = Buffer.concat([buffer, chunk]);
while (buffer.length > 0) {
let result;
try {
result = decodeFrame(buffer);
} catch (e) {
socket.end(encodeFrame(OPCODES.CLOSE, Buffer.alloc(0)));
clients.delete(socket);
return;
}
if (!result) break;
buffer = buffer.slice(result.bytesConsumed);
switch (result.opcode) {
case OPCODES.TEXT:
handleMessage(result.payload.toString());
break;
case OPCODES.CLOSE:
socket.end(encodeFrame(OPCODES.CLOSE, Buffer.alloc(0)));
clients.delete(socket);
return;
case OPCODES.PING:
socket.write(encodeFrame(OPCODES.PONG, result.payload));
break;
case OPCODES.PONG:
break;
default:
// Unsupported opcode — close with 1003
const closeBuf = Buffer.alloc(2);
closeBuf.writeUInt16BE(1003);
socket.end(encodeFrame(OPCODES.CLOSE, closeBuf));
clients.delete(socket);
return;
}
}
});
socket.on('close', () => clients.delete(socket));
socket.on('error', () => clients.delete(socket));
}
function handleMessage(text) {
let event;
try {
event = JSON.parse(text);
} catch (e) {
console.error('Failed to parse WebSocket message:', e.message);
return;
}
console.log(JSON.stringify({ source: 'user-event', ...event }));
if (event.choice) {
const eventsFile = path.join(SCREEN_DIR, '.events');
fs.appendFileSync(eventsFile, JSON.stringify(event) + '\n');
}
}
function broadcast(msg) {
const frame = encodeFrame(OPCODES.TEXT, Buffer.from(JSON.stringify(msg)));
for (const socket of clients) {
try { socket.write(frame); } catch (e) { clients.delete(socket); }
}
}
```
- [ ] **Step 5: Add debounce timer map**
```js
const debounceTimers = new Map();
```
File watching logic is inlined in `startServer` (Step 6) to keep watcher lifecycle together with server lifecycle and include an `error` handler per spec.
- [ ] **Step 6: Add startServer function and conditional main**
`frameTemplate` and `helperInjection` are already at module scope (Step 2). `startServer` just creates the screen dir, starts the HTTP server, watcher, and logs startup info.
```js
function startServer() {
if (!fs.existsSync(SCREEN_DIR)) fs.mkdirSync(SCREEN_DIR, { recursive: true });
const server = http.createServer(handleRequest);
server.on('upgrade', handleUpgrade);
const watcher = fs.watch(SCREEN_DIR, (eventType, filename) => {
if (!filename || !filename.endsWith('.html')) return;
if (debounceTimers.has(filename)) clearTimeout(debounceTimers.get(filename));
debounceTimers.set(filename, setTimeout(() => {
debounceTimers.delete(filename);
const filePath = path.join(SCREEN_DIR, filename);
if (eventType === 'rename' && fs.existsSync(filePath)) {
const eventsFile = path.join(SCREEN_DIR, '.events');
if (fs.existsSync(eventsFile)) fs.unlinkSync(eventsFile);
console.log(JSON.stringify({ type: 'screen-added', file: filePath }));
} else if (eventType === 'change') {
console.log(JSON.stringify({ type: 'screen-updated', file: filePath }));
}
broadcast({ type: 'reload' });
}, 100));
});
watcher.on('error', (err) => console.error('fs.watch error:', err.message));
server.listen(PORT, HOST, () => {
const info = JSON.stringify({
type: 'server-started', port: Number(PORT), host: HOST,
url_host: URL_HOST, url: 'http://' + URL_HOST + ':' + PORT,
screen_dir: SCREEN_DIR
});
console.log(info);
fs.writeFileSync(path.join(SCREEN_DIR, '.server-info'), info + '\n');
});
}
if (require.main === module) {
startServer();
}
```
- [ ] **Step 7: Run integration tests**
The test directory already has a `package.json` with `ws` as a dependency. Install it if needed, then run tests.
Run: `cd tests/brainstorm-server && npm install && node server.test.js`
Expected: All tests pass
- [ ] **Step 8: Commit**
```bash
git add skills/brainstorming/scripts/server.js
git commit -m "Add HTTP server, WebSocket handling, and file watching to server.js"
```
---
## Chunk 3: Swap and Cleanup
### Task 3: Update start-server.sh and remove old files
**Files:**
- Modify: `skills/brainstorming/scripts/start-server.sh:94,100`
- Modify: `.gitignore:6`
- Delete: `skills/brainstorming/scripts/index.js`
- Delete: `skills/brainstorming/scripts/package.json`
- Delete: `skills/brainstorming/scripts/package-lock.json`
- Delete: `skills/brainstorming/scripts/node_modules/` (entire directory)
- [ ] **Step 1: Update start-server.sh — change `index.js` to `server.js`**
Two lines to change:
Line 94: `env BRAINSTORM_DIR="$SCREEN_DIR" BRAINSTORM_HOST="$BIND_HOST" BRAINSTORM_URL_HOST="$URL_HOST" node server.js`
Line 100: `nohup env BRAINSTORM_DIR="$SCREEN_DIR" BRAINSTORM_HOST="$BIND_HOST" BRAINSTORM_URL_HOST="$URL_HOST" node server.js > "$LOG_FILE" 2>&1 &`
- [ ] **Step 2: Remove the gitignore exception for node_modules**
In `.gitignore`, delete line 6: `!skills/brainstorming/scripts/node_modules/`
- [ ] **Step 3: Delete old files**
```bash
git rm skills/brainstorming/scripts/index.js
git rm skills/brainstorming/scripts/package.json
git rm skills/brainstorming/scripts/package-lock.json
git rm -r skills/brainstorming/scripts/node_modules/
```
- [ ] **Step 4: Run both test suites**
Run: `cd tests/brainstorm-server && node ws-protocol.test.js && node server.test.js`
Expected: All tests pass
- [ ] **Step 5: Commit**
```bash
git add skills/brainstorming/scripts/ .gitignore
git commit -m "Remove vendored node_modules, swap to zero-dep server.js"
```
### Task 4: Manual smoke test
- [ ] **Step 1: Start the server manually**
```bash
cd skills/brainstorming/scripts
BRAINSTORM_DIR=/tmp/brainstorm-smoke BRAINSTORM_PORT=9876 node server.js
```
Expected: `server-started` JSON printed with port 9876
- [ ] **Step 2: Open browser to http://localhost:9876**
Expected: Waiting page with "Waiting for Claude to push a screen..."
- [ ] **Step 3: Write an HTML file to the screen directory**
```bash
echo '<h2>Hello from smoke test</h2>' > /tmp/brainstorm-smoke/test.html
```
Expected: Browser reloads and shows "Hello from smoke test" wrapped in frame template
- [ ] **Step 4: Verify WebSocket works — check browser console**
Open browser dev tools. The WebSocket connection should show as connected (no errors in console). The frame template's status indicator should show "Connected".
- [ ] **Step 5: Stop server with Ctrl-C, clean up**
```bash
rm -rf /tmp/brainstorm-smoke
```

View File

@@ -0,0 +1,564 @@
# Codex App Compatibility Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Make `using-git-worktrees`, `finishing-a-development-branch`, and related skills work in the Codex App's sandboxed worktree environment without breaking existing behavior.
**Architecture:** Read-only environment detection (`git-dir` vs `git-common-dir`) at the start of two skills. If already in a linked worktree, skip creation. If on detached HEAD, emit a handoff payload instead of the 4-option menu. Sandbox fallback catches permission errors during worktree creation.
**Tech Stack:** Git, Markdown (skill files are instruction documents, not executable code)
**Spec:** `docs/superpowers/specs/2026-03-23-codex-app-compatibility-design.md`
---
## File Structure
| File | Responsibility | Action |
|---|---|---|
| `skills/using-git-worktrees/SKILL.md` | Worktree creation + isolation | Add Step 0 detection + sandbox fallback |
| `skills/finishing-a-development-branch/SKILL.md` | Branch finishing workflow | Add Step 1.5 detection + cleanup guard |
| `skills/subagent-driven-development/SKILL.md` | Plan execution with subagents | Update Integration description |
| `skills/executing-plans/SKILL.md` | Plan execution inline | Update Integration description |
| `skills/using-superpowers/references/codex-tools.md` | Codex platform reference | Add detection + finishing docs |
---
### Task 1: Add Step 0 to `using-git-worktrees`
**Files:**
- Modify: `skills/using-git-worktrees/SKILL.md:14-15` (insert after Overview, before Directory Selection Process)
- [ ] **Step 1: Read the current skill file**
Read `skills/using-git-worktrees/SKILL.md` in full. Identify the exact insertion point: after the "Announce at start" line (line 14) and before "## Directory Selection Process" (line 16).
- [ ] **Step 2: Insert Step 0 section**
Insert the following between the Overview section and "## Directory Selection Process":
```markdown
## Step 0: Check if Already in an Isolated Workspace
Before creating a worktree, check if one already exists:
```bash
GIT_DIR=$(cd "$(git rev-parse --git-dir)" 2>/dev/null && pwd -P)
GIT_COMMON=$(cd "$(git rev-parse --git-common-dir)" 2>/dev/null && pwd -P)
BRANCH=$(git branch --show-current)
```
**If `GIT_DIR` differs from `GIT_COMMON`:** You are already inside a linked worktree (created by the Codex App, Claude Code's Agent tool, a previous skill run, or the user). Do NOT create another worktree. Instead:
1. Run project setup (auto-detect package manager as in "Run Project Setup" below)
2. Verify clean baseline (run tests as in "Verify Clean Baseline" below)
3. Report with branch state:
- On a branch: "Already in an isolated workspace at `<path>` on branch `<name>`. Tests passing. Ready to implement."
- Detached HEAD: "Already in an isolated workspace at `<path>` (detached HEAD, externally managed). Tests passing. Note: branch creation needed at finish time. Ready to implement."
After reporting, STOP. Do not continue to Directory Selection or Creation Steps.
**If `GIT_DIR` equals `GIT_COMMON`:** Proceed with the full worktree creation flow below.
**Sandbox fallback:** If you proceed to Creation Steps but `git worktree add -b` fails with a permission error (e.g., "Operation not permitted"), treat this as a late-detected restricted environment. Fall back to the behavior above — run setup and baseline tests in the current directory, report accordingly, and STOP.
```
- [ ] **Step 3: Verify the insertion**
Read the file again. Confirm:
- Step 0 appears between Overview and Directory Selection Process
- The rest of the file (Directory Selection, Safety Verification, Creation Steps, etc.) is unchanged
- No duplicate sections or broken markdown
- [ ] **Step 4: Commit**
```bash
git add skills/using-git-worktrees/SKILL.md
git commit -m "feat(using-git-worktrees): add Step 0 environment detection (PRI-823)
Skip worktree creation when already in a linked worktree. Includes
sandbox fallback for permission errors on git worktree add."
```
---
### Task 2: Update `using-git-worktrees` Integration section
**Files:**
- Modify: `skills/using-git-worktrees/SKILL.md:211-215` (Integration > Called by)
- [ ] **Step 1: Update the three "Called by" entries**
Change lines 212-214 from:
```markdown
- **brainstorming** (Phase 4) - REQUIRED when design is approved and implementation follows
- **subagent-driven-development** - REQUIRED before executing any tasks
- **executing-plans** - REQUIRED before executing any tasks
```
To:
```markdown
- **brainstorming** - REQUIRED: Ensures isolated workspace (creates one or verifies existing)
- **subagent-driven-development** - REQUIRED: Ensures isolated workspace (creates one or verifies existing)
- **executing-plans** - REQUIRED: Ensures isolated workspace (creates one or verifies existing)
```
- [ ] **Step 2: Verify the Integration section**
Read the Integration section. Confirm all three entries are updated, "Pairs with" is unchanged.
- [ ] **Step 3: Commit**
```bash
git add skills/using-git-worktrees/SKILL.md
git commit -m "docs(using-git-worktrees): update Integration descriptions (PRI-823)
Clarify that skill ensures a workspace exists, not that it always creates one."
```
---
### Task 3: Add Step 1.5 to `finishing-a-development-branch`
**Files:**
- Modify: `skills/finishing-a-development-branch/SKILL.md:38` (insert after Step 1, before Step 2)
- [ ] **Step 1: Read the current skill file**
Read `skills/finishing-a-development-branch/SKILL.md` in full. Identify the insertion point: after "**If tests pass:** Continue to Step 2." (line 38) and before "### Step 2: Determine Base Branch" (line 40).
- [ ] **Step 2: Insert Step 1.5 section**
Insert the following between Step 1 and Step 2:
```markdown
### Step 1.5: Detect Environment
```bash
GIT_DIR=$(cd "$(git rev-parse --git-dir)" 2>/dev/null && pwd -P)
GIT_COMMON=$(cd "$(git rev-parse --git-common-dir)" 2>/dev/null && pwd -P)
BRANCH=$(git branch --show-current)
```
**Path A — `GIT_DIR` differs from `GIT_COMMON` AND `BRANCH` is empty (externally managed worktree, detached HEAD):**
First, ensure all work is staged and committed (`git add` + `git commit`).
Then present this to the user (do NOT present the 4-option menu):
```
Implementation complete. All tests passing.
Current HEAD: <full-commit-sha>
This workspace is externally managed (detached HEAD).
I cannot create branches, push, or open PRs from here.
⚠ These commits are on a detached HEAD. If you do not create a branch,
they may be lost when this workspace is cleaned up.
If your host application provides these controls:
- "Create branch" — to name a branch, then commit/push/PR
- "Hand off to local" — to move changes to your local checkout
Suggested branch name: <ticket-id/short-description>
Suggested commit message: <summary-of-work>
```
Branch name: use ticket ID if available (e.g., `pri-823/codex-compat`), otherwise slugify the first 5 words of the plan title, otherwise omit. Avoid sensitive content in branch names.
Skip to Step 5 (cleanup is a no-op — see guard below).
**Path B — `GIT_DIR` differs from `GIT_COMMON` AND `BRANCH` exists (externally managed worktree, named branch):**
Proceed to Step 2 and present the 4-option menu as normal.
**Path C — `GIT_DIR` equals `GIT_COMMON` (normal environment):**
Proceed to Step 2 and present the 4-option menu as normal.
```
- [ ] **Step 3: Verify the insertion**
Read the file again. Confirm:
- Step 1.5 appears between Step 1 and Step 2
- Steps 2-5 are unchanged
- Path A handoff includes commit SHA and data loss warning
- Paths B and C proceed to Step 2 normally
- [ ] **Step 4: Commit**
```bash
git add skills/finishing-a-development-branch/SKILL.md
git commit -m "feat(finishing-a-development-branch): add Step 1.5 environment detection (PRI-823)
Detect externally managed worktrees with detached HEAD and emit handoff
payload instead of 4-option menu. Includes commit SHA and data loss warning."
```
---
### Task 4: Add Step 5 cleanup guard to `finishing-a-development-branch`
**Files:**
- Modify: `skills/finishing-a-development-branch/SKILL.md` (Step 5: Cleanup Worktree — find by section heading, line numbers will have shifted after Task 3)
- [ ] **Step 1: Read the current Step 5 section**
Find the "### Step 5: Cleanup Worktree" section in `skills/finishing-a-development-branch/SKILL.md` (line numbers will have shifted after Task 3's insertion). The current Step 5 is:
```markdown
### Step 5: Cleanup Worktree
**For Options 1, 2, 4:**
Check if in worktree:
```bash
git worktree list | grep $(git branch --show-current)
```
If yes:
```bash
git worktree remove <worktree-path>
```
**For Option 3:** Keep worktree.
```
- [ ] **Step 2: Add the cleanup guard before existing logic**
Replace the Step 5 section with:
```markdown
### Step 5: Cleanup Worktree
**First, check if worktree is externally managed:**
```bash
GIT_DIR=$(cd "$(git rev-parse --git-dir)" 2>/dev/null && pwd -P)
GIT_COMMON=$(cd "$(git rev-parse --git-common-dir)" 2>/dev/null && pwd -P)
```
If `GIT_DIR` differs from `GIT_COMMON`: skip worktree removal — the host environment owns this workspace.
**Otherwise, for Options 1 and 4:**
Check if in worktree:
```bash
git worktree list | grep $(git branch --show-current)
```
If yes:
```bash
git worktree remove <worktree-path>
```
**For Option 3:** Keep worktree.
```
Note: the original text said "For Options 1, 2, 4" but the Quick Reference table and Common Mistakes section say "Options 1 & 4 only." This edit aligns Step 5 with those sections.
- [ ] **Step 3: Verify the replacement**
Read Step 5. Confirm:
- Cleanup guard (re-detection) appears first
- Existing removal logic preserved for non-externally-managed worktrees
- "Options 1 and 4" (not "1, 2, 4") matches Quick Reference and Common Mistakes
- [ ] **Step 4: Commit**
```bash
git add skills/finishing-a-development-branch/SKILL.md
git commit -m "feat(finishing-a-development-branch): add Step 5 cleanup guard (PRI-823)
Re-detect externally managed worktree at cleanup time and skip removal.
Also fixes pre-existing inconsistency: cleanup now correctly says
Options 1 and 4 only, matching Quick Reference and Common Mistakes."
```
---
### Task 5: Update Integration lines in `subagent-driven-development` and `executing-plans`
**Files:**
- Modify: `skills/subagent-driven-development/SKILL.md:268`
- Modify: `skills/executing-plans/SKILL.md:68`
- [ ] **Step 1: Update `subagent-driven-development`**
Change line 268 from:
```
- **superpowers:using-git-worktrees** - REQUIRED: Set up isolated workspace before starting
```
To:
```
- **superpowers:using-git-worktrees** - REQUIRED: Ensures isolated workspace (creates one or verifies existing)
```
- [ ] **Step 2: Update `executing-plans`**
Change line 68 from:
```
- **superpowers:using-git-worktrees** - REQUIRED: Set up isolated workspace before starting
```
To:
```
- **superpowers:using-git-worktrees** - REQUIRED: Ensures isolated workspace (creates one or verifies existing)
```
- [ ] **Step 3: Verify both files**
Read line 268 of `skills/subagent-driven-development/SKILL.md` and line 68 of `skills/executing-plans/SKILL.md`. Confirm both say "Ensures isolated workspace (creates one or verifies existing)".
- [ ] **Step 4: Commit**
```bash
git add skills/subagent-driven-development/SKILL.md skills/executing-plans/SKILL.md
git commit -m "docs(sdd, executing-plans): update worktree Integration descriptions (PRI-823)
Clarify that using-git-worktrees ensures a workspace exists rather than
always creating one."
```
---
### Task 6: Add environment detection docs to `codex-tools.md`
**Files:**
- Modify: `skills/using-superpowers/references/codex-tools.md:25` (append at end)
- [ ] **Step 1: Read the current file**
Read `skills/using-superpowers/references/codex-tools.md` in full. Confirm it ends at line 25-26 after the multi_agent section.
- [ ] **Step 2: Append two new sections**
Add at the end of the file:
```markdown
## Environment Detection
Skills that create worktrees or finish branches should detect their
environment with read-only git commands before proceeding:
```bash
GIT_DIR=$(cd "$(git rev-parse --git-dir)" 2>/dev/null && pwd -P)
GIT_COMMON=$(cd "$(git rev-parse --git-common-dir)" 2>/dev/null && pwd -P)
BRANCH=$(git branch --show-current)
```
- `GIT_DIR != GIT_COMMON` → already in a linked worktree (skip creation)
- `BRANCH` empty → detached HEAD (cannot branch/push/PR from sandbox)
See `using-git-worktrees` Step 0 and `finishing-a-development-branch`
Step 1.5 for how each skill uses these signals.
## Codex App Finishing
When the sandbox blocks branch/push operations (detached HEAD in an
externally managed worktree), the agent commits all work and informs
the user to use the App's native controls:
- **"Create branch"** — names the branch, then commit/push/PR via App UI
- **"Hand off to local"** — transfers work to the user's local checkout
The agent can still run tests, stage files, and output suggested branch
names, commit messages, and PR descriptions for the user to copy.
```
- [ ] **Step 3: Verify the additions**
Read the full file. Confirm:
- Two new sections appear after the existing content
- Bash code block renders correctly (not escaped)
- Cross-references to Step 0 and Step 1.5 are present
- [ ] **Step 4: Commit**
```bash
git add skills/using-superpowers/references/codex-tools.md
git commit -m "docs(codex-tools): add environment detection and App finishing docs (PRI-823)
Document the git-dir vs git-common-dir detection pattern and the Codex
App's native finishing flow for skills that need to adapt."
```
---
### Task 7: Automated test — environment detection
**Files:**
- Create: `tests/codex-app-compat/test-environment-detection.sh`
- [ ] **Step 1: Create test directory**
```bash
mkdir -p tests/codex-app-compat
```
- [ ] **Step 2: Write the detection test script**
Create `tests/codex-app-compat/test-environment-detection.sh`:
```bash
#!/usr/bin/env bash
set -euo pipefail
# Test environment detection logic from PRI-823
# Tests the git-dir vs git-common-dir comparison used by
# using-git-worktrees Step 0 and finishing-a-development-branch Step 1.5
PASS=0
FAIL=0
TEMP_DIR=$(mktemp -d)
trap "rm -rf $TEMP_DIR" EXIT
log_pass() { echo " PASS: $1"; PASS=$((PASS + 1)); }
log_fail() { echo " FAIL: $1"; FAIL=$((FAIL + 1)); }
# Helper: run detection and return "linked" or "normal"
detect_worktree() {
local git_dir git_common
git_dir=$(cd "$(git rev-parse --git-dir)" 2>/dev/null && pwd -P)
git_common=$(cd "$(git rev-parse --git-common-dir)" 2>/dev/null && pwd -P)
if [ "$git_dir" != "$git_common" ]; then
echo "linked"
else
echo "normal"
fi
}
echo "=== Test 1: Normal repo detection ==="
cd "$TEMP_DIR"
git init test-repo > /dev/null 2>&1
cd test-repo
git commit --allow-empty -m "init" > /dev/null 2>&1
result=$(detect_worktree)
if [ "$result" = "normal" ]; then
log_pass "Normal repo detected as normal"
else
log_fail "Normal repo detected as '$result' (expected 'normal')"
fi
echo "=== Test 2: Linked worktree detection ==="
git worktree add "$TEMP_DIR/test-wt" -b test-branch > /dev/null 2>&1
cd "$TEMP_DIR/test-wt"
result=$(detect_worktree)
if [ "$result" = "linked" ]; then
log_pass "Linked worktree detected as linked"
else
log_fail "Linked worktree detected as '$result' (expected 'linked')"
fi
echo "=== Test 3: Detached HEAD detection ==="
git checkout --detach HEAD > /dev/null 2>&1
branch=$(git branch --show-current)
if [ -z "$branch" ]; then
log_pass "Detached HEAD: branch is empty"
else
log_fail "Detached HEAD: branch is '$branch' (expected empty)"
fi
echo "=== Test 4: Linked worktree + detached HEAD (Codex App simulation) ==="
result=$(detect_worktree)
branch=$(git branch --show-current)
if [ "$result" = "linked" ] && [ -z "$branch" ]; then
log_pass "Codex App simulation: linked + detached HEAD"
else
log_fail "Codex App simulation: result='$result', branch='$branch'"
fi
echo "=== Test 5: Cleanup guard — linked worktree should NOT remove ==="
cd "$TEMP_DIR/test-wt"
result=$(detect_worktree)
if [ "$result" = "linked" ]; then
log_pass "Cleanup guard: linked worktree correctly detected (would skip removal)"
else
log_fail "Cleanup guard: expected 'linked', got '$result'"
fi
echo "=== Test 6: Cleanup guard — main repo SHOULD remove ==="
cd "$TEMP_DIR/test-repo"
result=$(detect_worktree)
if [ "$result" = "normal" ]; then
log_pass "Cleanup guard: main repo correctly detected (would proceed with removal)"
else
log_fail "Cleanup guard: expected 'normal', got '$result'"
fi
# Cleanup worktree before temp dir removal
cd "$TEMP_DIR/test-repo"
git worktree remove "$TEMP_DIR/test-wt" > /dev/null 2>&1 || true
echo ""
echo "=== Results: $PASS passed, $FAIL failed ==="
if [ "$FAIL" -gt 0 ]; then
exit 1
fi
```
- [ ] **Step 3: Make it executable and run it**
```bash
chmod +x tests/codex-app-compat/test-environment-detection.sh
./tests/codex-app-compat/test-environment-detection.sh
```
Expected output: 6 passed, 0 failed.
- [ ] **Step 4: Commit**
```bash
git add tests/codex-app-compat/test-environment-detection.sh
git commit -m "test: add environment detection tests for Codex App compat (PRI-823)
Tests git-dir vs git-common-dir comparison in normal repo, linked
worktree, detached HEAD, and cleanup guard scenarios."
```
---
### Task 8: Final verification
**Files:**
- Read: all 5 modified skill files
- [ ] **Step 1: Run the automated detection tests**
```bash
./tests/codex-app-compat/test-environment-detection.sh
```
Expected: 6 passed, 0 failed.
- [ ] **Step 2: Read each modified file and verify changes**
Read each file end-to-end:
- `skills/using-git-worktrees/SKILL.md` — Step 0 present, rest unchanged
- `skills/finishing-a-development-branch/SKILL.md` — Step 1.5 present, cleanup guard present, rest unchanged
- `skills/subagent-driven-development/SKILL.md` — line 268 updated
- `skills/executing-plans/SKILL.md` — line 68 updated
- `skills/using-superpowers/references/codex-tools.md` — two new sections at end
- [ ] **Step 3: Verify no unintended changes**
```bash
git diff --stat HEAD~7
```
Should show exactly 6 files changed (5 skill files + 1 test file). No other files modified.
- [ ] **Step 4: Run existing test suite**
If test runner exists:
```bash
# Run skill-triggering tests
./tests/skill-triggering/run-all.sh 2>/dev/null || echo "Skill triggering tests not available in this environment"
# Run SDD integration test
./tests/claude-code/test-subagent-driven-development-integration.sh 2>/dev/null || echo "SDD integration test not available in this environment"
```
Note: these tests require Claude Code with `--dangerously-skip-permissions`. If not available, document that regression tests should be run manually.

View File

@@ -0,0 +1,118 @@
# Zero-Dependency Brainstorm Server
Replace the brainstorm companion server's vendored node_modules (express, ws, chokidar — 714 tracked files) with a single zero-dependency `server.js` using only Node.js built-ins.
## Motivation
Vendoring node_modules into the git repo creates a supply chain risk: frozen dependencies don't get security patches, 714 files of third-party code are committed without audit, and modifications to vendored code look like normal commits. While the actual risk is low (localhost-only dev server), eliminating it is straightforward.
## Architecture
A single `server.js` file (~250-300 lines) using `http`, `crypto`, `fs`, and `path`. The file serves two roles:
- **When run directly** (`node server.js`): starts the HTTP/WebSocket server
- **When required** (`require('./server.js')`): exports WebSocket protocol functions for unit testing
### WebSocket Protocol
Implements RFC 6455 for text frames only:
**Handshake:** Compute `Sec-WebSocket-Accept` from client's `Sec-WebSocket-Key` using SHA-1 + the RFC 6455 magic GUID. Return 101 Switching Protocols.
**Frame decoding (client to server):** Handle three masked length encodings:
- Small: payload < 126 bytes
- Medium: 126-65535 bytes (16-bit extended)
- Large: > 65535 bytes (64-bit extended)
XOR-unmask payload using 4-byte mask key. Return `{ opcode, payload, bytesConsumed }` or `null` for incomplete buffers. Reject unmasked frames.
**Frame encoding (server to client):** Unmasked frames with the same three length encodings.
**Opcodes handled:** TEXT (0x01), CLOSE (0x08), PING (0x09), PONG (0x0A). Unrecognized opcodes get a close frame with status 1003 (Unsupported Data).
**Deliberately skipped:** Binary frames, fragmented messages, extensions (permessage-deflate), subprotocols. These are unnecessary for small JSON text messages between localhost clients. Extensions and subprotocols are negotiated in the handshake — by not advertising them, they are never active.
**Buffer accumulation:** Each connection maintains a buffer. On `data`, append and loop `decodeFrame` until it returns null or buffer is empty.
### HTTP Server
Three routes:
1. **`GET /`** — Serve newest `.html` from screen directory by mtime. Detect full documents vs fragments, wrap fragments in frame template, inject helper.js. Return `text/html`. When no `.html` files exist, serve a hardcoded waiting page ("Waiting for Claude to push a screen...") with helper.js injected.
2. **`GET /files/*`** — Serve static files from screen directory with MIME type lookup from a hardcoded extension map (html, css, js, png, jpg, gif, svg, json). Return 404 if not found.
3. **Everything else** — 404.
WebSocket upgrade handled via the `'upgrade'` event on the HTTP server, separate from the request handler.
### Configuration
Environment variables (all optional):
- `BRAINSTORM_PORT` — port to bind (default: random high port 49152-65535)
- `BRAINSTORM_HOST` — interface to bind (default: `127.0.0.1`)
- `BRAINSTORM_URL_HOST` — hostname for the URL in startup JSON (default: `localhost` when host is `127.0.0.1`, otherwise same as host)
- `BRAINSTORM_DIR` — screen directory path (default: `/tmp/brainstorm`)
### Startup Sequence
1. Create `SCREEN_DIR` if it doesn't exist (`mkdirSync` recursive)
2. Load frame template and helper.js from `__dirname`
3. Start HTTP server on configured host/port
4. Start `fs.watch` on `SCREEN_DIR`
5. On successful listen, log `server-started` JSON to stdout: `{ type, port, host, url_host, url, screen_dir }`
6. Write the same JSON to `SCREEN_DIR/.server-info` so agents can find connection details when stdout is hidden (background execution)
### Application-Level WebSocket Messages
When a TEXT frame arrives from a client:
1. Parse as JSON. If parsing fails, log to stderr and continue.
2. Log to stdout as `{ source: 'user-event', ...event }`.
3. If the event contains a `choice` property, append the JSON to `SCREEN_DIR/.events` (one line per event).
### File Watching
`fs.watch(SCREEN_DIR)` replaces chokidar. On HTML file events:
- On new file (`rename` event for a file that exists): delete `.events` file if present (`unlinkSync`), log `screen-added` to stdout as JSON
- On file change (`change` event): log `screen-updated` to stdout as JSON (do NOT clear `.events`)
- Both events: send `{ type: 'reload' }` to all connected WebSocket clients
Debounce per-filename with ~100ms timeout to prevent duplicate events (common on macOS and Linux).
### Error Handling
- Malformed JSON from WebSocket clients: log to stderr, continue
- Unhandled opcodes: close with status 1003
- Client disconnects: remove from broadcast set
- `fs.watch` errors: log to stderr, continue
- No graceful shutdown logic — shell scripts handle process lifecycle via SIGTERM
## What Changes
| Before | After |
|---|---|
| `index.js` + `package.json` + `package-lock.json` + 714 `node_modules` files | `server.js` (single file) |
| express, ws, chokidar dependencies | none |
| No static file serving | `/files/*` serves from screen directory |
## What Stays the Same
- `helper.js` — no changes
- `frame-template.html` — no changes
- `start-server.sh` — one-line update: `index.js` to `server.js`
- `stop-server.sh` — no changes
- `visual-companion.md` — no changes
- All existing server behavior and external contract
## Platform Compatibility
- `server.js` uses only cross-platform Node built-ins
- `fs.watch` is reliable for single flat directories on macOS, Linux, and Windows
- Shell scripts require bash (Git Bash on Windows, which is required for Claude Code)
## Testing
**Unit tests** (`ws-protocol.test.js`): Test WebSocket frame encoding/decoding, handshake computation, and protocol edge cases directly by requiring `server.js` exports.
**Integration tests** (`server.test.js`): Test full server behavior — HTTP serving, WebSocket communication, file watching, brainstorming workflow. Uses `ws` npm package as a test-only client dependency (not shipped to end users).

View File

@@ -0,0 +1,244 @@
# Codex App Compatibility: Worktree and Finishing Skill Adaptation
Make superpowers skills work in the Codex App's sandboxed worktree environment without breaking existing Claude Code or Codex CLI behavior.
**Ticket:** PRI-823
## Motivation
The Codex App runs agents inside git worktrees it manages — detached HEAD, located under `$CODEX_HOME/worktrees/`, with a Seatbelt sandbox that blocks `git checkout -b`, `git push`, and network access. Three superpowers skills assume unrestricted git access: `using-git-worktrees` creates manual worktrees with named branches, `finishing-a-development-branch` merges/pushes/PRs by branch name, and `subagent-driven-development` requires both.
The Codex CLI (open source terminal tool) does NOT have this conflict — it has no built-in worktree management. Our manual worktree approach fills an isolation gap there. The problem is specifically with the Codex App.
## Empirical Findings
Tested in the Codex App on 2026-03-23:
| Operation | workspace-write sandbox | Full access sandbox |
|---|---|---|
| `git add` | Works | Works |
| `git commit` | Works | Works |
| `git checkout -b` | **Blocked** (can't write `.git/refs/heads/`) | Works |
| `git push` | **Blocked** (network + `.git/refs/remotes/`) | Works |
| `gh pr create` | **Blocked** (network) | Works |
| `git status/diff/log` | Works | Works |
Additional findings:
- `spawn_agent` subagents **share** the parent thread's filesystem (confirmed via marker file test)
- "Create branch" button appears in the App header regardless of which branch the worktree was started from
- The App's native finishing flow: Create branch → Commit modal → Commit and push / Commit and create PR
- `network_access = true` config is silently broken on macOS (issue #10390)
## Design: Read-Only Environment Detection
Three read-only git commands detect the environment without side effects:
```bash
GIT_DIR=$(cd "$(git rev-parse --git-dir)" 2>/dev/null && pwd -P)
GIT_COMMON=$(cd "$(git rev-parse --git-common-dir)" 2>/dev/null && pwd -P)
BRANCH=$(git branch --show-current)
```
Two signals derived:
- **IN_LINKED_WORKTREE:** `GIT_DIR != GIT_COMMON` — the agent is in a worktree created by something else (Codex App, Claude Code Agent tool, previous skill run, or the user)
- **ON_DETACHED_HEAD:** `BRANCH` is empty — no named branch exists
Why `git-dir != git-common-dir` instead of checking `show-toplevel`:
- In a normal repo, both resolve to the same `.git` directory
- In a linked worktree, `git-dir` is `.git/worktrees/<name>` while `git-common-dir` is `.git`
- In a submodule, both are equal — avoiding a false positive that `show-toplevel` would produce
- Resolving via `cd && pwd -P` handles the relative-path problem (`git-common-dir` returns `.git` relative in normal repos but absolute in worktrees) and symlinks (macOS `/tmp``/private/tmp`)
### Decision Matrix
| Linked Worktree? | Detached HEAD? | Environment | Action |
|---|---|---|---|
| No | No | Claude Code / Codex CLI / normal git | Full skill behavior (unchanged) |
| Yes | Yes | Codex App worktree (workspace-write) | Skip worktree creation; handoff payload at finish |
| Yes | No | Codex App (Full access) or manual worktree | Skip worktree creation; full finishing flow |
| No | Yes | Unusual (manual detached HEAD) | Create worktree normally; warn at finish |
## Changes
### 1. `using-git-worktrees/SKILL.md` — Add Step 0 (~12 lines)
New section between "Overview" and "Directory Selection Process":
**Step 0: Check if Already in an Isolated Workspace**
Run the detection commands. If `GIT_DIR != GIT_COMMON`, skip worktree creation entirely. Instead:
1. Skip to "Run Project Setup" subsection under Creation Steps — `npm install` etc. is idempotent, worth running for safety
2. Then "Verify Clean Baseline" — run tests
3. Report with branch state:
- On a branch: "Already in an isolated workspace at `<path>` on branch `<name>`. Tests passing. Ready to implement."
- Detached HEAD: "Already in an isolated workspace at `<path>` (detached HEAD, externally managed). Tests passing. Note: branch creation needed at finish time. Ready to implement."
If `GIT_DIR == GIT_COMMON`, proceed with the full worktree creation flow (unchanged).
Safety verification (.gitignore check) is skipped when Step 0 fires — irrelevant for externally-created worktrees.
Update the Integration section's "Called by" entries. Change the description on each from context-specific text to: "Ensures isolated workspace (creates one or verifies existing)". For example, the `subagent-driven-development` entry changes from "REQUIRED: Set up isolated workspace before starting" to "REQUIRED: Ensures isolated workspace (creates one or verifies existing)".
**Sandbox fallback:** If `GIT_DIR == GIT_COMMON` and the skill proceeds to Creation Steps, but `git worktree add -b` fails with a permission error (e.g., Seatbelt sandbox denial), treat this as a late-detected restricted environment. Fall back to the Step 0 "already in workspace" behavior — skip creation, run setup and baseline tests in the current directory, report accordingly.
After reporting in Step 0, STOP. Do not continue to Directory Selection or Creation Steps.
**Everything else unchanged:** Directory Selection, Safety Verification, Creation Steps, Project Setup, Baseline Tests, Quick Reference, Common Mistakes, Red Flags.
### 2. `finishing-a-development-branch/SKILL.md` — Add Step 1.5 + cleanup guard (~20 lines)
**Step 1.5: Detect Environment** (after Step 1 "Verify Tests", before Step 2 "Determine Base Branch")
Run the detection commands. Three paths:
- **Path A** skips Steps 2 and 3 entirely (no base branch or options needed).
- **Paths B and C** proceed through Step 2 (Determine Base Branch) and Step 3 (Present Options) as normal.
**Path A — Externally managed worktree + detached HEAD** (`GIT_DIR != GIT_COMMON` AND `BRANCH` empty):
First, ensure all work is staged and committed (`git add` + `git commit`). The Codex App's finishing controls operate on committed work.
Then present this to the user (do NOT present the 4-option menu):
```
Implementation complete. All tests passing.
Current HEAD: <full-commit-sha>
This workspace is externally managed (detached HEAD).
I cannot create branches, push, or open PRs from here.
⚠ These commits are on a detached HEAD. If you do not create a branch,
they may be lost when this workspace is cleaned up.
If your host application provides these controls:
- "Create branch" — to name a branch, then commit/push/PR
- "Hand off to local" — to move changes to your local checkout
Suggested branch name: <ticket-id/short-description>
Suggested commit message: <summary-of-work>
```
Branch name derivation: use the ticket ID if available (e.g., `pri-823/codex-compat`), otherwise slugify the first 5 words of the plan title, otherwise omit the suggestion. Avoid including sensitive content (vulnerability descriptions, customer names) in branch names.
Skip to Step 5 (cleanup is a no-op for externally managed worktrees).
**Path B — Externally managed worktree + named branch** (`GIT_DIR != GIT_COMMON` AND `BRANCH` exists):
Present the 4-option menu as normal. (The Step 5 cleanup guard will re-detect the externally managed state independently.)
**Path C — Normal environment** (`GIT_DIR == GIT_COMMON`):
Present the 4-option menu as today (unchanged).
**Step 5 cleanup guard:**
Re-run the `GIT_DIR` vs `GIT_COMMON` detection at cleanup time (do not rely on earlier skill output — the finishing skill may run in a different session). If `GIT_DIR != GIT_COMMON`, skip `git worktree remove` — the host environment owns this workspace.
Otherwise, check and remove as today. Note: the existing Step 5 text says "For Options 1, 2, 4" but the Quick Reference table and Common Mistakes section say "Options 1 & 4 only." The new guard is added before this existing logic and does not change which options trigger cleanup.
**Everything else unchanged:** Options 1-4 logic, Quick Reference, Common Mistakes, Red Flags.
### 3. `subagent-driven-development/SKILL.md` and `executing-plans/SKILL.md` — 1 line edit each
Both skills have an identical Integration section line. Change from:
```
- superpowers:using-git-worktrees - REQUIRED: Set up isolated workspace before starting
```
To:
```
- superpowers:using-git-worktrees - REQUIRED: Ensures isolated workspace (creates one or verifies existing)
```
**Everything else unchanged:** Dispatch/review loop, prompt templates, model selection, status handling, red flags.
### 4. `codex-tools.md` — Add environment detection docs (~15 lines)
Two new sections at the end:
**Environment Detection:**
```markdown
## Environment Detection
Skills that create worktrees or finish branches should detect their
environment with read-only git commands before proceeding:
\```bash
GIT_DIR=$(cd "$(git rev-parse --git-dir)" 2>/dev/null && pwd -P)
GIT_COMMON=$(cd "$(git rev-parse --git-common-dir)" 2>/dev/null && pwd -P)
BRANCH=$(git branch --show-current)
\```
- `GIT_DIR != GIT_COMMON` → already in a linked worktree (skip creation)
- `BRANCH` empty → detached HEAD (cannot branch/push/PR from sandbox)
See `using-git-worktrees` Step 0 and `finishing-a-development-branch`
Step 1.5 for how each skill uses these signals.
```
**Codex App Finishing:**
```markdown
## Codex App Finishing
When the sandbox blocks branch/push operations (detached HEAD in an
externally managed worktree), the agent commits all work and informs
the user to use the App's native controls:
- **"Create branch"** — names the branch, then commit/push/PR via App UI
- **"Hand off to local"** — transfers work to the user's local checkout
The agent can still run tests, stage files, and output suggested branch
names, commit messages, and PR descriptions for the user to copy.
```
## What Does NOT Change
- `implementer-prompt.md`, `spec-reviewer-prompt.md`, `code-quality-reviewer-prompt.md` — subagent prompts untouched
- `executing-plans/SKILL.md` — only the 1-line Integration description changes (same as `subagent-driven-development`); all runtime behavior is unchanged
- `dispatching-parallel-agents/SKILL.md` — no worktree or finishing operations
- `.codex/INSTALL.md` — installation process unchanged
- The 4-option finishing menu — preserved exactly for Claude Code and Codex CLI
- The full worktree creation flow — preserved exactly for non-worktree environments
- Subagent dispatch/review/iterate loop — unchanged (filesystem sharing confirmed)
## Scope Summary
| File | Change |
|---|---|
| `skills/using-git-worktrees/SKILL.md` | +12 lines (Step 0) |
| `skills/finishing-a-development-branch/SKILL.md` | +20 lines (Step 1.5 + cleanup guard) |
| `skills/subagent-driven-development/SKILL.md` | 1 line edit |
| `skills/executing-plans/SKILL.md` | 1 line edit |
| `skills/using-superpowers/references/codex-tools.md` | +15 lines |
~50 lines added/changed across 5 files. Zero new files. Zero breaking changes.
## Future Considerations
If a third skill needs the same detection pattern, extract it into a shared `references/environment-detection.md` file (Approach B). Not needed now — only 2 skills use it.
## Test Plan
### Automated (run in Claude Code after implementation)
1. Normal repo detection — assert IN_LINKED_WORKTREE=false
2. Linked worktree detection — `git worktree add` test worktree, assert IN_LINKED_WORKTREE=true
3. Detached HEAD detection — `git checkout --detach`, assert ON_DETACHED_HEAD=true
4. Finishing skill handoff output — verify handoff message (not 4-option menu) in restricted environment
5. **Step 5 cleanup guard** — create a linked worktree (`git worktree add /tmp/test-cleanup -b test-cleanup`), `cd` into it, run the Step 5 cleanup detection (`GIT_DIR` vs `GIT_COMMON`), assert it would NOT call `git worktree remove`. Then `cd` back to main repo, run the same detection, assert it WOULD call `git worktree remove`. Clean up test worktree afterward.
### Manual Codex App Tests (5 tests)
1. Detection in Worktree thread (workspace-write) — verify GIT_DIR != GIT_COMMON, empty branch
2. Detection in Worktree thread (Full access) — same detection, different sandbox behavior
3. Finishing skill handoff format — verify agent emits handoff payload, not 4-option menu
4. Full lifecycle — detection → commit → finishing detection → correct behavior → cleanup
5. **Sandbox fallback in Local thread** — Start a Codex App **Local thread** (workspace-write sandbox). Prompt: "Use the superpowers skill `using-git-worktrees` to set up an isolated workspace for implementing a small change." Pre-check: `git checkout -b test-sandbox-check` should fail with `Operation not permitted`. Expected: the skill detects `GIT_DIR == GIT_COMMON` (normal repo), attempts `git worktree add -b`, hits Seatbelt denial, falls back to Step 0 "already in workspace" behavior — runs setup, baseline tests, reports ready from current directory. Pass: agent recovers gracefully without cryptic error messages. Fail: agent prints raw Seatbelt error, retries, or gives up with confusing output.
### Regression
- Existing Claude Code skill-triggering tests still pass
- Existing subagent-driven-development integration tests still pass
- Normal Claude Code session: full worktree creation + 4-option finishing still works

View File

@@ -148,7 +148,7 @@ exit /b
CMDBLOCK
# Unix shell runs from here
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
SCRIPT_NAME="$1"
shift
"${SCRIPT_DIR}/${SCRIPT_NAME}" "$@"

View File

@@ -1,6 +1,6 @@
{
"name": "superpowers",
"description": "Core skills library: TDD, debugging, collaboration patterns, and proven techniques",
"version": "5.0.0",
"version": "5.0.7",
"contextFileName": "GEMINI.md"
}

10
hooks/hooks-cursor.json Normal file
View File

@@ -0,0 +1,10 @@
{
"version": 1,
"hooks": {
"sessionStart": [
{
"command": "./hooks/session-start"
}
]
}
}

View File

@@ -2,7 +2,7 @@
"hooks": {
"SessionStart": [
{
"matcher": "startup|resume|clear|compact",
"matcher": "startup|clear|compact",
"hooks": [
{
"type": "command",

View File

@@ -4,7 +4,7 @@
set -euo pipefail
# Determine plugin root directory
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PLUGIN_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
# Check if legacy skills directory exists and build warning
@@ -35,27 +35,23 @@ warning_escaped=$(escape_for_json "$warning_message")
session_context="<EXTREMELY_IMPORTANT>\nYou have superpowers.\n\n**Below is the full content of your 'superpowers:using-superpowers' skill - your introduction to using skills. For all other skills, use the 'Skill' tool:**\n\n${using_superpowers_escaped}\n\n${warning_escaped}\n</EXTREMELY_IMPORTANT>"
# Output context injection as JSON.
# Cursor hooks expect additional_context.
# Claude Code hooks expect hookSpecificOutput.additionalContext.
# Claude Code reads BOTH fields without deduplication, so we must only
# emit the field consumed by the current platform to avoid double injection.
if [ -n "${CLAUDE_PLUGIN_ROOT:-}" ]; then
# Claude Code sets CLAUDE_PLUGIN_ROOT — emit only hookSpecificOutput
cat <<EOF
{
"hookSpecificOutput": {
"hookEventName": "SessionStart",
"additionalContext": "${session_context}"
}
}
EOF
# Cursor hooks expect additional_context (snake_case).
# Claude Code hooks expect hookSpecificOutput.additionalContext (nested).
# Copilot CLI (v1.0.11+) and others expect additionalContext (top-level, SDK standard).
# Claude Code reads BOTH additional_context and hookSpecificOutput without
# deduplication, so we must emit only the field the current platform consumes.
#
# Uses printf instead of heredoc to work around bash 5.3+ heredoc hang.
# See: https://github.com/obra/superpowers/issues/571
if [ -n "${CURSOR_PLUGIN_ROOT:-}" ]; then
# Cursor sets CURSOR_PLUGIN_ROOT (may also set CLAUDE_PLUGIN_ROOT)
printf '{\n "additional_context": "%s"\n}\n' "$session_context"
elif [ -n "${CLAUDE_PLUGIN_ROOT:-}" ] && [ -z "${COPILOT_CLI:-}" ]; then
# Claude Code sets CLAUDE_PLUGIN_ROOT without COPILOT_CLI
printf '{\n "hookSpecificOutput": {\n "hookEventName": "SessionStart",\n "additionalContext": "%s"\n }\n}\n' "$session_context"
else
# Other platforms (Cursor, etc.) — emit only additional_context
cat <<EOF
{
"additional_context": "${session_context}"
}
EOF
# Copilot CLI (sets COPILOT_CLI=1) or unknown platform — SDK standard format
printf '{\n "additionalContext": "%s"\n}\n' "$session_context"
fi
exit 0

6
package.json Normal file
View File

@@ -0,0 +1,6 @@
{
"name": "superpowers",
"version": "5.0.7",
"type": "module",
"main": ".opencode/plugins/superpowers.js"
}

220
scripts/bump-version.sh Executable file
View File

@@ -0,0 +1,220 @@
#!/usr/bin/env bash
#
# bump-version.sh — bump version numbers across all declared files,
# with drift detection and repo-wide audit for missed files.
#
# Usage:
# bump-version.sh <new-version> Bump all declared files to new version
# bump-version.sh --check Report current versions (detect drift)
# bump-version.sh --audit Check + grep repo for old version strings
#
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
CONFIG="$REPO_ROOT/.version-bump.json"
if [[ ! -f "$CONFIG" ]]; then
echo "error: .version-bump.json not found at $CONFIG" >&2
exit 1
fi
# --- helpers ---
# Read a dotted field path from a JSON file.
# Handles both simple ("version") and nested ("plugins.0.version") paths.
read_json_field() {
local file="$1" field="$2"
# Convert dot-path to jq path: "plugins.0.version" -> .plugins[0].version
local jq_path
jq_path=$(echo "$field" | sed -E 's/\.([0-9]+)/[\1]/g' | sed 's/^/./' | sed 's/\.\././g')
jq -r "$jq_path" "$file"
}
# Write a dotted field path in a JSON file, preserving formatting.
write_json_field() {
local file="$1" field="$2" value="$3"
local jq_path
jq_path=$(echo "$field" | sed -E 's/\.([0-9]+)/[\1]/g' | sed 's/^/./' | sed 's/\.\././g')
local tmp="${file}.tmp"
jq "$jq_path = \"$value\"" "$file" > "$tmp" && mv "$tmp" "$file"
}
# Read the list of declared files from config.
# Outputs lines of "path<TAB>field"
declared_files() {
jq -r '.files[] | "\(.path)\t\(.field)"' "$CONFIG"
}
# Read the audit exclude patterns from config.
audit_excludes() {
jq -r '.audit.exclude[]' "$CONFIG" 2>/dev/null
}
# --- commands ---
cmd_check() {
local has_drift=0
local versions=()
echo "Version check:"
echo ""
while IFS=$'\t' read -r path field; do
local fullpath="$REPO_ROOT/$path"
if [[ ! -f "$fullpath" ]]; then
printf " %-45s MISSING\n" "$path ($field)"
has_drift=1
continue
fi
local ver
ver=$(read_json_field "$fullpath" "$field")
printf " %-45s %s\n" "$path ($field)" "$ver"
versions+=("$ver")
done < <(declared_files)
echo ""
# Check if all versions match
local unique
unique=$(printf '%s\n' "${versions[@]}" | sort -u | wc -l | tr -d ' ')
if [[ "$unique" -gt 1 ]]; then
echo "DRIFT DETECTED — versions are not in sync:"
printf '%s\n' "${versions[@]}" | sort | uniq -c | sort -rn | while read -r count ver; do
echo " $ver ($count files)"
done
has_drift=1
else
echo "All declared files are in sync at ${versions[0]}"
fi
return $has_drift
}
cmd_audit() {
# First run check
cmd_check || true
echo ""
# Determine the current version (most common across declared files)
local current_version
current_version=$(
while IFS=$'\t' read -r path field; do
local fullpath="$REPO_ROOT/$path"
[[ -f "$fullpath" ]] && read_json_field "$fullpath" "$field"
done < <(declared_files) | sort | uniq -c | sort -rn | head -1 | awk '{print $2}'
)
if [[ -z "$current_version" ]]; then
echo "error: could not determine current version" >&2
return 1
fi
echo "Audit: scanning repo for version string '$current_version'..."
echo ""
# Build grep exclude args
local -a exclude_args=()
while IFS= read -r pattern; do
exclude_args+=("--exclude=$pattern" "--exclude-dir=$pattern")
done < <(audit_excludes)
# Also always exclude binary files and .git
exclude_args+=("--exclude-dir=.git" "--exclude-dir=node_modules" "--binary-files=without-match")
# Get list of declared paths for comparison
local -a declared_paths=()
while IFS=$'\t' read -r path _field; do
declared_paths+=("$path")
done < <(declared_files)
# Grep for the version string
local found_undeclared=0
while IFS= read -r match; do
local match_file
match_file=$(echo "$match" | cut -d: -f1)
# Make path relative to repo root
local rel_path="${match_file#$REPO_ROOT/}"
# Check if this file is in the declared list
local is_declared=0
for dp in "${declared_paths[@]}"; do
if [[ "$rel_path" == "$dp" ]]; then
is_declared=1
break
fi
done
if [[ "$is_declared" -eq 0 ]]; then
if [[ "$found_undeclared" -eq 0 ]]; then
echo "UNDECLARED files containing '$current_version':"
found_undeclared=1
fi
echo " $match"
fi
done < <(grep -rn "${exclude_args[@]}" -F "$current_version" "$REPO_ROOT" 2>/dev/null || true)
if [[ "$found_undeclared" -eq 0 ]]; then
echo "No undeclared files contain the version string. All clear."
else
echo ""
echo "Review the above files — if they should be bumped, add them to .version-bump.json"
echo "If they should be skipped, add them to the audit.exclude list."
fi
}
cmd_bump() {
local new_version="$1"
# Validate semver-ish format
if ! echo "$new_version" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+'; then
echo "error: '$new_version' doesn't look like a version (expected X.Y.Z)" >&2
exit 1
fi
echo "Bumping all declared files to $new_version..."
echo ""
while IFS=$'\t' read -r path field; do
local fullpath="$REPO_ROOT/$path"
if [[ ! -f "$fullpath" ]]; then
echo " SKIP (missing): $path"
continue
fi
local old_ver
old_ver=$(read_json_field "$fullpath" "$field")
write_json_field "$fullpath" "$field" "$new_version"
printf " %-45s %s -> %s\n" "$path ($field)" "$old_ver" "$new_version"
done < <(declared_files)
echo ""
echo "Done. Running audit to check for missed files..."
echo ""
cmd_audit
}
# --- main ---
case "${1:-}" in
--check)
cmd_check
;;
--audit)
cmd_audit
;;
--help|-h|"")
echo "Usage: bump-version.sh <new-version> | --check | --audit"
echo ""
echo " <new-version> Bump all declared files to the given version"
echo " --check Show current versions, detect drift"
echo " --audit Check + scan repo for undeclared version references"
exit 0
;;
--*)
echo "error: unknown flag '$1'" >&2
exit 1
;;
*)
cmd_bump "$1"
;;
esac

388
scripts/sync-to-codex-plugin.sh Executable file
View File

@@ -0,0 +1,388 @@
#!/usr/bin/env bash
#
# sync-to-codex-plugin.sh
#
# Sync this superpowers checkout → prime-radiant-inc/openai-codex-plugins.
# Clones the fork fresh into a temp dir, rsyncs upstream content, regenerates
# the Codex overlay file (.codex-plugin/plugin.json) inline, commits, pushes a
# sync branch, and opens a PR.
# Path/user agnostic — auto-detects upstream from script location.
#
# Deterministic: running twice against the same upstream SHA produces PRs with
# identical diffs, so two back-to-back runs can verify the tool itself.
#
# Usage:
# ./scripts/sync-to-codex-plugin.sh # full run
# ./scripts/sync-to-codex-plugin.sh -n # dry run
# ./scripts/sync-to-codex-plugin.sh -y # skip confirm
# ./scripts/sync-to-codex-plugin.sh --local PATH # existing checkout
# ./scripts/sync-to-codex-plugin.sh --base BRANCH # default: main
# ./scripts/sync-to-codex-plugin.sh --bootstrap --assets-src DIR # create initial plugin
#
# Bootstrap mode: skips the "plugin must exist on base" check and seeds
# plugins/superpowers/assets/ from --assets-src <dir> which must contain
# PrimeRadiant_Favicon.svg and PrimeRadiant_Favicon.png. Run once by one
# team member to create the initial PR; every subsequent run is a normal
# (non-bootstrap) sync.
#
# Requires: bash, rsync, git, gh (authenticated), python3.
set -euo pipefail
# =============================================================================
# Config — edit as upstream or canonical plugin shape evolves
# =============================================================================
FORK="prime-radiant-inc/openai-codex-plugins"
DEFAULT_BASE="main"
DEST_REL="plugins/superpowers"
# Paths in upstream that should NOT land in the embedded plugin.
# The Codex-only paths are here too — they're managed by generate/bootstrap
# steps, not by rsync.
#
# All patterns use a leading "/" to anchor them to the source root.
# Unanchored patterns like "scripts/" would match any directory named
# "scripts" at any depth — including legitimate nested dirs like
# skills/brainstorming/scripts/. Anchoring prevents that.
# (.DS_Store is intentionally unanchored — Finder creates them everywhere.)
EXCLUDES=(
# Dotfiles and infra — top-level only
"/.claude/"
"/.claude-plugin/"
"/.codex/"
"/.cursor-plugin/"
"/.git/"
"/.gitattributes"
"/.github/"
"/.gitignore"
"/.opencode/"
"/.version-bump.json"
"/.worktrees/"
".DS_Store"
# Root ceremony files
"/AGENTS.md"
"/CHANGELOG.md"
"/CLAUDE.md"
"/GEMINI.md"
"/RELEASE-NOTES.md"
"/gemini-extension.json"
"/package.json"
# Directories not shipped by canonical Codex plugins
"/commands/"
"/docs/"
"/hooks/"
"/lib/"
"/scripts/"
"/tests/"
"/tmp/"
# Codex-only paths — managed outside rsync
"/.codex-plugin/"
"/assets/"
)
# =============================================================================
# Generated overlay file
# =============================================================================
# Writes the Codex plugin manifest to "$1" with the given upstream version.
# Args: dest_path, version
generate_plugin_json() {
local dest="$1"
local version="$2"
mkdir -p "$(dirname "$dest")"
cat > "$dest" <<EOF
{
"name": "superpowers",
"version": "$version",
"description": "An agentic skills framework & software development methodology that works: planning, TDD, debugging, and collaboration workflows.",
"author": {
"name": "Jesse Vincent",
"email": "jesse@fsck.com",
"url": "https://github.com/obra"
},
"homepage": "https://github.com/obra/superpowers",
"repository": "https://github.com/obra/superpowers",
"license": "MIT",
"keywords": [
"brainstorming",
"subagent-driven-development",
"skills",
"planning",
"tdd",
"debugging",
"code-review",
"workflow"
],
"skills": "./skills/",
"interface": {
"displayName": "Superpowers",
"shortDescription": "Planning, TDD, debugging, and delivery workflows for coding agents",
"longDescription": "Use Superpowers to guide agent work through brainstorming, implementation planning, test-driven development, systematic debugging, parallel execution, code review, and finish-the-branch workflows.",
"developerName": "Jesse Vincent",
"category": "Coding",
"capabilities": [
"Interactive",
"Read",
"Write"
],
"defaultPrompt": [
"I've got an idea for something I'd like to build.",
"Let's add a feature to this project."
],
"brandColor": "#F59E0B",
"composerIcon": "./assets/superpowers-small.svg",
"logo": "./assets/app-icon.png",
"screenshots": []
}
}
EOF
}
# =============================================================================
# Args
# =============================================================================
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
UPSTREAM="$(cd "$SCRIPT_DIR/.." && pwd)"
BASE="$DEFAULT_BASE"
DRY_RUN=0
YES=0
LOCAL_CHECKOUT=""
BOOTSTRAP=0
ASSETS_SRC=""
usage() {
sed -n 's/^# \{0,1\}//;2,27p' "$0"
exit "${1:-0}"
}
while [[ $# -gt 0 ]]; do
case "$1" in
-n|--dry-run) DRY_RUN=1; shift ;;
-y|--yes) YES=1; shift ;;
--local) LOCAL_CHECKOUT="$2"; shift 2 ;;
--base) BASE="$2"; shift 2 ;;
--bootstrap) BOOTSTRAP=1; shift ;;
--assets-src) ASSETS_SRC="$2"; shift 2 ;;
-h|--help) usage 0 ;;
*) echo "Unknown arg: $1" >&2; usage 2 ;;
esac
done
# =============================================================================
# Preflight
# =============================================================================
die() { echo "ERROR: $*" >&2; exit 1; }
command -v rsync >/dev/null || die "rsync not found in PATH"
command -v git >/dev/null || die "git not found in PATH"
command -v gh >/dev/null || die "gh not found — install GitHub CLI"
command -v python3 >/dev/null || die "python3 not found in PATH"
gh auth status >/dev/null 2>&1 || die "gh not authenticated — run 'gh auth login'"
[[ -d "$UPSTREAM/.git" ]] || die "upstream '$UPSTREAM' is not a git checkout"
[[ -f "$UPSTREAM/package.json" ]] || die "upstream has no package.json — cannot read version"
# Bootstrap-mode validation
if [[ $BOOTSTRAP -eq 1 ]]; then
[[ -n "$ASSETS_SRC" ]] || die "--bootstrap requires --assets-src <path>"
ASSETS_SRC="$(cd "$ASSETS_SRC" 2>/dev/null && pwd)" || die "assets source '$ASSETS_SRC' is not a directory"
[[ -f "$ASSETS_SRC/PrimeRadiant_Favicon.svg" ]] || die "assets source missing PrimeRadiant_Favicon.svg"
[[ -f "$ASSETS_SRC/PrimeRadiant_Favicon.png" ]] || die "assets source missing PrimeRadiant_Favicon.png"
fi
# Read the upstream version from package.json
UPSTREAM_VERSION="$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1]))["version"])' "$UPSTREAM/package.json")"
[[ -n "$UPSTREAM_VERSION" ]] || die "could not read 'version' from upstream package.json"
UPSTREAM_BRANCH="$(cd "$UPSTREAM" && git branch --show-current)"
UPSTREAM_SHA="$(cd "$UPSTREAM" && git rev-parse HEAD)"
UPSTREAM_SHORT="$(cd "$UPSTREAM" && git rev-parse --short HEAD)"
confirm() {
[[ $YES -eq 1 ]] && return 0
read -rp "$1 [y/N] " ans
[[ "$ans" == "y" || "$ans" == "Y" ]]
}
if [[ "$UPSTREAM_BRANCH" != "main" ]]; then
echo "WARNING: upstream is on '$UPSTREAM_BRANCH', not 'main'"
confirm "Sync from '$UPSTREAM_BRANCH' anyway?" || exit 1
fi
UPSTREAM_STATUS="$(cd "$UPSTREAM" && git status --porcelain)"
if [[ -n "$UPSTREAM_STATUS" ]]; then
echo "WARNING: upstream has uncommitted changes:"
echo "$UPSTREAM_STATUS" | sed 's/^/ /'
echo "Sync will use working-tree state, not HEAD ($UPSTREAM_SHORT)."
confirm "Continue anyway?" || exit 1
fi
# =============================================================================
# Prepare destination (clone fork fresh, or use --local)
# =============================================================================
CLEANUP_DIR=""
cleanup() {
[[ -n "$CLEANUP_DIR" ]] && rm -rf "$CLEANUP_DIR"
}
trap cleanup EXIT
if [[ -n "$LOCAL_CHECKOUT" ]]; then
DEST_REPO="$(cd "$LOCAL_CHECKOUT" && pwd)"
[[ -d "$DEST_REPO/.git" ]] || die "--local path '$DEST_REPO' is not a git checkout"
else
echo "Cloning $FORK..."
CLEANUP_DIR="$(mktemp -d)"
DEST_REPO="$CLEANUP_DIR/openai-codex-plugins"
gh repo clone "$FORK" "$DEST_REPO" >/dev/null
fi
DEST="$DEST_REPO/$DEST_REL"
# Checkout base branch
cd "$DEST_REPO"
git checkout -q "$BASE" 2>/dev/null || die "base branch '$BASE' doesn't exist in $FORK"
# Plugin-existence check depends on mode
if [[ $BOOTSTRAP -eq 1 ]]; then
[[ ! -d "$DEST" ]] || die "--bootstrap but base branch '$BASE' already has '$DEST_REL/' — use normal sync instead"
mkdir -p "$DEST"
else
[[ -d "$DEST" ]] || die "base branch '$BASE' has no '$DEST_REL/' — use --bootstrap + --assets-src, or pass --base <branch>"
fi
# =============================================================================
# Create sync branch
# =============================================================================
TIMESTAMP="$(date -u +%Y%m%d-%H%M%S)"
if [[ $BOOTSTRAP -eq 1 ]]; then
SYNC_BRANCH="bootstrap/superpowers-${UPSTREAM_SHORT}-${TIMESTAMP}"
else
SYNC_BRANCH="sync/superpowers-${UPSTREAM_SHORT}-${TIMESTAMP}"
fi
git checkout -q -b "$SYNC_BRANCH"
# =============================================================================
# Build rsync args
# =============================================================================
RSYNC_ARGS=(-av --delete)
for pat in "${EXCLUDES[@]}"; do RSYNC_ARGS+=(--exclude="$pat"); done
# =============================================================================
# Dry run preview (always shown)
# =============================================================================
echo ""
echo "Upstream: $UPSTREAM ($UPSTREAM_BRANCH @ $UPSTREAM_SHORT)"
echo "Version: $UPSTREAM_VERSION"
echo "Fork: $FORK"
echo "Base: $BASE"
echo "Branch: $SYNC_BRANCH"
if [[ $BOOTSTRAP -eq 1 ]]; then
echo "Mode: BOOTSTRAP (creating initial plugin from scratch)"
echo "Assets: $ASSETS_SRC"
fi
echo ""
echo "=== Preview (rsync --dry-run) ==="
rsync "${RSYNC_ARGS[@]}" --dry-run --itemize-changes "$UPSTREAM/" "$DEST/"
echo "=== End preview ==="
echo ""
echo "Overlay file (.codex-plugin/plugin.json) will be regenerated with"
echo "version $UPSTREAM_VERSION regardless of rsync output."
if [[ $BOOTSTRAP -eq 1 ]]; then
echo "Assets (superpowers-small.svg, app-icon.png) will be seeded from:"
echo " $ASSETS_SRC"
fi
if [[ $DRY_RUN -eq 1 ]]; then
echo ""
echo "Dry run only. Nothing was changed or pushed."
exit 0
fi
# =============================================================================
# Apply
# =============================================================================
echo ""
confirm "Apply changes, push branch, and open PR?" || { echo "Aborted."; exit 1; }
echo ""
echo "Syncing upstream content..."
rsync "${RSYNC_ARGS[@]}" "$UPSTREAM/" "$DEST/"
if [[ $BOOTSTRAP -eq 1 ]]; then
echo "Seeding brand assets..."
mkdir -p "$DEST/assets"
cp "$ASSETS_SRC/PrimeRadiant_Favicon.svg" "$DEST/assets/superpowers-small.svg"
cp "$ASSETS_SRC/PrimeRadiant_Favicon.png" "$DEST/assets/app-icon.png"
fi
echo "Regenerating overlay file..."
generate_plugin_json "$DEST/.codex-plugin/plugin.json" "$UPSTREAM_VERSION"
# Bail early if nothing actually changed
cd "$DEST_REPO"
if [[ -z "$(git status --porcelain "$DEST_REL")" ]]; then
echo "No changes — embedded plugin was already in sync with upstream $UPSTREAM_SHORT (v$UPSTREAM_VERSION)."
exit 0
fi
# =============================================================================
# Commit, push, open PR
# =============================================================================
git add "$DEST_REL"
if [[ $BOOTSTRAP -eq 1 ]]; then
COMMIT_TITLE="bootstrap superpowers v$UPSTREAM_VERSION from upstream main @ $UPSTREAM_SHORT"
PR_BODY="Initial bootstrap of the superpowers plugin from upstream \`main\` @ \`$UPSTREAM_SHORT\` (v$UPSTREAM_VERSION).
Creates \`plugins/superpowers/\` from scratch: upstream content via rsync, \`.codex-plugin/plugin.json\` regenerated inline, brand assets seeded from a local Brand Assets directory.
Run via: \`scripts/sync-to-codex-plugin.sh --bootstrap --assets-src <path>\`
Upstream commit: https://github.com/obra/superpowers/commit/$UPSTREAM_SHA
This is a one-time bootstrap. Subsequent syncs will be normal (non-bootstrap) runs and will not touch the \`assets/\` directory."
else
COMMIT_TITLE="sync superpowers v$UPSTREAM_VERSION from upstream main @ $UPSTREAM_SHORT"
PR_BODY="Automated sync from superpowers upstream \`main\` @ \`$UPSTREAM_SHORT\` (v$UPSTREAM_VERSION).
Run via: \`scripts/sync-to-codex-plugin.sh\`
Upstream commit: https://github.com/obra/superpowers/commit/$UPSTREAM_SHA
Running the sync tool again against the same upstream SHA should produce a PR with an identical diff — use that to verify the tool is behaving."
fi
git commit --quiet -m "$COMMIT_TITLE
Automated sync via scripts/sync-to-codex-plugin.sh
Upstream: https://github.com/obra/superpowers/commit/$UPSTREAM_SHA
Branch: $SYNC_BRANCH"
echo "Pushing $SYNC_BRANCH to $FORK..."
git push -u origin "$SYNC_BRANCH" --quiet
echo "Opening PR..."
PR_URL="$(gh pr create \
--repo "$FORK" \
--base "$BASE" \
--head "$SYNC_BRANCH" \
--title "$COMMIT_TITLE" \
--body "$PR_BODY")"
PR_NUM="${PR_URL##*/}"
DIFF_URL="https://github.com/$FORK/pull/$PR_NUM/files"
echo ""
echo "PR opened: $PR_URL"
echo "Diff view: $DIFF_URL"

View File

@@ -27,7 +27,7 @@ You MUST create a task for each of these items and complete them in order:
4. **Propose 2-3 approaches** — with trade-offs and your recommendation
5. **Present design** — in sections scaled to their complexity, get user approval after each section
6. **Write design doc** — save to `docs/superpowers/specs/YYYY-MM-DD-<topic>-design.md` and commit
7. **Spec review loop**dispatch spec-document-reviewer subagent; fix issues and re-dispatch until approved (max 5 iterations, then surface to human)
7. **Spec self-review**quick inline check for placeholders, contradictions, ambiguity, scope (see below)
8. **User reviews written spec** — ask user to review the spec file before proceeding
9. **Transition to implementation** — invoke writing-plans skill to create implementation plan
@@ -43,8 +43,7 @@ digraph brainstorming {
"Present design sections" [shape=box];
"User approves design?" [shape=diamond];
"Write design doc" [shape=box];
"Spec review loop" [shape=box];
"Spec review passed?" [shape=diamond];
"Spec self-review\n(fix inline)" [shape=box];
"User reviews spec?" [shape=diamond];
"Invoke writing-plans skill" [shape=doublecircle];
@@ -57,10 +56,8 @@ digraph brainstorming {
"Present design sections" -> "User approves design?";
"User approves design?" -> "Present design sections" [label="no, revise"];
"User approves design?" -> "Write design doc" [label="yes"];
"Write design doc" -> "Spec review loop";
"Spec review loop" -> "Spec review passed?";
"Spec review passed?" -> "Spec review loop" [label="issues found,\nfix and re-dispatch"];
"Spec review passed?" -> "User reviews spec?" [label="approved"];
"Write design doc" -> "Spec self-review\n(fix inline)";
"Spec self-review\n(fix inline)" -> "User reviews spec?";
"User reviews spec?" -> "Write design doc" [label="changes requested"];
"User reviews spec?" -> "Invoke writing-plans skill" [label="approved"];
}
@@ -116,12 +113,15 @@ digraph brainstorming {
- Use elements-of-style:writing-clearly-and-concisely skill if available
- Commit the design document to git
**Spec Review Loop:**
After writing the spec document:
**Spec Self-Review:**
After writing the spec document, look at it with fresh eyes:
1. Dispatch spec-document-reviewer subagent (see spec-document-reviewer-prompt.md)
2. If Issues Found: fix, re-dispatch, repeat until Approved
3. If loop exceeds 5 iterations, surface to human for guidance
1. **Placeholder scan:** Any "TBD", "TODO", incomplete sections, or vague requirements? Fix them.
2. **Internal consistency:** Do any sections contradict each other? Does the architecture match the feature descriptions?
3. **Scope check:** Is this focused enough for a single implementation plan, or does it need decomposition?
4. **Ambiguity check:** Could any requirement be interpreted two different ways? If so, pick one and make it explicit.
Fix any issues inline. No need to re-review — just fix and move on.
**User Review Gate:**
After the spec review loop passes, ask the user to review the written spec before proceeding:

View File

@@ -1,6 +1,7 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Superpowers Brainstorming</title>
<style>
/*

View File

@@ -1,147 +0,0 @@
const express = require('express');
const http = require('http');
const WebSocket = require('ws');
const chokidar = require('chokidar');
const fs = require('fs');
const path = require('path');
const PORT = process.env.BRAINSTORM_PORT || (49152 + Math.floor(Math.random() * 16383));
const HOST = process.env.BRAINSTORM_HOST || '127.0.0.1';
const URL_HOST = process.env.BRAINSTORM_URL_HOST || (HOST === '127.0.0.1' ? 'localhost' : HOST);
const SCREEN_DIR = process.env.BRAINSTORM_DIR || '/tmp/brainstorm';
if (!fs.existsSync(SCREEN_DIR)) {
fs.mkdirSync(SCREEN_DIR, { recursive: true });
}
// Load frame template and helper script once at startup
const frameTemplate = fs.readFileSync(path.join(__dirname, 'frame-template.html'), 'utf-8');
const helperScript = fs.readFileSync(path.join(__dirname, 'helper.js'), 'utf-8');
const helperInjection = `<script>\n${helperScript}\n</script>`;
// Detect whether content is a full HTML document or a bare fragment
function isFullDocument(html) {
const trimmed = html.trimStart().toLowerCase();
return trimmed.startsWith('<!doctype') || trimmed.startsWith('<html');
}
// Wrap a content fragment in the frame template
function wrapInFrame(content) {
return frameTemplate.replace('<!-- CONTENT -->', content);
}
// Find the newest .html file in the directory by mtime
function getNewestScreen() {
const files = fs.readdirSync(SCREEN_DIR)
.filter(f => f.endsWith('.html'))
.map(f => ({
name: f,
path: path.join(SCREEN_DIR, f),
mtime: fs.statSync(path.join(SCREEN_DIR, f)).mtime.getTime()
}))
.sort((a, b) => b.mtime - a.mtime);
return files.length > 0 ? files[0].path : null;
}
const WAITING_PAGE = `<!DOCTYPE html>
<html>
<head>
<title>Brainstorm Companion</title>
<style>
body { font-family: system-ui, sans-serif; padding: 2rem; max-width: 800px; margin: 0 auto; }
h1 { color: #333; }
p { color: #666; }
</style>
</head>
<body>
<h1>Brainstorm Companion</h1>
<p>Waiting for Claude to push a screen...</p>
</body>
</html>`;
const app = express();
const server = http.createServer(app);
const wss = new WebSocket.Server({ server });
const clients = new Set();
wss.on('connection', (ws) => {
clients.add(ws);
ws.on('close', () => clients.delete(ws));
ws.on('message', (data) => {
const event = JSON.parse(data.toString());
console.log(JSON.stringify({ source: 'user-event', ...event }));
// Write user events to .events file for Claude to read
if (event.choice) {
const eventsFile = path.join(SCREEN_DIR, '.events');
fs.appendFileSync(eventsFile, JSON.stringify(event) + '\n');
}
});
});
// Serve newest screen with helper.js injected
app.get('/', (req, res) => {
const screenFile = getNewestScreen();
let html;
if (!screenFile) {
html = WAITING_PAGE;
} else {
const raw = fs.readFileSync(screenFile, 'utf-8');
html = isFullDocument(raw) ? raw : wrapInFrame(raw);
}
// Inject helper script
if (html.includes('</body>')) {
html = html.replace('</body>', `${helperInjection}\n</body>`);
} else {
html += helperInjection;
}
res.type('html').send(html);
});
// Watch for new or changed .html files
chokidar.watch(SCREEN_DIR, { ignoreInitial: true })
.on('add', (filePath) => {
if (filePath.endsWith('.html')) {
// Clear events from previous screen
const eventsFile = path.join(SCREEN_DIR, '.events');
if (fs.existsSync(eventsFile)) fs.unlinkSync(eventsFile);
console.log(JSON.stringify({ type: 'screen-added', file: filePath }));
clients.forEach(ws => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ type: 'reload' }));
}
});
}
})
.on('change', (filePath) => {
if (filePath.endsWith('.html')) {
console.log(JSON.stringify({ type: 'screen-updated', file: filePath }));
clients.forEach(ws => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ type: 'reload' }));
}
});
}
});
server.listen(PORT, HOST, () => {
const info = JSON.stringify({
type: 'server-started',
port: PORT,
host: HOST,
url_host: URL_HOST,
url: `http://${URL_HOST}:${PORT}`,
screen_dir: SCREEN_DIR
});
console.log(info);
// Write to .server-info so agents can find connection details
// even when stdout is hidden (e.g. background execution)
const fs = require('fs');
const path = require('path');
fs.writeFileSync(path.join(SCREEN_DIR, '.server-info'), info + '\n');
});

View File

@@ -1 +0,0 @@
../mime/cli.js

File diff suppressed because it is too large Load Diff

View File

@@ -1,243 +0,0 @@
1.3.8 / 2022-02-02
==================
* deps: mime-types@~2.1.34
- deps: mime-db@~1.51.0
* deps: negotiator@0.6.3
1.3.7 / 2019-04-29
==================
* deps: negotiator@0.6.2
- Fix sorting charset, encoding, and language with extra parameters
1.3.6 / 2019-04-28
==================
* deps: mime-types@~2.1.24
- deps: mime-db@~1.40.0
1.3.5 / 2018-02-28
==================
* deps: mime-types@~2.1.18
- deps: mime-db@~1.33.0
1.3.4 / 2017-08-22
==================
* deps: mime-types@~2.1.16
- deps: mime-db@~1.29.0
1.3.3 / 2016-05-02
==================
* deps: mime-types@~2.1.11
- deps: mime-db@~1.23.0
* deps: negotiator@0.6.1
- perf: improve `Accept` parsing speed
- perf: improve `Accept-Charset` parsing speed
- perf: improve `Accept-Encoding` parsing speed
- perf: improve `Accept-Language` parsing speed
1.3.2 / 2016-03-08
==================
* deps: mime-types@~2.1.10
- Fix extension of `application/dash+xml`
- Update primary extension for `audio/mp4`
- deps: mime-db@~1.22.0
1.3.1 / 2016-01-19
==================
* deps: mime-types@~2.1.9
- deps: mime-db@~1.21.0
1.3.0 / 2015-09-29
==================
* deps: mime-types@~2.1.7
- deps: mime-db@~1.19.0
* deps: negotiator@0.6.0
- Fix including type extensions in parameters in `Accept` parsing
- Fix parsing `Accept` parameters with quoted equals
- Fix parsing `Accept` parameters with quoted semicolons
- Lazy-load modules from main entry point
- perf: delay type concatenation until needed
- perf: enable strict mode
- perf: hoist regular expressions
- perf: remove closures getting spec properties
- perf: remove a closure from media type parsing
- perf: remove property delete from media type parsing
1.2.13 / 2015-09-06
===================
* deps: mime-types@~2.1.6
- deps: mime-db@~1.18.0
1.2.12 / 2015-07-30
===================
* deps: mime-types@~2.1.4
- deps: mime-db@~1.16.0
1.2.11 / 2015-07-16
===================
* deps: mime-types@~2.1.3
- deps: mime-db@~1.15.0
1.2.10 / 2015-07-01
===================
* deps: mime-types@~2.1.2
- deps: mime-db@~1.14.0
1.2.9 / 2015-06-08
==================
* deps: mime-types@~2.1.1
- perf: fix deopt during mapping
1.2.8 / 2015-06-07
==================
* deps: mime-types@~2.1.0
- deps: mime-db@~1.13.0
* perf: avoid argument reassignment & argument slice
* perf: avoid negotiator recursive construction
* perf: enable strict mode
* perf: remove unnecessary bitwise operator
1.2.7 / 2015-05-10
==================
* deps: negotiator@0.5.3
- Fix media type parameter matching to be case-insensitive
1.2.6 / 2015-05-07
==================
* deps: mime-types@~2.0.11
- deps: mime-db@~1.9.1
* deps: negotiator@0.5.2
- Fix comparing media types with quoted values
- Fix splitting media types with quoted commas
1.2.5 / 2015-03-13
==================
* deps: mime-types@~2.0.10
- deps: mime-db@~1.8.0
1.2.4 / 2015-02-14
==================
* Support Node.js 0.6
* deps: mime-types@~2.0.9
- deps: mime-db@~1.7.0
* deps: negotiator@0.5.1
- Fix preference sorting to be stable for long acceptable lists
1.2.3 / 2015-01-31
==================
* deps: mime-types@~2.0.8
- deps: mime-db@~1.6.0
1.2.2 / 2014-12-30
==================
* deps: mime-types@~2.0.7
- deps: mime-db@~1.5.0
1.2.1 / 2014-12-30
==================
* deps: mime-types@~2.0.5
- deps: mime-db@~1.3.1
1.2.0 / 2014-12-19
==================
* deps: negotiator@0.5.0
- Fix list return order when large accepted list
- Fix missing identity encoding when q=0 exists
- Remove dynamic building of Negotiator class
1.1.4 / 2014-12-10
==================
* deps: mime-types@~2.0.4
- deps: mime-db@~1.3.0
1.1.3 / 2014-11-09
==================
* deps: mime-types@~2.0.3
- deps: mime-db@~1.2.0
1.1.2 / 2014-10-14
==================
* deps: negotiator@0.4.9
- Fix error when media type has invalid parameter
1.1.1 / 2014-09-28
==================
* deps: mime-types@~2.0.2
- deps: mime-db@~1.1.0
* deps: negotiator@0.4.8
- Fix all negotiations to be case-insensitive
- Stable sort preferences of same quality according to client order
1.1.0 / 2014-09-02
==================
* update `mime-types`
1.0.7 / 2014-07-04
==================
* Fix wrong type returned from `type` when match after unknown extension
1.0.6 / 2014-06-24
==================
* deps: negotiator@0.4.7
1.0.5 / 2014-06-20
==================
* fix crash when unknown extension given
1.0.4 / 2014-06-19
==================
* use `mime-types`
1.0.3 / 2014-06-11
==================
* deps: negotiator@0.4.6
- Order by specificity when quality is the same
1.0.2 / 2014-05-29
==================
* Fix interpretation when header not in request
* deps: pin negotiator@0.4.5
1.0.1 / 2014-01-18
==================
* Identity encoding isn't always acceptable
* deps: negotiator@~0.4.0
1.0.0 / 2013-12-27
==================
* Genesis

View File

@@ -1,23 +0,0 @@
(The MIT License)
Copyright (c) 2014 Jonathan Ong <me@jongleberry.com>
Copyright (c) 2015 Douglas Christopher Wilson <doug@somethingdoug.com>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -1,140 +0,0 @@
# accepts
[![NPM Version][npm-version-image]][npm-url]
[![NPM Downloads][npm-downloads-image]][npm-url]
[![Node.js Version][node-version-image]][node-version-url]
[![Build Status][github-actions-ci-image]][github-actions-ci-url]
[![Test Coverage][coveralls-image]][coveralls-url]
Higher level content negotiation based on [negotiator](https://www.npmjs.com/package/negotiator).
Extracted from [koa](https://www.npmjs.com/package/koa) for general use.
In addition to negotiator, it allows:
- Allows types as an array or arguments list, ie `(['text/html', 'application/json'])`
as well as `('text/html', 'application/json')`.
- Allows type shorthands such as `json`.
- Returns `false` when no types match
- Treats non-existent headers as `*`
## Installation
This is a [Node.js](https://nodejs.org/en/) module available through the
[npm registry](https://www.npmjs.com/). Installation is done using the
[`npm install` command](https://docs.npmjs.com/getting-started/installing-npm-packages-locally):
```sh
$ npm install accepts
```
## API
```js
var accepts = require('accepts')
```
### accepts(req)
Create a new `Accepts` object for the given `req`.
#### .charset(charsets)
Return the first accepted charset. If nothing in `charsets` is accepted,
then `false` is returned.
#### .charsets()
Return the charsets that the request accepts, in the order of the client's
preference (most preferred first).
#### .encoding(encodings)
Return the first accepted encoding. If nothing in `encodings` is accepted,
then `false` is returned.
#### .encodings()
Return the encodings that the request accepts, in the order of the client's
preference (most preferred first).
#### .language(languages)
Return the first accepted language. If nothing in `languages` is accepted,
then `false` is returned.
#### .languages()
Return the languages that the request accepts, in the order of the client's
preference (most preferred first).
#### .type(types)
Return the first accepted type (and it is returned as the same text as what
appears in the `types` array). If nothing in `types` is accepted, then `false`
is returned.
The `types` array can contain full MIME types or file extensions. Any value
that is not a full MIME types is passed to `require('mime-types').lookup`.
#### .types()
Return the types that the request accepts, in the order of the client's
preference (most preferred first).
## Examples
### Simple type negotiation
This simple example shows how to use `accepts` to return a different typed
respond body based on what the client wants to accept. The server lists it's
preferences in order and will get back the best match between the client and
server.
```js
var accepts = require('accepts')
var http = require('http')
function app (req, res) {
var accept = accepts(req)
// the order of this list is significant; should be server preferred order
switch (accept.type(['json', 'html'])) {
case 'json':
res.setHeader('Content-Type', 'application/json')
res.write('{"hello":"world!"}')
break
case 'html':
res.setHeader('Content-Type', 'text/html')
res.write('<b>hello, world!</b>')
break
default:
// the fallback is text/plain, so no need to specify it above
res.setHeader('Content-Type', 'text/plain')
res.write('hello, world!')
break
}
res.end()
}
http.createServer(app).listen(3000)
```
You can test this out with the cURL program:
```sh
curl -I -H'Accept: text/html' http://localhost:3000/
```
## License
[MIT](LICENSE)
[coveralls-image]: https://badgen.net/coveralls/c/github/jshttp/accepts/master
[coveralls-url]: https://coveralls.io/r/jshttp/accepts?branch=master
[github-actions-ci-image]: https://badgen.net/github/checks/jshttp/accepts/master?label=ci
[github-actions-ci-url]: https://github.com/jshttp/accepts/actions/workflows/ci.yml
[node-version-image]: https://badgen.net/npm/node/accepts
[node-version-url]: https://nodejs.org/en/download
[npm-downloads-image]: https://badgen.net/npm/dm/accepts
[npm-url]: https://npmjs.org/package/accepts
[npm-version-image]: https://badgen.net/npm/v/accepts

View File

@@ -1,238 +0,0 @@
/*!
* accepts
* Copyright(c) 2014 Jonathan Ong
* Copyright(c) 2015 Douglas Christopher Wilson
* MIT Licensed
*/
'use strict'
/**
* Module dependencies.
* @private
*/
var Negotiator = require('negotiator')
var mime = require('mime-types')
/**
* Module exports.
* @public
*/
module.exports = Accepts
/**
* Create a new Accepts object for the given req.
*
* @param {object} req
* @public
*/
function Accepts (req) {
if (!(this instanceof Accepts)) {
return new Accepts(req)
}
this.headers = req.headers
this.negotiator = new Negotiator(req)
}
/**
* Check if the given `type(s)` is acceptable, returning
* the best match when true, otherwise `undefined`, in which
* case you should respond with 406 "Not Acceptable".
*
* The `type` value may be a single mime type string
* such as "application/json", the extension name
* such as "json" or an array `["json", "html", "text/plain"]`. When a list
* or array is given the _best_ match, if any is returned.
*
* Examples:
*
* // Accept: text/html
* this.types('html');
* // => "html"
*
* // Accept: text/*, application/json
* this.types('html');
* // => "html"
* this.types('text/html');
* // => "text/html"
* this.types('json', 'text');
* // => "json"
* this.types('application/json');
* // => "application/json"
*
* // Accept: text/*, application/json
* this.types('image/png');
* this.types('png');
* // => undefined
*
* // Accept: text/*;q=.5, application/json
* this.types(['html', 'json']);
* this.types('html', 'json');
* // => "json"
*
* @param {String|Array} types...
* @return {String|Array|Boolean}
* @public
*/
Accepts.prototype.type =
Accepts.prototype.types = function (types_) {
var types = types_
// support flattened arguments
if (types && !Array.isArray(types)) {
types = new Array(arguments.length)
for (var i = 0; i < types.length; i++) {
types[i] = arguments[i]
}
}
// no types, return all requested types
if (!types || types.length === 0) {
return this.negotiator.mediaTypes()
}
// no accept header, return first given type
if (!this.headers.accept) {
return types[0]
}
var mimes = types.map(extToMime)
var accepts = this.negotiator.mediaTypes(mimes.filter(validMime))
var first = accepts[0]
return first
? types[mimes.indexOf(first)]
: false
}
/**
* Return accepted encodings or best fit based on `encodings`.
*
* Given `Accept-Encoding: gzip, deflate`
* an array sorted by quality is returned:
*
* ['gzip', 'deflate']
*
* @param {String|Array} encodings...
* @return {String|Array}
* @public
*/
Accepts.prototype.encoding =
Accepts.prototype.encodings = function (encodings_) {
var encodings = encodings_
// support flattened arguments
if (encodings && !Array.isArray(encodings)) {
encodings = new Array(arguments.length)
for (var i = 0; i < encodings.length; i++) {
encodings[i] = arguments[i]
}
}
// no encodings, return all requested encodings
if (!encodings || encodings.length === 0) {
return this.negotiator.encodings()
}
return this.negotiator.encodings(encodings)[0] || false
}
/**
* Return accepted charsets or best fit based on `charsets`.
*
* Given `Accept-Charset: utf-8, iso-8859-1;q=0.2, utf-7;q=0.5`
* an array sorted by quality is returned:
*
* ['utf-8', 'utf-7', 'iso-8859-1']
*
* @param {String|Array} charsets...
* @return {String|Array}
* @public
*/
Accepts.prototype.charset =
Accepts.prototype.charsets = function (charsets_) {
var charsets = charsets_
// support flattened arguments
if (charsets && !Array.isArray(charsets)) {
charsets = new Array(arguments.length)
for (var i = 0; i < charsets.length; i++) {
charsets[i] = arguments[i]
}
}
// no charsets, return all requested charsets
if (!charsets || charsets.length === 0) {
return this.negotiator.charsets()
}
return this.negotiator.charsets(charsets)[0] || false
}
/**
* Return accepted languages or best fit based on `langs`.
*
* Given `Accept-Language: en;q=0.8, es, pt`
* an array sorted by quality is returned:
*
* ['es', 'pt', 'en']
*
* @param {String|Array} langs...
* @return {Array|String}
* @public
*/
Accepts.prototype.lang =
Accepts.prototype.langs =
Accepts.prototype.language =
Accepts.prototype.languages = function (languages_) {
var languages = languages_
// support flattened arguments
if (languages && !Array.isArray(languages)) {
languages = new Array(arguments.length)
for (var i = 0; i < languages.length; i++) {
languages[i] = arguments[i]
}
}
// no languages, return all requested languages
if (!languages || languages.length === 0) {
return this.negotiator.languages()
}
return this.negotiator.languages(languages)[0] || false
}
/**
* Convert extnames to mime.
*
* @param {String} type
* @return {String}
* @private
*/
function extToMime (type) {
return type.indexOf('/') === -1
? mime.lookup(type)
: type
}
/**
* Check if mime is valid.
*
* @param {String} type
* @return {String}
* @private
*/
function validMime (type) {
return typeof type === 'string'
}

View File

@@ -1,47 +0,0 @@
{
"name": "accepts",
"description": "Higher-level content negotiation",
"version": "1.3.8",
"contributors": [
"Douglas Christopher Wilson <doug@somethingdoug.com>",
"Jonathan Ong <me@jongleberry.com> (http://jongleberry.com)"
],
"license": "MIT",
"repository": "jshttp/accepts",
"dependencies": {
"mime-types": "~2.1.34",
"negotiator": "0.6.3"
},
"devDependencies": {
"deep-equal": "1.0.1",
"eslint": "7.32.0",
"eslint-config-standard": "14.1.1",
"eslint-plugin-import": "2.25.4",
"eslint-plugin-markdown": "2.2.1",
"eslint-plugin-node": "11.1.0",
"eslint-plugin-promise": "4.3.1",
"eslint-plugin-standard": "4.1.0",
"mocha": "9.2.0",
"nyc": "15.1.0"
},
"files": [
"LICENSE",
"HISTORY.md",
"index.js"
],
"engines": {
"node": ">= 0.6"
},
"scripts": {
"lint": "eslint .",
"test": "mocha --reporter spec --check-leaks --bail test/",
"test-ci": "nyc --reporter=lcov --reporter=text npm test",
"test-cov": "nyc --reporter=html --reporter=text npm test"
},
"keywords": [
"content",
"negotiation",
"accept",
"accepts"
]
}

View File

@@ -1,15 +0,0 @@
The ISC License
Copyright (c) 2019 Elan Shanker, Paul Miller (https://paulmillr.com)
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

View File

@@ -1,87 +0,0 @@
anymatch [![Build Status](https://travis-ci.org/micromatch/anymatch.svg?branch=master)](https://travis-ci.org/micromatch/anymatch) [![Coverage Status](https://img.shields.io/coveralls/micromatch/anymatch.svg?branch=master)](https://coveralls.io/r/micromatch/anymatch?branch=master)
======
Javascript module to match a string against a regular expression, glob, string,
or function that takes the string as an argument and returns a truthy or falsy
value. The matcher can also be an array of any or all of these. Useful for
allowing a very flexible user-defined config to define things like file paths.
__Note: This module has Bash-parity, please be aware that Windows-style backslashes are not supported as separators. See https://github.com/micromatch/micromatch#backslashes for more information.__
Usage
-----
```sh
npm install anymatch
```
#### anymatch(matchers, testString, [returnIndex], [options])
* __matchers__: (_Array|String|RegExp|Function_)
String to be directly matched, string with glob patterns, regular expression
test, function that takes the testString as an argument and returns a truthy
value if it should be matched, or an array of any number and mix of these types.
* __testString__: (_String|Array_) The string to test against the matchers. If
passed as an array, the first element of the array will be used as the
`testString` for non-function matchers, while the entire array will be applied
as the arguments for function matchers.
* __options__: (_Object_ [optional]_) Any of the [picomatch](https://github.com/micromatch/picomatch#options) options.
* __returnIndex__: (_Boolean [optional]_) If true, return the array index of
the first matcher that that testString matched, or -1 if no match, instead of a
boolean result.
```js
const anymatch = require('anymatch');
const matchers = [ 'path/to/file.js', 'path/anyjs/**/*.js', /foo.js$/, string => string.includes('bar') && string.length > 10 ] ;
anymatch(matchers, 'path/to/file.js'); // true
anymatch(matchers, 'path/anyjs/baz.js'); // true
anymatch(matchers, 'path/to/foo.js'); // true
anymatch(matchers, 'path/to/bar.js'); // true
anymatch(matchers, 'bar.js'); // false
// returnIndex = true
anymatch(matchers, 'foo.js', {returnIndex: true}); // 2
anymatch(matchers, 'path/anyjs/foo.js', {returnIndex: true}); // 1
// any picomatc
// using globs to match directories and their children
anymatch('node_modules', 'node_modules'); // true
anymatch('node_modules', 'node_modules/somelib/index.js'); // false
anymatch('node_modules/**', 'node_modules/somelib/index.js'); // true
anymatch('node_modules/**', '/absolute/path/to/node_modules/somelib/index.js'); // false
anymatch('**/node_modules/**', '/absolute/path/to/node_modules/somelib/index.js'); // true
const matcher = anymatch(matchers);
['foo.js', 'bar.js'].filter(matcher); // [ 'foo.js' ]
anymatch master*
```
#### anymatch(matchers)
You can also pass in only your matcher(s) to get a curried function that has
already been bound to the provided matching criteria. This can be used as an
`Array#filter` callback.
```js
var matcher = anymatch(matchers);
matcher('path/to/file.js'); // true
matcher('path/anyjs/baz.js', true); // 1
['foo.js', 'bar.js'].filter(matcher); // ['foo.js']
```
Changelog
----------
[See release notes page on GitHub](https://github.com/micromatch/anymatch/releases)
- **v3.0:** Removed `startIndex` and `endIndex` arguments. Node 8.x-only.
- **v2.0:** [micromatch](https://github.com/jonschlinkert/micromatch) moves away from minimatch-parity and inline with Bash. This includes handling backslashes differently (see https://github.com/micromatch/micromatch#backslashes for more information).
- **v1.2:** anymatch uses [micromatch](https://github.com/jonschlinkert/micromatch)
for glob pattern matching. Issues with glob pattern matching should be
reported directly to the [micromatch issue tracker](https://github.com/jonschlinkert/micromatch/issues).
License
-------
[ISC](https://raw.github.com/micromatch/anymatch/master/LICENSE)

View File

@@ -1,20 +0,0 @@
type AnymatchFn = (testString: string) => boolean;
type AnymatchPattern = string|RegExp|AnymatchFn;
type AnymatchMatcher = AnymatchPattern|AnymatchPattern[]
type AnymatchTester = {
(testString: string|any[], returnIndex: true): number;
(testString: string|any[]): boolean;
}
type PicomatchOptions = {dot: boolean};
declare const anymatch: {
(matchers: AnymatchMatcher): AnymatchTester;
(matchers: AnymatchMatcher, testString: null, returnIndex: true | PicomatchOptions): AnymatchTester;
(matchers: AnymatchMatcher, testString: string|any[], returnIndex: true | PicomatchOptions): number;
(matchers: AnymatchMatcher, testString: string|any[]): boolean;
}
export {AnymatchMatcher as Matcher}
export {AnymatchTester as Tester}
export default anymatch

View File

@@ -1,104 +0,0 @@
'use strict';
Object.defineProperty(exports, "__esModule", { value: true });
const picomatch = require('picomatch');
const normalizePath = require('normalize-path');
/**
* @typedef {(testString: string) => boolean} AnymatchFn
* @typedef {string|RegExp|AnymatchFn} AnymatchPattern
* @typedef {AnymatchPattern|AnymatchPattern[]} AnymatchMatcher
*/
const BANG = '!';
const DEFAULT_OPTIONS = {returnIndex: false};
const arrify = (item) => Array.isArray(item) ? item : [item];
/**
* @param {AnymatchPattern} matcher
* @param {object} options
* @returns {AnymatchFn}
*/
const createPattern = (matcher, options) => {
if (typeof matcher === 'function') {
return matcher;
}
if (typeof matcher === 'string') {
const glob = picomatch(matcher, options);
return (string) => matcher === string || glob(string);
}
if (matcher instanceof RegExp) {
return (string) => matcher.test(string);
}
return (string) => false;
};
/**
* @param {Array<Function>} patterns
* @param {Array<Function>} negPatterns
* @param {String|Array} args
* @param {Boolean} returnIndex
* @returns {boolean|number}
*/
const matchPatterns = (patterns, negPatterns, args, returnIndex) => {
const isList = Array.isArray(args);
const _path = isList ? args[0] : args;
if (!isList && typeof _path !== 'string') {
throw new TypeError('anymatch: second argument must be a string: got ' +
Object.prototype.toString.call(_path))
}
const path = normalizePath(_path, false);
for (let index = 0; index < negPatterns.length; index++) {
const nglob = negPatterns[index];
if (nglob(path)) {
return returnIndex ? -1 : false;
}
}
const applied = isList && [path].concat(args.slice(1));
for (let index = 0; index < patterns.length; index++) {
const pattern = patterns[index];
if (isList ? pattern(...applied) : pattern(path)) {
return returnIndex ? index : true;
}
}
return returnIndex ? -1 : false;
};
/**
* @param {AnymatchMatcher} matchers
* @param {Array|string} testString
* @param {object} options
* @returns {boolean|number|Function}
*/
const anymatch = (matchers, testString, options = DEFAULT_OPTIONS) => {
if (matchers == null) {
throw new TypeError('anymatch: specify first argument');
}
const opts = typeof options === 'boolean' ? {returnIndex: options} : options;
const returnIndex = opts.returnIndex || false;
// Early cache for matchers.
const mtchers = arrify(matchers);
const negatedGlobs = mtchers
.filter(item => typeof item === 'string' && item.charAt(0) === BANG)
.map(item => item.slice(1))
.map(item => picomatch(item, opts));
const patterns = mtchers
.filter(item => typeof item !== 'string' || (typeof item === 'string' && item.charAt(0) !== BANG))
.map(matcher => createPattern(matcher, opts));
if (testString == null) {
return (testString, ri = false) => {
const returnIndex = typeof ri === 'boolean' ? ri : false;
return matchPatterns(patterns, negatedGlobs, testString, returnIndex);
}
}
return matchPatterns(patterns, negatedGlobs, testString, returnIndex);
};
anymatch.default = anymatch;
module.exports = anymatch;

View File

@@ -1,48 +0,0 @@
{
"name": "anymatch",
"version": "3.1.3",
"description": "Matches strings against configurable strings, globs, regular expressions, and/or functions",
"files": [
"index.js",
"index.d.ts"
],
"dependencies": {
"normalize-path": "^3.0.0",
"picomatch": "^2.0.4"
},
"author": {
"name": "Elan Shanker",
"url": "https://github.com/es128"
},
"license": "ISC",
"homepage": "https://github.com/micromatch/anymatch",
"repository": {
"type": "git",
"url": "https://github.com/micromatch/anymatch"
},
"keywords": [
"match",
"any",
"string",
"file",
"fs",
"list",
"glob",
"regex",
"regexp",
"regular",
"expression",
"function"
],
"scripts": {
"test": "nyc mocha",
"mocha": "mocha"
},
"devDependencies": {
"mocha": "^6.1.3",
"nyc": "^14.0.0"
},
"engines": {
"node": ">= 8"
}
}

View File

@@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (c) 2014 Blake Embrey (hello@blakeembrey.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -1,43 +0,0 @@
# Array Flatten
[![NPM version][npm-image]][npm-url]
[![NPM downloads][downloads-image]][downloads-url]
[![Build status][travis-image]][travis-url]
[![Test coverage][coveralls-image]][coveralls-url]
> Flatten an array of nested arrays into a single flat array. Accepts an optional depth.
## Installation
```
npm install array-flatten --save
```
## Usage
```javascript
var flatten = require('array-flatten')
flatten([1, [2, [3, [4, [5], 6], 7], 8], 9])
//=> [1, 2, 3, 4, 5, 6, 7, 8, 9]
flatten([1, [2, [3, [4, [5], 6], 7], 8], 9], 2)
//=> [1, 2, 3, [4, [5], 6], 7, 8, 9]
(function () {
flatten(arguments) //=> [1, 2, 3]
})(1, [2, 3])
```
## License
MIT
[npm-image]: https://img.shields.io/npm/v/array-flatten.svg?style=flat
[npm-url]: https://npmjs.org/package/array-flatten
[downloads-image]: https://img.shields.io/npm/dm/array-flatten.svg?style=flat
[downloads-url]: https://npmjs.org/package/array-flatten
[travis-image]: https://img.shields.io/travis/blakeembrey/array-flatten.svg?style=flat
[travis-url]: https://travis-ci.org/blakeembrey/array-flatten
[coveralls-image]: https://img.shields.io/coveralls/blakeembrey/array-flatten.svg?style=flat
[coveralls-url]: https://coveralls.io/r/blakeembrey/array-flatten?branch=master

View File

@@ -1,64 +0,0 @@
'use strict'
/**
* Expose `arrayFlatten`.
*/
module.exports = arrayFlatten
/**
* Recursive flatten function with depth.
*
* @param {Array} array
* @param {Array} result
* @param {Number} depth
* @return {Array}
*/
function flattenWithDepth (array, result, depth) {
for (var i = 0; i < array.length; i++) {
var value = array[i]
if (depth > 0 && Array.isArray(value)) {
flattenWithDepth(value, result, depth - 1)
} else {
result.push(value)
}
}
return result
}
/**
* Recursive flatten function. Omitting depth is slightly faster.
*
* @param {Array} array
* @param {Array} result
* @return {Array}
*/
function flattenForever (array, result) {
for (var i = 0; i < array.length; i++) {
var value = array[i]
if (Array.isArray(value)) {
flattenForever(value, result)
} else {
result.push(value)
}
}
return result
}
/**
* Flatten an array, with the ability to define a depth.
*
* @param {Array} array
* @param {Number} depth
* @return {Array}
*/
function arrayFlatten (array, depth) {
if (depth == null) {
return flattenForever(array, [])
}
return flattenWithDepth(array, [], depth)
}

View File

@@ -1,39 +0,0 @@
{
"name": "array-flatten",
"version": "1.1.1",
"description": "Flatten an array of nested arrays into a single flat array",
"main": "array-flatten.js",
"files": [
"array-flatten.js",
"LICENSE"
],
"scripts": {
"test": "istanbul cover _mocha -- -R spec"
},
"repository": {
"type": "git",
"url": "git://github.com/blakeembrey/array-flatten.git"
},
"keywords": [
"array",
"flatten",
"arguments",
"depth"
],
"author": {
"name": "Blake Embrey",
"email": "hello@blakeembrey.com",
"url": "http://blakeembrey.me"
},
"license": "MIT",
"bugs": {
"url": "https://github.com/blakeembrey/array-flatten/issues"
},
"homepage": "https://github.com/blakeembrey/array-flatten",
"devDependencies": {
"istanbul": "^0.3.13",
"mocha": "^2.2.4",
"pre-commit": "^1.0.7",
"standard": "^3.7.3"
}
}

View File

@@ -1,263 +0,0 @@
[
"3dm",
"3ds",
"3g2",
"3gp",
"7z",
"a",
"aac",
"adp",
"afdesign",
"afphoto",
"afpub",
"ai",
"aif",
"aiff",
"alz",
"ape",
"apk",
"appimage",
"ar",
"arj",
"asf",
"au",
"avi",
"bak",
"baml",
"bh",
"bin",
"bk",
"bmp",
"btif",
"bz2",
"bzip2",
"cab",
"caf",
"cgm",
"class",
"cmx",
"cpio",
"cr2",
"cur",
"dat",
"dcm",
"deb",
"dex",
"djvu",
"dll",
"dmg",
"dng",
"doc",
"docm",
"docx",
"dot",
"dotm",
"dra",
"DS_Store",
"dsk",
"dts",
"dtshd",
"dvb",
"dwg",
"dxf",
"ecelp4800",
"ecelp7470",
"ecelp9600",
"egg",
"eol",
"eot",
"epub",
"exe",
"f4v",
"fbs",
"fh",
"fla",
"flac",
"flatpak",
"fli",
"flv",
"fpx",
"fst",
"fvt",
"g3",
"gh",
"gif",
"graffle",
"gz",
"gzip",
"h261",
"h263",
"h264",
"icns",
"ico",
"ief",
"img",
"ipa",
"iso",
"jar",
"jpeg",
"jpg",
"jpgv",
"jpm",
"jxr",
"key",
"ktx",
"lha",
"lib",
"lvp",
"lz",
"lzh",
"lzma",
"lzo",
"m3u",
"m4a",
"m4v",
"mar",
"mdi",
"mht",
"mid",
"midi",
"mj2",
"mka",
"mkv",
"mmr",
"mng",
"mobi",
"mov",
"movie",
"mp3",
"mp4",
"mp4a",
"mpeg",
"mpg",
"mpga",
"mxu",
"nef",
"npx",
"numbers",
"nupkg",
"o",
"odp",
"ods",
"odt",
"oga",
"ogg",
"ogv",
"otf",
"ott",
"pages",
"pbm",
"pcx",
"pdb",
"pdf",
"pea",
"pgm",
"pic",
"png",
"pnm",
"pot",
"potm",
"potx",
"ppa",
"ppam",
"ppm",
"pps",
"ppsm",
"ppsx",
"ppt",
"pptm",
"pptx",
"psd",
"pya",
"pyc",
"pyo",
"pyv",
"qt",
"rar",
"ras",
"raw",
"resources",
"rgb",
"rip",
"rlc",
"rmf",
"rmvb",
"rpm",
"rtf",
"rz",
"s3m",
"s7z",
"scpt",
"sgi",
"shar",
"snap",
"sil",
"sketch",
"slk",
"smv",
"snk",
"so",
"stl",
"suo",
"sub",
"swf",
"tar",
"tbz",
"tbz2",
"tga",
"tgz",
"thmx",
"tif",
"tiff",
"tlz",
"ttc",
"ttf",
"txz",
"udf",
"uvh",
"uvi",
"uvm",
"uvp",
"uvs",
"uvu",
"viv",
"vob",
"war",
"wav",
"wax",
"wbmp",
"wdp",
"weba",
"webm",
"webp",
"whl",
"wim",
"wm",
"wma",
"wmv",
"wmx",
"woff",
"woff2",
"wrm",
"wvx",
"xbm",
"xif",
"xla",
"xlam",
"xls",
"xlsb",
"xlsm",
"xlsx",
"xlt",
"xltm",
"xltx",
"xm",
"xmind",
"xpi",
"xpm",
"xwd",
"xz",
"z",
"zip",
"zipx"
]

View File

@@ -1,3 +0,0 @@
declare const binaryExtensionsJson: readonly string[];
export = binaryExtensionsJson;

View File

@@ -1,14 +0,0 @@
/**
List of binary file extensions.
@example
```
import binaryExtensions = require('binary-extensions');
console.log(binaryExtensions);
//=> ['3ds', '3g2', …]
```
*/
declare const binaryExtensions: readonly string[];
export = binaryExtensions;

View File

@@ -1 +0,0 @@
module.exports = require('./binary-extensions.json');

View File

@@ -1,10 +0,0 @@
MIT License
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
Copyright (c) Paul Miller (https://paulmillr.com)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -1,40 +0,0 @@
{
"name": "binary-extensions",
"version": "2.3.0",
"description": "List of binary file extensions",
"license": "MIT",
"repository": "sindresorhus/binary-extensions",
"funding": "https://github.com/sponsors/sindresorhus",
"author": {
"name": "Sindre Sorhus",
"email": "sindresorhus@gmail.com",
"url": "https://sindresorhus.com"
},
"sideEffects": false,
"engines": {
"node": ">=8"
},
"scripts": {
"test": "xo && ava && tsd"
},
"files": [
"index.js",
"index.d.ts",
"binary-extensions.json",
"binary-extensions.json.d.ts"
],
"keywords": [
"binary",
"extensions",
"extension",
"file",
"json",
"list",
"array"
],
"devDependencies": {
"ava": "^1.4.1",
"tsd": "^0.7.2",
"xo": "^0.24.0"
}
}

View File

@@ -1,25 +0,0 @@
# binary-extensions
> List of binary file extensions
The list is just a [JSON file](binary-extensions.json) and can be used anywhere.
## Install
```sh
npm install binary-extensions
```
## Usage
```js
const binaryExtensions = require('binary-extensions');
console.log(binaryExtensions);
//=> ['3ds', '3g2', …]
```
## Related
- [is-binary-path](https://github.com/sindresorhus/is-binary-path) - Check if a filepath is a binary file
- [text-extensions](https://github.com/sindresorhus/text-extensions) - List of text file extensions

View File

@@ -1,680 +0,0 @@
1.20.4 / 2025-12-01
===================
* deps: qs@~6.14.0
* deps: use tilde notation for dependencies
* deps: http-errors@~2.0.1
* deps: raw-body@~2.5.3
1.20.3 / 2024-09-10
===================
* deps: qs@6.13.0
* add `depth` option to customize the depth level in the parser
* IMPORTANT: The default `depth` level for parsing URL-encoded data is now `32` (previously was `Infinity`)
1.20.2 / 2023-02-21
===================
* Fix strict json error message on Node.js 19+
* deps: content-type@~1.0.5
- perf: skip value escaping when unnecessary
* deps: raw-body@2.5.2
1.20.1 / 2022-10-06
===================
* deps: qs@6.11.0
* perf: remove unnecessary object clone
1.20.0 / 2022-04-02
===================
* Fix error message for json parse whitespace in `strict`
* Fix internal error when inflated body exceeds limit
* Prevent loss of async hooks context
* Prevent hanging when request already read
* deps: depd@2.0.0
- Replace internal `eval` usage with `Function` constructor
- Use instance methods on `process` to check for listeners
* deps: http-errors@2.0.0
- deps: depd@2.0.0
- deps: statuses@2.0.1
* deps: on-finished@2.4.1
* deps: qs@6.10.3
* deps: raw-body@2.5.1
- deps: http-errors@2.0.0
1.19.2 / 2022-02-15
===================
* deps: bytes@3.1.2
* deps: qs@6.9.7
* Fix handling of `__proto__` keys
* deps: raw-body@2.4.3
- deps: bytes@3.1.2
1.19.1 / 2021-12-10
===================
* deps: bytes@3.1.1
* deps: http-errors@1.8.1
- deps: inherits@2.0.4
- deps: toidentifier@1.0.1
- deps: setprototypeof@1.2.0
* deps: qs@6.9.6
* deps: raw-body@2.4.2
- deps: bytes@3.1.1
- deps: http-errors@1.8.1
* deps: safe-buffer@5.2.1
* deps: type-is@~1.6.18
1.19.0 / 2019-04-25
===================
* deps: bytes@3.1.0
- Add petabyte (`pb`) support
* deps: http-errors@1.7.2
- Set constructor name when possible
- deps: setprototypeof@1.1.1
- deps: statuses@'>= 1.5.0 < 2'
* deps: iconv-lite@0.4.24
- Added encoding MIK
* deps: qs@6.7.0
- Fix parsing array brackets after index
* deps: raw-body@2.4.0
- deps: bytes@3.1.0
- deps: http-errors@1.7.2
- deps: iconv-lite@0.4.24
* deps: type-is@~1.6.17
- deps: mime-types@~2.1.24
- perf: prevent internal `throw` on invalid type
1.18.3 / 2018-05-14
===================
* Fix stack trace for strict json parse error
* deps: depd@~1.1.2
- perf: remove argument reassignment
* deps: http-errors@~1.6.3
- deps: depd@~1.1.2
- deps: setprototypeof@1.1.0
- deps: statuses@'>= 1.3.1 < 2'
* deps: iconv-lite@0.4.23
- Fix loading encoding with year appended
- Fix deprecation warnings on Node.js 10+
* deps: qs@6.5.2
* deps: raw-body@2.3.3
- deps: http-errors@1.6.3
- deps: iconv-lite@0.4.23
* deps: type-is@~1.6.16
- deps: mime-types@~2.1.18
1.18.2 / 2017-09-22
===================
* deps: debug@2.6.9
* perf: remove argument reassignment
1.18.1 / 2017-09-12
===================
* deps: content-type@~1.0.4
- perf: remove argument reassignment
- perf: skip parameter parsing when no parameters
* deps: iconv-lite@0.4.19
- Fix ISO-8859-1 regression
- Update Windows-1255
* deps: qs@6.5.1
- Fix parsing & compacting very deep objects
* deps: raw-body@2.3.2
- deps: iconv-lite@0.4.19
1.18.0 / 2017-09-08
===================
* Fix JSON strict violation error to match native parse error
* Include the `body` property on verify errors
* Include the `type` property on all generated errors
* Use `http-errors` to set status code on errors
* deps: bytes@3.0.0
* deps: debug@2.6.8
* deps: depd@~1.1.1
- Remove unnecessary `Buffer` loading
* deps: http-errors@~1.6.2
- deps: depd@1.1.1
* deps: iconv-lite@0.4.18
- Add support for React Native
- Add a warning if not loaded as utf-8
- Fix CESU-8 decoding in Node.js 8
- Improve speed of ISO-8859-1 encoding
* deps: qs@6.5.0
* deps: raw-body@2.3.1
- Use `http-errors` for standard emitted errors
- deps: bytes@3.0.0
- deps: iconv-lite@0.4.18
- perf: skip buffer decoding on overage chunk
* perf: prevent internal `throw` when missing charset
1.17.2 / 2017-05-17
===================
* deps: debug@2.6.7
- Fix `DEBUG_MAX_ARRAY_LENGTH`
- deps: ms@2.0.0
* deps: type-is@~1.6.15
- deps: mime-types@~2.1.15
1.17.1 / 2017-03-06
===================
* deps: qs@6.4.0
- Fix regression parsing keys starting with `[`
1.17.0 / 2017-03-01
===================
* deps: http-errors@~1.6.1
- Make `message` property enumerable for `HttpError`s
- deps: setprototypeof@1.0.3
* deps: qs@6.3.1
- Fix compacting nested arrays
1.16.1 / 2017-02-10
===================
* deps: debug@2.6.1
- Fix deprecation messages in WebStorm and other editors
- Undeprecate `DEBUG_FD` set to `1` or `2`
1.16.0 / 2017-01-17
===================
* deps: debug@2.6.0
- Allow colors in workers
- Deprecated `DEBUG_FD` environment variable
- Fix error when running under React Native
- Use same color for same namespace
- deps: ms@0.7.2
* deps: http-errors@~1.5.1
- deps: inherits@2.0.3
- deps: setprototypeof@1.0.2
- deps: statuses@'>= 1.3.1 < 2'
* deps: iconv-lite@0.4.15
- Added encoding MS-31J
- Added encoding MS-932
- Added encoding MS-936
- Added encoding MS-949
- Added encoding MS-950
- Fix GBK/GB18030 handling of Euro character
* deps: qs@6.2.1
- Fix array parsing from skipping empty values
* deps: raw-body@~2.2.0
- deps: iconv-lite@0.4.15
* deps: type-is@~1.6.14
- deps: mime-types@~2.1.13
1.15.2 / 2016-06-19
===================
* deps: bytes@2.4.0
* deps: content-type@~1.0.2
- perf: enable strict mode
* deps: http-errors@~1.5.0
- Use `setprototypeof` module to replace `__proto__` setting
- deps: statuses@'>= 1.3.0 < 2'
- perf: enable strict mode
* deps: qs@6.2.0
* deps: raw-body@~2.1.7
- deps: bytes@2.4.0
- perf: remove double-cleanup on happy path
* deps: type-is@~1.6.13
- deps: mime-types@~2.1.11
1.15.1 / 2016-05-05
===================
* deps: bytes@2.3.0
- Drop partial bytes on all parsed units
- Fix parsing byte string that looks like hex
* deps: raw-body@~2.1.6
- deps: bytes@2.3.0
* deps: type-is@~1.6.12
- deps: mime-types@~2.1.10
1.15.0 / 2016-02-10
===================
* deps: http-errors@~1.4.0
- Add `HttpError` export, for `err instanceof createError.HttpError`
- deps: inherits@2.0.1
- deps: statuses@'>= 1.2.1 < 2'
* deps: qs@6.1.0
* deps: type-is@~1.6.11
- deps: mime-types@~2.1.9
1.14.2 / 2015-12-16
===================
* deps: bytes@2.2.0
* deps: iconv-lite@0.4.13
* deps: qs@5.2.0
* deps: raw-body@~2.1.5
- deps: bytes@2.2.0
- deps: iconv-lite@0.4.13
* deps: type-is@~1.6.10
- deps: mime-types@~2.1.8
1.14.1 / 2015-09-27
===================
* Fix issue where invalid charset results in 400 when `verify` used
* deps: iconv-lite@0.4.12
- Fix CESU-8 decoding in Node.js 4.x
* deps: raw-body@~2.1.4
- Fix masking critical errors from `iconv-lite`
- deps: iconv-lite@0.4.12
* deps: type-is@~1.6.9
- deps: mime-types@~2.1.7
1.14.0 / 2015-09-16
===================
* Fix JSON strict parse error to match syntax errors
* Provide static `require` analysis in `urlencoded` parser
* deps: depd@~1.1.0
- Support web browser loading
* deps: qs@5.1.0
* deps: raw-body@~2.1.3
- Fix sync callback when attaching data listener causes sync read
* deps: type-is@~1.6.8
- Fix type error when given invalid type to match against
- deps: mime-types@~2.1.6
1.13.3 / 2015-07-31
===================
* deps: type-is@~1.6.6
- deps: mime-types@~2.1.4
1.13.2 / 2015-07-05
===================
* deps: iconv-lite@0.4.11
* deps: qs@4.0.0
- Fix dropping parameters like `hasOwnProperty`
- Fix user-visible incompatibilities from 3.1.0
- Fix various parsing edge cases
* deps: raw-body@~2.1.2
- Fix error stack traces to skip `makeError`
- deps: iconv-lite@0.4.11
* deps: type-is@~1.6.4
- deps: mime-types@~2.1.2
- perf: enable strict mode
- perf: remove argument reassignment
1.13.1 / 2015-06-16
===================
* deps: qs@2.4.2
- Downgraded from 3.1.0 because of user-visible incompatibilities
1.13.0 / 2015-06-14
===================
* Add `statusCode` property on `Error`s, in addition to `status`
* Change `type` default to `application/json` for JSON parser
* Change `type` default to `application/x-www-form-urlencoded` for urlencoded parser
* Provide static `require` analysis
* Use the `http-errors` module to generate errors
* deps: bytes@2.1.0
- Slight optimizations
* deps: iconv-lite@0.4.10
- The encoding UTF-16 without BOM now defaults to UTF-16LE when detection fails
- Leading BOM is now removed when decoding
* deps: on-finished@~2.3.0
- Add defined behavior for HTTP `CONNECT` requests
- Add defined behavior for HTTP `Upgrade` requests
- deps: ee-first@1.1.1
* deps: qs@3.1.0
- Fix dropping parameters like `hasOwnProperty`
- Fix various parsing edge cases
- Parsed object now has `null` prototype
* deps: raw-body@~2.1.1
- Use `unpipe` module for unpiping requests
- deps: iconv-lite@0.4.10
* deps: type-is@~1.6.3
- deps: mime-types@~2.1.1
- perf: reduce try block size
- perf: remove bitwise operations
* perf: enable strict mode
* perf: remove argument reassignment
* perf: remove delete call
1.12.4 / 2015-05-10
===================
* deps: debug@~2.2.0
* deps: qs@2.4.2
- Fix allowing parameters like `constructor`
* deps: on-finished@~2.2.1
* deps: raw-body@~2.0.1
- Fix a false-positive when unpiping in Node.js 0.8
- deps: bytes@2.0.1
* deps: type-is@~1.6.2
- deps: mime-types@~2.0.11
1.12.3 / 2015-04-15
===================
* Slight efficiency improvement when not debugging
* deps: depd@~1.0.1
* deps: iconv-lite@0.4.8
- Add encoding alias UNICODE-1-1-UTF-7
* deps: raw-body@1.3.4
- Fix hanging callback if request aborts during read
- deps: iconv-lite@0.4.8
1.12.2 / 2015-03-16
===================
* deps: qs@2.4.1
- Fix error when parameter `hasOwnProperty` is present
1.12.1 / 2015-03-15
===================
* deps: debug@~2.1.3
- Fix high intensity foreground color for bold
- deps: ms@0.7.0
* deps: type-is@~1.6.1
- deps: mime-types@~2.0.10
1.12.0 / 2015-02-13
===================
* add `debug` messages
* accept a function for the `type` option
* use `content-type` to parse `Content-Type` headers
* deps: iconv-lite@0.4.7
- Gracefully support enumerables on `Object.prototype`
* deps: raw-body@1.3.3
- deps: iconv-lite@0.4.7
* deps: type-is@~1.6.0
- fix argument reassignment
- fix false-positives in `hasBody` `Transfer-Encoding` check
- support wildcard for both type and subtype (`*/*`)
- deps: mime-types@~2.0.9
1.11.0 / 2015-01-30
===================
* make internal `extended: true` depth limit infinity
* deps: type-is@~1.5.6
- deps: mime-types@~2.0.8
1.10.2 / 2015-01-20
===================
* deps: iconv-lite@0.4.6
- Fix rare aliases of single-byte encodings
* deps: raw-body@1.3.2
- deps: iconv-lite@0.4.6
1.10.1 / 2015-01-01
===================
* deps: on-finished@~2.2.0
* deps: type-is@~1.5.5
- deps: mime-types@~2.0.7
1.10.0 / 2014-12-02
===================
* make internal `extended: true` array limit dynamic
1.9.3 / 2014-11-21
==================
* deps: iconv-lite@0.4.5
- Fix Windows-31J and X-SJIS encoding support
* deps: qs@2.3.3
- Fix `arrayLimit` behavior
* deps: raw-body@1.3.1
- deps: iconv-lite@0.4.5
* deps: type-is@~1.5.3
- deps: mime-types@~2.0.3
1.9.2 / 2014-10-27
==================
* deps: qs@2.3.2
- Fix parsing of mixed objects and values
1.9.1 / 2014-10-22
==================
* deps: on-finished@~2.1.1
- Fix handling of pipelined requests
* deps: qs@2.3.0
- Fix parsing of mixed implicit and explicit arrays
* deps: type-is@~1.5.2
- deps: mime-types@~2.0.2
1.9.0 / 2014-09-24
==================
* include the charset in "unsupported charset" error message
* include the encoding in "unsupported content encoding" error message
* deps: depd@~1.0.0
1.8.4 / 2014-09-23
==================
* fix content encoding to be case-insensitive
1.8.3 / 2014-09-19
==================
* deps: qs@2.2.4
- Fix issue with object keys starting with numbers truncated
1.8.2 / 2014-09-15
==================
* deps: depd@0.4.5
1.8.1 / 2014-09-07
==================
* deps: media-typer@0.3.0
* deps: type-is@~1.5.1
1.8.0 / 2014-09-05
==================
* make empty-body-handling consistent between chunked requests
- empty `json` produces `{}`
- empty `raw` produces `new Buffer(0)`
- empty `text` produces `''`
- empty `urlencoded` produces `{}`
* deps: qs@2.2.3
- Fix issue where first empty value in array is discarded
* deps: type-is@~1.5.0
- fix `hasbody` to be true for `content-length: 0`
1.7.0 / 2014-09-01
==================
* add `parameterLimit` option to `urlencoded` parser
* change `urlencoded` extended array limit to 100
* respond with 413 when over `parameterLimit` in `urlencoded`
1.6.7 / 2014-08-29
==================
* deps: qs@2.2.2
- Remove unnecessary cloning
1.6.6 / 2014-08-27
==================
* deps: qs@2.2.0
- Array parsing fix
- Performance improvements
1.6.5 / 2014-08-16
==================
* deps: on-finished@2.1.0
1.6.4 / 2014-08-14
==================
* deps: qs@1.2.2
1.6.3 / 2014-08-10
==================
* deps: qs@1.2.1
1.6.2 / 2014-08-07
==================
* deps: qs@1.2.0
- Fix parsing array of objects
1.6.1 / 2014-08-06
==================
* deps: qs@1.1.0
- Accept urlencoded square brackets
- Accept empty values in implicit array notation
1.6.0 / 2014-08-05
==================
* deps: qs@1.0.2
- Complete rewrite
- Limits array length to 20
- Limits object depth to 5
- Limits parameters to 1,000
1.5.2 / 2014-07-27
==================
* deps: depd@0.4.4
- Work-around v8 generating empty stack traces
1.5.1 / 2014-07-26
==================
* deps: depd@0.4.3
- Fix exception when global `Error.stackTraceLimit` is too low
1.5.0 / 2014-07-20
==================
* deps: depd@0.4.2
- Add `TRACE_DEPRECATION` environment variable
- Remove non-standard grey color from color output
- Support `--no-deprecation` argument
- Support `--trace-deprecation` argument
* deps: iconv-lite@0.4.4
- Added encoding UTF-7
* deps: raw-body@1.3.0
- deps: iconv-lite@0.4.4
- Added encoding UTF-7
- Fix `Cannot switch to old mode now` error on Node.js 0.10+
* deps: type-is@~1.3.2
1.4.3 / 2014-06-19
==================
* deps: type-is@1.3.1
- fix global variable leak
1.4.2 / 2014-06-19
==================
* deps: type-is@1.3.0
- improve type parsing
1.4.1 / 2014-06-19
==================
* fix urlencoded extended deprecation message
1.4.0 / 2014-06-19
==================
* add `text` parser
* add `raw` parser
* check accepted charset in content-type (accepts utf-8)
* check accepted encoding in content-encoding (accepts identity)
* deprecate `bodyParser()` middleware; use `.json()` and `.urlencoded()` as needed
* deprecate `urlencoded()` without provided `extended` option
* lazy-load urlencoded parsers
* parsers split into files for reduced mem usage
* support gzip and deflate bodies
- set `inflate: false` to turn off
* deps: raw-body@1.2.2
- Support all encodings from `iconv-lite`
1.3.1 / 2014-06-11
==================
* deps: type-is@1.2.1
- Switch dependency from mime to mime-types@1.0.0
1.3.0 / 2014-05-31
==================
* add `extended` option to urlencoded parser
1.2.2 / 2014-05-27
==================
* deps: raw-body@1.1.6
- assert stream encoding on node.js 0.8
- assert stream encoding on node.js < 0.10.6
- deps: bytes@1
1.2.1 / 2014-05-26
==================
* invoke `next(err)` after request fully read
- prevents hung responses and socket hang ups
1.2.0 / 2014-05-11
==================
* add `verify` option
* deps: type-is@1.2.0
- support suffix matching
1.1.2 / 2014-05-11
==================
* improve json parser speed
1.1.1 / 2014-05-11
==================
* fix repeated limit parsing with every request
1.1.0 / 2014-05-10
==================
* add `type` option
* deps: pin for safety and consistency
1.0.2 / 2014-04-14
==================
* use `type-is` module
1.0.1 / 2014-03-20
==================
* lower default limits to 100kb

View File

@@ -1,23 +0,0 @@
(The MIT License)
Copyright (c) 2014 Jonathan Ong <me@jongleberry.com>
Copyright (c) 2014-2015 Douglas Christopher Wilson <doug@somethingdoug.com>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -1,476 +0,0 @@
# body-parser
[![NPM Version][npm-version-image]][npm-url]
[![NPM Downloads][npm-downloads-image]][npm-url]
[![Build Status][ci-image]][ci-url]
[![Test Coverage][coveralls-image]][coveralls-url]
[![OpenSSF Scorecard Badge][ossf-scorecard-badge]][ossf-scorecard-visualizer]
Node.js body parsing middleware.
Parse incoming request bodies in a middleware before your handlers, available
under the `req.body` property.
**Note** As `req.body`'s shape is based on user-controlled input, all
properties and values in this object are untrusted and should be validated
before trusting. For example, `req.body.foo.toString()` may fail in multiple
ways, for example the `foo` property may not be there or may not be a string,
and `toString` may not be a function and instead a string or other user input.
[Learn about the anatomy of an HTTP transaction in Node.js](https://nodejs.org/en/docs/guides/anatomy-of-an-http-transaction/).
_This does not handle multipart bodies_, due to their complex and typically
large nature. For multipart bodies, you may be interested in the following
modules:
* [busboy](https://www.npmjs.org/package/busboy#readme) and
[connect-busboy](https://www.npmjs.org/package/connect-busboy#readme)
* [multiparty](https://www.npmjs.org/package/multiparty#readme) and
[connect-multiparty](https://www.npmjs.org/package/connect-multiparty#readme)
* [formidable](https://www.npmjs.org/package/formidable#readme)
* [multer](https://www.npmjs.org/package/multer#readme)
This module provides the following parsers:
* [JSON body parser](#bodyparserjsonoptions)
* [Raw body parser](#bodyparserrawoptions)
* [Text body parser](#bodyparsertextoptions)
* [URL-encoded form body parser](#bodyparserurlencodedoptions)
Other body parsers you might be interested in:
- [body](https://www.npmjs.org/package/body#readme)
- [co-body](https://www.npmjs.org/package/co-body#readme)
## Installation
```sh
$ npm install body-parser
```
## API
```js
var bodyParser = require('body-parser')
```
The `bodyParser` object exposes various factories to create middlewares. All
middlewares will populate the `req.body` property with the parsed body when
the `Content-Type` request header matches the `type` option, or an empty
object (`{}`) if there was no body to parse, the `Content-Type` was not matched,
or an error occurred.
The various errors returned by this module are described in the
[errors section](#errors).
### bodyParser.json([options])
Returns middleware that only parses `json` and only looks at requests where
the `Content-Type` header matches the `type` option. This parser accepts any
Unicode encoding of the body and supports automatic inflation of `gzip` and
`deflate` encodings.
A new `body` object containing the parsed data is populated on the `request`
object after the middleware (i.e. `req.body`).
#### Options
The `json` function takes an optional `options` object that may contain any of
the following keys:
##### inflate
When set to `true`, then deflated (compressed) bodies will be inflated; when
`false`, deflated bodies are rejected. Defaults to `true`.
##### limit
Controls the maximum request body size. If this is a number, then the value
specifies the number of bytes; if it is a string, the value is passed to the
[bytes](https://www.npmjs.com/package/bytes) library for parsing. Defaults
to `'100kb'`.
##### reviver
The `reviver` option is passed directly to `JSON.parse` as the second
argument. You can find more information on this argument
[in the MDN documentation about JSON.parse](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse#Example.3A_Using_the_reviver_parameter).
##### strict
When set to `true`, will only accept arrays and objects; when `false` will
accept anything `JSON.parse` accepts. Defaults to `true`.
##### type
The `type` option is used to determine what media type the middleware will
parse. This option can be a string, array of strings, or a function. If not a
function, `type` option is passed directly to the
[type-is](https://www.npmjs.org/package/type-is#readme) library and this can
be an extension name (like `json`), a mime type (like `application/json`), or
a mime type with a wildcard (like `*/*` or `*/json`). If a function, the `type`
option is called as `fn(req)` and the request is parsed if it returns a truthy
value. Defaults to `application/json`.
##### verify
The `verify` option, if supplied, is called as `verify(req, res, buf, encoding)`,
where `buf` is a `Buffer` of the raw request body and `encoding` is the
encoding of the request. The parsing can be aborted by throwing an error.
### bodyParser.raw([options])
Returns middleware that parses all bodies as a `Buffer` and only looks at
requests where the `Content-Type` header matches the `type` option. This
parser supports automatic inflation of `gzip` and `deflate` encodings.
A new `body` object containing the parsed data is populated on the `request`
object after the middleware (i.e. `req.body`). This will be a `Buffer` object
of the body.
#### Options
The `raw` function takes an optional `options` object that may contain any of
the following keys:
##### inflate
When set to `true`, then deflated (compressed) bodies will be inflated; when
`false`, deflated bodies are rejected. Defaults to `true`.
##### limit
Controls the maximum request body size. If this is a number, then the value
specifies the number of bytes; if it is a string, the value is passed to the
[bytes](https://www.npmjs.com/package/bytes) library for parsing. Defaults
to `'100kb'`.
##### type
The `type` option is used to determine what media type the middleware will
parse. This option can be a string, array of strings, or a function.
If not a function, `type` option is passed directly to the
[type-is](https://www.npmjs.org/package/type-is#readme) library and this
can be an extension name (like `bin`), a mime type (like
`application/octet-stream`), or a mime type with a wildcard (like `*/*` or
`application/*`). If a function, the `type` option is called as `fn(req)`
and the request is parsed if it returns a truthy value. Defaults to
`application/octet-stream`.
##### verify
The `verify` option, if supplied, is called as `verify(req, res, buf, encoding)`,
where `buf` is a `Buffer` of the raw request body and `encoding` is the
encoding of the request. The parsing can be aborted by throwing an error.
### bodyParser.text([options])
Returns middleware that parses all bodies as a string and only looks at
requests where the `Content-Type` header matches the `type` option. This
parser supports automatic inflation of `gzip` and `deflate` encodings.
A new `body` string containing the parsed data is populated on the `request`
object after the middleware (i.e. `req.body`). This will be a string of the
body.
#### Options
The `text` function takes an optional `options` object that may contain any of
the following keys:
##### defaultCharset
Specify the default character set for the text content if the charset is not
specified in the `Content-Type` header of the request. Defaults to `utf-8`.
##### inflate
When set to `true`, then deflated (compressed) bodies will be inflated; when
`false`, deflated bodies are rejected. Defaults to `true`.
##### limit
Controls the maximum request body size. If this is a number, then the value
specifies the number of bytes; if it is a string, the value is passed to the
[bytes](https://www.npmjs.com/package/bytes) library for parsing. Defaults
to `'100kb'`.
##### type
The `type` option is used to determine what media type the middleware will
parse. This option can be a string, array of strings, or a function. If not
a function, `type` option is passed directly to the
[type-is](https://www.npmjs.org/package/type-is#readme) library and this can
be an extension name (like `txt`), a mime type (like `text/plain`), or a mime
type with a wildcard (like `*/*` or `text/*`). If a function, the `type`
option is called as `fn(req)` and the request is parsed if it returns a
truthy value. Defaults to `text/plain`.
##### verify
The `verify` option, if supplied, is called as `verify(req, res, buf, encoding)`,
where `buf` is a `Buffer` of the raw request body and `encoding` is the
encoding of the request. The parsing can be aborted by throwing an error.
### bodyParser.urlencoded([options])
Returns middleware that only parses `urlencoded` bodies and only looks at
requests where the `Content-Type` header matches the `type` option. This
parser accepts only UTF-8 encoding of the body and supports automatic
inflation of `gzip` and `deflate` encodings.
A new `body` object containing the parsed data is populated on the `request`
object after the middleware (i.e. `req.body`). This object will contain
key-value pairs, where the value can be a string or array (when `extended` is
`false`), or any type (when `extended` is `true`).
#### Options
The `urlencoded` function takes an optional `options` object that may contain
any of the following keys:
##### extended
The `extended` option allows to choose between parsing the URL-encoded data
with the `querystring` library (when `false`) or the `qs` library (when
`true`). The "extended" syntax allows for rich objects and arrays to be
encoded into the URL-encoded format, allowing for a JSON-like experience
with URL-encoded. For more information, please
[see the qs library](https://www.npmjs.org/package/qs#readme).
Defaults to `true`, but using the default has been deprecated. Please
research into the difference between `qs` and `querystring` and choose the
appropriate setting.
##### inflate
When set to `true`, then deflated (compressed) bodies will be inflated; when
`false`, deflated bodies are rejected. Defaults to `true`.
##### limit
Controls the maximum request body size. If this is a number, then the value
specifies the number of bytes; if it is a string, the value is passed to the
[bytes](https://www.npmjs.com/package/bytes) library for parsing. Defaults
to `'100kb'`.
##### parameterLimit
The `parameterLimit` option controls the maximum number of parameters that
are allowed in the URL-encoded data. If a request contains more parameters
than this value, a 413 will be returned to the client. Defaults to `1000`.
##### type
The `type` option is used to determine what media type the middleware will
parse. This option can be a string, array of strings, or a function. If not
a function, `type` option is passed directly to the
[type-is](https://www.npmjs.org/package/type-is#readme) library and this can
be an extension name (like `urlencoded`), a mime type (like
`application/x-www-form-urlencoded`), or a mime type with a wildcard (like
`*/x-www-form-urlencoded`). If a function, the `type` option is called as
`fn(req)` and the request is parsed if it returns a truthy value. Defaults
to `application/x-www-form-urlencoded`.
##### verify
The `verify` option, if supplied, is called as `verify(req, res, buf, encoding)`,
where `buf` is a `Buffer` of the raw request body and `encoding` is the
encoding of the request. The parsing can be aborted by throwing an error.
#### depth
The `depth` option is used to configure the maximum depth of the `qs` library when `extended` is `true`. This allows you to limit the amount of keys that are parsed and can be useful to prevent certain types of abuse. Defaults to `32`. It is recommended to keep this value as low as possible.
## Errors
The middlewares provided by this module create errors using the
[`http-errors` module](https://www.npmjs.com/package/http-errors). The errors
will typically have a `status`/`statusCode` property that contains the suggested
HTTP response code, an `expose` property to determine if the `message` property
should be displayed to the client, a `type` property to determine the type of
error without matching against the `message`, and a `body` property containing
the read body, if available.
The following are the common errors created, though any error can come through
for various reasons.
### content encoding unsupported
This error will occur when the request had a `Content-Encoding` header that
contained an encoding but the "inflation" option was set to `false`. The
`status` property is set to `415`, the `type` property is set to
`'encoding.unsupported'`, and the `charset` property will be set to the
encoding that is unsupported.
### entity parse failed
This error will occur when the request contained an entity that could not be
parsed by the middleware. The `status` property is set to `400`, the `type`
property is set to `'entity.parse.failed'`, and the `body` property is set to
the entity value that failed parsing.
### entity verify failed
This error will occur when the request contained an entity that could not be
failed verification by the defined `verify` option. The `status` property is
set to `403`, the `type` property is set to `'entity.verify.failed'`, and the
`body` property is set to the entity value that failed verification.
### request aborted
This error will occur when the request is aborted by the client before reading
the body has finished. The `received` property will be set to the number of
bytes received before the request was aborted and the `expected` property is
set to the number of expected bytes. The `status` property is set to `400`
and `type` property is set to `'request.aborted'`.
### request entity too large
This error will occur when the request body's size is larger than the "limit"
option. The `limit` property will be set to the byte limit and the `length`
property will be set to the request body's length. The `status` property is
set to `413` and the `type` property is set to `'entity.too.large'`.
### request size did not match content length
This error will occur when the request's length did not match the length from
the `Content-Length` header. This typically occurs when the request is malformed,
typically when the `Content-Length` header was calculated based on characters
instead of bytes. The `status` property is set to `400` and the `type` property
is set to `'request.size.invalid'`.
### stream encoding should not be set
This error will occur when something called the `req.setEncoding` method prior
to this middleware. This module operates directly on bytes only and you cannot
call `req.setEncoding` when using this module. The `status` property is set to
`500` and the `type` property is set to `'stream.encoding.set'`.
### stream is not readable
This error will occur when the request is no longer readable when this middleware
attempts to read it. This typically means something other than a middleware from
this module read the request body already and the middleware was also configured to
read the same request. The `status` property is set to `500` and the `type`
property is set to `'stream.not.readable'`.
### too many parameters
This error will occur when the content of the request exceeds the configured
`parameterLimit` for the `urlencoded` parser. The `status` property is set to
`413` and the `type` property is set to `'parameters.too.many'`.
### unsupported charset "BOGUS"
This error will occur when the request had a charset parameter in the
`Content-Type` header, but the `iconv-lite` module does not support it OR the
parser does not support it. The charset is contained in the message as well
as in the `charset` property. The `status` property is set to `415`, the
`type` property is set to `'charset.unsupported'`, and the `charset` property
is set to the charset that is unsupported.
### unsupported content encoding "bogus"
This error will occur when the request had a `Content-Encoding` header that
contained an unsupported encoding. The encoding is contained in the message
as well as in the `encoding` property. The `status` property is set to `415`,
the `type` property is set to `'encoding.unsupported'`, and the `encoding`
property is set to the encoding that is unsupported.
### The input exceeded the depth
This error occurs when using `bodyParser.urlencoded` with the `extended` property set to `true` and the input exceeds the configured `depth` option. The `status` property is set to `400`. It is recommended to review the `depth` option and evaluate if it requires a higher value. When the `depth` option is set to `32` (default value), the error will not be thrown.
## Examples
### Express/Connect top-level generic
This example demonstrates adding a generic JSON and URL-encoded parser as a
top-level middleware, which will parse the bodies of all incoming requests.
This is the simplest setup.
```js
var express = require('express')
var bodyParser = require('body-parser')
var app = express()
// parse application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({ extended: false }))
// parse application/json
app.use(bodyParser.json())
app.use(function (req, res) {
res.setHeader('Content-Type', 'text/plain')
res.write('you posted:\n')
res.end(JSON.stringify(req.body, null, 2))
})
```
### Express route-specific
This example demonstrates adding body parsers specifically to the routes that
need them. In general, this is the most recommended way to use body-parser with
Express.
```js
var express = require('express')
var bodyParser = require('body-parser')
var app = express()
// create application/json parser
var jsonParser = bodyParser.json()
// create application/x-www-form-urlencoded parser
var urlencodedParser = bodyParser.urlencoded({ extended: false })
// POST /login gets urlencoded bodies
app.post('/login', urlencodedParser, function (req, res) {
res.send('welcome, ' + req.body.username)
})
// POST /api/users gets JSON bodies
app.post('/api/users', jsonParser, function (req, res) {
// create user in req.body
})
```
### Change accepted type for parsers
All the parsers accept a `type` option which allows you to change the
`Content-Type` that the middleware will parse.
```js
var express = require('express')
var bodyParser = require('body-parser')
var app = express()
// parse various different custom JSON types as JSON
app.use(bodyParser.json({ type: 'application/*+json' }))
// parse some custom thing into a Buffer
app.use(bodyParser.raw({ type: 'application/vnd.custom-type' }))
// parse an HTML body into a string
app.use(bodyParser.text({ type: 'text/html' }))
```
## License
[MIT](LICENSE)
[ci-image]: https://badgen.net/github/checks/expressjs/body-parser/master?label=ci
[ci-url]: https://github.com/expressjs/body-parser/actions/workflows/ci.yml
[coveralls-image]: https://badgen.net/coveralls/c/github/expressjs/body-parser/master
[coveralls-url]: https://coveralls.io/r/expressjs/body-parser?branch=master
[node-version-image]: https://badgen.net/npm/node/body-parser
[node-version-url]: https://nodejs.org/en/download
[npm-downloads-image]: https://badgen.net/npm/dm/body-parser
[npm-url]: https://npmjs.org/package/body-parser
[npm-version-image]: https://badgen.net/npm/v/body-parser
[ossf-scorecard-badge]: https://api.scorecard.dev/projects/github.com/expressjs/body-parser/badge
[ossf-scorecard-visualizer]: https://ossf.github.io/scorecard-visualizer/#/projects/github.com/expressjs/body-parser

View File

@@ -1,156 +0,0 @@
/*!
* body-parser
* Copyright(c) 2014-2015 Douglas Christopher Wilson
* MIT Licensed
*/
'use strict'
/**
* Module dependencies.
* @private
*/
var deprecate = require('depd')('body-parser')
/**
* Cache of loaded parsers.
* @private
*/
var parsers = Object.create(null)
/**
* @typedef Parsers
* @type {function}
* @property {function} json
* @property {function} raw
* @property {function} text
* @property {function} urlencoded
*/
/**
* Module exports.
* @type {Parsers}
*/
exports = module.exports = deprecate.function(bodyParser,
'bodyParser: use individual json/urlencoded middlewares')
/**
* JSON parser.
* @public
*/
Object.defineProperty(exports, 'json', {
configurable: true,
enumerable: true,
get: createParserGetter('json')
})
/**
* Raw parser.
* @public
*/
Object.defineProperty(exports, 'raw', {
configurable: true,
enumerable: true,
get: createParserGetter('raw')
})
/**
* Text parser.
* @public
*/
Object.defineProperty(exports, 'text', {
configurable: true,
enumerable: true,
get: createParserGetter('text')
})
/**
* URL-encoded parser.
* @public
*/
Object.defineProperty(exports, 'urlencoded', {
configurable: true,
enumerable: true,
get: createParserGetter('urlencoded')
})
/**
* Create a middleware to parse json and urlencoded bodies.
*
* @param {object} [options]
* @return {function}
* @deprecated
* @public
*/
function bodyParser (options) {
// use default type for parsers
var opts = Object.create(options || null, {
type: {
configurable: true,
enumerable: true,
value: undefined,
writable: true
}
})
var _urlencoded = exports.urlencoded(opts)
var _json = exports.json(opts)
return function bodyParser (req, res, next) {
_json(req, res, function (err) {
if (err) return next(err)
_urlencoded(req, res, next)
})
}
}
/**
* Create a getter for loading a parser.
* @private
*/
function createParserGetter (name) {
return function get () {
return loadParser(name)
}
}
/**
* Load a parser module.
* @private
*/
function loadParser (parserName) {
var parser = parsers[parserName]
if (parser !== undefined) {
return parser
}
// this uses a switch for static require analysis
switch (parserName) {
case 'json':
parser = require('./lib/types/json')
break
case 'raw':
parser = require('./lib/types/raw')
break
case 'text':
parser = require('./lib/types/text')
break
case 'urlencoded':
parser = require('./lib/types/urlencoded')
break
}
// store to prevent invoking require()
return (parsers[parserName] = parser)
}

View File

@@ -1,205 +0,0 @@
/*!
* body-parser
* Copyright(c) 2014-2015 Douglas Christopher Wilson
* MIT Licensed
*/
'use strict'
/**
* Module dependencies.
* @private
*/
var createError = require('http-errors')
var destroy = require('destroy')
var getBody = require('raw-body')
var iconv = require('iconv-lite')
var onFinished = require('on-finished')
var unpipe = require('unpipe')
var zlib = require('zlib')
/**
* Module exports.
*/
module.exports = read
/**
* Read a request into a buffer and parse.
*
* @param {object} req
* @param {object} res
* @param {function} next
* @param {function} parse
* @param {function} debug
* @param {object} options
* @private
*/
function read (req, res, next, parse, debug, options) {
var length
var opts = options
var stream
// flag as parsed
req._body = true
// read options
var encoding = opts.encoding !== null
? opts.encoding
: null
var verify = opts.verify
try {
// get the content stream
stream = contentstream(req, debug, opts.inflate)
length = stream.length
stream.length = undefined
} catch (err) {
return next(err)
}
// set raw-body options
opts.length = length
opts.encoding = verify
? null
: encoding
// assert charset is supported
if (opts.encoding === null && encoding !== null && !iconv.encodingExists(encoding)) {
return next(createError(415, 'unsupported charset "' + encoding.toUpperCase() + '"', {
charset: encoding.toLowerCase(),
type: 'charset.unsupported'
}))
}
// read body
debug('read body')
getBody(stream, opts, function (error, body) {
if (error) {
var _error
if (error.type === 'encoding.unsupported') {
// echo back charset
_error = createError(415, 'unsupported charset "' + encoding.toUpperCase() + '"', {
charset: encoding.toLowerCase(),
type: 'charset.unsupported'
})
} else {
// set status code on error
_error = createError(400, error)
}
// unpipe from stream and destroy
if (stream !== req) {
unpipe(req)
destroy(stream, true)
}
// read off entire request
dump(req, function onfinished () {
next(createError(400, _error))
})
return
}
// verify
if (verify) {
try {
debug('verify body')
verify(req, res, body, encoding)
} catch (err) {
next(createError(403, err, {
body: body,
type: err.type || 'entity.verify.failed'
}))
return
}
}
// parse
var str = body
try {
debug('parse body')
str = typeof body !== 'string' && encoding !== null
? iconv.decode(body, encoding)
: body
req.body = parse(str)
} catch (err) {
next(createError(400, err, {
body: str,
type: err.type || 'entity.parse.failed'
}))
return
}
next()
})
}
/**
* Get the content stream of the request.
*
* @param {object} req
* @param {function} debug
* @param {boolean} [inflate=true]
* @return {object}
* @api private
*/
function contentstream (req, debug, inflate) {
var encoding = (req.headers['content-encoding'] || 'identity').toLowerCase()
var length = req.headers['content-length']
var stream
debug('content-encoding "%s"', encoding)
if (inflate === false && encoding !== 'identity') {
throw createError(415, 'content encoding unsupported', {
encoding: encoding,
type: 'encoding.unsupported'
})
}
switch (encoding) {
case 'deflate':
stream = zlib.createInflate()
debug('inflate body')
req.pipe(stream)
break
case 'gzip':
stream = zlib.createGunzip()
debug('gunzip body')
req.pipe(stream)
break
case 'identity':
stream = req
stream.length = length
break
default:
throw createError(415, 'unsupported content encoding "' + encoding + '"', {
encoding: encoding,
type: 'encoding.unsupported'
})
}
return stream
}
/**
* Dump the contents of a request.
*
* @param {object} req
* @param {function} callback
* @api private
*/
function dump (req, callback) {
if (onFinished.isFinished(req)) {
callback(null)
} else {
onFinished(req, callback)
req.resume()
}
}

View File

@@ -1,247 +0,0 @@
/*!
* body-parser
* Copyright(c) 2014 Jonathan Ong
* Copyright(c) 2014-2015 Douglas Christopher Wilson
* MIT Licensed
*/
'use strict'
/**
* Module dependencies.
* @private
*/
var bytes = require('bytes')
var contentType = require('content-type')
var createError = require('http-errors')
var debug = require('debug')('body-parser:json')
var read = require('../read')
var typeis = require('type-is')
/**
* Module exports.
*/
module.exports = json
/**
* RegExp to match the first non-space in a string.
*
* Allowed whitespace is defined in RFC 7159:
*
* ws = *(
* %x20 / ; Space
* %x09 / ; Horizontal tab
* %x0A / ; Line feed or New line
* %x0D ) ; Carriage return
*/
var FIRST_CHAR_REGEXP = /^[\x20\x09\x0a\x0d]*([^\x20\x09\x0a\x0d])/ // eslint-disable-line no-control-regex
var JSON_SYNTAX_CHAR = '#'
var JSON_SYNTAX_REGEXP = /#+/g
/**
* Create a middleware to parse JSON bodies.
*
* @param {object} [options]
* @return {function}
* @public
*/
function json (options) {
var opts = options || {}
var limit = typeof opts.limit !== 'number'
? bytes.parse(opts.limit || '100kb')
: opts.limit
var inflate = opts.inflate !== false
var reviver = opts.reviver
var strict = opts.strict !== false
var type = opts.type || 'application/json'
var verify = opts.verify || false
if (verify !== false && typeof verify !== 'function') {
throw new TypeError('option verify must be function')
}
// create the appropriate type checking function
var shouldParse = typeof type !== 'function'
? typeChecker(type)
: type
function parse (body) {
if (body.length === 0) {
// special-case empty json body, as it's a common client-side mistake
// TODO: maybe make this configurable or part of "strict" option
return {}
}
if (strict) {
var first = firstchar(body)
if (first !== '{' && first !== '[') {
debug('strict violation')
throw createStrictSyntaxError(body, first)
}
}
try {
debug('parse json')
return JSON.parse(body, reviver)
} catch (e) {
throw normalizeJsonSyntaxError(e, {
message: e.message,
stack: e.stack
})
}
}
return function jsonParser (req, res, next) {
if (req._body) {
debug('body already parsed')
next()
return
}
req.body = req.body || {}
// skip requests without bodies
if (!typeis.hasBody(req)) {
debug('skip empty body')
next()
return
}
debug('content-type %j', req.headers['content-type'])
// determine if request should be parsed
if (!shouldParse(req)) {
debug('skip parsing')
next()
return
}
// assert charset per RFC 7159 sec 8.1
var charset = getCharset(req) || 'utf-8'
if (charset.slice(0, 4) !== 'utf-') {
debug('invalid charset')
next(createError(415, 'unsupported charset "' + charset.toUpperCase() + '"', {
charset: charset,
type: 'charset.unsupported'
}))
return
}
// read
read(req, res, next, parse, debug, {
encoding: charset,
inflate: inflate,
limit: limit,
verify: verify
})
}
}
/**
* Create strict violation syntax error matching native error.
*
* @param {string} str
* @param {string} char
* @return {Error}
* @private
*/
function createStrictSyntaxError (str, char) {
var index = str.indexOf(char)
var partial = ''
if (index !== -1) {
partial = str.substring(0, index) + JSON_SYNTAX_CHAR
for (var i = index + 1; i < str.length; i++) {
partial += JSON_SYNTAX_CHAR
}
}
try {
JSON.parse(partial); /* istanbul ignore next */ throw new SyntaxError('strict violation')
} catch (e) {
return normalizeJsonSyntaxError(e, {
message: e.message.replace(JSON_SYNTAX_REGEXP, function (placeholder) {
return str.substring(index, index + placeholder.length)
}),
stack: e.stack
})
}
}
/**
* Get the first non-whitespace character in a string.
*
* @param {string} str
* @return {function}
* @private
*/
function firstchar (str) {
var match = FIRST_CHAR_REGEXP.exec(str)
return match
? match[1]
: undefined
}
/**
* Get the charset of a request.
*
* @param {object} req
* @api private
*/
function getCharset (req) {
try {
return (contentType.parse(req).parameters.charset || '').toLowerCase()
} catch (e) {
return undefined
}
}
/**
* Normalize a SyntaxError for JSON.parse.
*
* @param {SyntaxError} error
* @param {object} obj
* @return {SyntaxError}
*/
function normalizeJsonSyntaxError (error, obj) {
var keys = Object.getOwnPropertyNames(error)
for (var i = 0; i < keys.length; i++) {
var key = keys[i]
if (key !== 'stack' && key !== 'message') {
delete error[key]
}
}
// replace stack before message for Node.js 0.10 and below
error.stack = obj.stack.replace(error.message, obj.message)
error.message = obj.message
return error
}
/**
* Get the simple type checker.
*
* @param {string} type
* @return {function}
*/
function typeChecker (type) {
return function checkType (req) {
return Boolean(typeis(req, type))
}
}

View File

@@ -1,101 +0,0 @@
/*!
* body-parser
* Copyright(c) 2014-2015 Douglas Christopher Wilson
* MIT Licensed
*/
'use strict'
/**
* Module dependencies.
*/
var bytes = require('bytes')
var debug = require('debug')('body-parser:raw')
var read = require('../read')
var typeis = require('type-is')
/**
* Module exports.
*/
module.exports = raw
/**
* Create a middleware to parse raw bodies.
*
* @param {object} [options]
* @return {function}
* @api public
*/
function raw (options) {
var opts = options || {}
var inflate = opts.inflate !== false
var limit = typeof opts.limit !== 'number'
? bytes.parse(opts.limit || '100kb')
: opts.limit
var type = opts.type || 'application/octet-stream'
var verify = opts.verify || false
if (verify !== false && typeof verify !== 'function') {
throw new TypeError('option verify must be function')
}
// create the appropriate type checking function
var shouldParse = typeof type !== 'function'
? typeChecker(type)
: type
function parse (buf) {
return buf
}
return function rawParser (req, res, next) {
if (req._body) {
debug('body already parsed')
next()
return
}
req.body = req.body || {}
// skip requests without bodies
if (!typeis.hasBody(req)) {
debug('skip empty body')
next()
return
}
debug('content-type %j', req.headers['content-type'])
// determine if request should be parsed
if (!shouldParse(req)) {
debug('skip parsing')
next()
return
}
// read
read(req, res, next, parse, debug, {
encoding: null,
inflate: inflate,
limit: limit,
verify: verify
})
}
}
/**
* Get the simple type checker.
*
* @param {string} type
* @return {function}
*/
function typeChecker (type) {
return function checkType (req) {
return Boolean(typeis(req, type))
}
}

View File

@@ -1,121 +0,0 @@
/*!
* body-parser
* Copyright(c) 2014-2015 Douglas Christopher Wilson
* MIT Licensed
*/
'use strict'
/**
* Module dependencies.
*/
var bytes = require('bytes')
var contentType = require('content-type')
var debug = require('debug')('body-parser:text')
var read = require('../read')
var typeis = require('type-is')
/**
* Module exports.
*/
module.exports = text
/**
* Create a middleware to parse text bodies.
*
* @param {object} [options]
* @return {function}
* @api public
*/
function text (options) {
var opts = options || {}
var defaultCharset = opts.defaultCharset || 'utf-8'
var inflate = opts.inflate !== false
var limit = typeof opts.limit !== 'number'
? bytes.parse(opts.limit || '100kb')
: opts.limit
var type = opts.type || 'text/plain'
var verify = opts.verify || false
if (verify !== false && typeof verify !== 'function') {
throw new TypeError('option verify must be function')
}
// create the appropriate type checking function
var shouldParse = typeof type !== 'function'
? typeChecker(type)
: type
function parse (buf) {
return buf
}
return function textParser (req, res, next) {
if (req._body) {
debug('body already parsed')
next()
return
}
req.body = req.body || {}
// skip requests without bodies
if (!typeis.hasBody(req)) {
debug('skip empty body')
next()
return
}
debug('content-type %j', req.headers['content-type'])
// determine if request should be parsed
if (!shouldParse(req)) {
debug('skip parsing')
next()
return
}
// get charset
var charset = getCharset(req) || defaultCharset
// read
read(req, res, next, parse, debug, {
encoding: charset,
inflate: inflate,
limit: limit,
verify: verify
})
}
}
/**
* Get the charset of a request.
*
* @param {object} req
* @api private
*/
function getCharset (req) {
try {
return (contentType.parse(req).parameters.charset || '').toLowerCase()
} catch (e) {
return undefined
}
}
/**
* Get the simple type checker.
*
* @param {string} type
* @return {function}
*/
function typeChecker (type) {
return function checkType (req) {
return Boolean(typeis(req, type))
}
}

View File

@@ -1,300 +0,0 @@
/*!
* body-parser
* Copyright(c) 2014 Jonathan Ong
* Copyright(c) 2014-2015 Douglas Christopher Wilson
* MIT Licensed
*/
'use strict'
/**
* Module dependencies.
* @private
*/
var bytes = require('bytes')
var contentType = require('content-type')
var createError = require('http-errors')
var debug = require('debug')('body-parser:urlencoded')
var deprecate = require('depd')('body-parser')
var read = require('../read')
var typeis = require('type-is')
/**
* Module exports.
*/
module.exports = urlencoded
/**
* Cache of parser modules.
*/
var parsers = Object.create(null)
/**
* Create a middleware to parse urlencoded bodies.
*
* @param {object} [options]
* @return {function}
* @public
*/
function urlencoded (options) {
var opts = options || {}
// notice because option default will flip in next major
if (opts.extended === undefined) {
deprecate('undefined extended: provide extended option')
}
var extended = opts.extended !== false
var inflate = opts.inflate !== false
var limit = typeof opts.limit !== 'number'
? bytes.parse(opts.limit || '100kb')
: opts.limit
var type = opts.type || 'application/x-www-form-urlencoded'
var verify = opts.verify || false
if (verify !== false && typeof verify !== 'function') {
throw new TypeError('option verify must be function')
}
// create the appropriate query parser
var queryparse = extended
? extendedparser(opts)
: simpleparser(opts)
// create the appropriate type checking function
var shouldParse = typeof type !== 'function'
? typeChecker(type)
: type
function parse (body) {
return body.length
? queryparse(body)
: {}
}
return function urlencodedParser (req, res, next) {
if (req._body) {
debug('body already parsed')
next()
return
}
req.body = req.body || {}
// skip requests without bodies
if (!typeis.hasBody(req)) {
debug('skip empty body')
next()
return
}
debug('content-type %j', req.headers['content-type'])
// determine if request should be parsed
if (!shouldParse(req)) {
debug('skip parsing')
next()
return
}
// assert charset
var charset = getCharset(req) || 'utf-8'
if (charset !== 'utf-8') {
debug('invalid charset')
next(createError(415, 'unsupported charset "' + charset.toUpperCase() + '"', {
charset: charset,
type: 'charset.unsupported'
}))
return
}
// read
read(req, res, next, parse, debug, {
debug: debug,
encoding: charset,
inflate: inflate,
limit: limit,
verify: verify
})
}
}
/**
* Get the extended query parser.
*
* @param {object} options
*/
function extendedparser (options) {
var parameterLimit = options.parameterLimit !== undefined
? options.parameterLimit
: 1000
var depth = options.depth !== undefined ? options.depth : 32
var parse = parser('qs')
if (isNaN(parameterLimit) || parameterLimit < 1) {
throw new TypeError('option parameterLimit must be a positive number')
}
if (isNaN(depth) || depth < 0) {
throw new TypeError('option depth must be a zero or a positive number')
}
if (isFinite(parameterLimit)) {
parameterLimit = parameterLimit | 0
}
return function queryparse (body) {
var paramCount = parameterCount(body, parameterLimit)
if (paramCount === undefined) {
debug('too many parameters')
throw createError(413, 'too many parameters', {
type: 'parameters.too.many'
})
}
var arrayLimit = Math.max(100, paramCount)
debug('parse extended urlencoding')
try {
return parse(body, {
allowPrototypes: true,
arrayLimit: arrayLimit,
depth: depth,
strictDepth: true,
parameterLimit: parameterLimit
})
} catch (err) {
if (err instanceof RangeError) {
throw createError(400, 'The input exceeded the depth', {
type: 'querystring.parse.rangeError'
})
} else {
throw err
}
}
}
}
/**
* Get the charset of a request.
*
* @param {object} req
* @api private
*/
function getCharset (req) {
try {
return (contentType.parse(req).parameters.charset || '').toLowerCase()
} catch (e) {
return undefined
}
}
/**
* Count the number of parameters, stopping once limit reached
*
* @param {string} body
* @param {number} limit
* @api private
*/
function parameterCount (body, limit) {
var count = 0
var index = 0
while ((index = body.indexOf('&', index)) !== -1) {
count++
index++
if (count === limit) {
return undefined
}
}
return count
}
/**
* Get parser for module name dynamically.
*
* @param {string} name
* @return {function}
* @api private
*/
function parser (name) {
var mod = parsers[name]
if (mod !== undefined) {
return mod.parse
}
// this uses a switch for static require analysis
switch (name) {
case 'qs':
mod = require('qs')
break
case 'querystring':
mod = require('querystring')
break
}
// store to prevent invoking require()
parsers[name] = mod
return mod.parse
}
/**
* Get the simple query parser.
*
* @param {object} options
*/
function simpleparser (options) {
var parameterLimit = options.parameterLimit !== undefined
? options.parameterLimit
: 1000
var parse = parser('querystring')
if (isNaN(parameterLimit) || parameterLimit < 1) {
throw new TypeError('option parameterLimit must be a positive number')
}
if (isFinite(parameterLimit)) {
parameterLimit = parameterLimit | 0
}
return function queryparse (body) {
var paramCount = parameterCount(body, parameterLimit)
if (paramCount === undefined) {
debug('too many parameters')
throw createError(413, 'too many parameters', {
type: 'parameters.too.many'
})
}
debug('parse urlencoding')
return parse(body, undefined, undefined, { maxKeys: parameterLimit })
}
}
/**
* Get the simple type checker.
*
* @param {string} type
* @return {function}
*/
function typeChecker (type) {
return function checkType (req) {
return Boolean(typeis(req, type))
}
}

View File

@@ -1,55 +0,0 @@
{
"name": "body-parser",
"description": "Node.js body parsing middleware",
"version": "1.20.4",
"contributors": [
"Douglas Christopher Wilson <doug@somethingdoug.com>",
"Jonathan Ong <me@jongleberry.com> (http://jongleberry.com)"
],
"license": "MIT",
"repository": "expressjs/body-parser",
"dependencies": {
"bytes": "~3.1.2",
"content-type": "~1.0.5",
"debug": "2.6.9",
"depd": "2.0.0",
"destroy": "~1.2.0",
"http-errors": "~2.0.1",
"iconv-lite": "~0.4.24",
"on-finished": "~2.4.1",
"qs": "~6.14.0",
"raw-body": "~2.5.3",
"type-is": "~1.6.18",
"unpipe": "~1.0.0"
},
"devDependencies": {
"eslint": "8.34.0",
"eslint-config-standard": "14.1.1",
"eslint-plugin-import": "2.27.5",
"eslint-plugin-markdown": "3.0.0",
"eslint-plugin-node": "11.1.0",
"eslint-plugin-promise": "6.1.1",
"eslint-plugin-standard": "4.1.0",
"methods": "1.1.2",
"mocha": "10.2.0",
"nyc": "15.1.0",
"safe-buffer": "5.2.1",
"supertest": "6.3.3"
},
"files": [
"lib/",
"LICENSE",
"HISTORY.md",
"index.js"
],
"engines": {
"node": ">= 0.8",
"npm": "1.2.8000 || >= 1.4.16"
},
"scripts": {
"lint": "eslint .",
"test": "mocha --require test/support/env --reporter spec --check-leaks --bail test/",
"test-ci": "nyc --reporter=lcov --reporter=text npm test",
"test-cov": "nyc --reporter=html --reporter=text npm test"
}
}

View File

@@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (c) 2014-present, Jon Schlinkert.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -1,586 +0,0 @@
# braces [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=W8YFZ425KND68) [![NPM version](https://img.shields.io/npm/v/braces.svg?style=flat)](https://www.npmjs.com/package/braces) [![NPM monthly downloads](https://img.shields.io/npm/dm/braces.svg?style=flat)](https://npmjs.org/package/braces) [![NPM total downloads](https://img.shields.io/npm/dt/braces.svg?style=flat)](https://npmjs.org/package/braces) [![Linux Build Status](https://img.shields.io/travis/micromatch/braces.svg?style=flat&label=Travis)](https://travis-ci.org/micromatch/braces)
> Bash-like brace expansion, implemented in JavaScript. Safer than other brace expansion libs, with complete support for the Bash 4.3 braces specification, without sacrificing speed.
Please consider following this project's author, [Jon Schlinkert](https://github.com/jonschlinkert), and consider starring the project to show your :heart: and support.
## Install
Install with [npm](https://www.npmjs.com/):
```sh
$ npm install --save braces
```
## v3.0.0 Released!!
See the [changelog](CHANGELOG.md) for details.
## Why use braces?
Brace patterns make globs more powerful by adding the ability to match specific ranges and sequences of characters.
- **Accurate** - complete support for the [Bash 4.3 Brace Expansion](www.gnu.org/software/bash/) specification (passes all of the Bash braces tests)
- **[fast and performant](#benchmarks)** - Starts fast, runs fast and [scales well](#performance) as patterns increase in complexity.
- **Organized code base** - The parser and compiler are easy to maintain and update when edge cases crop up.
- **Well-tested** - Thousands of test assertions, and passes all of the Bash, minimatch, and [brace-expansion](https://github.com/juliangruber/brace-expansion) unit tests (as of the date this was written).
- **Safer** - You shouldn't have to worry about users defining aggressive or malicious brace patterns that can break your application. Braces takes measures to prevent malicious regex that can be used for DDoS attacks (see [catastrophic backtracking](https://www.regular-expressions.info/catastrophic.html)).
- [Supports lists](#lists) - (aka "sets") `a/{b,c}/d` => `['a/b/d', 'a/c/d']`
- [Supports sequences](#sequences) - (aka "ranges") `{01..03}` => `['01', '02', '03']`
- [Supports steps](#steps) - (aka "increments") `{2..10..2}` => `['2', '4', '6', '8', '10']`
- [Supports escaping](#escaping) - To prevent evaluation of special characters.
## Usage
The main export is a function that takes one or more brace `patterns` and `options`.
```js
const braces = require('braces');
// braces(patterns[, options]);
console.log(braces(['{01..05}', '{a..e}']));
//=> ['(0[1-5])', '([a-e])']
console.log(braces(['{01..05}', '{a..e}'], { expand: true }));
//=> ['01', '02', '03', '04', '05', 'a', 'b', 'c', 'd', 'e']
```
### Brace Expansion vs. Compilation
By default, brace patterns are compiled into strings that are optimized for creating regular expressions and matching.
**Compiled**
```js
console.log(braces('a/{x,y,z}/b'));
//=> ['a/(x|y|z)/b']
console.log(braces(['a/{01..20}/b', 'a/{1..5}/b']));
//=> [ 'a/(0[1-9]|1[0-9]|20)/b', 'a/([1-5])/b' ]
```
**Expanded**
Enable brace expansion by setting the `expand` option to true, or by using [braces.expand()](#expand) (returns an array similar to what you'd expect from Bash, or `echo {1..5}`, or [minimatch](https://github.com/isaacs/minimatch)):
```js
console.log(braces('a/{x,y,z}/b', { expand: true }));
//=> ['a/x/b', 'a/y/b', 'a/z/b']
console.log(braces.expand('{01..10}'));
//=> ['01','02','03','04','05','06','07','08','09','10']
```
### Lists
Expand lists (like Bash "sets"):
```js
console.log(braces('a/{foo,bar,baz}/*.js'));
//=> ['a/(foo|bar|baz)/*.js']
console.log(braces.expand('a/{foo,bar,baz}/*.js'));
//=> ['a/foo/*.js', 'a/bar/*.js', 'a/baz/*.js']
```
### Sequences
Expand ranges of characters (like Bash "sequences"):
```js
console.log(braces.expand('{1..3}')); // ['1', '2', '3']
console.log(braces.expand('a/{1..3}/b')); // ['a/1/b', 'a/2/b', 'a/3/b']
console.log(braces('{a..c}', { expand: true })); // ['a', 'b', 'c']
console.log(braces('foo/{a..c}', { expand: true })); // ['foo/a', 'foo/b', 'foo/c']
// supports zero-padded ranges
console.log(braces('a/{01..03}/b')); //=> ['a/(0[1-3])/b']
console.log(braces('a/{001..300}/b')); //=> ['a/(0{2}[1-9]|0[1-9][0-9]|[12][0-9]{2}|300)/b']
```
See [fill-range](https://github.com/jonschlinkert/fill-range) for all available range-expansion options.
### Steppped ranges
Steps, or increments, may be used with ranges:
```js
console.log(braces.expand('{2..10..2}'));
//=> ['2', '4', '6', '8', '10']
console.log(braces('{2..10..2}'));
//=> ['(2|4|6|8|10)']
```
When the [.optimize](#optimize) method is used, or [options.optimize](#optionsoptimize) is set to true, sequences are passed to [to-regex-range](https://github.com/jonschlinkert/to-regex-range) for expansion.
### Nesting
Brace patterns may be nested. The results of each expanded string are not sorted, and left to right order is preserved.
**"Expanded" braces**
```js
console.log(braces.expand('a{b,c,/{x,y}}/e'));
//=> ['ab/e', 'ac/e', 'a/x/e', 'a/y/e']
console.log(braces.expand('a/{x,{1..5},y}/c'));
//=> ['a/x/c', 'a/1/c', 'a/2/c', 'a/3/c', 'a/4/c', 'a/5/c', 'a/y/c']
```
**"Optimized" braces**
```js
console.log(braces('a{b,c,/{x,y}}/e'));
//=> ['a(b|c|/(x|y))/e']
console.log(braces('a/{x,{1..5},y}/c'));
//=> ['a/(x|([1-5])|y)/c']
```
### Escaping
**Escaping braces**
A brace pattern will not be expanded or evaluted if _either the opening or closing brace is escaped_:
```js
console.log(braces.expand('a\\{d,c,b}e'));
//=> ['a{d,c,b}e']
console.log(braces.expand('a{d,c,b\\}e'));
//=> ['a{d,c,b}e']
```
**Escaping commas**
Commas inside braces may also be escaped:
```js
console.log(braces.expand('a{b\\,c}d'));
//=> ['a{b,c}d']
console.log(braces.expand('a{d\\,c,b}e'));
//=> ['ad,ce', 'abe']
```
**Single items**
Following bash conventions, a brace pattern is also not expanded when it contains a single character:
```js
console.log(braces.expand('a{b}c'));
//=> ['a{b}c']
```
## Options
### options.maxLength
**Type**: `Number`
**Default**: `10,000`
**Description**: Limit the length of the input string. Useful when the input string is generated or your application allows users to pass a string, et cetera.
```js
console.log(braces('a/{b,c}/d', { maxLength: 3 })); //=> throws an error
```
### options.expand
**Type**: `Boolean`
**Default**: `undefined`
**Description**: Generate an "expanded" brace pattern (alternatively you can use the `braces.expand()` method, which does the same thing).
```js
console.log(braces('a/{b,c}/d', { expand: true }));
//=> [ 'a/b/d', 'a/c/d' ]
```
### options.nodupes
**Type**: `Boolean`
**Default**: `undefined`
**Description**: Remove duplicates from the returned array.
### options.rangeLimit
**Type**: `Number`
**Default**: `1000`
**Description**: To prevent malicious patterns from being passed by users, an error is thrown when `braces.expand()` is used or `options.expand` is true and the generated range will exceed the `rangeLimit`.
You can customize `options.rangeLimit` or set it to `Inifinity` to disable this altogether.
**Examples**
```js
// pattern exceeds the "rangeLimit", so it's optimized automatically
console.log(braces.expand('{1..1000}'));
//=> ['([1-9]|[1-9][0-9]{1,2}|1000)']
// pattern does not exceed "rangeLimit", so it's NOT optimized
console.log(braces.expand('{1..100}'));
//=> ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23', '24', '25', '26', '27', '28', '29', '30', '31', '32', '33', '34', '35', '36', '37', '38', '39', '40', '41', '42', '43', '44', '45', '46', '47', '48', '49', '50', '51', '52', '53', '54', '55', '56', '57', '58', '59', '60', '61', '62', '63', '64', '65', '66', '67', '68', '69', '70', '71', '72', '73', '74', '75', '76', '77', '78', '79', '80', '81', '82', '83', '84', '85', '86', '87', '88', '89', '90', '91', '92', '93', '94', '95', '96', '97', '98', '99', '100']
```
### options.transform
**Type**: `Function`
**Default**: `undefined`
**Description**: Customize range expansion.
**Example: Transforming non-numeric values**
```js
const alpha = braces.expand('x/{a..e}/y', {
transform(value, index) {
// When non-numeric values are passed, "value" is a character code.
return 'foo/' + String.fromCharCode(value) + '-' + index;
},
});
console.log(alpha);
//=> [ 'x/foo/a-0/y', 'x/foo/b-1/y', 'x/foo/c-2/y', 'x/foo/d-3/y', 'x/foo/e-4/y' ]
```
**Example: Transforming numeric values**
```js
const numeric = braces.expand('{1..5}', {
transform(value) {
// when numeric values are passed, "value" is a number
return 'foo/' + value * 2;
},
});
console.log(numeric);
//=> [ 'foo/2', 'foo/4', 'foo/6', 'foo/8', 'foo/10' ]
```
### options.quantifiers
**Type**: `Boolean`
**Default**: `undefined`
**Description**: In regular expressions, quanitifiers can be used to specify how many times a token can be repeated. For example, `a{1,3}` will match the letter `a` one to three times.
Unfortunately, regex quantifiers happen to share the same syntax as [Bash lists](#lists)
The `quantifiers` option tells braces to detect when [regex quantifiers](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp#quantifiers) are defined in the given pattern, and not to try to expand them as lists.
**Examples**
```js
const braces = require('braces');
console.log(braces('a/b{1,3}/{x,y,z}'));
//=> [ 'a/b(1|3)/(x|y|z)' ]
console.log(braces('a/b{1,3}/{x,y,z}', { quantifiers: true }));
//=> [ 'a/b{1,3}/(x|y|z)' ]
console.log(braces('a/b{1,3}/{x,y,z}', { quantifiers: true, expand: true }));
//=> [ 'a/b{1,3}/x', 'a/b{1,3}/y', 'a/b{1,3}/z' ]
```
### options.keepEscaping
**Type**: `Boolean`
**Default**: `undefined`
**Description**: Do not strip backslashes that were used for escaping from the result.
## What is "brace expansion"?
Brace expansion is a type of parameter expansion that was made popular by unix shells for generating lists of strings, as well as regex-like matching when used alongside wildcards (globs).
In addition to "expansion", braces are also used for matching. In other words:
- [brace expansion](#brace-expansion) is for generating new lists
- [brace matching](#brace-matching) is for filtering existing lists
<details>
<summary><strong>More about brace expansion</strong> (click to expand)</summary>
There are two main types of brace expansion:
1. **lists**: which are defined using comma-separated values inside curly braces: `{a,b,c}`
2. **sequences**: which are defined using a starting value and an ending value, separated by two dots: `a{1..3}b`. Optionally, a third argument may be passed to define a "step" or increment to use: `a{1..100..10}b`. These are also sometimes referred to as "ranges".
Here are some example brace patterns to illustrate how they work:
**Sets**
```
{a,b,c} => a b c
{a,b,c}{1,2} => a1 a2 b1 b2 c1 c2
```
**Sequences**
```
{1..9} => 1 2 3 4 5 6 7 8 9
{4..-4} => 4 3 2 1 0 -1 -2 -3 -4
{1..20..3} => 1 4 7 10 13 16 19
{a..j} => a b c d e f g h i j
{j..a} => j i h g f e d c b a
{a..z..3} => a d g j m p s v y
```
**Combination**
Sets and sequences can be mixed together or used along with any other strings.
```
{a,b,c}{1..3} => a1 a2 a3 b1 b2 b3 c1 c2 c3
foo/{a,b,c}/bar => foo/a/bar foo/b/bar foo/c/bar
```
The fact that braces can be "expanded" from relatively simple patterns makes them ideal for quickly generating test fixtures, file paths, and similar use cases.
## Brace matching
In addition to _expansion_, brace patterns are also useful for performing regular-expression-like matching.
For example, the pattern `foo/{1..3}/bar` would match any of following strings:
```
foo/1/bar
foo/2/bar
foo/3/bar
```
But not:
```
baz/1/qux
baz/2/qux
baz/3/qux
```
Braces can also be combined with [glob patterns](https://github.com/jonschlinkert/micromatch) to perform more advanced wildcard matching. For example, the pattern `*/{1..3}/*` would match any of following strings:
```
foo/1/bar
foo/2/bar
foo/3/bar
baz/1/qux
baz/2/qux
baz/3/qux
```
## Brace matching pitfalls
Although brace patterns offer a user-friendly way of matching ranges or sets of strings, there are also some major disadvantages and potential risks you should be aware of.
### tldr
**"brace bombs"**
- brace expansion can eat up a huge amount of processing resources
- as brace patterns increase _linearly in size_, the system resources required to expand the pattern increase exponentially
- users can accidentally (or intentially) exhaust your system's resources resulting in the equivalent of a DoS attack (bonus: no programming knowledge is required!)
For a more detailed explanation with examples, see the [geometric complexity](#geometric-complexity) section.
### The solution
Jump to the [performance section](#performance) to see how Braces solves this problem in comparison to other libraries.
### Geometric complexity
At minimum, brace patterns with sets limited to two elements have quadradic or `O(n^2)` complexity. But the complexity of the algorithm increases exponentially as the number of sets, _and elements per set_, increases, which is `O(n^c)`.
For example, the following sets demonstrate quadratic (`O(n^2)`) complexity:
```
{1,2}{3,4} => (2X2) => 13 14 23 24
{1,2}{3,4}{5,6} => (2X2X2) => 135 136 145 146 235 236 245 246
```
But add an element to a set, and we get a n-fold Cartesian product with `O(n^c)` complexity:
```
{1,2,3}{4,5,6}{7,8,9} => (3X3X3) => 147 148 149 157 158 159 167 168 169 247 248
249 257 258 259 267 268 269 347 348 349 357
358 359 367 368 369
```
Now, imagine how this complexity grows given that each element is a n-tuple:
```
{1..100}{1..100} => (100X100) => 10,000 elements (38.4 kB)
{1..100}{1..100}{1..100} => (100X100X100) => 1,000,000 elements (5.76 MB)
```
Although these examples are clearly contrived, they demonstrate how brace patterns can quickly grow out of control.
**More information**
Interested in learning more about brace expansion?
- [linuxjournal/bash-brace-expansion](http://www.linuxjournal.com/content/bash-brace-expansion)
- [rosettacode/Brace_expansion](https://rosettacode.org/wiki/Brace_expansion)
- [cartesian product](https://en.wikipedia.org/wiki/Cartesian_product)
</details>
## Performance
Braces is not only screaming fast, it's also more accurate the other brace expansion libraries.
### Better algorithms
Fortunately there is a solution to the ["brace bomb" problem](#brace-matching-pitfalls): _don't expand brace patterns into an array when they're used for matching_.
Instead, convert the pattern into an optimized regular expression. This is easier said than done, and braces is the only library that does this currently.
**The proof is in the numbers**
Minimatch gets exponentially slower as patterns increase in complexity, braces does not. The following results were generated using `braces()` and `minimatch.braceExpand()`, respectively.
| **Pattern** | **braces** | **[minimatch][]** |
| --------------------------- | ------------------- | ---------------------------- |
| `{1..9007199254740991}`[^1] | `298 B` (5ms 459μs) | N/A (freezes) |
| `{1..1000000000000000}` | `41 B` (1ms 15μs) | N/A (freezes) |
| `{1..100000000000000}` | `40 B` (890μs) | N/A (freezes) |
| `{1..10000000000000}` | `39 B` (2ms 49μs) | N/A (freezes) |
| `{1..1000000000000}` | `38 B` (608μs) | N/A (freezes) |
| `{1..100000000000}` | `37 B` (397μs) | N/A (freezes) |
| `{1..10000000000}` | `35 B` (983μs) | N/A (freezes) |
| `{1..1000000000}` | `34 B` (798μs) | N/A (freezes) |
| `{1..100000000}` | `33 B` (733μs) | N/A (freezes) |
| `{1..10000000}` | `32 B` (5ms 632μs) | `78.89 MB` (16s 388ms 569μs) |
| `{1..1000000}` | `31 B` (1ms 381μs) | `6.89 MB` (1s 496ms 887μs) |
| `{1..100000}` | `30 B` (950μs) | `588.89 kB` (146ms 921μs) |
| `{1..10000}` | `29 B` (1ms 114μs) | `48.89 kB` (14ms 187μs) |
| `{1..1000}` | `28 B` (760μs) | `3.89 kB` (1ms 453μs) |
| `{1..100}` | `22 B` (345μs) | `291 B` (196μs) |
| `{1..10}` | `10 B` (533μs) | `20 B` (37μs) |
| `{1..3}` | `7 B` (190μs) | `5 B` (27μs) |
### Faster algorithms
When you need expansion, braces is still much faster.
_(the following results were generated using `braces.expand()` and `minimatch.braceExpand()`, respectively)_
| **Pattern** | **braces** | **[minimatch][]** |
| --------------- | --------------------------- | ---------------------------- |
| `{1..10000000}` | `78.89 MB` (2s 698ms 642μs) | `78.89 MB` (18s 601ms 974μs) |
| `{1..1000000}` | `6.89 MB` (458ms 576μs) | `6.89 MB` (1s 491ms 621μs) |
| `{1..100000}` | `588.89 kB` (20ms 728μs) | `588.89 kB` (156ms 919μs) |
| `{1..10000}` | `48.89 kB` (2ms 202μs) | `48.89 kB` (13ms 641μs) |
| `{1..1000}` | `3.89 kB` (1ms 796μs) | `3.89 kB` (1ms 958μs) |
| `{1..100}` | `291 B` (424μs) | `291 B` (211μs) |
| `{1..10}` | `20 B` (487μs) | `20 B` (72μs) |
| `{1..3}` | `5 B` (166μs) | `5 B` (27μs) |
If you'd like to run these comparisons yourself, see [test/support/generate.js](test/support/generate.js).
## Benchmarks
### Running benchmarks
Install dev dependencies:
```bash
npm i -d && npm benchmark
```
### Latest results
Braces is more accurate, without sacrificing performance.
```bash
● expand - range (expanded)
braces x 53,167 ops/sec ±0.12% (102 runs sampled)
minimatch x 11,378 ops/sec ±0.10% (102 runs sampled)
● expand - range (optimized for regex)
braces x 373,442 ops/sec ±0.04% (100 runs sampled)
minimatch x 3,262 ops/sec ±0.18% (100 runs sampled)
● expand - nested ranges (expanded)
braces x 33,921 ops/sec ±0.09% (99 runs sampled)
minimatch x 10,855 ops/sec ±0.28% (100 runs sampled)
● expand - nested ranges (optimized for regex)
braces x 287,479 ops/sec ±0.52% (98 runs sampled)
minimatch x 3,219 ops/sec ±0.28% (101 runs sampled)
● expand - set (expanded)
braces x 238,243 ops/sec ±0.19% (97 runs sampled)
minimatch x 538,268 ops/sec ±0.31% (96 runs sampled)
● expand - set (optimized for regex)
braces x 321,844 ops/sec ±0.10% (97 runs sampled)
minimatch x 140,600 ops/sec ±0.15% (100 runs sampled)
● expand - nested sets (expanded)
braces x 165,371 ops/sec ±0.42% (96 runs sampled)
minimatch x 337,720 ops/sec ±0.28% (100 runs sampled)
● expand - nested sets (optimized for regex)
braces x 242,948 ops/sec ±0.12% (99 runs sampled)
minimatch x 87,403 ops/sec ±0.79% (96 runs sampled)
```
## About
<details>
<summary><strong>Contributing</strong></summary>
Pull requests and stars are always welcome. For bugs and feature requests, [please create an issue](../../issues/new).
</details>
<details>
<summary><strong>Running Tests</strong></summary>
Running and reviewing unit tests is a great way to get familiarized with a library and its API. You can install dependencies and run tests with the following command:
```sh
$ npm install && npm test
```
</details>
<details>
<summary><strong>Building docs</strong></summary>
_(This project's readme.md is generated by [verb](https://github.com/verbose/verb-generate-readme), please don't edit the readme directly. Any changes to the readme must be made in the [.verb.md](.verb.md) readme template.)_
To generate the readme, run the following command:
```sh
$ npm install -g verbose/verb#dev verb-generate-readme && verb
```
</details>
### Contributors
| **Commits** | **Contributor** |
| ----------- | ------------------------------------------------------------- |
| 197 | [jonschlinkert](https://github.com/jonschlinkert) |
| 4 | [doowb](https://github.com/doowb) |
| 1 | [es128](https://github.com/es128) |
| 1 | [eush77](https://github.com/eush77) |
| 1 | [hemanth](https://github.com/hemanth) |
| 1 | [wtgtybhertgeghgtwtg](https://github.com/wtgtybhertgeghgtwtg) |
### Author
**Jon Schlinkert**
- [GitHub Profile](https://github.com/jonschlinkert)
- [Twitter Profile](https://twitter.com/jonschlinkert)
- [LinkedIn Profile](https://linkedin.com/in/jonschlinkert)
### License
Copyright © 2019, [Jon Schlinkert](https://github.com/jonschlinkert).
Released under the [MIT License](LICENSE).
---
_This file was generated by [verb-generate-readme](https://github.com/verbose/verb-generate-readme), v0.8.0, on April 08, 2019._

View File

@@ -1,170 +0,0 @@
'use strict';
const stringify = require('./lib/stringify');
const compile = require('./lib/compile');
const expand = require('./lib/expand');
const parse = require('./lib/parse');
/**
* Expand the given pattern or create a regex-compatible string.
*
* ```js
* const braces = require('braces');
* console.log(braces('{a,b,c}', { compile: true })); //=> ['(a|b|c)']
* console.log(braces('{a,b,c}')); //=> ['a', 'b', 'c']
* ```
* @param {String} `str`
* @param {Object} `options`
* @return {String}
* @api public
*/
const braces = (input, options = {}) => {
let output = [];
if (Array.isArray(input)) {
for (const pattern of input) {
const result = braces.create(pattern, options);
if (Array.isArray(result)) {
output.push(...result);
} else {
output.push(result);
}
}
} else {
output = [].concat(braces.create(input, options));
}
if (options && options.expand === true && options.nodupes === true) {
output = [...new Set(output)];
}
return output;
};
/**
* Parse the given `str` with the given `options`.
*
* ```js
* // braces.parse(pattern, [, options]);
* const ast = braces.parse('a/{b,c}/d');
* console.log(ast);
* ```
* @param {String} pattern Brace pattern to parse
* @param {Object} options
* @return {Object} Returns an AST
* @api public
*/
braces.parse = (input, options = {}) => parse(input, options);
/**
* Creates a braces string from an AST, or an AST node.
*
* ```js
* const braces = require('braces');
* let ast = braces.parse('foo/{a,b}/bar');
* console.log(stringify(ast.nodes[2])); //=> '{a,b}'
* ```
* @param {String} `input` Brace pattern or AST.
* @param {Object} `options`
* @return {Array} Returns an array of expanded values.
* @api public
*/
braces.stringify = (input, options = {}) => {
if (typeof input === 'string') {
return stringify(braces.parse(input, options), options);
}
return stringify(input, options);
};
/**
* Compiles a brace pattern into a regex-compatible, optimized string.
* This method is called by the main [braces](#braces) function by default.
*
* ```js
* const braces = require('braces');
* console.log(braces.compile('a/{b,c}/d'));
* //=> ['a/(b|c)/d']
* ```
* @param {String} `input` Brace pattern or AST.
* @param {Object} `options`
* @return {Array} Returns an array of expanded values.
* @api public
*/
braces.compile = (input, options = {}) => {
if (typeof input === 'string') {
input = braces.parse(input, options);
}
return compile(input, options);
};
/**
* Expands a brace pattern into an array. This method is called by the
* main [braces](#braces) function when `options.expand` is true. Before
* using this method it's recommended that you read the [performance notes](#performance))
* and advantages of using [.compile](#compile) instead.
*
* ```js
* const braces = require('braces');
* console.log(braces.expand('a/{b,c}/d'));
* //=> ['a/b/d', 'a/c/d'];
* ```
* @param {String} `pattern` Brace pattern
* @param {Object} `options`
* @return {Array} Returns an array of expanded values.
* @api public
*/
braces.expand = (input, options = {}) => {
if (typeof input === 'string') {
input = braces.parse(input, options);
}
let result = expand(input, options);
// filter out empty strings if specified
if (options.noempty === true) {
result = result.filter(Boolean);
}
// filter out duplicates if specified
if (options.nodupes === true) {
result = [...new Set(result)];
}
return result;
};
/**
* Processes a brace pattern and returns either an expanded array
* (if `options.expand` is true), a highly optimized regex-compatible string.
* This method is called by the main [braces](#braces) function.
*
* ```js
* const braces = require('braces');
* console.log(braces.create('user-{200..300}/project-{a,b,c}-{1..10}'))
* //=> 'user-(20[0-9]|2[1-9][0-9]|300)/project-(a|b|c)-([1-9]|10)'
* ```
* @param {String} `pattern` Brace pattern
* @param {Object} `options`
* @return {Array} Returns an array of expanded values.
* @api public
*/
braces.create = (input, options = {}) => {
if (input === '' || input.length < 3) {
return [input];
}
return options.expand !== true
? braces.compile(input, options)
: braces.expand(input, options);
};
/**
* Expose "braces"
*/
module.exports = braces;

View File

@@ -1,60 +0,0 @@
'use strict';
const fill = require('fill-range');
const utils = require('./utils');
const compile = (ast, options = {}) => {
const walk = (node, parent = {}) => {
const invalidBlock = utils.isInvalidBrace(parent);
const invalidNode = node.invalid === true && options.escapeInvalid === true;
const invalid = invalidBlock === true || invalidNode === true;
const prefix = options.escapeInvalid === true ? '\\' : '';
let output = '';
if (node.isOpen === true) {
return prefix + node.value;
}
if (node.isClose === true) {
console.log('node.isClose', prefix, node.value);
return prefix + node.value;
}
if (node.type === 'open') {
return invalid ? prefix + node.value : '(';
}
if (node.type === 'close') {
return invalid ? prefix + node.value : ')';
}
if (node.type === 'comma') {
return node.prev.type === 'comma' ? '' : invalid ? node.value : '|';
}
if (node.value) {
return node.value;
}
if (node.nodes && node.ranges > 0) {
const args = utils.reduce(node.nodes);
const range = fill(...args, { ...options, wrap: false, toRegex: true, strictZeros: true });
if (range.length !== 0) {
return args.length > 1 && range.length > 1 ? `(${range})` : range;
}
}
if (node.nodes) {
for (const child of node.nodes) {
output += walk(child, node);
}
}
return output;
};
return walk(ast);
};
module.exports = compile;

View File

@@ -1,57 +0,0 @@
'use strict';
module.exports = {
MAX_LENGTH: 10000,
// Digits
CHAR_0: '0', /* 0 */
CHAR_9: '9', /* 9 */
// Alphabet chars.
CHAR_UPPERCASE_A: 'A', /* A */
CHAR_LOWERCASE_A: 'a', /* a */
CHAR_UPPERCASE_Z: 'Z', /* Z */
CHAR_LOWERCASE_Z: 'z', /* z */
CHAR_LEFT_PARENTHESES: '(', /* ( */
CHAR_RIGHT_PARENTHESES: ')', /* ) */
CHAR_ASTERISK: '*', /* * */
// Non-alphabetic chars.
CHAR_AMPERSAND: '&', /* & */
CHAR_AT: '@', /* @ */
CHAR_BACKSLASH: '\\', /* \ */
CHAR_BACKTICK: '`', /* ` */
CHAR_CARRIAGE_RETURN: '\r', /* \r */
CHAR_CIRCUMFLEX_ACCENT: '^', /* ^ */
CHAR_COLON: ':', /* : */
CHAR_COMMA: ',', /* , */
CHAR_DOLLAR: '$', /* . */
CHAR_DOT: '.', /* . */
CHAR_DOUBLE_QUOTE: '"', /* " */
CHAR_EQUAL: '=', /* = */
CHAR_EXCLAMATION_MARK: '!', /* ! */
CHAR_FORM_FEED: '\f', /* \f */
CHAR_FORWARD_SLASH: '/', /* / */
CHAR_HASH: '#', /* # */
CHAR_HYPHEN_MINUS: '-', /* - */
CHAR_LEFT_ANGLE_BRACKET: '<', /* < */
CHAR_LEFT_CURLY_BRACE: '{', /* { */
CHAR_LEFT_SQUARE_BRACKET: '[', /* [ */
CHAR_LINE_FEED: '\n', /* \n */
CHAR_NO_BREAK_SPACE: '\u00A0', /* \u00A0 */
CHAR_PERCENT: '%', /* % */
CHAR_PLUS: '+', /* + */
CHAR_QUESTION_MARK: '?', /* ? */
CHAR_RIGHT_ANGLE_BRACKET: '>', /* > */
CHAR_RIGHT_CURLY_BRACE: '}', /* } */
CHAR_RIGHT_SQUARE_BRACKET: ']', /* ] */
CHAR_SEMICOLON: ';', /* ; */
CHAR_SINGLE_QUOTE: '\'', /* ' */
CHAR_SPACE: ' ', /* */
CHAR_TAB: '\t', /* \t */
CHAR_UNDERSCORE: '_', /* _ */
CHAR_VERTICAL_LINE: '|', /* | */
CHAR_ZERO_WIDTH_NOBREAK_SPACE: '\uFEFF' /* \uFEFF */
};

View File

@@ -1,113 +0,0 @@
'use strict';
const fill = require('fill-range');
const stringify = require('./stringify');
const utils = require('./utils');
const append = (queue = '', stash = '', enclose = false) => {
const result = [];
queue = [].concat(queue);
stash = [].concat(stash);
if (!stash.length) return queue;
if (!queue.length) {
return enclose ? utils.flatten(stash).map(ele => `{${ele}}`) : stash;
}
for (const item of queue) {
if (Array.isArray(item)) {
for (const value of item) {
result.push(append(value, stash, enclose));
}
} else {
for (let ele of stash) {
if (enclose === true && typeof ele === 'string') ele = `{${ele}}`;
result.push(Array.isArray(ele) ? append(item, ele, enclose) : item + ele);
}
}
}
return utils.flatten(result);
};
const expand = (ast, options = {}) => {
const rangeLimit = options.rangeLimit === undefined ? 1000 : options.rangeLimit;
const walk = (node, parent = {}) => {
node.queue = [];
let p = parent;
let q = parent.queue;
while (p.type !== 'brace' && p.type !== 'root' && p.parent) {
p = p.parent;
q = p.queue;
}
if (node.invalid || node.dollar) {
q.push(append(q.pop(), stringify(node, options)));
return;
}
if (node.type === 'brace' && node.invalid !== true && node.nodes.length === 2) {
q.push(append(q.pop(), ['{}']));
return;
}
if (node.nodes && node.ranges > 0) {
const args = utils.reduce(node.nodes);
if (utils.exceedsLimit(...args, options.step, rangeLimit)) {
throw new RangeError('expanded array length exceeds range limit. Use options.rangeLimit to increase or disable the limit.');
}
let range = fill(...args, options);
if (range.length === 0) {
range = stringify(node, options);
}
q.push(append(q.pop(), range));
node.nodes = [];
return;
}
const enclose = utils.encloseBrace(node);
let queue = node.queue;
let block = node;
while (block.type !== 'brace' && block.type !== 'root' && block.parent) {
block = block.parent;
queue = block.queue;
}
for (let i = 0; i < node.nodes.length; i++) {
const child = node.nodes[i];
if (child.type === 'comma' && node.type === 'brace') {
if (i === 1) queue.push('');
queue.push('');
continue;
}
if (child.type === 'close') {
q.push(append(q.pop(), queue, enclose));
continue;
}
if (child.value && child.type !== 'open') {
queue.push(append(queue.pop(), child.value));
continue;
}
if (child.nodes) {
walk(child, node);
}
}
return queue;
};
return utils.flatten(walk(ast));
};
module.exports = expand;

View File

@@ -1,331 +0,0 @@
'use strict';
const stringify = require('./stringify');
/**
* Constants
*/
const {
MAX_LENGTH,
CHAR_BACKSLASH, /* \ */
CHAR_BACKTICK, /* ` */
CHAR_COMMA, /* , */
CHAR_DOT, /* . */
CHAR_LEFT_PARENTHESES, /* ( */
CHAR_RIGHT_PARENTHESES, /* ) */
CHAR_LEFT_CURLY_BRACE, /* { */
CHAR_RIGHT_CURLY_BRACE, /* } */
CHAR_LEFT_SQUARE_BRACKET, /* [ */
CHAR_RIGHT_SQUARE_BRACKET, /* ] */
CHAR_DOUBLE_QUOTE, /* " */
CHAR_SINGLE_QUOTE, /* ' */
CHAR_NO_BREAK_SPACE,
CHAR_ZERO_WIDTH_NOBREAK_SPACE
} = require('./constants');
/**
* parse
*/
const parse = (input, options = {}) => {
if (typeof input !== 'string') {
throw new TypeError('Expected a string');
}
const opts = options || {};
const max = typeof opts.maxLength === 'number' ? Math.min(MAX_LENGTH, opts.maxLength) : MAX_LENGTH;
if (input.length > max) {
throw new SyntaxError(`Input length (${input.length}), exceeds max characters (${max})`);
}
const ast = { type: 'root', input, nodes: [] };
const stack = [ast];
let block = ast;
let prev = ast;
let brackets = 0;
const length = input.length;
let index = 0;
let depth = 0;
let value;
/**
* Helpers
*/
const advance = () => input[index++];
const push = node => {
if (node.type === 'text' && prev.type === 'dot') {
prev.type = 'text';
}
if (prev && prev.type === 'text' && node.type === 'text') {
prev.value += node.value;
return;
}
block.nodes.push(node);
node.parent = block;
node.prev = prev;
prev = node;
return node;
};
push({ type: 'bos' });
while (index < length) {
block = stack[stack.length - 1];
value = advance();
/**
* Invalid chars
*/
if (value === CHAR_ZERO_WIDTH_NOBREAK_SPACE || value === CHAR_NO_BREAK_SPACE) {
continue;
}
/**
* Escaped chars
*/
if (value === CHAR_BACKSLASH) {
push({ type: 'text', value: (options.keepEscaping ? value : '') + advance() });
continue;
}
/**
* Right square bracket (literal): ']'
*/
if (value === CHAR_RIGHT_SQUARE_BRACKET) {
push({ type: 'text', value: '\\' + value });
continue;
}
/**
* Left square bracket: '['
*/
if (value === CHAR_LEFT_SQUARE_BRACKET) {
brackets++;
let next;
while (index < length && (next = advance())) {
value += next;
if (next === CHAR_LEFT_SQUARE_BRACKET) {
brackets++;
continue;
}
if (next === CHAR_BACKSLASH) {
value += advance();
continue;
}
if (next === CHAR_RIGHT_SQUARE_BRACKET) {
brackets--;
if (brackets === 0) {
break;
}
}
}
push({ type: 'text', value });
continue;
}
/**
* Parentheses
*/
if (value === CHAR_LEFT_PARENTHESES) {
block = push({ type: 'paren', nodes: [] });
stack.push(block);
push({ type: 'text', value });
continue;
}
if (value === CHAR_RIGHT_PARENTHESES) {
if (block.type !== 'paren') {
push({ type: 'text', value });
continue;
}
block = stack.pop();
push({ type: 'text', value });
block = stack[stack.length - 1];
continue;
}
/**
* Quotes: '|"|`
*/
if (value === CHAR_DOUBLE_QUOTE || value === CHAR_SINGLE_QUOTE || value === CHAR_BACKTICK) {
const open = value;
let next;
if (options.keepQuotes !== true) {
value = '';
}
while (index < length && (next = advance())) {
if (next === CHAR_BACKSLASH) {
value += next + advance();
continue;
}
if (next === open) {
if (options.keepQuotes === true) value += next;
break;
}
value += next;
}
push({ type: 'text', value });
continue;
}
/**
* Left curly brace: '{'
*/
if (value === CHAR_LEFT_CURLY_BRACE) {
depth++;
const dollar = prev.value && prev.value.slice(-1) === '$' || block.dollar === true;
const brace = {
type: 'brace',
open: true,
close: false,
dollar,
depth,
commas: 0,
ranges: 0,
nodes: []
};
block = push(brace);
stack.push(block);
push({ type: 'open', value });
continue;
}
/**
* Right curly brace: '}'
*/
if (value === CHAR_RIGHT_CURLY_BRACE) {
if (block.type !== 'brace') {
push({ type: 'text', value });
continue;
}
const type = 'close';
block = stack.pop();
block.close = true;
push({ type, value });
depth--;
block = stack[stack.length - 1];
continue;
}
/**
* Comma: ','
*/
if (value === CHAR_COMMA && depth > 0) {
if (block.ranges > 0) {
block.ranges = 0;
const open = block.nodes.shift();
block.nodes = [open, { type: 'text', value: stringify(block) }];
}
push({ type: 'comma', value });
block.commas++;
continue;
}
/**
* Dot: '.'
*/
if (value === CHAR_DOT && depth > 0 && block.commas === 0) {
const siblings = block.nodes;
if (depth === 0 || siblings.length === 0) {
push({ type: 'text', value });
continue;
}
if (prev.type === 'dot') {
block.range = [];
prev.value += value;
prev.type = 'range';
if (block.nodes.length !== 3 && block.nodes.length !== 5) {
block.invalid = true;
block.ranges = 0;
prev.type = 'text';
continue;
}
block.ranges++;
block.args = [];
continue;
}
if (prev.type === 'range') {
siblings.pop();
const before = siblings[siblings.length - 1];
before.value += prev.value + value;
prev = before;
block.ranges--;
continue;
}
push({ type: 'dot', value });
continue;
}
/**
* Text
*/
push({ type: 'text', value });
}
// Mark imbalanced braces and brackets as invalid
do {
block = stack.pop();
if (block.type !== 'root') {
block.nodes.forEach(node => {
if (!node.nodes) {
if (node.type === 'open') node.isOpen = true;
if (node.type === 'close') node.isClose = true;
if (!node.nodes) node.type = 'text';
node.invalid = true;
}
});
// get the location of the block on parent.nodes (block's siblings)
const parent = stack[stack.length - 1];
const index = parent.nodes.indexOf(block);
// replace the (invalid) block with it's nodes
parent.nodes.splice(index, 1, ...block.nodes);
}
} while (stack.length > 0);
push({ type: 'eos' });
return ast;
};
module.exports = parse;

View File

@@ -1,32 +0,0 @@
'use strict';
const utils = require('./utils');
module.exports = (ast, options = {}) => {
const stringify = (node, parent = {}) => {
const invalidBlock = options.escapeInvalid && utils.isInvalidBrace(parent);
const invalidNode = node.invalid === true && options.escapeInvalid === true;
let output = '';
if (node.value) {
if ((invalidBlock || invalidNode) && utils.isOpenOrClose(node)) {
return '\\' + node.value;
}
return node.value;
}
if (node.value) {
return node.value;
}
if (node.nodes) {
for (const child of node.nodes) {
output += stringify(child);
}
}
return output;
};
return stringify(ast);
};

View File

@@ -1,122 +0,0 @@
'use strict';
exports.isInteger = num => {
if (typeof num === 'number') {
return Number.isInteger(num);
}
if (typeof num === 'string' && num.trim() !== '') {
return Number.isInteger(Number(num));
}
return false;
};
/**
* Find a node of the given type
*/
exports.find = (node, type) => node.nodes.find(node => node.type === type);
/**
* Find a node of the given type
*/
exports.exceedsLimit = (min, max, step = 1, limit) => {
if (limit === false) return false;
if (!exports.isInteger(min) || !exports.isInteger(max)) return false;
return ((Number(max) - Number(min)) / Number(step)) >= limit;
};
/**
* Escape the given node with '\\' before node.value
*/
exports.escapeNode = (block, n = 0, type) => {
const node = block.nodes[n];
if (!node) return;
if ((type && node.type === type) || node.type === 'open' || node.type === 'close') {
if (node.escaped !== true) {
node.value = '\\' + node.value;
node.escaped = true;
}
}
};
/**
* Returns true if the given brace node should be enclosed in literal braces
*/
exports.encloseBrace = node => {
if (node.type !== 'brace') return false;
if ((node.commas >> 0 + node.ranges >> 0) === 0) {
node.invalid = true;
return true;
}
return false;
};
/**
* Returns true if a brace node is invalid.
*/
exports.isInvalidBrace = block => {
if (block.type !== 'brace') return false;
if (block.invalid === true || block.dollar) return true;
if ((block.commas >> 0 + block.ranges >> 0) === 0) {
block.invalid = true;
return true;
}
if (block.open !== true || block.close !== true) {
block.invalid = true;
return true;
}
return false;
};
/**
* Returns true if a node is an open or close node
*/
exports.isOpenOrClose = node => {
if (node.type === 'open' || node.type === 'close') {
return true;
}
return node.open === true || node.close === true;
};
/**
* Reduce an array of text nodes.
*/
exports.reduce = nodes => nodes.reduce((acc, node) => {
if (node.type === 'text') acc.push(node.value);
if (node.type === 'range') node.type = 'text';
return acc;
}, []);
/**
* Flatten an array
*/
exports.flatten = (...args) => {
const result = [];
const flat = arr => {
for (let i = 0; i < arr.length; i++) {
const ele = arr[i];
if (Array.isArray(ele)) {
flat(ele);
continue;
}
if (ele !== undefined) {
result.push(ele);
}
}
return result;
};
flat(args);
return result;
};

View File

@@ -1,77 +0,0 @@
{
"name": "braces",
"description": "Bash-like brace expansion, implemented in JavaScript. Safer than other brace expansion libs, with complete support for the Bash 4.3 braces specification, without sacrificing speed.",
"version": "3.0.3",
"homepage": "https://github.com/micromatch/braces",
"author": "Jon Schlinkert (https://github.com/jonschlinkert)",
"contributors": [
"Brian Woodward (https://twitter.com/doowb)",
"Elan Shanker (https://github.com/es128)",
"Eugene Sharygin (https://github.com/eush77)",
"hemanth.hm (http://h3manth.com)",
"Jon Schlinkert (http://twitter.com/jonschlinkert)"
],
"repository": "micromatch/braces",
"bugs": {
"url": "https://github.com/micromatch/braces/issues"
},
"license": "MIT",
"files": [
"index.js",
"lib"
],
"main": "index.js",
"engines": {
"node": ">=8"
},
"scripts": {
"test": "mocha",
"benchmark": "node benchmark"
},
"dependencies": {
"fill-range": "^7.1.1"
},
"devDependencies": {
"ansi-colors": "^3.2.4",
"bash-path": "^2.0.1",
"gulp-format-md": "^2.0.0",
"mocha": "^6.1.1"
},
"keywords": [
"alpha",
"alphabetical",
"bash",
"brace",
"braces",
"expand",
"expansion",
"filepath",
"fill",
"fs",
"glob",
"globbing",
"letter",
"match",
"matches",
"matching",
"number",
"numerical",
"path",
"range",
"ranges",
"sh"
],
"verb": {
"toc": false,
"layout": "default",
"tasks": [
"readme"
],
"lint": {
"reflinks": true
},
"plugins": [
"gulp-format-md"
]
}
}

View File

@@ -1,97 +0,0 @@
3.1.2 / 2022-01-27
==================
* Fix return value for un-parsable strings
3.1.1 / 2021-11-15
==================
* Fix "thousandsSeparator" incorrecting formatting fractional part
3.1.0 / 2019-01-22
==================
* Add petabyte (`pb`) support
3.0.0 / 2017-08-31
==================
* Change "kB" to "KB" in format output
* Remove support for Node.js 0.6
* Remove support for ComponentJS
2.5.0 / 2017-03-24
==================
* Add option "unit"
2.4.0 / 2016-06-01
==================
* Add option "unitSeparator"
2.3.0 / 2016-02-15
==================
* Drop partial bytes on all parsed units
* Fix non-finite numbers to `.format` to return `null`
* Fix parsing byte string that looks like hex
* perf: hoist regular expressions
2.2.0 / 2015-11-13
==================
* add option "decimalPlaces"
* add option "fixedDecimals"
2.1.0 / 2015-05-21
==================
* add `.format` export
* add `.parse` export
2.0.2 / 2015-05-20
==================
* remove map recreation
* remove unnecessary object construction
2.0.1 / 2015-05-07
==================
* fix browserify require
* remove node.extend dependency
2.0.0 / 2015-04-12
==================
* add option "case"
* add option "thousandsSeparator"
* return "null" on invalid parse input
* support proper round-trip: bytes(bytes(num)) === num
* units no longer case sensitive when parsing
1.0.0 / 2014-05-05
==================
* add negative support. fixes #6
0.3.0 / 2014-03-19
==================
* added terabyte support
0.2.1 / 2013-04-01
==================
* add .component
0.2.0 / 2012-10-28
==================
* bytes(200).should.eql('200b')
0.1.0 / 2012-07-04
==================
* add bytes to string conversion [yields]

View File

@@ -1,23 +0,0 @@
(The MIT License)
Copyright (c) 2012-2014 TJ Holowaychuk <tj@vision-media.ca>
Copyright (c) 2015 Jed Watson <jed.watson@me.com>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -1,152 +0,0 @@
# Bytes utility
[![NPM Version][npm-image]][npm-url]
[![NPM Downloads][downloads-image]][downloads-url]
[![Build Status][ci-image]][ci-url]
[![Test Coverage][coveralls-image]][coveralls-url]
Utility to parse a string bytes (ex: `1TB`) to bytes (`1099511627776`) and vice-versa.
## Installation
This is a [Node.js](https://nodejs.org/en/) module available through the
[npm registry](https://www.npmjs.com/). Installation is done using the
[`npm install` command](https://docs.npmjs.com/getting-started/installing-npm-packages-locally):
```bash
$ npm install bytes
```
## Usage
```js
var bytes = require('bytes');
```
#### bytes(numberstring value, [options]): numberstringnull
Default export function. Delegates to either `bytes.format` or `bytes.parse` based on the type of `value`.
**Arguments**
| Name | Type | Description |
|---------|----------|--------------------|
| value | `number``string` | Number value to format or string value to parse |
| options | `Object` | Conversion options for `format` |
**Returns**
| Name | Type | Description |
|---------|------------------|-------------------------------------------------|
| results | `string``number``null` | Return null upon error. Numeric value in bytes, or string value otherwise. |
**Example**
```js
bytes(1024);
// output: '1KB'
bytes('1KB');
// output: 1024
```
#### bytes.format(number value, [options]): stringnull
Format the given value in bytes into a string. If the value is negative, it is kept as such. If it is a float, it is
rounded.
**Arguments**
| Name | Type | Description |
|---------|----------|--------------------|
| value | `number` | Value in bytes |
| options | `Object` | Conversion options |
**Options**
| Property | Type | Description |
|-------------------|--------|-----------------------------------------------------------------------------------------|
| decimalPlaces | `number``null` | Maximum number of decimal places to include in output. Default value to `2`. |
| fixedDecimals | `boolean``null` | Whether to always display the maximum number of decimal places. Default value to `false` |
| thousandsSeparator | `string``null` | Example of values: `' '`, `','` and `'.'`... Default value to `''`. |
| unit | `string``null` | The unit in which the result will be returned (B/KB/MB/GB/TB). Default value to `''` (which means auto detect). |
| unitSeparator | `string``null` | Separator to use between number and unit. Default value to `''`. |
**Returns**
| Name | Type | Description |
|---------|------------------|-------------------------------------------------|
| results | `string``null` | Return null upon error. String value otherwise. |
**Example**
```js
bytes.format(1024);
// output: '1KB'
bytes.format(1000);
// output: '1000B'
bytes.format(1000, {thousandsSeparator: ' '});
// output: '1 000B'
bytes.format(1024 * 1.7, {decimalPlaces: 0});
// output: '2KB'
bytes.format(1024, {unitSeparator: ' '});
// output: '1 KB'
```
#### bytes.parse(stringnumber value): numbernull
Parse the string value into an integer in bytes. If no unit is given, or `value`
is a number, it is assumed the value is in bytes.
Supported units and abbreviations are as follows and are case-insensitive:
* `b` for bytes
* `kb` for kilobytes
* `mb` for megabytes
* `gb` for gigabytes
* `tb` for terabytes
* `pb` for petabytes
The units are in powers of two, not ten. This means 1kb = 1024b according to this parser.
**Arguments**
| Name | Type | Description |
|---------------|--------|--------------------|
| value | `string``number` | String to parse, or number in bytes. |
**Returns**
| Name | Type | Description |
|---------|-------------|-------------------------|
| results | `number``null` | Return null upon error. Value in bytes otherwise. |
**Example**
```js
bytes.parse('1KB');
// output: 1024
bytes.parse('1024');
// output: 1024
bytes.parse(1024);
// output: 1024
```
## License
[MIT](LICENSE)
[ci-image]: https://badgen.net/github/checks/visionmedia/bytes.js/master?label=ci
[ci-url]: https://github.com/visionmedia/bytes.js/actions?query=workflow%3Aci
[coveralls-image]: https://badgen.net/coveralls/c/github/visionmedia/bytes.js/master
[coveralls-url]: https://coveralls.io/r/visionmedia/bytes.js?branch=master
[downloads-image]: https://badgen.net/npm/dm/bytes
[downloads-url]: https://npmjs.org/package/bytes
[npm-image]: https://badgen.net/npm/v/bytes
[npm-url]: https://npmjs.org/package/bytes

View File

@@ -1,170 +0,0 @@
/*!
* bytes
* Copyright(c) 2012-2014 TJ Holowaychuk
* Copyright(c) 2015 Jed Watson
* MIT Licensed
*/
'use strict';
/**
* Module exports.
* @public
*/
module.exports = bytes;
module.exports.format = format;
module.exports.parse = parse;
/**
* Module variables.
* @private
*/
var formatThousandsRegExp = /\B(?=(\d{3})+(?!\d))/g;
var formatDecimalsRegExp = /(?:\.0*|(\.[^0]+)0+)$/;
var map = {
b: 1,
kb: 1 << 10,
mb: 1 << 20,
gb: 1 << 30,
tb: Math.pow(1024, 4),
pb: Math.pow(1024, 5),
};
var parseRegExp = /^((-|\+)?(\d+(?:\.\d+)?)) *(kb|mb|gb|tb|pb)$/i;
/**
* Convert the given value in bytes into a string or parse to string to an integer in bytes.
*
* @param {string|number} value
* @param {{
* case: [string],
* decimalPlaces: [number]
* fixedDecimals: [boolean]
* thousandsSeparator: [string]
* unitSeparator: [string]
* }} [options] bytes options.
*
* @returns {string|number|null}
*/
function bytes(value, options) {
if (typeof value === 'string') {
return parse(value);
}
if (typeof value === 'number') {
return format(value, options);
}
return null;
}
/**
* Format the given value in bytes into a string.
*
* If the value is negative, it is kept as such. If it is a float,
* it is rounded.
*
* @param {number} value
* @param {object} [options]
* @param {number} [options.decimalPlaces=2]
* @param {number} [options.fixedDecimals=false]
* @param {string} [options.thousandsSeparator=]
* @param {string} [options.unit=]
* @param {string} [options.unitSeparator=]
*
* @returns {string|null}
* @public
*/
function format(value, options) {
if (!Number.isFinite(value)) {
return null;
}
var mag = Math.abs(value);
var thousandsSeparator = (options && options.thousandsSeparator) || '';
var unitSeparator = (options && options.unitSeparator) || '';
var decimalPlaces = (options && options.decimalPlaces !== undefined) ? options.decimalPlaces : 2;
var fixedDecimals = Boolean(options && options.fixedDecimals);
var unit = (options && options.unit) || '';
if (!unit || !map[unit.toLowerCase()]) {
if (mag >= map.pb) {
unit = 'PB';
} else if (mag >= map.tb) {
unit = 'TB';
} else if (mag >= map.gb) {
unit = 'GB';
} else if (mag >= map.mb) {
unit = 'MB';
} else if (mag >= map.kb) {
unit = 'KB';
} else {
unit = 'B';
}
}
var val = value / map[unit.toLowerCase()];
var str = val.toFixed(decimalPlaces);
if (!fixedDecimals) {
str = str.replace(formatDecimalsRegExp, '$1');
}
if (thousandsSeparator) {
str = str.split('.').map(function (s, i) {
return i === 0
? s.replace(formatThousandsRegExp, thousandsSeparator)
: s
}).join('.');
}
return str + unitSeparator + unit;
}
/**
* Parse the string value into an integer in bytes.
*
* If no unit is given, it is assumed the value is in bytes.
*
* @param {number|string} val
*
* @returns {number|null}
* @public
*/
function parse(val) {
if (typeof val === 'number' && !isNaN(val)) {
return val;
}
if (typeof val !== 'string') {
return null;
}
// Test if the string passed is valid
var results = parseRegExp.exec(val);
var floatValue;
var unit = 'b';
if (!results) {
// Nothing could be extracted from the given string
floatValue = parseInt(val, 10);
unit = 'b'
} else {
// Retrieve the value and the unit
floatValue = parseFloat(results[1]);
unit = results[4].toLowerCase();
}
if (isNaN(floatValue)) {
return null;
}
return Math.floor(map[unit] * floatValue);
}

View File

@@ -1,42 +0,0 @@
{
"name": "bytes",
"description": "Utility to parse a string bytes to bytes and vice-versa",
"version": "3.1.2",
"author": "TJ Holowaychuk <tj@vision-media.ca> (http://tjholowaychuk.com)",
"contributors": [
"Jed Watson <jed.watson@me.com>",
"Théo FIDRY <theo.fidry@gmail.com>"
],
"license": "MIT",
"keywords": [
"byte",
"bytes",
"utility",
"parse",
"parser",
"convert",
"converter"
],
"repository": "visionmedia/bytes.js",
"devDependencies": {
"eslint": "7.32.0",
"eslint-plugin-markdown": "2.2.1",
"mocha": "9.2.0",
"nyc": "15.1.0"
},
"files": [
"History.md",
"LICENSE",
"Readme.md",
"index.js"
],
"engines": {
"node": ">= 0.8"
},
"scripts": {
"lint": "eslint .",
"test": "mocha --check-leaks --reporter spec",
"test-ci": "nyc --reporter=lcov --reporter=text npm test",
"test-cov": "nyc --reporter=html --reporter=text npm test"
}
}

View File

@@ -1,17 +0,0 @@
{
"root": true,
"extends": "@ljharb",
"rules": {
"func-name-matching": 0,
"id-length": 0,
"new-cap": [2, {
"capIsNewExceptions": [
"GetIntrinsic",
],
}],
"no-extra-parens": 0,
"no-magic-numbers": 0,
},
}

View File

@@ -1,12 +0,0 @@
# These are supported funding model platforms
github: [ljharb]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: npm/call-bind-apply-helpers
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

View File

@@ -1,9 +0,0 @@
{
"all": true,
"check-coverage": false,
"reporter": ["text-summary", "text", "html", "json"],
"exclude": [
"coverage",
"test"
]
}

View File

@@ -1,30 +0,0 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [v1.0.2](https://github.com/ljharb/call-bind-apply-helpers/compare/v1.0.1...v1.0.2) - 2025-02-12
### Commits
- [types] improve inferred types [`e6f9586`](https://github.com/ljharb/call-bind-apply-helpers/commit/e6f95860a3c72879cb861a858cdfb8138fbedec1)
- [Dev Deps] update `@arethetypeswrong/cli`, `@ljharb/tsconfig`, `@types/tape`, `es-value-fixtures`, `for-each`, `has-strict-mode`, `object-inspect` [`e43d540`](https://github.com/ljharb/call-bind-apply-helpers/commit/e43d5409f97543bfbb11f345d47d8ce4e066d8c1)
## [v1.0.1](https://github.com/ljharb/call-bind-apply-helpers/compare/v1.0.0...v1.0.1) - 2024-12-08
### Commits
- [types] `reflectApply`: fix types [`4efc396`](https://github.com/ljharb/call-bind-apply-helpers/commit/4efc3965351a4f02cc55e836fa391d3d11ef2ef8)
- [Fix] `reflectApply`: oops, Reflect is not a function [`83cc739`](https://github.com/ljharb/call-bind-apply-helpers/commit/83cc7395de6b79b7730bdf092f1436f0b1263c75)
- [Dev Deps] update `@arethetypeswrong/cli` [`80bd5d3`](https://github.com/ljharb/call-bind-apply-helpers/commit/80bd5d3ae58b4f6b6995ce439dd5a1bcb178a940)
## v1.0.0 - 2024-12-05
### Commits
- Initial implementation, tests, readme [`7879629`](https://github.com/ljharb/call-bind-apply-helpers/commit/78796290f9b7430c9934d6f33d94ae9bc89fce04)
- Initial commit [`3f1dc16`](https://github.com/ljharb/call-bind-apply-helpers/commit/3f1dc164afc43285631b114a5f9dd9137b2b952f)
- npm init [`081df04`](https://github.com/ljharb/call-bind-apply-helpers/commit/081df048c312fcee400922026f6e97281200a603)
- Only apps should have lockfiles [`5b9ca0f`](https://github.com/ljharb/call-bind-apply-helpers/commit/5b9ca0fe8101ebfaf309c549caac4e0a017ed930)

View File

@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2024 Jordan Harband
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,62 +0,0 @@
# call-bind-apply-helpers <sup>[![Version Badge][npm-version-svg]][package-url]</sup>
[![github actions][actions-image]][actions-url]
[![coverage][codecov-image]][codecov-url]
[![dependency status][deps-svg]][deps-url]
[![dev dependency status][dev-deps-svg]][dev-deps-url]
[![License][license-image]][license-url]
[![Downloads][downloads-image]][downloads-url]
[![npm badge][npm-badge-png]][package-url]
Helper functions around Function call/apply/bind, for use in `call-bind`.
The only packages that should likely ever use this package directly are `call-bind` and `get-intrinsic`.
Please use `call-bind` unless you have a very good reason not to.
## Getting started
```sh
npm install --save call-bind-apply-helpers
```
## Usage/Examples
```js
const assert = require('assert');
const callBindBasic = require('call-bind-apply-helpers');
function f(a, b) {
assert.equal(this, 1);
assert.equal(a, 2);
assert.equal(b, 3);
assert.equal(arguments.length, 2);
}
const fBound = callBindBasic([f, 1]);
delete Function.prototype.call;
delete Function.prototype.bind;
fBound(2, 3);
```
## Tests
Clone the repo, `npm install`, and run `npm test`
[package-url]: https://npmjs.org/package/call-bind-apply-helpers
[npm-version-svg]: https://versionbadg.es/ljharb/call-bind-apply-helpers.svg
[deps-svg]: https://david-dm.org/ljharb/call-bind-apply-helpers.svg
[deps-url]: https://david-dm.org/ljharb/call-bind-apply-helpers
[dev-deps-svg]: https://david-dm.org/ljharb/call-bind-apply-helpers/dev-status.svg
[dev-deps-url]: https://david-dm.org/ljharb/call-bind-apply-helpers#info=devDependencies
[npm-badge-png]: https://nodei.co/npm/call-bind-apply-helpers.png?downloads=true&stars=true
[license-image]: https://img.shields.io/npm/l/call-bind-apply-helpers.svg
[license-url]: LICENSE
[downloads-image]: https://img.shields.io/npm/dm/call-bind-apply-helpers.svg
[downloads-url]: https://npm-stat.com/charts.html?package=call-bind-apply-helpers
[codecov-image]: https://codecov.io/gh/ljharb/call-bind-apply-helpers/branch/main/graphs/badge.svg
[codecov-url]: https://app.codecov.io/gh/ljharb/call-bind-apply-helpers/
[actions-image]: https://img.shields.io/endpoint?url=https://github-actions-badge-u3jn4tfpocch.runkit.sh/ljharb/call-bind-apply-helpers
[actions-url]: https://github.com/ljharb/call-bind-apply-helpers/actions

View File

@@ -1 +0,0 @@
export = Reflect.apply;

View File

@@ -1,10 +0,0 @@
'use strict';
var bind = require('function-bind');
var $apply = require('./functionApply');
var $call = require('./functionCall');
var $reflectApply = require('./reflectApply');
/** @type {import('./actualApply')} */
module.exports = $reflectApply || bind.call($call, $apply);

View File

@@ -1,19 +0,0 @@
import actualApply from './actualApply';
type TupleSplitHead<T extends any[], N extends number> = T['length'] extends N
? T
: T extends [...infer R, any]
? TupleSplitHead<R, N>
: never
type TupleSplitTail<T, N extends number, O extends any[] = []> = O['length'] extends N
? T
: T extends [infer F, ...infer R]
? TupleSplitTail<[...R], N, [...O, F]>
: never
type TupleSplit<T extends any[], N extends number> = [TupleSplitHead<T, N>, TupleSplitTail<T, N>]
declare function applyBind(...args: TupleSplit<Parameters<typeof actualApply>, 2>[1]): ReturnType<typeof actualApply>;
export = applyBind;

View File

@@ -1,10 +0,0 @@
'use strict';
var bind = require('function-bind');
var $apply = require('./functionApply');
var actualApply = require('./actualApply');
/** @type {import('./applyBind')} */
module.exports = function applyBind() {
return actualApply(bind, $apply, arguments);
};

View File

@@ -1 +0,0 @@
export = Function.prototype.apply;

View File

@@ -1,4 +0,0 @@
'use strict';
/** @type {import('./functionApply')} */
module.exports = Function.prototype.apply;

View File

@@ -1 +0,0 @@
export = Function.prototype.call;

View File

@@ -1,4 +0,0 @@
'use strict';
/** @type {import('./functionCall')} */
module.exports = Function.prototype.call;

View File

@@ -1,64 +0,0 @@
type RemoveFromTuple<
Tuple extends readonly unknown[],
RemoveCount extends number,
Index extends 1[] = []
> = Index["length"] extends RemoveCount
? Tuple
: Tuple extends [infer First, ...infer Rest]
? RemoveFromTuple<Rest, RemoveCount, [...Index, 1]>
: Tuple;
type ConcatTuples<
Prefix extends readonly unknown[],
Suffix extends readonly unknown[]
> = [...Prefix, ...Suffix];
type ExtractFunctionParams<T> = T extends (this: infer TThis, ...args: infer P extends readonly unknown[]) => infer R
? { thisArg: TThis; params: P; returnType: R }
: never;
type BindFunction<
T extends (this: any, ...args: any[]) => any,
TThis,
TBoundArgs extends readonly unknown[],
ReceiverBound extends boolean
> = ExtractFunctionParams<T> extends {
thisArg: infer OrigThis;
params: infer P extends readonly unknown[];
returnType: infer R;
}
? ReceiverBound extends true
? (...args: RemoveFromTuple<P, Extract<TBoundArgs["length"], number>>) => R extends [OrigThis, ...infer Rest]
? [TThis, ...Rest] // Replace `this` with `thisArg`
: R
: <U, RemainingArgs extends RemoveFromTuple<P, Extract<TBoundArgs["length"], number>>>(
thisArg: U,
...args: RemainingArgs
) => R extends [OrigThis, ...infer Rest]
? [U, ...ConcatTuples<TBoundArgs, Rest>] // Preserve bound args in return type
: R
: never;
declare function callBind<
const T extends (this: any, ...args: any[]) => any,
Extracted extends ExtractFunctionParams<T>,
const TBoundArgs extends Partial<Extracted["params"]> & readonly unknown[],
const TThis extends Extracted["thisArg"]
>(
args: [fn: T, thisArg: TThis, ...boundArgs: TBoundArgs]
): BindFunction<T, TThis, TBoundArgs, true>;
declare function callBind<
const T extends (this: any, ...args: any[]) => any,
Extracted extends ExtractFunctionParams<T>,
const TBoundArgs extends Partial<Extracted["params"]> & readonly unknown[]
>(
args: [fn: T, ...boundArgs: TBoundArgs]
): BindFunction<T, Extracted["thisArg"], TBoundArgs, false>;
declare function callBind<const TArgs extends readonly unknown[]>(
args: [fn: Exclude<TArgs[0], Function>, ...rest: TArgs]
): never;
// export as namespace callBind;
export = callBind;

View File

@@ -1,15 +0,0 @@
'use strict';
var bind = require('function-bind');
var $TypeError = require('es-errors/type');
var $call = require('./functionCall');
var $actualApply = require('./actualApply');
/** @type {(args: [Function, thisArg?: unknown, ...args: unknown[]]) => Function} TODO FIXME, find a way to use import('.') */
module.exports = function callBindBasic(args) {
if (args.length < 1 || typeof args[0] !== 'function') {
throw new $TypeError('a function is required');
}
return $actualApply(bind, $call, args);
};

View File

@@ -1,85 +0,0 @@
{
"name": "call-bind-apply-helpers",
"version": "1.0.2",
"description": "Helper functions around Function call/apply/bind, for use in `call-bind`",
"main": "index.js",
"exports": {
".": "./index.js",
"./actualApply": "./actualApply.js",
"./applyBind": "./applyBind.js",
"./functionApply": "./functionApply.js",
"./functionCall": "./functionCall.js",
"./reflectApply": "./reflectApply.js",
"./package.json": "./package.json"
},
"scripts": {
"prepack": "npmignore --auto --commentLines=auto",
"prepublish": "not-in-publish || npm run prepublishOnly",
"prepublishOnly": "safe-publish-latest",
"prelint": "evalmd README.md",
"lint": "eslint --ext=.js,.mjs .",
"postlint": "tsc -p . && attw -P",
"pretest": "npm run lint",
"tests-only": "nyc tape 'test/**/*.js'",
"test": "npm run tests-only",
"posttest": "npx npm@'>=10.2' audit --production",
"version": "auto-changelog && git add CHANGELOG.md",
"postversion": "auto-changelog && git add CHANGELOG.md && git commit --no-edit --amend && git tag -f \"v$(node -e \"console.log(require('./package.json').version)\")\""
},
"repository": {
"type": "git",
"url": "git+https://github.com/ljharb/call-bind-apply-helpers.git"
},
"author": "Jordan Harband <ljharb@gmail.com>",
"license": "MIT",
"bugs": {
"url": "https://github.com/ljharb/call-bind-apply-helpers/issues"
},
"homepage": "https://github.com/ljharb/call-bind-apply-helpers#readme",
"dependencies": {
"es-errors": "^1.3.0",
"function-bind": "^1.1.2"
},
"devDependencies": {
"@arethetypeswrong/cli": "^0.17.3",
"@ljharb/eslint-config": "^21.1.1",
"@ljharb/tsconfig": "^0.2.3",
"@types/for-each": "^0.3.3",
"@types/function-bind": "^1.1.10",
"@types/object-inspect": "^1.13.0",
"@types/tape": "^5.8.1",
"auto-changelog": "^2.5.0",
"encoding": "^0.1.13",
"es-value-fixtures": "^1.7.1",
"eslint": "=8.8.0",
"evalmd": "^0.0.19",
"for-each": "^0.3.5",
"has-strict-mode": "^1.1.0",
"in-publish": "^2.0.1",
"npmignore": "^0.3.1",
"nyc": "^10.3.2",
"object-inspect": "^1.13.4",
"safe-publish-latest": "^2.0.0",
"tape": "^5.9.0",
"typescript": "next"
},
"testling": {
"files": "test/index.js"
},
"auto-changelog": {
"output": "CHANGELOG.md",
"template": "keepachangelog",
"unreleased": false,
"commitLimit": false,
"backfillLimit": false,
"hideCredit": true
},
"publishConfig": {
"ignore": [
".github/workflows"
]
},
"engines": {
"node": ">= 0.4"
}
}

View File

@@ -1,3 +0,0 @@
declare const reflectApply: false | typeof Reflect.apply;
export = reflectApply;

Some files were not shown because too many files have changed in this diff Show More