@pinta-ai/pinta-musecode 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +71 -0
- package/LICENSE +136 -0
- package/README.md +431 -0
- package/dist/index.js +25 -0
- package/dist/index.mjs +26 -0
- package/hooks/fixture.example.json +11 -0
- package/hooks/managed-hooks.template.json +101 -0
- package/package.json +53 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0 (unreleased)
|
|
4
|
+
|
|
5
|
+
Initial scaffold — stage 1 (observation) of the MuseCode adapter plan.
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- Hook dispatch for all twelve Muse Code lifecycle events, with event-name
|
|
9
|
+
resolution from **both** argv and the stdin payload (argv wins).
|
|
10
|
+
- OTLP export with the `musecode` ingest discriminator and `muse.*` attributes,
|
|
11
|
+
reusing `@pinta-ai/core` for redaction, transport, retry queue, and traces.
|
|
12
|
+
- Per-turn trace rotation on `UserPromptSubmit`.
|
|
13
|
+
- Guard evaluation on `PreToolUse` and `PermissionRequest`, with the
|
|
14
|
+
enforce-before-telemetry ordering invariant covered by tests.
|
|
15
|
+
- Exemption for the six documented subagent control tools.
|
|
16
|
+
- `tools/doctor.ts` — environment preflight; fails on Linux without Node.
|
|
17
|
+
- `tools/capture-hook.mjs` + `tools/spike.ts` — stage-0 spike harness that
|
|
18
|
+
captures real hook invocations and reports payload shapes, event-name source,
|
|
19
|
+
undocumented events, and the surviving env allowlist.
|
|
20
|
+
|
|
21
|
+
### Verified
|
|
22
|
+
|
|
23
|
+
Stage 0 was executed against **`muse 0.1.0-R708.1`**. The adapter ran as a real
|
|
24
|
+
managed hook and emitted OTLP spans end to end. See README §Confirmed contracts.
|
|
25
|
+
Notable corrections this produced:
|
|
26
|
+
|
|
27
|
+
- The managed hooks file needs the `hooks` wrapper **and** a matcher-group level;
|
|
28
|
+
the previous template had neither and would have been ignored silently.
|
|
29
|
+
`schema_version` and `matcher` turned out to be optional. Now guarded by
|
|
30
|
+
`tests/managed-hooks.test.ts`.
|
|
31
|
+
- **Group and handler objects are deserialized strictly, and rejection is silent.**
|
|
32
|
+
One unrecognised key skips the whole event while the rest of the file keeps
|
|
33
|
+
working, so a typo removes monitoring from one event and leaves the adapter
|
|
34
|
+
looking healthy. Accepted handler keys: `type`, `command`, `commandWindows`,
|
|
35
|
+
`timeout`, `statusMessage`, `silent`, `async`.
|
|
36
|
+
- **The per-handler `env` block does not work** — it is one of the rejected keys,
|
|
37
|
+
despite appearing in the binary's strings. This promotes the env file from the
|
|
38
|
+
preferable channel to the only one.
|
|
39
|
+
- `timeout` is in **seconds**, not milliseconds.
|
|
40
|
+
- `managed_hooks_path` is a `settings.json` key, not an environment variable.
|
|
41
|
+
- The hook environment is filtered to 13 variables, confirming the env file as
|
|
42
|
+
the only usable config channel — and revealing that `XDG_CONFIG_HOME` is
|
|
43
|
+
stripped, so the env file must be written under `$HOME/.config/muse/`.
|
|
44
|
+
- `PreLLMCall`/`PostLLMCall` carry the full `messages` array, which justifies
|
|
45
|
+
keeping them opt-in.
|
|
46
|
+
- `SubagentStart` reports the child's `session_id`, not the parent's.
|
|
47
|
+
- The six subagent control tool names in `INTERNAL_TOOLS` are confirmed exactly
|
|
48
|
+
as listed, including `subagent_status`.
|
|
49
|
+
- `session.jsonl` records the hook events too, alongside token counts and
|
|
50
|
+
parent/child session links that the hook payload omits — so it is a workable
|
|
51
|
+
backstop for stage 3. Tool arguments were not observed and remain open.
|
|
52
|
+
- Hook cost is ~45 ms per invocation, essentially all Node start-up, and hooks
|
|
53
|
+
for different subagents run in parallel — 16 concurrent invocations finish in
|
|
54
|
+
123 ms. Five hooks fire on a turn that does nothing, two of them
|
|
55
|
+
`SubagentStart`, so subagent traffic is the volume driver rather than tools.
|
|
56
|
+
|
|
57
|
+
### Fixed
|
|
58
|
+
|
|
59
|
+
- **The deny contract was wrong.** `hookSpecificOutput.permissionDecision` is
|
|
60
|
+
ignored for `UserPromptSubmit` — the turn just proceeds. The working shape is
|
|
61
|
+
the top-level `{"decision":"block","reason":…}`. `renderDenyJson()` now
|
|
62
|
+
branches on the event family, and `PINTA_MUSE_DENY_FORMAT` defaults to `auto`,
|
|
63
|
+
pairing the JSON with exit 2 (measured to block on its own).
|
|
64
|
+
Verified end to end: the adapter's own output cancelled a live turn.
|
|
65
|
+
- Ruled out Muse Code's native OTLP export as a cheaper telemetry path — it
|
|
66
|
+
refuses to send to a non-Meta destination by design.
|
|
67
|
+
|
|
68
|
+
### Notes
|
|
69
|
+
- Enforcement is **off by default** (shadow mode). The deny wire contract is
|
|
70
|
+
unconfirmed and isolated in `src/core/decision.ts`; see README "Spike-pending".
|
|
71
|
+
- `PreLLMCall` / `PostLLMCall` are opt-in via `PINTA_MUSE_LLM_EVENTS=1`.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# PolyForm Noncommercial License 1.0.0
|
|
2
|
+
|
|
3
|
+
<https://polyformproject.org/licenses/noncommercial/1.0.0>
|
|
4
|
+
|
|
5
|
+
## Acceptance
|
|
6
|
+
|
|
7
|
+
In order to get any license under these terms, you must agree
|
|
8
|
+
to them as both strict obligations and conditions to all
|
|
9
|
+
your licenses.
|
|
10
|
+
|
|
11
|
+
## Copyright License
|
|
12
|
+
|
|
13
|
+
The licensor grants you a copyright license for the
|
|
14
|
+
software to do everything you might do with the software
|
|
15
|
+
that would otherwise infringe the licensor's copyright
|
|
16
|
+
in it for any permitted purpose. However, you may
|
|
17
|
+
only distribute the software according to [Distribution
|
|
18
|
+
License](#distribution-license) and make changes or new works
|
|
19
|
+
based on the software according to [Changes and New Works
|
|
20
|
+
License](#changes-and-new-works-license).
|
|
21
|
+
|
|
22
|
+
## Distribution License
|
|
23
|
+
|
|
24
|
+
The licensor grants you an additional copyright license to
|
|
25
|
+
distribute copies of the software. Your license to distribute
|
|
26
|
+
covers distributing the software with changes and new works
|
|
27
|
+
permitted by [Changes and New Works
|
|
28
|
+
License](#changes-and-new-works-license).
|
|
29
|
+
|
|
30
|
+
## Notices
|
|
31
|
+
|
|
32
|
+
You must ensure that anyone who gets a copy of any part of
|
|
33
|
+
the software from you also gets a copy of these terms or the
|
|
34
|
+
URL for them above, as well as copies of any plain-text lines
|
|
35
|
+
beginning with `Required Notice:` that the licensor provided
|
|
36
|
+
with the software. For example:
|
|
37
|
+
|
|
38
|
+
> Required Notice: Copyright Pinta AI (https://pinta.sh)
|
|
39
|
+
|
|
40
|
+
## Changes and New Works License
|
|
41
|
+
|
|
42
|
+
The licensor grants you an additional copyright license to make
|
|
43
|
+
changes and new works based on the software for any permitted
|
|
44
|
+
purpose.
|
|
45
|
+
|
|
46
|
+
## Patent License
|
|
47
|
+
|
|
48
|
+
The licensor grants you a patent license for the software that
|
|
49
|
+
covers patent claims the licensor can license, or becomes able
|
|
50
|
+
to license, that you would infringe by using the software.
|
|
51
|
+
|
|
52
|
+
## Noncommercial Purposes
|
|
53
|
+
|
|
54
|
+
Any noncommercial purpose is a permitted purpose.
|
|
55
|
+
|
|
56
|
+
## Personal Uses
|
|
57
|
+
|
|
58
|
+
Personal use for research, experiment, and testing for
|
|
59
|
+
the benefit of public knowledge, personal study, private
|
|
60
|
+
entertainment, hobby projects, amateur pursuits, or religious
|
|
61
|
+
observance, without any anticipated commercial application,
|
|
62
|
+
is use for a permitted purpose.
|
|
63
|
+
|
|
64
|
+
## Noncommercial Organizations
|
|
65
|
+
|
|
66
|
+
Use by any charitable organization, educational institution,
|
|
67
|
+
public research organization, public safety or health
|
|
68
|
+
organization, environmental protection organization, or
|
|
69
|
+
government institution is use for a permitted purpose
|
|
70
|
+
regardless of the source of funding or obligations resulting
|
|
71
|
+
from the funding.
|
|
72
|
+
|
|
73
|
+
## Fair Use
|
|
74
|
+
|
|
75
|
+
You may have "fair use" rights for the software under the
|
|
76
|
+
law. These terms do not limit them.
|
|
77
|
+
|
|
78
|
+
## No Other Rights
|
|
79
|
+
|
|
80
|
+
These terms do not allow you to sublicense or transfer any of
|
|
81
|
+
your licenses to anyone else, or prevent the licensor from
|
|
82
|
+
granting licenses to anyone else. These terms do not imply
|
|
83
|
+
any other licenses.
|
|
84
|
+
|
|
85
|
+
## Patent Defense
|
|
86
|
+
|
|
87
|
+
If you make any written claim that the software infringes or
|
|
88
|
+
contributes to infringement of any patent, your patent license
|
|
89
|
+
for the software granted under these terms ends immediately. If
|
|
90
|
+
your company makes such a claim, your patent license ends
|
|
91
|
+
immediately for work on behalf of your company.
|
|
92
|
+
|
|
93
|
+
## Violations
|
|
94
|
+
|
|
95
|
+
The first time you are notified in writing that you have
|
|
96
|
+
violated any of these terms, or done anything with the software
|
|
97
|
+
not covered by your licenses, your licenses can nonetheless
|
|
98
|
+
continue if you come into full compliance with these terms,
|
|
99
|
+
and take practical steps to correct past violations, within
|
|
100
|
+
32 days of receiving notice. Otherwise, all your licenses
|
|
101
|
+
end immediately.
|
|
102
|
+
|
|
103
|
+
## No Liability
|
|
104
|
+
|
|
105
|
+
***As far as the law allows, the software comes as is, without
|
|
106
|
+
any warranty or condition, and the licensor will not be liable
|
|
107
|
+
to you for any damages arising out of these terms or the use
|
|
108
|
+
or nature of the software, under any kind of legal claim.***
|
|
109
|
+
|
|
110
|
+
## Definitions
|
|
111
|
+
|
|
112
|
+
The **licensor** is the individual or entity offering these
|
|
113
|
+
terms, and the **software** is the software the licensor makes
|
|
114
|
+
available under these terms.
|
|
115
|
+
|
|
116
|
+
**You** refers to the individual or entity agreeing to these
|
|
117
|
+
terms.
|
|
118
|
+
|
|
119
|
+
**Your company** is any legal entity, sole proprietorship,
|
|
120
|
+
or other kind of organization that you work for, plus all
|
|
121
|
+
organizations that have control over, are under the control
|
|
122
|
+
of, or are under common control with that organization.
|
|
123
|
+
**Control** means ownership of substantially all the assets of
|
|
124
|
+
an entity, or the power to direct its management and policies
|
|
125
|
+
by vote, contract, or otherwise. Control can be direct or
|
|
126
|
+
indirect.
|
|
127
|
+
|
|
128
|
+
**Your licenses** are all the licenses granted to you for the
|
|
129
|
+
software under these terms.
|
|
130
|
+
|
|
131
|
+
**Use** means anything you do with the software requiring one
|
|
132
|
+
of your licenses.
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
Required Notice: Copyright (c) 2026 Pinta AI
|
package/README.md
ADDED
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
# pinta-musecode
|
|
2
|
+
|
|
3
|
+
Pinta AI adapter for **Meta Muse Code** — forwards Muse Code lifecycle hook
|
|
4
|
+
events to an OTLP collector and, optionally, enforces guard decisions.
|
|
5
|
+
|
|
6
|
+
> **Status: stage 1, verified end to end against `muse 0.1.0-R708.1`.**
|
|
7
|
+
> Telemetry and blocking are both confirmed against a real binary — the
|
|
8
|
+
> adapter's own deny output cancelled a live turn. Enforcement is still **off by
|
|
9
|
+
> default**, because the *tool-side* deny shape could not be exercised (the test
|
|
10
|
+
> account hit a billing error before any tool call) and because stage 2 wants
|
|
11
|
+
> false-positive data first. See [Confirmed contracts](#confirmed-contracts).
|
|
12
|
+
|
|
13
|
+
Implementation plan: [🎼 Meta MuseCode 어댑터 구현 계획](https://app.notion.com/p/3b7dcc09b89a8197b782e2d5ab96d1ea)
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Platform support
|
|
18
|
+
|
|
19
|
+
**macOS and Linux. Both are supported; there is nothing mac-specific in `src/`.**
|
|
20
|
+
|
|
21
|
+
Windows is not a gap in this adapter — Muse Code itself has no Windows build.
|
|
22
|
+
Meta ships *"a native binary on your path for macOS and Linux"*, and the sandbox
|
|
23
|
+
exists in exactly two forms (Seatbelt on macOS, bundled bubblewrap on Linux). So
|
|
24
|
+
there is no host to hook on Windows, and none of the `.cmd` wrapper handling the
|
|
25
|
+
other Pinta adapters carry applies here.
|
|
26
|
+
|
|
27
|
+
| OS | Muse Code | Node available | Result |
|
|
28
|
+
| --- | --- | --- | --- |
|
|
29
|
+
| macOS | yes | pinta-manager bundles it | works |
|
|
30
|
+
| Linux | yes | **no bundled Node** — literal `node` | works **iff** system Node is present |
|
|
31
|
+
| Windows | no | — | not applicable |
|
|
32
|
+
|
|
33
|
+
The only platform variable is therefore the **Node runtime on Linux**, not the
|
|
34
|
+
adapter code. See [Linux requires Node](#-linux-requires-node).
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## How it works
|
|
39
|
+
|
|
40
|
+
Muse Code binds a shell command to exactly one of its twelve lifecycle events.
|
|
41
|
+
The manager installs this adapter as a **managed hook**, which Muse Code treats
|
|
42
|
+
as pre-approved and runs without a trust step.
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
muse ──spawn──> node dist/index.js <EventName> ──stdin──> payload JSON
|
|
46
|
+
│
|
|
47
|
+
├─ PreToolUse / PermissionRequest ─> POST /guard/evaluate ─> DENY? ─> stdout
|
|
48
|
+
└─ everything else ────────────────> OTLP span
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Two properties are load-bearing and both are covered by tests:
|
|
52
|
+
|
|
53
|
+
- **Enforce before telemetry.** A DENY is written to stdout *before* any
|
|
54
|
+
telemetry work, so a telemetry failure can never bubble into the top-level
|
|
55
|
+
fail-open catch and silently allow a blocked tool.
|
|
56
|
+
- **Fail open, always.** No endpoint, a timeout, a non-200, a malformed payload,
|
|
57
|
+
or an unhandled throw all resolve to ALLOW. A security gate must not be able
|
|
58
|
+
to wedge the agent.
|
|
59
|
+
|
|
60
|
+
## Event coverage
|
|
61
|
+
|
|
62
|
+
All twelve documented events are routed.
|
|
63
|
+
|
|
64
|
+
| Event | Handling |
|
|
65
|
+
| --- | --- |
|
|
66
|
+
| `PreToolUse`, `PermissionRequest` | guard evaluation (+ optional deny) |
|
|
67
|
+
| `UserPromptSubmit` | opens a **new trace** — the per-turn boundary |
|
|
68
|
+
| `SessionStart`, `Stop` | observation |
|
|
69
|
+
| `PostToolUse` | observation |
|
|
70
|
+
| `SubagentStart`, `SubagentStop` | observation |
|
|
71
|
+
| `PreCompact`, `PostCompact` | observation |
|
|
72
|
+
| `PreLLMCall`, `PostLLMCall` | **skipped unless `PINTA_MUSE_LLM_EVENTS=1`** |
|
|
73
|
+
|
|
74
|
+
`PostLLMCall` is expected to fire per response chunk. `pinta-gemini` deliberately
|
|
75
|
+
skips the equivalent `AfterModel` for the same reason, so it is opt-in here until
|
|
76
|
+
the volume has actually been measured.
|
|
77
|
+
|
|
78
|
+
Unrecognised events are **not dropped** — they are forwarded as observations.
|
|
79
|
+
Muse Code is 0.1.x beta whose shim self-updates hourly, so parsing stays
|
|
80
|
+
envelope-open: known keys are validated, everything else rides through.
|
|
81
|
+
|
|
82
|
+
### Internal tool exemption
|
|
83
|
+
|
|
84
|
+
The lead agent drives its subagent team through native tools
|
|
85
|
+
(`subagent_spawn`, `subagent_status`, `subagent_send_message`, `subagent_cancel`,
|
|
86
|
+
`subagent_wait`, `subagent_read_result`). Blocking one of those does not stop a
|
|
87
|
+
risky action — it breaks the agent's own control loop and kills the turn. They
|
|
88
|
+
skip guard evaluation but are still recorded.
|
|
89
|
+
|
|
90
|
+
Only names confirmed in the official docs are exempt. **Do not add speculative
|
|
91
|
+
entries**: an over-broad exemption is a hole in the guard.
|
|
92
|
+
|
|
93
|
+
## Configuration
|
|
94
|
+
|
|
95
|
+
Muse Code runs hook commands with *"a cleared environment with a small
|
|
96
|
+
allowlist"*, so inherited process env cannot be relied on. The manager therefore
|
|
97
|
+
writes `~/.config/muse/pinta-musecode.env` (`KEY=VALUE` per line), which the
|
|
98
|
+
adapter loads before anything else reads `process.env`. This is the same
|
|
99
|
+
mechanism every other Pinta hook adapter uses.
|
|
100
|
+
|
|
101
|
+
`$XDG_CONFIG_HOME/muse` takes precedence over `~/.config/muse` when set.
|
|
102
|
+
|
|
103
|
+
| Variable | Meaning |
|
|
104
|
+
| --- | --- |
|
|
105
|
+
| `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` | OTLP/HTTP traces URL. Unset ⇒ telemetry silently disabled. |
|
|
106
|
+
| `OTEL_EXPORTER_OTLP_HEADERS` | Extra headers, e.g. `x-pinta-relay-token=…` |
|
|
107
|
+
| `PINTA_GUARD_ENDPOINT` | Guard URL. Unset ⇒ guard silently disabled. |
|
|
108
|
+
| `PINTA_RELAY_TOKEN` | Sent as `x-pinta-relay-token` on guard calls. |
|
|
109
|
+
| `PINTA_GUARD_DISABLED=1` | Force-disable the guard even with an endpoint set. |
|
|
110
|
+
| `PINTA_MUSE_ENFORCE=1` | **Leave a DENY in effect.** Default off = shadow mode. |
|
|
111
|
+
| `PINTA_MUSE_DENY_FORMAT` | `auto` (default), `json`, or `exit2`. See below. |
|
|
112
|
+
| `PINTA_MUSE_LLM_EVENTS=1` | Opt into `PreLLMCall` / `PostLLMCall`. |
|
|
113
|
+
| `PINTA_MUSECODE_DATA` | State dir. Default `~/.pinta/adaptors/pinta-musecode`. |
|
|
114
|
+
|
|
115
|
+
State deliberately does **not** live in the workspace: Muse Code treats `.muse`,
|
|
116
|
+
`.git` and `.agents` as read-only even inside an otherwise writable workspace.
|
|
117
|
+
|
|
118
|
+
### Shadow mode
|
|
119
|
+
|
|
120
|
+
Enforcement is off by default. The guard is still queried and its verdict still
|
|
121
|
+
rides on the span, but nothing is written back to the host. That is the plan's
|
|
122
|
+
staged rollout expressed in code:
|
|
123
|
+
|
|
124
|
+
> Muse Code's default approval mode catches only three dangerous commands, so
|
|
125
|
+
> **our DENY is the first block a user ever experiences.** One false positive and
|
|
126
|
+
> the hook gets deleted. Measure the false-positive rate in shadow first.
|
|
127
|
+
|
|
128
|
+
## Confirmed contracts
|
|
129
|
+
|
|
130
|
+
Verified against **`muse 0.1.0-R708.1`** on macOS by running the adapter as a
|
|
131
|
+
real managed hook. Anything not listed here is still inference.
|
|
132
|
+
|
|
133
|
+
Muse Code documents no hook API — `muse --help` does not mention hooks at all —
|
|
134
|
+
so everything below is **observed behaviour of one build**, established by
|
|
135
|
+
running the host and watching what it did. None of it is a supported interface,
|
|
136
|
+
and any of it can change without notice. The tests in `tests/` exist to catch
|
|
137
|
+
that: the host rejects a malformed hook file **silently**, so a contract change
|
|
138
|
+
would otherwise show up as telemetry quietly going missing rather than as an
|
|
139
|
+
error.
|
|
140
|
+
|
|
141
|
+
### Registration
|
|
142
|
+
|
|
143
|
+
`managed_hooks_path` is a key in Muse Code's **`settings.json`**, not an
|
|
144
|
+
environment variable. `TBH_MANAGED_HOOKS_PATH` exists in the binary but does not
|
|
145
|
+
register hooks. Inline `hooks` in `settings.json` did **not** fire in testing;
|
|
146
|
+
only the managed file did.
|
|
147
|
+
|
|
148
|
+
The file needs the `hooks` wrapper and the matcher-**group** level. Each was
|
|
149
|
+
tested on its own, holding everything else constant:
|
|
150
|
+
|
|
151
|
+
```json
|
|
152
|
+
{
|
|
153
|
+
"schema_version": 1,
|
|
154
|
+
"hooks": {
|
|
155
|
+
"PreToolUse": [
|
|
156
|
+
{ "matcher": "*", "hooks": [{ "type": "command", "command": "…" }] }
|
|
157
|
+
]
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
| Variant | Result |
|
|
163
|
+
| --- | --- |
|
|
164
|
+
| As above | fires |
|
|
165
|
+
| Handlers listed straight under the event name | **never fires** |
|
|
166
|
+
| Event names hoisted to the top level, no `hooks` wrapper | **never fires** |
|
|
167
|
+
| `schema_version` omitted | fires — it is optional |
|
|
168
|
+
| `matcher` omitted | fires — it is optional |
|
|
169
|
+
|
|
170
|
+
> ⚠️ **A malformed or missing managed hooks file is ignored in total silence** —
|
|
171
|
+
> no warning, no exit code, no log. `tests/managed-hooks.test.ts` guards the
|
|
172
|
+
> template against exactly this, because nothing else would catch it.
|
|
173
|
+
|
|
174
|
+
Worse, group and handler objects are **deserialized strictly, and a rejection is
|
|
175
|
+
also silent**. One unrecognised key and the whole event is skipped while the rest
|
|
176
|
+
of the file keeps working — so a typo removes monitoring from one event and
|
|
177
|
+
leaves the adapter looking healthy:
|
|
178
|
+
|
|
179
|
+
| Handler key | |
|
|
180
|
+
| --- | --- |
|
|
181
|
+
| `type` `command` `commandWindows` `timeout` `statusMessage` `silent` `async` | accepted |
|
|
182
|
+
| **`env`** | **rejected — event skipped** |
|
|
183
|
+
| `shell` | rejected — event skipped |
|
|
184
|
+
| `enabled` on a matcher group | rejected — event skipped |
|
|
185
|
+
| any unknown key | rejected — event skipped |
|
|
186
|
+
|
|
187
|
+
Unknown keys at the *top* level are tolerated, which is why the template can
|
|
188
|
+
carry `_note`-style documentation.
|
|
189
|
+
|
|
190
|
+
`timeout` is in **seconds**: a hook sleeping 3s under `timeout: 1` was killed at
|
|
191
|
+
~1s wall.
|
|
192
|
+
|
|
193
|
+
The rejected `env` key is the reason configuration goes through an env file.
|
|
194
|
+
The binary's strings advertise it, but there is no working in-file way to pass a
|
|
195
|
+
variable to a hook.
|
|
196
|
+
|
|
197
|
+
### The hook environment
|
|
198
|
+
|
|
199
|
+
Hook commands get an environment filtered to **thirteen** variables:
|
|
200
|
+
|
|
201
|
+
```
|
|
202
|
+
HOME LANG LOGNAME OLDPWD PATH PWD SHELL SHLVL TERM TMPDIR USER _ __CF_USER_TEXT_ENCODING
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Consequences, all load-bearing:
|
|
206
|
+
|
|
207
|
+
- **No `PINTA_*` or `OTEL_*` shell export reaches the hook.** The env file is the
|
|
208
|
+
only configuration channel that works.
|
|
209
|
+
- **`XDG_CONFIG_HOME` is stripped too**, so inside a hook the config dir can only
|
|
210
|
+
resolve to `$HOME/.config/muse`. pinta-manager must write the env file there
|
|
211
|
+
even for users who set `XDG_CONFIG_HOME`, or the adapter silently no-ops.
|
|
212
|
+
- **`PATH` is inherited in full**, so a bare `node` resolves fine.
|
|
213
|
+
|
|
214
|
+
### Payload
|
|
215
|
+
|
|
216
|
+
`hook_event_name` **is** present in the payload on every event, and the event
|
|
217
|
+
name also arrives on argv. The adapter prefers argv and falls back to the
|
|
218
|
+
payload.
|
|
219
|
+
|
|
220
|
+
Present on every event: `hook_event_name`, `session_id`, `cwd`, `model`,
|
|
221
|
+
`permission_mode`, `transcript_path` (which may be `null`; `model` may be
|
|
222
|
+
`"unknown"`). `turn_id` rides on everything except `SessionStart`.
|
|
223
|
+
|
|
224
|
+
| Event | Additional keys |
|
|
225
|
+
| --- | --- |
|
|
226
|
+
| `SessionStart` | `source` |
|
|
227
|
+
| `UserPromptSubmit` | `prompt` |
|
|
228
|
+
| `PreToolUse` | `tool_name`, `tool_input`, `tool_use_id` |
|
|
229
|
+
| `PostToolUse` | + `tool_response` |
|
|
230
|
+
| `PreLLMCall` | `attempt`, `provider`, `request_id`, `step`, `messages`, `tools`, `message_count`, `tool_count` |
|
|
231
|
+
| `PostLLMCall` | + `response_id`, `finish_reason`, `output_text_preview`, `status`, `error`, `tool_call_count`, `usage` |
|
|
232
|
+
| `SubagentStart` | `subagent_id`, `child_session_id` |
|
|
233
|
+
| `Stop` | `last_assistant_message`, `stop_hook_active` |
|
|
234
|
+
|
|
235
|
+
> ⚠️ **`PreLLMCall`/`PostLLMCall` carry the full `messages` array and tool
|
|
236
|
+
> schemas** — the entire conversation, not a preview. This is why they are
|
|
237
|
+
> opt-in and absent from the template.
|
|
238
|
+
|
|
239
|
+
> ⚠️ **On `SubagentStart`, `session_id` is the *child's* session id**, not the
|
|
240
|
+
> parent's, and no parent id is present. Correlate subagents through the trace
|
|
241
|
+
> file (which the adapter does) rather than through `session_id`.
|
|
242
|
+
|
|
243
|
+
### Deny contract — measured
|
|
244
|
+
|
|
245
|
+
There is **no single deny shape**, and the plan's warning not to assume one was
|
|
246
|
+
right. Verified with a live managed `UserPromptSubmit` hook:
|
|
247
|
+
|
|
248
|
+
| Hook output | Result |
|
|
249
|
+
| --- | --- |
|
|
250
|
+
| `{"decision":"block","reason":…}` | **BLOCKED** |
|
|
251
|
+
| `{"hookSpecificOutput":{…,"permissionDecision":"deny"}}` | ignored, turn proceeded |
|
|
252
|
+
| `{"hookSpecificOutput":{…,"decision":"deny"}}` | ignored, turn proceeded |
|
|
253
|
+
| `{"continue":false,"stopReason":…}` | ignored, turn proceeded |
|
|
254
|
+
| exit code **2** | **BLOCKED** |
|
|
255
|
+
| exit code 1 | proceeded |
|
|
256
|
+
| unparseable stdout | proceeded |
|
|
257
|
+
| missing hook binary | proceeded |
|
|
258
|
+
|
|
259
|
+
So the host is **fail-open on everything except exit 2**. A crashed adapter
|
|
260
|
+
cannot wedge the agent — but it also cannot enforce, which is why stage 3 needs
|
|
261
|
+
bypass visibility rather than trusting the hook to always run.
|
|
262
|
+
|
|
263
|
+
`PINTA_MUSE_DENY_FORMAT` now defaults to `auto`, which picks the shape by event
|
|
264
|
+
family and pairs it with exit 2 (the two channels are independent, so a deny
|
|
265
|
+
still lands even if a host update rejects the JSON). `json` and `exit2` force a
|
|
266
|
+
single channel.
|
|
267
|
+
|
|
268
|
+
> ⚠️ Sending the wrong family's shape is **ignored silently** — no error, turn
|
|
269
|
+
> proceeds. A security control that fails open without saying so is the worst
|
|
270
|
+
> case, so `renderDenyJson()` branches on the event and
|
|
271
|
+
> `tests/core/decision.test.ts` pins both branches.
|
|
272
|
+
|
|
273
|
+
The tool-side shape (`PreToolUse` / `PermissionRequest`) could **not** be
|
|
274
|
+
exercised — the account hit `402 billing_error` before any tool call. It comes
|
|
275
|
+
from the binary's own validation strings, which require `hookSpecificOutput` to
|
|
276
|
+
carry a `hookEventName` matching the firing event alongside `permissionDecision`
|
|
277
|
+
/ `permissionDecisionReason`, and which mention "pre-tool hook blocked tool use"
|
|
278
|
+
and "permission hook denied tool use". Strong evidence, not measurement.
|
|
279
|
+
|
|
280
|
+
### Native OTLP is not an alternative
|
|
281
|
+
|
|
282
|
+
Muse Code ships its own OTLP telemetry, so "just point Muse Code at our
|
|
283
|
+
collector and skip the hooks" looks attractive. It does not work. `telemetry.destination`
|
|
284
|
+
is an enum (`legacy` | `consolidated` | `edge` | `external`), not a URL, and:
|
|
285
|
+
|
|
286
|
+
- `external` + `OTEL_EXPORTER_OTLP_ENDPOINT` at our own host → **export DISABLED**;
|
|
287
|
+
the binary detects the non-baseline destination and withholds the bearer
|
|
288
|
+
credential on purpose.
|
|
289
|
+
- `edge` / `consolidated` → internal-network destinations, **not available in
|
|
290
|
+
public builds**.
|
|
291
|
+
|
|
292
|
+
The hook adapter is therefore the only viable telemetry path, not merely the
|
|
293
|
+
chosen one.
|
|
294
|
+
|
|
295
|
+
### Cost
|
|
296
|
+
|
|
297
|
+
Measured on an 18-core M-series Mac, `--provider echo`, adapter built to `dist/`:
|
|
298
|
+
|
|
299
|
+
| | |
|
|
300
|
+
| --- | --- |
|
|
301
|
+
| Hook invocations on a trivial turn | **5** — `SessionStart`, `UserPromptSubmit`, `SubagentStart` ×2, `Stop` |
|
|
302
|
+
| One adapter invocation, cold | **~45 ms** (p50 44, max 49) — dominated by Node start-up |
|
|
303
|
+
| Turn wall-clock, no hooks → 5 hooks | 0.28 s → 0.47 s |
|
|
304
|
+
| 16 concurrent invocations | **123 ms wall** (7.7 ms/hook amortised) |
|
|
305
|
+
|
|
306
|
+
Hooks for different subagents run **in parallel**, not serially: two
|
|
307
|
+
`SubagentStart` handlers each sleeping 1s cost 1.5s of wall clock, not 2.5s. So
|
|
308
|
+
the 16-subagent case is bounded by the slowest hook rather than by their sum.
|
|
309
|
+
|
|
310
|
+
Two things follow. Node start-up is essentially the entire per-hook cost, which
|
|
311
|
+
is the strongest argument for a native build if `PreToolUse` is ever enabled on
|
|
312
|
+
a hot loop. And `SubagentStart` fires twice even on a turn that does nothing —
|
|
313
|
+
subagent traffic is the volume driver here, not tool calls.
|
|
314
|
+
|
|
315
|
+
### The session log is a viable backstop
|
|
316
|
+
|
|
317
|
+
`~/.local/share/muse/sessions/YYYY/MM/DD/<session-id>/session.jsonl`, with
|
|
318
|
+
subagents nested underneath as `subagent/<child-id>/session.jsonl`. Every line is
|
|
319
|
+
a JSON envelope (`record_type: "event"`, plus `sequence`, `recorded_at`,
|
|
320
|
+
`payload_type`). Surveyed 73 files / 1351 lines, reading keys only:
|
|
321
|
+
|
|
322
|
+
- **Hook events are recorded here too** — `SessionStart`, `UserPromptSubmit`,
|
|
323
|
+
`Stop`, `SubagentStart`, `PreLLMCall`, `PostLLMCall` all appear as
|
|
324
|
+
`payload.event.event`. The log is a superset of the hook stream, so stage 3 can
|
|
325
|
+
reconcile against it to detect a bypassed or crashed adapter.
|
|
326
|
+
- **Token counts are present**: `model_completed.usage.{input,output,cached,reasoning}_tokens`,
|
|
327
|
+
and again under `goal_usage_attribution.record.quantity.*`.
|
|
328
|
+
- **Byte accounting is present but coarse** — `model_input_trace_recorded` reports
|
|
329
|
+
lane and sample byte counts, not per-edit diff sizes.
|
|
330
|
+
- **Parent/child correlation exists here**, unlike in the hook payload:
|
|
331
|
+
`parent_session_id`, `parent_run_id` and `child_session_log_path`. The directory
|
|
332
|
+
layout alone recovers the parent that `SubagentStart` omits.
|
|
333
|
+
- Tool arguments were **not** observed — no session in the sample reached a tool
|
|
334
|
+
call, so this remains open alongside the tool-side deny shape.
|
|
335
|
+
|
|
336
|
+
### Still unconfirmed
|
|
337
|
+
|
|
338
|
+
- The tool-side deny shape, on a live tool call (needs a working account).
|
|
339
|
+
- Whether tool arguments and per-edit diff bytes appear in `session.jsonl`.
|
|
340
|
+
- Whether the TUI and `muse exec` emit identical payload shapes.
|
|
341
|
+
- Whether `read_skill` must be exempted. It is a **real tool** (confirmed in the
|
|
342
|
+
binary, with a `skill_read_ledger` behind it), not the speculative name it was
|
|
343
|
+
assumed to be — but its argument shape is unknown, so it is deliberately still
|
|
344
|
+
NOT exempted. Exempting it would widen the unguarded surface.
|
|
345
|
+
|
|
346
|
+
### Running the spike
|
|
347
|
+
|
|
348
|
+
`tools/` ships a harness that answers most of the above in one pass. Point Muse
|
|
349
|
+
Code's hooks at the capture script instead of the adapter, use Muse Code
|
|
350
|
+
normally, then read the report.
|
|
351
|
+
|
|
352
|
+
```bash
|
|
353
|
+
npx tsx tools/spike.ts hooks > ~/.config/muse/pinta-capture-hooks.json
|
|
354
|
+
# then add to ~/.config/muse/settings.json:
|
|
355
|
+
# "managed_hooks_path": "/Users/<you>/.config/muse/pinta-capture-hooks.json"
|
|
356
|
+
# ... use Muse Code for a while ...
|
|
357
|
+
npx tsx tools/spike.ts report
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
There is no `muse hooks validate` command, and a bad file is ignored silently,
|
|
361
|
+
so confirm registration by checking that the capture file actually appears at
|
|
362
|
+
`~/.pinta/spike/capture.jsonl` after one session.
|
|
363
|
+
|
|
364
|
+
The report gives, per event: the union of payload keys, **which keys are not
|
|
365
|
+
always present** (the ones that silently break a transformer later), whether the
|
|
366
|
+
event name came from argv or the payload, parse failures, the slowest
|
|
367
|
+
invocation, any undocumented event the host has started emitting, and the env
|
|
368
|
+
keys that survived on every single capture — that last set *is* the allowlist.
|
|
369
|
+
|
|
370
|
+
`tools/capture-hook.mjs` is dependency-free plain ESM (no build step) and always
|
|
371
|
+
exits 0, so it cannot disturb the session it observes. Setting
|
|
372
|
+
`PINTA_SPIKE_DENY=<Event>` makes it emit a probe denial for that one event, which
|
|
373
|
+
is how the deny wire contract gets confirmed against a real session — use a
|
|
374
|
+
throwaway workspace, because it really does try to block.
|
|
375
|
+
|
|
376
|
+
## ⚠️ Linux requires Node
|
|
377
|
+
|
|
378
|
+
Muse Code installs as a **single native binary** and runs on macOS and Linux
|
|
379
|
+
only, so its users have no reason to have Node installed. pinta-manager bundles
|
|
380
|
+
a Node binary for macOS and Windows but **not for Linux**, where it invokes the
|
|
381
|
+
literal `node` token.
|
|
382
|
+
|
|
383
|
+
Those two facts collide exactly on Linux: enrollment succeeds and the hooks then
|
|
384
|
+
**silently never run** — a machine that looks protected and is not.
|
|
385
|
+
|
|
386
|
+
This is a runtime-availability problem, **not a portability one**: the adapter
|
|
387
|
+
runs on Linux perfectly well once Node is present. Until the manager ships a
|
|
388
|
+
Linux Node bundle or a native adapter build, the rule is simply that
|
|
389
|
+
**enrollment must fail loudly when Node is missing on Linux**, rather than leave
|
|
390
|
+
a silently unprotected machine. Run the doctor before enrolling:
|
|
391
|
+
|
|
392
|
+
```bash
|
|
393
|
+
npx tsx tools/doctor.ts # exits non-zero on a blocking problem
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
## Development
|
|
397
|
+
|
|
398
|
+
```bash
|
|
399
|
+
npm install # @pinta-ai/core comes from GitHub Packages (needs read:packages)
|
|
400
|
+
npm run typecheck
|
|
401
|
+
npm test
|
|
402
|
+
npm run build # dist/index.js (CJS hook) + dist/index.mjs (ESM, sidecar-importable)
|
|
403
|
+
npm run doctor
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
`dist/index.mjs` guards its direct-exec path, so the pinta-manager sidecar can
|
|
407
|
+
`import()` it without reading stdin, dispatching a hook, or exiting the process.
|
|
408
|
+
|
|
409
|
+
## Release and distribution
|
|
410
|
+
|
|
411
|
+
Not published yet. `@pinta-ai/pinta-musecode` is not on npmjs and the adaptor is
|
|
412
|
+
not listed in `pinta-catalog`, so pinta-manager cannot install it — CI being
|
|
413
|
+
green is necessary but not sufficient.
|
|
414
|
+
|
|
415
|
+
Two things have to happen, in order, and neither can be done from this
|
|
416
|
+
repository alone:
|
|
417
|
+
|
|
418
|
+
1. **First publish by hand.** `publish.yml` uses OIDC trusted publishing, which
|
|
419
|
+
cannot create a package that does not exist yet
|
|
420
|
+
([npm/cli#8544](https://github.com/npm/cli/issues/8544)). Someone with
|
|
421
|
+
publish rights on the `@pinta-ai` scope runs `npm publish --access public`
|
|
422
|
+
once; every tag after that goes through the workflow.
|
|
423
|
+
2. **Register in `pinta-catalog`**, which pins the tarball by sha256 — so the
|
|
424
|
+
manifest cannot be written before step 1.
|
|
425
|
+
|
|
426
|
+
Full commands, the manifest, and why this adaptor's manifest differs from the
|
|
427
|
+
other hook adaptors are in [`PUBLISHING.md`](PUBLISHING.md).
|
|
428
|
+
|
|
429
|
+
## License
|
|
430
|
+
|
|
431
|
+
PolyForm Noncommercial 1.0.0
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";var pt=Object.create;var ue=Object.defineProperty;var dt=Object.getOwnPropertyDescriptor;var gt=Object.getOwnPropertyNames;var _t=Object.getPrototypeOf,ht=Object.prototype.hasOwnProperty;var m=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports);var mt=(e,t,r,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of gt(t))!ht.call(e,o)&&o!==r&&ue(e,o,{get:()=>t[o],enumerable:!(n=dt(t,o))||n.enumerable});return e};var y=(e,t,r)=>(r=e!=null?pt(_t(e)):{},mt(t||!e||!e.__esModule?ue(r,"default",{value:e,enumerable:!0}):r,e));var V=m(p=>{"use strict";Object.defineProperty(p,"__esModule",{value:!0});p.PATTERNS=p.MAX_BYTES=void 0;p.truncateTo=ce;p.truncate=yt;p.collectMatches=le;p.resolveOverlaps=fe;p.applyMatches=pe;p.redact=Pt;p.MAX_BYTES=102400;function ce(e,t){let r=Buffer.from(e,"utf-8");return r.length<=t?e:`${r.subarray(0,Math.max(0,t)).toString("utf-8")}\u2026[TRUNCATED:${r.length}]`}function yt(e){return ce(e,p.MAX_BYTES)}p.PATTERNS=[{type:"aws_access_key",regex:/AKIA[0-9A-Z]{16}/g},{type:"aws_secret_key",regex:/(?:aws[_-]?secret(?:[_-]?(?:access)?[_-]?key)?)\s*[:=]\s*["']?([A-Za-z0-9/+=]{40})(?![A-Za-z0-9/+=])/gi,captureGroup:1},{type:"gcp_service_account",regex:/\{[\s\S]{0,200}?"type"\s*:\s*"service_account"[\s\S]*?\}/g},{type:"github_token",regex:/gh[pousr]_[A-Za-z0-9]{36,}/g},{type:"gitlab_token",regex:/glpat-[A-Za-z0-9_-]{20}/g},{type:"slack_token",regex:/xox[abrsp]-[0-9A-Za-z-]{10,}/g},{type:"openai_key",regex:/sk-(?:proj-)?[A-Za-z0-9_-]{40,}/g},{type:"anthropic_key",regex:/sk-ant-[A-Za-z0-9_-]{50,}/g},{type:"stripe_key",regex:/(?:sk|rk|pk)_(?:live|test)_[A-Za-z0-9]{20,}/g},{type:"jwt",regex:/eyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/g},{type:"private_key_block",regex:/-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g},{type:"bearer_token",regex:/bearer\s+([A-Za-z0-9._~+/=-]{12,})/gi,captureGroup:1},{type:"basic_auth",regex:/basic\s+([A-Za-z0-9+/=]{12,})/gi,captureGroup:1},{type:"db_url_password",regex:/\b(?:postgres|postgresql|mysql|mariadb|mongodb(?:\+srv)?|redis):\/\/[^:\s/]+:([^@\s]+)@/gi,captureGroup:1},{type:"cli_password_flag",regex:/(?:--password|--pass|--pwd)[=\s]([^\s'"]+)/g,captureGroup:1},{type:"cli_password_short",regex:/\s-p([^\s'"]+)/g,captureGroup:1,requireContext:"bash"},{type:"env_var_secret",regex:/^(?:export\s+)?([A-Z][A-Z0-9_]*(?:KEY|SECRET|TOKEN|PASSWORD|PASSWD|PWD|API_KEY)[A-Z0-9_]*)\s*=\s*["']?([^\s"'\n]+)/gm,captureGroup:2}];function le(e,t){let r=[];for(let n of p.PATTERNS){if(n.requireContext&&n.requireContext!==t.context)continue;let o=new RegExp(n.regex.source,n.regex.flags),i;for(;(i=o.exec(e))!==null;){let a=n.captureGroup??0,u=i[a];if(u===void 0){i.index===o.lastIndex&&o.lastIndex++;continue}let c=i.index,h=i.index+i[0].length,w=c+i[0].indexOf(u),ft=w+u.length;r.push({start:c,end:h,replaceStart:w,replaceEnd:ft,type:n.type}),i.index===o.lastIndex&&o.lastIndex++}}return r}function fe(e){let t=[...e].sort((o,i)=>o.start!==i.start?o.start-i.start:i.end-i.start-(o.end-o.start)),r=[],n=-1;for(let o of t)o.start<n||(r.push(o),n=o.end);return r}function pe(e,t){let r=[...t].sort((o,i)=>i.replaceStart-o.replaceStart),n=e;for(let o of r)n=n.slice(0,o.replaceStart)+`[REDACTED:${o.type}]`+n.slice(o.replaceEnd);return n}function Pt(e,t={}){if(e.length===0)return e;let r=le(e,t);if(r.length===0)return e;let n=fe(r);return pe(e,n)}});var U=m(f=>{"use strict";var Et=f&&f.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(f,"__esModule",{value:!0});f.MAX_SPAN_BYTES=void 0;f.ulidToTraceId=he;f.newSpanId=me;f.snakeCase=Tt;f.toOtlpValue=ye;f.attrsFromRecord=bt;f.guardAttrs=Pe;f.clientAttrs=Ee;f.enforceSpanBudget=ve;f.buildPayload=Ot;f.mergeBatch=kt;var vt=Et(require("crypto")),G=V(),St="0123456789ABCDEFGHJKMNPQRSTVWXYZ";function he(e){if(e.length!==26)throw new Error(`ulidToTraceId: expected 26 chars, got ${e.length}`);let t=0n;for(let n of e){let o=St.indexOf(n);if(o<0)throw new Error(`ulidToTraceId: invalid Crockford char "${n}"`);t=t<<5n|BigInt(o)}let r=(1n<<128n)-1n;return t&=r,t.toString(16).padStart(32,"0")}function me(){return vt.default.randomBytes(8).toString("hex")}function Tt(e){return e.replace(/([a-z0-9])([A-Z])/g,"$1_$2").replace(/([A-Z])([A-Z][a-z])/g,"$1_$2").toLowerCase()}var de=new Set;function R(e,t,r){let n=(0,G.truncate)(t);if((r.skipRedactKeys??de).has(e))return n;let o=(r.bashContextKeys??de).has(e)?"bash":void 0;return(0,G.redact)(n,{context:o})}function ye(e,t,r={}){if(t==null)return null;switch(typeof t){case"string":return{stringValue:R(e,t,r)};case"boolean":return{boolValue:t};case"number":return Number.isInteger(t)?{intValue:t}:{doubleValue:t};case"object":try{return{stringValue:R(e,JSON.stringify(t),r)}}catch{return{stringValue:R(e,String(t),r)}}default:return{stringValue:R(e,String(t),r)}}}function bt(e,t,r={}){let n=[];for(let[o,i]of Object.entries(e)){let a=`${t}.${o}`,u=ye(a,i,r);u!==null&&n.push({key:a,value:u})}return n}function Pe(e){let t=[{key:"pinta.guard.decision",value:{stringValue:e.decision.toLowerCase()}},{key:"pinta.guard.duration_ms",value:{intValue:e.durationMs}}];return e.reason&&t.push({key:"pinta.guard.matched_rule",value:{stringValue:e.reason}}),e.failOpenReason&&t.push({key:"pinta.guard.fail_open_reason",value:{stringValue:e.failOpenReason}}),t}function Ee(e){return[{key:"pinta.client.rtt_ms",value:Number.isInteger(e.rttMs)?{intValue:e.rttMs}:{doubleValue:e.rttMs}},{key:"pinta.client.op",value:{stringValue:e.op}}]}f.MAX_SPAN_BYTES=800*1024;function ge(e){let t=JSON.stringify(e);return t===void 0?0:Buffer.byteLength(t,"utf-8")}var _e=1024;function ve(e,t=f.MAX_SPAN_BYTES){let r=ge(e);for(let n=0;n<100&&r>t;n++){let o=null,i=0;for(let c of e.attributes){if(!("stringValue"in c.value))continue;let h=Buffer.byteLength(c.value.stringValue,"utf-8");h>i&&(i=h,o=c.value)}if(o===null||i<=_e)break;let a=r-t,u=Math.max(_e,i-a-64);o.stringValue=(0,G.truncateTo)(o.stringValue,u),r=ge(e)}return e}function Ot(e){let t=e.now??Date.now(),r=(BigInt(t)*1000000n).toString(),n=[...e.attributes];e.guard&&n.push(...Pe(e.guard));let o=e.clientTiming??(e.guard?.clientRttMs!==void 0?{op:"guard",rttMs:e.guard.clientRttMs}:null);o&&n.push(...Ee(o));let i={traceId:he(e.traceId),spanId:me(),name:e.spanName,kind:e.spanKind??1,startTimeUnixNano:r,endTimeUnixNano:r,attributes:n};return ve(i),{resourceSpans:[{resource:{attributes:e.resource},scopeSpans:[{scope:e.scope,spans:[i]}]}]}}function kt(e){let t=[];for(let r of e)t.push(...r.resourceSpans);return{resourceSpans:t}}});var Se=m(X=>{"use strict";Object.defineProperty(X,"__esModule",{value:!0});X.evaluateGuard=xt;var At=1e4,wt="pinta-core";function Y(e,t){return{decision:"ALLOW",reason:null,userMessage:null,durationMs:e,clientRttMs:e,failOpenReason:t}}var Mt=new Set(["ALLOW","DENY","REVIEW"]);async function xt(e,t,r={}){if(!t||r.disabled)return null;let n=r.timeoutMs??At,o=Date.now(),i=new AbortController,a=setTimeout(()=>i.abort(),n);try{let u=await fetch(t,{method:"POST",headers:{"content-type":"application/json","user-agent":r.userAgent??wt,"x-pinta-relay-token":r.token??""},body:JSON.stringify({input:e}),signal:i.signal});if(u.status!==200)return Y(Date.now()-o,"error");let c=await u.json(),h=c?.durationMs===void 0||typeof c.durationMs=="number"&&Number.isFinite(c.durationMs);if(!c||!Mt.has(c.decision)||!h)return Y(Date.now()-o,"error");let w=Date.now()-o;return{decision:c.decision,reason:c.reason??null,userMessage:c.userMessage??null,durationMs:c.durationMs??w,clientRttMs:w}}catch(u){let c=u.name,h=c==="AbortError"||c==="TimeoutError"?"timeout":"error";return Y(Date.now()-o,h)}finally{clearTimeout(a)}}});var j=m(M=>{"use strict";var Rt=M&&M.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(M,"__esModule",{value:!0});M.atomicWriteFileSync=jt;var Te=Rt(require("fs"));function jt(e,t){let r=`${e}.${process.pid}.tmp`;Te.default.writeFileSync(r,t),Te.default.renameSync(r,e)}});var Z=m(P=>{"use strict";var be=P&&P.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(P,"__esModule",{value:!0});P.MemoryRetryQueue=P.DiskRetryQueue=void 0;var d=be(require("fs")),D=be(require("path")),Dt=j(),N=1e3,Nt=50,It=5,z=class{filePath;lockPath;logPrefix;constructor(t,r){this.filePath=D.default.join(t,"failed-spans.jsonl"),this.lockPath=this.filePath+".lock",this.logPrefix=r}enqueue(t){try{d.default.mkdirSync(D.default.dirname(this.filePath),{recursive:!0});let r=JSON.stringify({savedAt:new Date().toISOString(),payload:t})+`
|
|
2
|
+
`;d.default.appendFileSync(this.filePath,r),this.trim()}catch(r){process.stderr.write(`[${this.logPrefix}] retry-queue enqueue failed: ${r}
|
|
3
|
+
`)}}readAll(){try{let t=d.default.readFileSync(this.filePath,"utf-8"),r=[];for(let n of t.split(`
|
|
4
|
+
`))if(n.trim())try{let o=JSON.parse(n);Array.isArray(o?.payload?.resourceSpans)&&r.push(o)}catch{}return r}catch{return[]}}rewrite(t){try{if(t.length===0){d.default.existsSync(this.filePath)&&d.default.unlinkSync(this.filePath);return}d.default.mkdirSync(D.default.dirname(this.filePath),{recursive:!0}),(0,Dt.atomicWriteFileSync)(this.filePath,t.map(r=>JSON.stringify(r)).join(`
|
|
5
|
+
`)+`
|
|
6
|
+
`)}catch(r){process.stderr.write(`[${this.logPrefix}] retry-queue rewrite failed: ${r}
|
|
7
|
+
`)}}tryAcquireLock(){let t=Date.now();try{d.default.mkdirSync(D.default.dirname(this.lockPath),{recursive:!0})}catch(r){return process.stderr.write(`[${this.logPrefix}] retry-queue lock mkdir failed: ${r}
|
|
8
|
+
`),!1}for(;Date.now()-t<Nt;)try{let r=d.default.openSync(this.lockPath,"wx");return d.default.writeSync(r,String(process.pid)),d.default.closeSync(r),!0}catch(r){if(r?.code!=="EEXIST")return process.stderr.write(`[${this.logPrefix}] retry-queue lock open failed: ${r}
|
|
9
|
+
`),!1;try{let i=d.default.statSync(this.lockPath);if(Date.now()-i.mtimeMs>3e4){d.default.unlinkSync(this.lockPath);continue}}catch{}let n=It,o=Date.now()+n;for(;Date.now()<o;);}return!1}release(){try{d.default.unlinkSync(this.lockPath)}catch{}}trim(){let t=this.readAll();if(t.length<=N)return;let r=t.length-N;process.stderr.write(`[${this.logPrefix}] retry-queue full, dropping ${r} oldest entries
|
|
10
|
+
`),this.rewrite(t.slice(r))}};P.DiskRetryQueue=z;var K=class{entries=[];enqueue(t){this.entries.push(t),this.entries.length>N&&this.entries.splice(0,this.entries.length-N)}drain(){let t=this.entries;return this.entries=[],t}get size(){return this.entries.length}};P.MemoryRetryQueue=K});var xe=m(l=>{"use strict";Object.defineProperty(l,"__esModule",{value:!0});l.MemoryTransport=l.DiskTransport=l.MAX_POST_BYTES=void 0;l.parseHeadersEnv=we;l.envOptionsResolver=Me;l.postOtlp=x;var Oe=Z(),ke=U(),Ct=5e3;l.MAX_POST_BYTES=900*1024;function I(e){let t=JSON.stringify(e);return t===void 0?0:Buffer.byteLength(t,"utf-8")}function Ae(e,t,r){let n=[],o=[],i=[],a=0;for(let u of e){let c=t(u);if(c>r){o.push(u);continue}i.length>0&&a+c>r&&(n.push(i),i=[],a=0),i.push(u),a+=c}return i.length>0&&n.push(i),{chunks:n,oversize:o}}function we(e){if(!e)return{};if(typeof e=="object")return{...e};let t={};for(let r of e.split(",")){let[n,...o]=r.split("=");n&&o.length>0&&(t[n.trim()]=o.join("=").trim())}return t}function Me(){let e=process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT,t=process.env.OTEL_EXPORTER_OTLP_ENDPOINT,r;return e?r=e.replace(/\/+$/,""):t&&(r=t.replace(/\/+$/,"")+"/v1/traces"),r?{endpoint:r,headers:we(process.env.OTEL_EXPORTER_OTLP_HEADERS)}:null}async function x(e,t,r){let n=new AbortController,o=setTimeout(()=>n.abort(),Ct);try{let i=await fetch(t.endpoint,{method:"POST",headers:{"Content-Type":"application/json",...t.headers},body:JSON.stringify(e),signal:n.signal});if(!i.ok){let a="";try{a=(await i.text()).slice(0,200)}catch{}let u=i.status===401||i.status===403?" \u2014 check OTEL_EXPORTER_OTLP_HEADERS (relay token)":i.status===404?" \u2014 check OTEL_EXPORTER_OTLP_TRACES_ENDPOINT path":i.status>=500?" \u2014 collector may be down":"";return process.stderr.write(`[${r}] OTLP POST ${i.status} ${t.endpoint}${u}${a?` body=${a}`:""}
|
|
11
|
+
`),!1}return!0}catch(i){return process.stderr.write(`[${r}] OTLP POST failed: ${i.message??String(i)}
|
|
12
|
+
`),!1}finally{clearTimeout(o)}}var W=class{queue;logPrefix;resolveOptions;constructor(t){this.queue=new Oe.DiskRetryQueue(t.pluginData,t.logPrefix),this.logPrefix=t.logPrefix,this.resolveOptions=t.resolveOptions??Me}async send(t){let r=this.resolveOptions();if(!r||this.dropIfOversized(t))return;await x(t,r,this.logPrefix)||this.queue.enqueue(t)}async flush(){let t=this.resolveOptions();if(t&&this.queue.tryAcquireLock())try{let r=this.queue.readAll();if(r.length===0)return;let{chunks:n,oversize:o}=Ae(r,u=>I(u.payload),l.MAX_POST_BYTES);o.length>0&&process.stderr.write(`[${this.logPrefix}] dropping ${o.length} oversized retry ${o.length===1?"entry":"entries"} (each > ${l.MAX_POST_BYTES} bytes \u2014 the backend caps the request body, so they can never be delivered)
|
|
13
|
+
`);let i=[].concat(...n),a=0;for(let u of n){if(!await x((0,ke.mergeBatch)(u.map(h=>h.payload)),t,this.logPrefix))break;a+=u.length}this.queue.rewrite(i.slice(a))}catch(r){process.stderr.write(`[${this.logPrefix}] flush failed: ${r?.message??String(r)}
|
|
14
|
+
`)}finally{this.queue.release()}}dropIfOversized(t){let r=I(t);return r<=l.MAX_POST_BYTES?!1:(process.stderr.write(`[${this.logPrefix}] dropping oversized span payload (${r} > ${l.MAX_POST_BYTES} bytes) \u2014 undeliverable, not queued
|
|
15
|
+
`),!0)}};l.DiskTransport=W;var J=class{queue=new Oe.MemoryRetryQueue;logPrefix;resolveOptions;constructor(t){this.logPrefix=t.logPrefix,this.resolveOptions=t.resolveOptions}async send(t){let r=this.resolveOptions();if(!r)return;if(I(t)>l.MAX_POST_BYTES){process.stderr.write(`[${this.logPrefix}] dropping oversized span payload (> ${l.MAX_POST_BYTES} bytes) \u2014 undeliverable, not buffered
|
|
16
|
+
`);return}await x(t,r,this.logPrefix)||this.queue.enqueue(t)}async flush(){let t=this.resolveOptions();if(!t)return;let r=this.queue.drain();if(r.length===0)return;let{chunks:n,oversize:o}=Ae(r,I,l.MAX_POST_BYTES);o.length>0&&process.stderr.write(`[${this.logPrefix}] dropping ${o.length} oversized buffered ${o.length===1?"payload":"payloads"} (each > ${l.MAX_POST_BYTES} bytes \u2014 undeliverable)
|
|
17
|
+
`);let i=[].concat(...n),a=0;for(let u of n){if(!await x((0,ke.mergeBatch)(u),t,this.logPrefix))break;a+=u.length}for(let u of i.slice(a))this.queue.enqueue(u)}};l.MemoryTransport=J});var te=m(S=>{"use strict";var ee=S&&S.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(S,"__esModule",{value:!0});S.TraceManager=void 0;S.generateUlid=je;var Re=ee(require("fs")),Bt=ee(require("path")),Lt=ee(require("crypto")),$t=j(),H="0123456789ABCDEFGHJKMNPQRSTVWXYZ";function je(){let e=Date.now(),t="",r=e;for(let i=0;i<10;i++)t=H[r&31]+t,r=Math.floor(r/32);let n=Lt.default.randomBytes(10),o="";for(let i=0;i<10;i++)o+=H[n[i]&31];for(;o.length<16;)o+=H[0];return t+o}var Q=class{tracePath;constructor(t){this.tracePath=t}newTrace(){let t=je();return this.save(t),t}currentTrace(){try{let t=Re.default.readFileSync(this.tracePath,"utf-8"),{traceId:r}=JSON.parse(t);if(r)return r}catch{}return this.newTrace()}save(t){try{Re.default.mkdirSync(Bt.default.dirname(this.tracePath),{recursive:!0}),(0,$t.atomicWriteFileSync)(this.tracePath,JSON.stringify({traceId:t}))}catch(r){process.stderr.write(`[pinta-core] trace persistence failed: ${r}
|
|
18
|
+
`)}}};S.TraceManager=Q});var Be=m(E=>{"use strict";var Ne=E&&E.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(E,"__esModule",{value:!0});E.MemorySessionTraceManager=E.DiskSessionTraceManager=void 0;var De=Ne(require("fs")),qt=Ne(require("path")),Ie=te(),Ft=j(),Ce=200,re=class{tracePath;maxSessions;constructor(t,r={}){this.tracePath=t,this.maxSessions=r.maxSessions??Ce}read(){try{let t=JSON.parse(De.default.readFileSync(this.tracePath,"utf-8"));if(t&&typeof t=="object"&&!("traceId"in t))return t}catch{}return{}}write(t){try{De.default.mkdirSync(qt.default.dirname(this.tracePath),{recursive:!0});let r=Object.entries(t),n=r.length>this.maxSessions?Object.fromEntries(r.slice(-this.maxSessions)):t;(0,Ft.atomicWriteFileSync)(this.tracePath,JSON.stringify(n))}catch{}}newTrace(t){let r=t||"default",n=(0,Ie.generateUlid)(),o=this.read();return o[r]=n,this.write(o),n}currentTrace(t){let r=t||"default",n=this.read()[r];return n||this.newTrace(r)}};E.DiskSessionTraceManager=re;var ne=class{map=new Map;maxSessions;constructor(t={}){this.maxSessions=t.maxSessions??Ce}newTrace(t){let r=t||"default",n=(0,Ie.generateUlid)();if(this.map.set(r,n),this.map.size>this.maxSessions){let o=this.map.keys().next().value;o!==void 0&&this.map.delete(o)}return n}currentTrace(t){let r=t||"default";return this.map.get(r)??this.newTrace(r)}};E.MemorySessionTraceManager=ne});var qe=m(T=>{"use strict";var oe=T&&T.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(T,"__esModule",{value:!0});T.envFilePath=Ut;T.parseEnvFile=$e;T.loadEnvFile=Yt;var Vt=oe(require("node:fs")),Gt=oe(require("node:os")),Le=oe(require("node:path"));function Ut(e,t,r){let n=r?process.env[r]:void 0,o=n&&n.length>0?n:Le.default.join(Gt.default.homedir(),e);return Le.default.join(o,t)}function $e(e){let t={};for(let r of e.split(`
|
|
19
|
+
`)){let n=r.trim();if(!n||n.startsWith("#"))continue;let o=n.indexOf("=");if(o<0)continue;let i=n.slice(0,o).trim(),a=n.slice(o+1).trim();(a.startsWith('"')&&a.endsWith('"')||a.startsWith("'")&&a.endsWith("'"))&&(a=a.slice(1,-1)),i&&(t[i]=a)}return t}function Yt(e){let t;try{t=Vt.default.readFileSync(e,"utf-8")}catch{return}let r=$e(t);for(let[n,o]of Object.entries(r))process.env[n]===void 0&&(process.env[n]=o)}});var O=m(s=>{"use strict";Object.defineProperty(s,"__esModule",{value:!0});s.loadEnvFile=s.parseEnvFile=s.envFilePath=s.MemorySessionTraceManager=s.DiskSessionTraceManager=s.generateUlid=s.TraceManager=s.MAX_POST_BYTES=s.parseHeadersEnv=s.envOptionsResolver=s.postOtlp=s.MemoryTransport=s.DiskTransport=s.MemoryRetryQueue=s.DiskRetryQueue=s.evaluateGuard=s.MAX_SPAN_BYTES=s.enforceSpanBudget=s.mergeBatch=s.buildPayload=s.clientAttrs=s.guardAttrs=s.attrsFromRecord=s.toOtlpValue=s.snakeCase=s.newSpanId=s.ulidToTraceId=s.MAX_BYTES=s.PATTERNS=s.applyMatches=s.resolveOverlaps=s.collectMatches=s.truncateTo=s.truncate=s.redact=void 0;var v=V();Object.defineProperty(s,"redact",{enumerable:!0,get:function(){return v.redact}});Object.defineProperty(s,"truncate",{enumerable:!0,get:function(){return v.truncate}});Object.defineProperty(s,"truncateTo",{enumerable:!0,get:function(){return v.truncateTo}});Object.defineProperty(s,"collectMatches",{enumerable:!0,get:function(){return v.collectMatches}});Object.defineProperty(s,"resolveOverlaps",{enumerable:!0,get:function(){return v.resolveOverlaps}});Object.defineProperty(s,"applyMatches",{enumerable:!0,get:function(){return v.applyMatches}});Object.defineProperty(s,"PATTERNS",{enumerable:!0,get:function(){return v.PATTERNS}});Object.defineProperty(s,"MAX_BYTES",{enumerable:!0,get:function(){return v.MAX_BYTES}});var _=U();Object.defineProperty(s,"ulidToTraceId",{enumerable:!0,get:function(){return _.ulidToTraceId}});Object.defineProperty(s,"newSpanId",{enumerable:!0,get:function(){return _.newSpanId}});Object.defineProperty(s,"snakeCase",{enumerable:!0,get:function(){return _.snakeCase}});Object.defineProperty(s,"toOtlpValue",{enumerable:!0,get:function(){return _.toOtlpValue}});Object.defineProperty(s,"attrsFromRecord",{enumerable:!0,get:function(){return _.attrsFromRecord}});Object.defineProperty(s,"guardAttrs",{enumerable:!0,get:function(){return _.guardAttrs}});Object.defineProperty(s,"clientAttrs",{enumerable:!0,get:function(){return _.clientAttrs}});Object.defineProperty(s,"buildPayload",{enumerable:!0,get:function(){return _.buildPayload}});Object.defineProperty(s,"mergeBatch",{enumerable:!0,get:function(){return _.mergeBatch}});Object.defineProperty(s,"enforceSpanBudget",{enumerable:!0,get:function(){return _.enforceSpanBudget}});Object.defineProperty(s,"MAX_SPAN_BYTES",{enumerable:!0,get:function(){return _.MAX_SPAN_BYTES}});var Xt=Se();Object.defineProperty(s,"evaluateGuard",{enumerable:!0,get:function(){return Xt.evaluateGuard}});var Fe=Z();Object.defineProperty(s,"DiskRetryQueue",{enumerable:!0,get:function(){return Fe.DiskRetryQueue}});Object.defineProperty(s,"MemoryRetryQueue",{enumerable:!0,get:function(){return Fe.MemoryRetryQueue}});var b=xe();Object.defineProperty(s,"DiskTransport",{enumerable:!0,get:function(){return b.DiskTransport}});Object.defineProperty(s,"MemoryTransport",{enumerable:!0,get:function(){return b.MemoryTransport}});Object.defineProperty(s,"postOtlp",{enumerable:!0,get:function(){return b.postOtlp}});Object.defineProperty(s,"envOptionsResolver",{enumerable:!0,get:function(){return b.envOptionsResolver}});Object.defineProperty(s,"parseHeadersEnv",{enumerable:!0,get:function(){return b.parseHeadersEnv}});Object.defineProperty(s,"MAX_POST_BYTES",{enumerable:!0,get:function(){return b.MAX_POST_BYTES}});var Ve=te();Object.defineProperty(s,"TraceManager",{enumerable:!0,get:function(){return Ve.TraceManager}});Object.defineProperty(s,"generateUlid",{enumerable:!0,get:function(){return Ve.generateUlid}});var Ge=Be();Object.defineProperty(s,"DiskSessionTraceManager",{enumerable:!0,get:function(){return Ge.DiskSessionTraceManager}});Object.defineProperty(s,"MemorySessionTraceManager",{enumerable:!0,get:function(){return Ge.MemorySessionTraceManager}});var se=qe();Object.defineProperty(s,"envFilePath",{enumerable:!0,get:function(){return se.envFilePath}});Object.defineProperty(s,"parseEnvFile",{enumerable:!0,get:function(){return se.parseEnvFile}});Object.defineProperty(s,"loadEnvFile",{enumerable:!0,get:function(){return se.loadEnvFile}})});var Ue=y(require("node:os")),C=y(require("node:path")),ie=y(O());var zt="pinta-musecode.env";function Kt(){let e=process.env.XDG_CONFIG_HOME;return e&&e.length>0?C.default.join(e,"muse"):C.default.join(Ue.default.homedir(),".config","muse")}function Zt(){return C.default.join(Kt(),zt)}function Ye(e=Zt()){(0,ie.loadEnvFile)(e)}var Xe=y(require("node:os")),ae=y(require("node:path"));function Wt(){return process.env.PINTA_MUSECODE_DATA||process.env.PINTA_PLUGIN_DATA||ae.default.join(Xe.default.homedir(),".pinta","adaptors","pinta-musecode")}function ze(){let e=Wt();return{pluginData:e,tracePath:ae.default.join(e,"trace.json")}}function Ke(){return process.env.PINTA_MUSE_ENFORCE==="1"}function Ze(){return process.env.PINTA_MUSE_LLM_EVENTS==="1"}var Jt=["SessionStart","UserPromptSubmit","PreToolUse","PermissionRequest","PostToolUse","PreLLMCall","PostLLMCall","PreCompact","PostCompact","SubagentStart","SubagentStop","Stop"],Ht=new Set(Jt);function Qt(e){return Ht.has(e)}var er=["hook_event_name","event","hook","hook_event","eventName"];function We(e,t){for(let r of e){let n=r.startsWith("--event=")?r.slice(8):r;if(Qt(n))return n}for(let r of er){let n=t[r];if(typeof n=="string"&&n.length>0)return n}return null}function B(e){return e==="PreToolUse"||e==="PermissionRequest"}function Je(e){return e==="PreLLMCall"||e==="PostLLMCall"}var tr=new Set(["subagent_spawn","subagent_status","subagent_send_message","subagent_cancel","subagent_wait","subagent_read_result"]);function He(e){return e!==void 0&&tr.has(e)}var Qe=y(O()),rr=1e4,nr="pinta-musecode/0.1.0";function et(e,t){return(0,Qe.evaluateGuard)(e,t,{timeoutMs:rr,token:process.env.PINTA_RELAY_TOKEN??"",disabled:process.env.PINTA_GUARD_DISABLED==="1",userAgent:nr})}function or(){let e=process.env.PINTA_MUSE_DENY_FORMAT;return e==="exit2"||e==="json"?e:"auto"}function sr(e,t){return B(e)?{hookSpecificOutput:{hookEventName:e,permissionDecision:"deny",permissionDecisionReason:t}}:{decision:"block",reason:t}}function tt(e,t,r={}){let n=r.stdout??(a=>process.stdout.write(a)),o=r.stderr??(a=>process.stderr.write(a)),i=or();return i==="exit2"?(o(`${t}
|
|
20
|
+
`),{written:!0,exitCode:2}):(n(JSON.stringify(sr(e,t))+`
|
|
21
|
+
`),i==="json"?{written:!0,exitCode:0}:(o(`${t}
|
|
22
|
+
`),{written:!0,exitCode:2}))}var rt=y(O()),L=class extends rt.DiskTransport{constructor(t){super({pluginData:t.pluginData,logPrefix:"pinta-musecode"})}};var nt=y(O()),$=class extends nt.TraceManager{constructor(t){super(t.tracePath)}};var F=y(require("node:os")),k=y(O()),ot="0.1.0",ir="musecode",g="muse",q;function ar(){if(q===void 0)try{q=F.default.userInfo().username}catch{q=process.env.USER??process.env.LOGNAME??(typeof process.getuid=="function"?String(process.getuid()):"unknown")}return q}function ur(e){for(let t of["muse_version","version","cli_version"]){let r=e[t];if(typeof r=="string"&&r.length>0)return r}return process.env.MUSE_VERSION??"unknown"}var cr=new Set([`${g}.hook`,`${g}.tool_name`,`${g}.tool_use_id`,`${g}.session_id`,`${g}.cwd`,`${g}.permission_mode`,`${g}.agent_id`,`${g}.agent_type`]),lr=new Set([`${g}.tool_input`,`${g}.tool_response`]),fr={skipRedactKeys:cr,bashContextKeys:lr};function pr(e){let t=[];t.push({key:"ingest.type",value:{stringValue:ir}}),t.push({key:`${g}.hook`,value:{stringValue:e.hook_event_name}});let r={};for(let[n,o]of Object.entries(e))n!=="hook_event_name"&&(r[n]=o);return t.push(...(0,k.attrsFromRecord)(r,g,fr)),t}function dr(e){return[{key:"service.name",value:{stringValue:"muse-code"}},{key:"service.version",value:{stringValue:ur(e)}},{key:"telemetry.sdk.name",value:{stringValue:"pinta-musecode"}},{key:"telemetry.sdk.language",value:{stringValue:"nodejs"}},{key:"telemetry.sdk.version",value:{stringValue:ot}},{key:"process.pid",value:{intValue:process.pid}},{key:"process.owner",value:{stringValue:ar()}},{key:"host.name",value:{stringValue:F.default.hostname()}},{key:"host.arch",value:{stringValue:F.default.arch()}}]}function st(e){return(0,k.buildPayload)({traceId:e.traceId,spanName:`${g}.${(0,k.snakeCase)(e.event.hook_event_name)}`,attributes:pr(e.event),resource:dr(e.event),scope:{name:"pinta-musecode",version:ot},now:e.now,guard:e.guard})}async function gr(e,t,r={}){let n=new L(t);await n.flush();let o=new $(t),i=r.traceMode==="new"?o.newTrace():o.currentTrace(),a=st({event:e,traceId:i,guard:r.guard});await n.send(a)}async function A(e,t,r={}){try{await gr(e,t,r)}catch(n){process.stderr.write(`[pinta-musecode] telemetry emit failed: ${n}
|
|
23
|
+
`)}}async function it(e,t){if(He(e.tool_name))return await A(e,t),0;let r=typeof e.tool_input=="string"?e.tool_input:JSON.stringify(e.tool_input),n=await et({spanId:e.session_id??"unknown",toolName:e.tool_name,toolInput:e.tool_input,rawTextFields:{toolInput:r??""}},process.env.PINTA_GUARD_ENDPOINT),o=0;if(n?.decision==="DENY"&&Ke()){let i=n.userMessage??n.reason??"guard_deny";o=tt(e.hook_event_name,i).exitCode}return await A(e,t,{guard:n}),o}async function at(e,t){return await A(e,t),0}async function ut(e,t){return await A(e,t,{traceMode:"new"}),0}async function ct(e){return 0}async function _r(){let e=[];for await(let t of process.stdin)e.push(t);return Buffer.concat(e).toString("utf-8")}function hr(e){let t=e.trim();if(t.length===0)return{};try{let r=JSON.parse(t);if(r!==null&&typeof r=="object"&&!Array.isArray(r))return r}catch{}return{}}async function mr(e,t){let r=e.hook_event_name;return B(r)?it(e,t):r==="UserPromptSubmit"?ut(e,t):Je(r)&&!Ze()?ct(e):at(e,t)}async function lt(e=process.argv.slice(2)){let t=0;try{let r=ze(),n=hr(await _r()),o=We(e,n);if(o===null)return process.stderr.write(`[pinta-musecode] could not resolve hook event name; skipping
|
|
24
|
+
`),0;let i={...n,hook_event_name:o};t=await mr(i,r)}catch(r){process.stderr.write(`[pinta-musecode] error: ${r}
|
|
25
|
+
`),t=0}return t}Ye();async function yr(){let e=await lt();process.exit(e)}yr();
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import{createRequire as __pintaCreateRequire}from 'node:module';const require=__pintaCreateRequire(import.meta.url);
|
|
2
|
+
var ft=Object.create;var ue=Object.defineProperty;var pt=Object.getOwnPropertyDescriptor;var dt=Object.getOwnPropertyNames;var gt=Object.getPrototypeOf,_t=Object.prototype.hasOwnProperty;var _=(e=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(e,{get:(t,r)=>(typeof require<"u"?require:t)[r]}):e)(function(e){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+e+'" is not supported')});var y=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports);var ht=(e,t,r,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of dt(t))!_t.call(e,o)&&o!==r&&ue(e,o,{get:()=>t[o],enumerable:!(n=pt(t,o))||n.enumerable});return e};var M=(e,t,r)=>(r=e!=null?ft(gt(e)):{},ht(t||!e||!e.__esModule?ue(r,"default",{value:e,enumerable:!0}):r,e));var F=y(p=>{"use strict";Object.defineProperty(p,"__esModule",{value:!0});p.PATTERNS=p.MAX_BYTES=void 0;p.truncateTo=ce;p.truncate=mt;p.collectMatches=le;p.resolveOverlaps=fe;p.applyMatches=pe;p.redact=yt;p.MAX_BYTES=102400;function ce(e,t){let r=Buffer.from(e,"utf-8");return r.length<=t?e:`${r.subarray(0,Math.max(0,t)).toString("utf-8")}\u2026[TRUNCATED:${r.length}]`}function mt(e){return ce(e,p.MAX_BYTES)}p.PATTERNS=[{type:"aws_access_key",regex:/AKIA[0-9A-Z]{16}/g},{type:"aws_secret_key",regex:/(?:aws[_-]?secret(?:[_-]?(?:access)?[_-]?key)?)\s*[:=]\s*["']?([A-Za-z0-9/+=]{40})(?![A-Za-z0-9/+=])/gi,captureGroup:1},{type:"gcp_service_account",regex:/\{[\s\S]{0,200}?"type"\s*:\s*"service_account"[\s\S]*?\}/g},{type:"github_token",regex:/gh[pousr]_[A-Za-z0-9]{36,}/g},{type:"gitlab_token",regex:/glpat-[A-Za-z0-9_-]{20}/g},{type:"slack_token",regex:/xox[abrsp]-[0-9A-Za-z-]{10,}/g},{type:"openai_key",regex:/sk-(?:proj-)?[A-Za-z0-9_-]{40,}/g},{type:"anthropic_key",regex:/sk-ant-[A-Za-z0-9_-]{50,}/g},{type:"stripe_key",regex:/(?:sk|rk|pk)_(?:live|test)_[A-Za-z0-9]{20,}/g},{type:"jwt",regex:/eyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/g},{type:"private_key_block",regex:/-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g},{type:"bearer_token",regex:/bearer\s+([A-Za-z0-9._~+/=-]{12,})/gi,captureGroup:1},{type:"basic_auth",regex:/basic\s+([A-Za-z0-9+/=]{12,})/gi,captureGroup:1},{type:"db_url_password",regex:/\b(?:postgres|postgresql|mysql|mariadb|mongodb(?:\+srv)?|redis):\/\/[^:\s/]+:([^@\s]+)@/gi,captureGroup:1},{type:"cli_password_flag",regex:/(?:--password|--pass|--pwd)[=\s]([^\s'"]+)/g,captureGroup:1},{type:"cli_password_short",regex:/\s-p([^\s'"]+)/g,captureGroup:1,requireContext:"bash"},{type:"env_var_secret",regex:/^(?:export\s+)?([A-Z][A-Z0-9_]*(?:KEY|SECRET|TOKEN|PASSWORD|PASSWD|PWD|API_KEY)[A-Z0-9_]*)\s*=\s*["']?([^\s"'\n]+)/gm,captureGroup:2}];function le(e,t){let r=[];for(let n of p.PATTERNS){if(n.requireContext&&n.requireContext!==t.context)continue;let o=new RegExp(n.regex.source,n.regex.flags),i;for(;(i=o.exec(e))!==null;){let a=n.captureGroup??0,u=i[a];if(u===void 0){i.index===o.lastIndex&&o.lastIndex++;continue}let c=i.index,m=i.index+i[0].length,w=c+i[0].indexOf(u),lt=w+u.length;r.push({start:c,end:m,replaceStart:w,replaceEnd:lt,type:n.type}),i.index===o.lastIndex&&o.lastIndex++}}return r}function fe(e){let t=[...e].sort((o,i)=>o.start!==i.start?o.start-i.start:i.end-i.start-(o.end-o.start)),r=[],n=-1;for(let o of t)o.start<n||(r.push(o),n=o.end);return r}function pe(e,t){let r=[...t].sort((o,i)=>i.replaceStart-o.replaceStart),n=e;for(let o of r)n=n.slice(0,o.replaceStart)+`[REDACTED:${o.type}]`+n.slice(o.replaceEnd);return n}function yt(e,t={}){if(e.length===0)return e;let r=le(e,t);if(r.length===0)return e;let n=fe(r);return pe(e,n)}});var G=y(f=>{"use strict";var Et=f&&f.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(f,"__esModule",{value:!0});f.MAX_SPAN_BYTES=void 0;f.ulidToTraceId=he;f.newSpanId=me;f.snakeCase=St;f.toOtlpValue=ye;f.attrsFromRecord=Tt;f.guardAttrs=Ee;f.clientAttrs=Pe;f.enforceSpanBudget=ve;f.buildPayload=bt;f.mergeBatch=Ot;var Pt=Et(_("crypto")),V=F(),vt="0123456789ABCDEFGHJKMNPQRSTVWXYZ";function he(e){if(e.length!==26)throw new Error(`ulidToTraceId: expected 26 chars, got ${e.length}`);let t=0n;for(let n of e){let o=vt.indexOf(n);if(o<0)throw new Error(`ulidToTraceId: invalid Crockford char "${n}"`);t=t<<5n|BigInt(o)}let r=(1n<<128n)-1n;return t&=r,t.toString(16).padStart(32,"0")}function me(){return Pt.default.randomBytes(8).toString("hex")}function St(e){return e.replace(/([a-z0-9])([A-Z])/g,"$1_$2").replace(/([A-Z])([A-Z][a-z])/g,"$1_$2").toLowerCase()}var de=new Set;function j(e,t,r){let n=(0,V.truncate)(t);if((r.skipRedactKeys??de).has(e))return n;let o=(r.bashContextKeys??de).has(e)?"bash":void 0;return(0,V.redact)(n,{context:o})}function ye(e,t,r={}){if(t==null)return null;switch(typeof t){case"string":return{stringValue:j(e,t,r)};case"boolean":return{boolValue:t};case"number":return Number.isInteger(t)?{intValue:t}:{doubleValue:t};case"object":try{return{stringValue:j(e,JSON.stringify(t),r)}}catch{return{stringValue:j(e,String(t),r)}}default:return{stringValue:j(e,String(t),r)}}}function Tt(e,t,r={}){let n=[];for(let[o,i]of Object.entries(e)){let a=`${t}.${o}`,u=ye(a,i,r);u!==null&&n.push({key:a,value:u})}return n}function Ee(e){let t=[{key:"pinta.guard.decision",value:{stringValue:e.decision.toLowerCase()}},{key:"pinta.guard.duration_ms",value:{intValue:e.durationMs}}];return e.reason&&t.push({key:"pinta.guard.matched_rule",value:{stringValue:e.reason}}),e.failOpenReason&&t.push({key:"pinta.guard.fail_open_reason",value:{stringValue:e.failOpenReason}}),t}function Pe(e){return[{key:"pinta.client.rtt_ms",value:Number.isInteger(e.rttMs)?{intValue:e.rttMs}:{doubleValue:e.rttMs}},{key:"pinta.client.op",value:{stringValue:e.op}}]}f.MAX_SPAN_BYTES=800*1024;function ge(e){let t=JSON.stringify(e);return t===void 0?0:Buffer.byteLength(t,"utf-8")}var _e=1024;function ve(e,t=f.MAX_SPAN_BYTES){let r=ge(e);for(let n=0;n<100&&r>t;n++){let o=null,i=0;for(let c of e.attributes){if(!("stringValue"in c.value))continue;let m=Buffer.byteLength(c.value.stringValue,"utf-8");m>i&&(i=m,o=c.value)}if(o===null||i<=_e)break;let a=r-t,u=Math.max(_e,i-a-64);o.stringValue=(0,V.truncateTo)(o.stringValue,u),r=ge(e)}return e}function bt(e){let t=e.now??Date.now(),r=(BigInt(t)*1000000n).toString(),n=[...e.attributes];e.guard&&n.push(...Ee(e.guard));let o=e.clientTiming??(e.guard?.clientRttMs!==void 0?{op:"guard",rttMs:e.guard.clientRttMs}:null);o&&n.push(...Pe(o));let i={traceId:he(e.traceId),spanId:me(),name:e.spanName,kind:e.spanKind??1,startTimeUnixNano:r,endTimeUnixNano:r,attributes:n};return ve(i),{resourceSpans:[{resource:{attributes:e.resource},scopeSpans:[{scope:e.scope,spans:[i]}]}]}}function Ot(e){let t=[];for(let r of e)t.push(...r.resourceSpans);return{resourceSpans:t}}});var Se=y(Y=>{"use strict";Object.defineProperty(Y,"__esModule",{value:!0});Y.evaluateGuard=Mt;var kt=1e4,At="pinta-core";function U(e,t){return{decision:"ALLOW",reason:null,userMessage:null,durationMs:e,clientRttMs:e,failOpenReason:t}}var wt=new Set(["ALLOW","DENY","REVIEW"]);async function Mt(e,t,r={}){if(!t||r.disabled)return null;let n=r.timeoutMs??kt,o=Date.now(),i=new AbortController,a=setTimeout(()=>i.abort(),n);try{let u=await fetch(t,{method:"POST",headers:{"content-type":"application/json","user-agent":r.userAgent??At,"x-pinta-relay-token":r.token??""},body:JSON.stringify({input:e}),signal:i.signal});if(u.status!==200)return U(Date.now()-o,"error");let c=await u.json(),m=c?.durationMs===void 0||typeof c.durationMs=="number"&&Number.isFinite(c.durationMs);if(!c||!wt.has(c.decision)||!m)return U(Date.now()-o,"error");let w=Date.now()-o;return{decision:c.decision,reason:c.reason??null,userMessage:c.userMessage??null,durationMs:c.durationMs??w,clientRttMs:w}}catch(u){let c=u.name,m=c==="AbortError"||c==="TimeoutError"?"timeout":"error";return U(Date.now()-o,m)}finally{clearTimeout(a)}}});var D=y(x=>{"use strict";var xt=x&&x.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(x,"__esModule",{value:!0});x.atomicWriteFileSync=Rt;var Te=xt(_("fs"));function Rt(e,t){let r=`${e}.${process.pid}.tmp`;Te.default.writeFileSync(r,t),Te.default.renameSync(r,e)}});var K=y(E=>{"use strict";var be=E&&E.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(E,"__esModule",{value:!0});E.MemoryRetryQueue=E.DiskRetryQueue=void 0;var d=be(_("fs")),N=be(_("path")),jt=D(),I=1e3,Dt=50,Nt=5,X=class{filePath;lockPath;logPrefix;constructor(t,r){this.filePath=N.default.join(t,"failed-spans.jsonl"),this.lockPath=this.filePath+".lock",this.logPrefix=r}enqueue(t){try{d.default.mkdirSync(N.default.dirname(this.filePath),{recursive:!0});let r=JSON.stringify({savedAt:new Date().toISOString(),payload:t})+`
|
|
3
|
+
`;d.default.appendFileSync(this.filePath,r),this.trim()}catch(r){process.stderr.write(`[${this.logPrefix}] retry-queue enqueue failed: ${r}
|
|
4
|
+
`)}}readAll(){try{let t=d.default.readFileSync(this.filePath,"utf-8"),r=[];for(let n of t.split(`
|
|
5
|
+
`))if(n.trim())try{let o=JSON.parse(n);Array.isArray(o?.payload?.resourceSpans)&&r.push(o)}catch{}return r}catch{return[]}}rewrite(t){try{if(t.length===0){d.default.existsSync(this.filePath)&&d.default.unlinkSync(this.filePath);return}d.default.mkdirSync(N.default.dirname(this.filePath),{recursive:!0}),(0,jt.atomicWriteFileSync)(this.filePath,t.map(r=>JSON.stringify(r)).join(`
|
|
6
|
+
`)+`
|
|
7
|
+
`)}catch(r){process.stderr.write(`[${this.logPrefix}] retry-queue rewrite failed: ${r}
|
|
8
|
+
`)}}tryAcquireLock(){let t=Date.now();try{d.default.mkdirSync(N.default.dirname(this.lockPath),{recursive:!0})}catch(r){return process.stderr.write(`[${this.logPrefix}] retry-queue lock mkdir failed: ${r}
|
|
9
|
+
`),!1}for(;Date.now()-t<Dt;)try{let r=d.default.openSync(this.lockPath,"wx");return d.default.writeSync(r,String(process.pid)),d.default.closeSync(r),!0}catch(r){if(r?.code!=="EEXIST")return process.stderr.write(`[${this.logPrefix}] retry-queue lock open failed: ${r}
|
|
10
|
+
`),!1;try{let i=d.default.statSync(this.lockPath);if(Date.now()-i.mtimeMs>3e4){d.default.unlinkSync(this.lockPath);continue}}catch{}let n=Nt,o=Date.now()+n;for(;Date.now()<o;);}return!1}release(){try{d.default.unlinkSync(this.lockPath)}catch{}}trim(){let t=this.readAll();if(t.length<=I)return;let r=t.length-I;process.stderr.write(`[${this.logPrefix}] retry-queue full, dropping ${r} oldest entries
|
|
11
|
+
`),this.rewrite(t.slice(r))}};E.DiskRetryQueue=X;var z=class{entries=[];enqueue(t){this.entries.push(t),this.entries.length>I&&this.entries.splice(0,this.entries.length-I)}drain(){let t=this.entries;return this.entries=[],t}get size(){return this.entries.length}};E.MemoryRetryQueue=z});var xe=y(l=>{"use strict";Object.defineProperty(l,"__esModule",{value:!0});l.MemoryTransport=l.DiskTransport=l.MAX_POST_BYTES=void 0;l.parseHeadersEnv=we;l.envOptionsResolver=Me;l.postOtlp=R;var Oe=K(),ke=G(),It=5e3;l.MAX_POST_BYTES=900*1024;function C(e){let t=JSON.stringify(e);return t===void 0?0:Buffer.byteLength(t,"utf-8")}function Ae(e,t,r){let n=[],o=[],i=[],a=0;for(let u of e){let c=t(u);if(c>r){o.push(u);continue}i.length>0&&a+c>r&&(n.push(i),i=[],a=0),i.push(u),a+=c}return i.length>0&&n.push(i),{chunks:n,oversize:o}}function we(e){if(!e)return{};if(typeof e=="object")return{...e};let t={};for(let r of e.split(",")){let[n,...o]=r.split("=");n&&o.length>0&&(t[n.trim()]=o.join("=").trim())}return t}function Me(){let e=process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT,t=process.env.OTEL_EXPORTER_OTLP_ENDPOINT,r;return e?r=e.replace(/\/+$/,""):t&&(r=t.replace(/\/+$/,"")+"/v1/traces"),r?{endpoint:r,headers:we(process.env.OTEL_EXPORTER_OTLP_HEADERS)}:null}async function R(e,t,r){let n=new AbortController,o=setTimeout(()=>n.abort(),It);try{let i=await fetch(t.endpoint,{method:"POST",headers:{"Content-Type":"application/json",...t.headers},body:JSON.stringify(e),signal:n.signal});if(!i.ok){let a="";try{a=(await i.text()).slice(0,200)}catch{}let u=i.status===401||i.status===403?" \u2014 check OTEL_EXPORTER_OTLP_HEADERS (relay token)":i.status===404?" \u2014 check OTEL_EXPORTER_OTLP_TRACES_ENDPOINT path":i.status>=500?" \u2014 collector may be down":"";return process.stderr.write(`[${r}] OTLP POST ${i.status} ${t.endpoint}${u}${a?` body=${a}`:""}
|
|
12
|
+
`),!1}return!0}catch(i){return process.stderr.write(`[${r}] OTLP POST failed: ${i.message??String(i)}
|
|
13
|
+
`),!1}finally{clearTimeout(o)}}var Z=class{queue;logPrefix;resolveOptions;constructor(t){this.queue=new Oe.DiskRetryQueue(t.pluginData,t.logPrefix),this.logPrefix=t.logPrefix,this.resolveOptions=t.resolveOptions??Me}async send(t){let r=this.resolveOptions();if(!r||this.dropIfOversized(t))return;await R(t,r,this.logPrefix)||this.queue.enqueue(t)}async flush(){let t=this.resolveOptions();if(t&&this.queue.tryAcquireLock())try{let r=this.queue.readAll();if(r.length===0)return;let{chunks:n,oversize:o}=Ae(r,u=>C(u.payload),l.MAX_POST_BYTES);o.length>0&&process.stderr.write(`[${this.logPrefix}] dropping ${o.length} oversized retry ${o.length===1?"entry":"entries"} (each > ${l.MAX_POST_BYTES} bytes \u2014 the backend caps the request body, so they can never be delivered)
|
|
14
|
+
`);let i=[].concat(...n),a=0;for(let u of n){if(!await R((0,ke.mergeBatch)(u.map(m=>m.payload)),t,this.logPrefix))break;a+=u.length}this.queue.rewrite(i.slice(a))}catch(r){process.stderr.write(`[${this.logPrefix}] flush failed: ${r?.message??String(r)}
|
|
15
|
+
`)}finally{this.queue.release()}}dropIfOversized(t){let r=C(t);return r<=l.MAX_POST_BYTES?!1:(process.stderr.write(`[${this.logPrefix}] dropping oversized span payload (${r} > ${l.MAX_POST_BYTES} bytes) \u2014 undeliverable, not queued
|
|
16
|
+
`),!0)}};l.DiskTransport=Z;var W=class{queue=new Oe.MemoryRetryQueue;logPrefix;resolveOptions;constructor(t){this.logPrefix=t.logPrefix,this.resolveOptions=t.resolveOptions}async send(t){let r=this.resolveOptions();if(!r)return;if(C(t)>l.MAX_POST_BYTES){process.stderr.write(`[${this.logPrefix}] dropping oversized span payload (> ${l.MAX_POST_BYTES} bytes) \u2014 undeliverable, not buffered
|
|
17
|
+
`);return}await R(t,r,this.logPrefix)||this.queue.enqueue(t)}async flush(){let t=this.resolveOptions();if(!t)return;let r=this.queue.drain();if(r.length===0)return;let{chunks:n,oversize:o}=Ae(r,C,l.MAX_POST_BYTES);o.length>0&&process.stderr.write(`[${this.logPrefix}] dropping ${o.length} oversized buffered ${o.length===1?"payload":"payloads"} (each > ${l.MAX_POST_BYTES} bytes \u2014 undeliverable)
|
|
18
|
+
`);let i=[].concat(...n),a=0;for(let u of n){if(!await R((0,ke.mergeBatch)(u),t,this.logPrefix))break;a+=u.length}for(let u of i.slice(a))this.queue.enqueue(u)}};l.MemoryTransport=W});var ee=y(S=>{"use strict";var Q=S&&S.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(S,"__esModule",{value:!0});S.TraceManager=void 0;S.generateUlid=je;var Re=Q(_("fs")),Ct=Q(_("path")),Bt=Q(_("crypto")),Lt=D(),J="0123456789ABCDEFGHJKMNPQRSTVWXYZ";function je(){let e=Date.now(),t="",r=e;for(let i=0;i<10;i++)t=J[r&31]+t,r=Math.floor(r/32);let n=Bt.default.randomBytes(10),o="";for(let i=0;i<10;i++)o+=J[n[i]&31];for(;o.length<16;)o+=J[0];return t+o}var H=class{tracePath;constructor(t){this.tracePath=t}newTrace(){let t=je();return this.save(t),t}currentTrace(){try{let t=Re.default.readFileSync(this.tracePath,"utf-8"),{traceId:r}=JSON.parse(t);if(r)return r}catch{}return this.newTrace()}save(t){try{Re.default.mkdirSync(Ct.default.dirname(this.tracePath),{recursive:!0}),(0,Lt.atomicWriteFileSync)(this.tracePath,JSON.stringify({traceId:t}))}catch(r){process.stderr.write(`[pinta-core] trace persistence failed: ${r}
|
|
19
|
+
`)}}};S.TraceManager=H});var Be=y(P=>{"use strict";var Ne=P&&P.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(P,"__esModule",{value:!0});P.MemorySessionTraceManager=P.DiskSessionTraceManager=void 0;var De=Ne(_("fs")),$t=Ne(_("path")),Ie=ee(),qt=D(),Ce=200,te=class{tracePath;maxSessions;constructor(t,r={}){this.tracePath=t,this.maxSessions=r.maxSessions??Ce}read(){try{let t=JSON.parse(De.default.readFileSync(this.tracePath,"utf-8"));if(t&&typeof t=="object"&&!("traceId"in t))return t}catch{}return{}}write(t){try{De.default.mkdirSync($t.default.dirname(this.tracePath),{recursive:!0});let r=Object.entries(t),n=r.length>this.maxSessions?Object.fromEntries(r.slice(-this.maxSessions)):t;(0,qt.atomicWriteFileSync)(this.tracePath,JSON.stringify(n))}catch{}}newTrace(t){let r=t||"default",n=(0,Ie.generateUlid)(),o=this.read();return o[r]=n,this.write(o),n}currentTrace(t){let r=t||"default",n=this.read()[r];return n||this.newTrace(r)}};P.DiskSessionTraceManager=te;var re=class{map=new Map;maxSessions;constructor(t={}){this.maxSessions=t.maxSessions??Ce}newTrace(t){let r=t||"default",n=(0,Ie.generateUlid)();if(this.map.set(r,n),this.map.size>this.maxSessions){let o=this.map.keys().next().value;o!==void 0&&this.map.delete(o)}return n}currentTrace(t){let r=t||"default";return this.map.get(r)??this.newTrace(r)}};P.MemorySessionTraceManager=re});var qe=y(T=>{"use strict";var ne=T&&T.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(T,"__esModule",{value:!0});T.envFilePath=Gt;T.parseEnvFile=$e;T.loadEnvFile=Ut;var Ft=ne(_("node:fs")),Vt=ne(_("node:os")),Le=ne(_("node:path"));function Gt(e,t,r){let n=r?process.env[r]:void 0,o=n&&n.length>0?n:Le.default.join(Vt.default.homedir(),e);return Le.default.join(o,t)}function $e(e){let t={};for(let r of e.split(`
|
|
20
|
+
`)){let n=r.trim();if(!n||n.startsWith("#"))continue;let o=n.indexOf("=");if(o<0)continue;let i=n.slice(0,o).trim(),a=n.slice(o+1).trim();(a.startsWith('"')&&a.endsWith('"')||a.startsWith("'")&&a.endsWith("'"))&&(a=a.slice(1,-1)),i&&(t[i]=a)}return t}function Ut(e){let t;try{t=Ft.default.readFileSync(e,"utf-8")}catch{return}let r=$e(t);for(let[n,o]of Object.entries(r))process.env[n]===void 0&&(process.env[n]=o)}});var O=y(s=>{"use strict";Object.defineProperty(s,"__esModule",{value:!0});s.loadEnvFile=s.parseEnvFile=s.envFilePath=s.MemorySessionTraceManager=s.DiskSessionTraceManager=s.generateUlid=s.TraceManager=s.MAX_POST_BYTES=s.parseHeadersEnv=s.envOptionsResolver=s.postOtlp=s.MemoryTransport=s.DiskTransport=s.MemoryRetryQueue=s.DiskRetryQueue=s.evaluateGuard=s.MAX_SPAN_BYTES=s.enforceSpanBudget=s.mergeBatch=s.buildPayload=s.clientAttrs=s.guardAttrs=s.attrsFromRecord=s.toOtlpValue=s.snakeCase=s.newSpanId=s.ulidToTraceId=s.MAX_BYTES=s.PATTERNS=s.applyMatches=s.resolveOverlaps=s.collectMatches=s.truncateTo=s.truncate=s.redact=void 0;var v=F();Object.defineProperty(s,"redact",{enumerable:!0,get:function(){return v.redact}});Object.defineProperty(s,"truncate",{enumerable:!0,get:function(){return v.truncate}});Object.defineProperty(s,"truncateTo",{enumerable:!0,get:function(){return v.truncateTo}});Object.defineProperty(s,"collectMatches",{enumerable:!0,get:function(){return v.collectMatches}});Object.defineProperty(s,"resolveOverlaps",{enumerable:!0,get:function(){return v.resolveOverlaps}});Object.defineProperty(s,"applyMatches",{enumerable:!0,get:function(){return v.applyMatches}});Object.defineProperty(s,"PATTERNS",{enumerable:!0,get:function(){return v.PATTERNS}});Object.defineProperty(s,"MAX_BYTES",{enumerable:!0,get:function(){return v.MAX_BYTES}});var h=G();Object.defineProperty(s,"ulidToTraceId",{enumerable:!0,get:function(){return h.ulidToTraceId}});Object.defineProperty(s,"newSpanId",{enumerable:!0,get:function(){return h.newSpanId}});Object.defineProperty(s,"snakeCase",{enumerable:!0,get:function(){return h.snakeCase}});Object.defineProperty(s,"toOtlpValue",{enumerable:!0,get:function(){return h.toOtlpValue}});Object.defineProperty(s,"attrsFromRecord",{enumerable:!0,get:function(){return h.attrsFromRecord}});Object.defineProperty(s,"guardAttrs",{enumerable:!0,get:function(){return h.guardAttrs}});Object.defineProperty(s,"clientAttrs",{enumerable:!0,get:function(){return h.clientAttrs}});Object.defineProperty(s,"buildPayload",{enumerable:!0,get:function(){return h.buildPayload}});Object.defineProperty(s,"mergeBatch",{enumerable:!0,get:function(){return h.mergeBatch}});Object.defineProperty(s,"enforceSpanBudget",{enumerable:!0,get:function(){return h.enforceSpanBudget}});Object.defineProperty(s,"MAX_SPAN_BYTES",{enumerable:!0,get:function(){return h.MAX_SPAN_BYTES}});var Yt=Se();Object.defineProperty(s,"evaluateGuard",{enumerable:!0,get:function(){return Yt.evaluateGuard}});var Fe=K();Object.defineProperty(s,"DiskRetryQueue",{enumerable:!0,get:function(){return Fe.DiskRetryQueue}});Object.defineProperty(s,"MemoryRetryQueue",{enumerable:!0,get:function(){return Fe.MemoryRetryQueue}});var b=xe();Object.defineProperty(s,"DiskTransport",{enumerable:!0,get:function(){return b.DiskTransport}});Object.defineProperty(s,"MemoryTransport",{enumerable:!0,get:function(){return b.MemoryTransport}});Object.defineProperty(s,"postOtlp",{enumerable:!0,get:function(){return b.postOtlp}});Object.defineProperty(s,"envOptionsResolver",{enumerable:!0,get:function(){return b.envOptionsResolver}});Object.defineProperty(s,"parseHeadersEnv",{enumerable:!0,get:function(){return b.parseHeadersEnv}});Object.defineProperty(s,"MAX_POST_BYTES",{enumerable:!0,get:function(){return b.MAX_POST_BYTES}});var Ve=ee();Object.defineProperty(s,"TraceManager",{enumerable:!0,get:function(){return Ve.TraceManager}});Object.defineProperty(s,"generateUlid",{enumerable:!0,get:function(){return Ve.generateUlid}});var Ge=Be();Object.defineProperty(s,"DiskSessionTraceManager",{enumerable:!0,get:function(){return Ge.DiskSessionTraceManager}});Object.defineProperty(s,"MemorySessionTraceManager",{enumerable:!0,get:function(){return Ge.MemorySessionTraceManager}});var oe=qe();Object.defineProperty(s,"envFilePath",{enumerable:!0,get:function(){return oe.envFilePath}});Object.defineProperty(s,"parseEnvFile",{enumerable:!0,get:function(){return oe.parseEnvFile}});Object.defineProperty(s,"loadEnvFile",{enumerable:!0,get:function(){return oe.loadEnvFile}})});import{realpathSync as Er}from"node:fs";import{pathToFileURL as Pr}from"node:url";var ie=M(O());import Xt from"node:os";import se from"node:path";var zt="pinta-musecode.env";function Kt(){let e=process.env.XDG_CONFIG_HOME;return e&&e.length>0?se.join(e,"muse"):se.join(Xt.homedir(),".config","muse")}function Zt(){return se.join(Kt(),zt)}function Ue(e=Zt()){(0,ie.loadEnvFile)(e)}import Wt from"node:os";import Ye from"node:path";function Jt(){return process.env.PINTA_MUSECODE_DATA||process.env.PINTA_PLUGIN_DATA||Ye.join(Wt.homedir(),".pinta","adaptors","pinta-musecode")}function Xe(){let e=Jt();return{pluginData:e,tracePath:Ye.join(e,"trace.json")}}function ze(){return process.env.PINTA_MUSE_ENFORCE==="1"}function Ke(){return process.env.PINTA_MUSE_LLM_EVENTS==="1"}var Ht=["SessionStart","UserPromptSubmit","PreToolUse","PermissionRequest","PostToolUse","PreLLMCall","PostLLMCall","PreCompact","PostCompact","SubagentStart","SubagentStop","Stop"],Qt=new Set(Ht);function er(e){return Qt.has(e)}var tr=["hook_event_name","event","hook","hook_event","eventName"];function Ze(e,t){for(let r of e){let n=r.startsWith("--event=")?r.slice(8):r;if(er(n))return n}for(let r of tr){let n=t[r];if(typeof n=="string"&&n.length>0)return n}return null}function B(e){return e==="PreToolUse"||e==="PermissionRequest"}function We(e){return e==="PreLLMCall"||e==="PostLLMCall"}var rr=new Set(["subagent_spawn","subagent_status","subagent_send_message","subagent_cancel","subagent_wait","subagent_read_result"]);function Je(e){return e!==void 0&&rr.has(e)}var He=M(O()),nr=1e4,or="pinta-musecode/0.1.0";function Qe(e,t){return(0,He.evaluateGuard)(e,t,{timeoutMs:nr,token:process.env.PINTA_RELAY_TOKEN??"",disabled:process.env.PINTA_GUARD_DISABLED==="1",userAgent:or})}function sr(){let e=process.env.PINTA_MUSE_DENY_FORMAT;return e==="exit2"||e==="json"?e:"auto"}function ir(e,t){return B(e)?{hookSpecificOutput:{hookEventName:e,permissionDecision:"deny",permissionDecisionReason:t}}:{decision:"block",reason:t}}function et(e,t,r={}){let n=r.stdout??(a=>process.stdout.write(a)),o=r.stderr??(a=>process.stderr.write(a)),i=sr();return i==="exit2"?(o(`${t}
|
|
21
|
+
`),{written:!0,exitCode:2}):(n(JSON.stringify(ir(e,t))+`
|
|
22
|
+
`),i==="json"?{written:!0,exitCode:0}:(o(`${t}
|
|
23
|
+
`),{written:!0,exitCode:2}))}var tt=M(O()),L=class extends tt.DiskTransport{constructor(t){super({pluginData:t.pluginData,logPrefix:"pinta-musecode"})}};var rt=M(O()),$=class extends rt.TraceManager{constructor(t){super(t.tracePath)}};var k=M(O());import ae from"node:os";var nt="0.1.0",ar="musecode",g="muse",q;function ur(){if(q===void 0)try{q=ae.userInfo().username}catch{q=process.env.USER??process.env.LOGNAME??(typeof process.getuid=="function"?String(process.getuid()):"unknown")}return q}function cr(e){for(let t of["muse_version","version","cli_version"]){let r=e[t];if(typeof r=="string"&&r.length>0)return r}return process.env.MUSE_VERSION??"unknown"}var lr=new Set([`${g}.hook`,`${g}.tool_name`,`${g}.tool_use_id`,`${g}.session_id`,`${g}.cwd`,`${g}.permission_mode`,`${g}.agent_id`,`${g}.agent_type`]),fr=new Set([`${g}.tool_input`,`${g}.tool_response`]),pr={skipRedactKeys:lr,bashContextKeys:fr};function dr(e){let t=[];t.push({key:"ingest.type",value:{stringValue:ar}}),t.push({key:`${g}.hook`,value:{stringValue:e.hook_event_name}});let r={};for(let[n,o]of Object.entries(e))n!=="hook_event_name"&&(r[n]=o);return t.push(...(0,k.attrsFromRecord)(r,g,pr)),t}function gr(e){return[{key:"service.name",value:{stringValue:"muse-code"}},{key:"service.version",value:{stringValue:cr(e)}},{key:"telemetry.sdk.name",value:{stringValue:"pinta-musecode"}},{key:"telemetry.sdk.language",value:{stringValue:"nodejs"}},{key:"telemetry.sdk.version",value:{stringValue:nt}},{key:"process.pid",value:{intValue:process.pid}},{key:"process.owner",value:{stringValue:ur()}},{key:"host.name",value:{stringValue:ae.hostname()}},{key:"host.arch",value:{stringValue:ae.arch()}}]}function ot(e){return(0,k.buildPayload)({traceId:e.traceId,spanName:`${g}.${(0,k.snakeCase)(e.event.hook_event_name)}`,attributes:dr(e.event),resource:gr(e.event),scope:{name:"pinta-musecode",version:nt},now:e.now,guard:e.guard})}async function _r(e,t,r={}){let n=new L(t);await n.flush();let o=new $(t),i=r.traceMode==="new"?o.newTrace():o.currentTrace(),a=ot({event:e,traceId:i,guard:r.guard});await n.send(a)}async function A(e,t,r={}){try{await _r(e,t,r)}catch(n){process.stderr.write(`[pinta-musecode] telemetry emit failed: ${n}
|
|
24
|
+
`)}}async function st(e,t){if(Je(e.tool_name))return await A(e,t),0;let r=typeof e.tool_input=="string"?e.tool_input:JSON.stringify(e.tool_input),n=await Qe({spanId:e.session_id??"unknown",toolName:e.tool_name,toolInput:e.tool_input,rawTextFields:{toolInput:r??""}},process.env.PINTA_GUARD_ENDPOINT),o=0;if(n?.decision==="DENY"&&ze()){let i=n.userMessage??n.reason??"guard_deny";o=et(e.hook_event_name,i).exitCode}return await A(e,t,{guard:n}),o}async function it(e,t){return await A(e,t),0}async function at(e,t){return await A(e,t,{traceMode:"new"}),0}async function ut(e){return 0}async function hr(){let e=[];for await(let t of process.stdin)e.push(t);return Buffer.concat(e).toString("utf-8")}function mr(e){let t=e.trim();if(t.length===0)return{};try{let r=JSON.parse(t);if(r!==null&&typeof r=="object"&&!Array.isArray(r))return r}catch{}return{}}async function yr(e,t){let r=e.hook_event_name;return B(r)?st(e,t):r==="UserPromptSubmit"?at(e,t):We(r)&&!Ke()?ut(e):it(e,t)}async function ct(e=process.argv.slice(2)){let t=0;try{let r=Xe(),n=mr(await hr()),o=Ze(e,n);if(o===null)return process.stderr.write(`[pinta-musecode] could not resolve hook event name; skipping
|
|
25
|
+
`),0;let i={...n,hook_event_name:o};t=await yr(i,r)}catch(r){process.stderr.write(`[pinta-musecode] error: ${r}
|
|
26
|
+
`),t=0}return t}Ue();function vr(){let e=process.argv[1];if(!e)return!1;try{return import.meta.url===Pr(Er(e)).href}catch{return!1}}async function Sr(){let e=await ct();process.exit(e)}vr()&&Sr();
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_note": "TEMPLATE — a fixture for `muse hooks run <key> --fixture ./fixture.json`. The two top-level keys are the only part confirmed by the official docs: a required `event` matching the hook's event, and a required `stdin` object holding the payload the hook receives. The CONTENTS of `stdin` below are a guess and are exactly what the stage-0 spike must replace with a real capture.",
|
|
3
|
+
"event": "PreToolUse",
|
|
4
|
+
"stdin": {
|
|
5
|
+
"session_id": "00000000-0000-0000-0000-000000000000",
|
|
6
|
+
"cwd": "/path/to/workspace",
|
|
7
|
+
"tool_name": "bash",
|
|
8
|
+
"tool_use_id": "tool-1",
|
|
9
|
+
"tool_input": { "command": "rm -rf /tmp/example" }
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_note": "Managed hooks file. The path is set by the `managed_hooks_path` key in Muse Code's settings.json ($XDG_CONFIG_HOME/muse/settings.json, else ~/.config/muse/settings.json). Managed hooks are pre-approved and need no `trust` step. Verified against muse 0.1.0-R708.1.",
|
|
3
|
+
"_schema": "Two nesting levels are mandatory, measured one at a time against muse 0.1.0-R708.1: the `hooks` wrapper, and the matcher-GROUP level between the event name and the handlers. Hoisting event names to the top level, or listing handlers directly under an event name, makes the file ignored SILENTLY. `schema_version` and `matcher` are both optional; they are kept here because they cost nothing and document intent.",
|
|
4
|
+
"_strict": "Unknown keys are tolerated at the TOP level (hence these underscore notes) but are FATAL inside a matcher group or a handler — and fatal means the whole event is skipped with no error, no exit code and no log. Accepted handler keys: type, command, commandWindows, timeout, statusMessage, silent, async. REJECTED: env, shell (and `enabled` on a matcher group). Do not add a key here without probing it first; a typo silently disables monitoring for that event.",
|
|
5
|
+
"_env": "The rejection of the handler-level `env` key is why the adaptor is configured through an env FILE. It is not a stylistic preference — there is no in-file way to pass a variable to a hook, and Muse Code filters the inherited environment down to a fixed allowlist that no PINTA_* or OTEL_* name survives.",
|
|
6
|
+
"_timeout": "`timeout` is in SECONDS. Measured: a hook sleeping 3s under `timeout: 1` was killed at ~1s wall. Left unset here so the host default applies; pinta-manager may set it per deployment.",
|
|
7
|
+
"_placeholder": "${PINTA_MUSECODE_ROOT} is substituted by pinta-manager at enroll time with the adaptor install directory. PATH is inherited in full by hook commands, so a bare `node` resolves; pinta-manager still writes an absolute interpreter path so the hook cannot break when the user's PATH changes.",
|
|
8
|
+
"_omitted": "PreLLMCall and PostLLMCall are intentionally absent. Both carry the FULL `messages` array and tool schemas, so enabling them ships conversation content to the collector — opt in with PINTA_MUSE_LLM_EVENTS=1 only after a redaction review.",
|
|
9
|
+
"schema_version": 1,
|
|
10
|
+
"hooks": {
|
|
11
|
+
"SessionStart": [
|
|
12
|
+
{
|
|
13
|
+
"matcher": "*",
|
|
14
|
+
"hooks": [
|
|
15
|
+
{ "type": "command", "command": "node ${PINTA_MUSECODE_ROOT}/dist/index.js SessionStart" }
|
|
16
|
+
]
|
|
17
|
+
}
|
|
18
|
+
],
|
|
19
|
+
"UserPromptSubmit": [
|
|
20
|
+
{
|
|
21
|
+
"matcher": "*",
|
|
22
|
+
"hooks": [
|
|
23
|
+
{
|
|
24
|
+
"type": "command",
|
|
25
|
+
"command": "node ${PINTA_MUSECODE_ROOT}/dist/index.js UserPromptSubmit"
|
|
26
|
+
}
|
|
27
|
+
]
|
|
28
|
+
}
|
|
29
|
+
],
|
|
30
|
+
"PreToolUse": [
|
|
31
|
+
{
|
|
32
|
+
"matcher": "*",
|
|
33
|
+
"hooks": [
|
|
34
|
+
{ "type": "command", "command": "node ${PINTA_MUSECODE_ROOT}/dist/index.js PreToolUse" }
|
|
35
|
+
]
|
|
36
|
+
}
|
|
37
|
+
],
|
|
38
|
+
"PermissionRequest": [
|
|
39
|
+
{
|
|
40
|
+
"matcher": "*",
|
|
41
|
+
"hooks": [
|
|
42
|
+
{
|
|
43
|
+
"type": "command",
|
|
44
|
+
"command": "node ${PINTA_MUSECODE_ROOT}/dist/index.js PermissionRequest"
|
|
45
|
+
}
|
|
46
|
+
]
|
|
47
|
+
}
|
|
48
|
+
],
|
|
49
|
+
"PostToolUse": [
|
|
50
|
+
{
|
|
51
|
+
"matcher": "*",
|
|
52
|
+
"hooks": [
|
|
53
|
+
{ "type": "command", "command": "node ${PINTA_MUSECODE_ROOT}/dist/index.js PostToolUse" }
|
|
54
|
+
]
|
|
55
|
+
}
|
|
56
|
+
],
|
|
57
|
+
"PreCompact": [
|
|
58
|
+
{
|
|
59
|
+
"matcher": "*",
|
|
60
|
+
"hooks": [
|
|
61
|
+
{ "type": "command", "command": "node ${PINTA_MUSECODE_ROOT}/dist/index.js PreCompact" }
|
|
62
|
+
]
|
|
63
|
+
}
|
|
64
|
+
],
|
|
65
|
+
"PostCompact": [
|
|
66
|
+
{
|
|
67
|
+
"matcher": "*",
|
|
68
|
+
"hooks": [
|
|
69
|
+
{ "type": "command", "command": "node ${PINTA_MUSECODE_ROOT}/dist/index.js PostCompact" }
|
|
70
|
+
]
|
|
71
|
+
}
|
|
72
|
+
],
|
|
73
|
+
"SubagentStart": [
|
|
74
|
+
{
|
|
75
|
+
"matcher": "*",
|
|
76
|
+
"hooks": [
|
|
77
|
+
{
|
|
78
|
+
"type": "command",
|
|
79
|
+
"command": "node ${PINTA_MUSECODE_ROOT}/dist/index.js SubagentStart"
|
|
80
|
+
}
|
|
81
|
+
]
|
|
82
|
+
}
|
|
83
|
+
],
|
|
84
|
+
"SubagentStop": [
|
|
85
|
+
{
|
|
86
|
+
"matcher": "*",
|
|
87
|
+
"hooks": [
|
|
88
|
+
{ "type": "command", "command": "node ${PINTA_MUSECODE_ROOT}/dist/index.js SubagentStop" }
|
|
89
|
+
]
|
|
90
|
+
}
|
|
91
|
+
],
|
|
92
|
+
"Stop": [
|
|
93
|
+
{
|
|
94
|
+
"matcher": "*",
|
|
95
|
+
"hooks": [
|
|
96
|
+
{ "type": "command", "command": "node ${PINTA_MUSECODE_ROOT}/dist/index.js Stop" }
|
|
97
|
+
]
|
|
98
|
+
}
|
|
99
|
+
]
|
|
100
|
+
}
|
|
101
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pinta-ai/pinta-musecode",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Generic OTLP forwarder for Meta Muse Code hook events",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"import": "./dist/index.mjs",
|
|
9
|
+
"require": "./dist/index.js"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"license": "PolyForm-Noncommercial-1.0.0",
|
|
13
|
+
"files": [
|
|
14
|
+
"dist",
|
|
15
|
+
"hooks",
|
|
16
|
+
"LICENSE",
|
|
17
|
+
"README.md",
|
|
18
|
+
"CHANGELOG.md"
|
|
19
|
+
],
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "https://github.com/pinta-ai/pinta-musecode.git"
|
|
23
|
+
},
|
|
24
|
+
"homepage": "https://github.com/pinta-ai/pinta-musecode#readme",
|
|
25
|
+
"bugs": {
|
|
26
|
+
"url": "https://github.com/pinta-ai/pinta-musecode/issues"
|
|
27
|
+
},
|
|
28
|
+
"scripts": {
|
|
29
|
+
"typecheck": "tsc --noEmit",
|
|
30
|
+
"build": "tsc --noEmit && npm run build:cjs && npm run build:esm",
|
|
31
|
+
"build:cjs": "esbuild src/index.ts --bundle --platform=node --format=cjs --target=node18 --minify --outfile=dist/index.js",
|
|
32
|
+
"build:esm": "esbuild src/index.mts --bundle --platform=node --format=esm --target=node18 --minify --banner:js=\"import{createRequire as __pintaCreateRequire}from 'node:module';const require=__pintaCreateRequire(import.meta.url);\" --outfile=dist/index.mjs",
|
|
33
|
+
"dev": "tsc --watch",
|
|
34
|
+
"test": "vitest run",
|
|
35
|
+
"test:watch": "vitest",
|
|
36
|
+
"doctor": "tsx tools/doctor.ts",
|
|
37
|
+
"spike:hooks": "tsx tools/spike.ts hooks",
|
|
38
|
+
"spike:report": "tsx tools/spike.ts report",
|
|
39
|
+
"prepublishOnly": "npm run build"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@pinta-ai/core": "^0.5.0",
|
|
43
|
+
"@types/node": "^22.0.0",
|
|
44
|
+
"esbuild": "^0.24.0",
|
|
45
|
+
"tsx": "^4.0.0",
|
|
46
|
+
"typescript": "^5.7.0",
|
|
47
|
+
"vitest": "^2.1.0"
|
|
48
|
+
},
|
|
49
|
+
"publishConfig": {
|
|
50
|
+
"registry": "https://registry.npmjs.org",
|
|
51
|
+
"access": "public"
|
|
52
|
+
}
|
|
53
|
+
}
|