@onlooker-community/ecosystem 0.2.1 → 0.3.1
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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.github/workflows/release.yml +4 -2
- package/.release-please-manifest.json +1 -1
- package/CHANGELOG.md +20 -0
- package/hooks/hooks.json +20 -0
- package/package.json +3 -3
- package/scripts/hooks/skill-usage-tracker.sh +51 -0
- package/scripts/lib/onlooker-event.mjs +72 -2
- package/scripts/lib/onlooker-schema.sh +1 -0
- package/scripts/lib/skill-usage.sh +21 -0
- package/test/bats/config.bats +23 -0
- package/test/bats/skill-usage-tracker.bats +76 -0
- package/test/fixtures/hook-inputs/pre-tool-use-skill.json +11 -0
- package/test/fixtures/hook-inputs/user-prompt-expansion-skill.json +10 -0
- package/test/node/schema-events.test.mjs +29 -1
|
@@ -12,6 +12,7 @@ permissions:
|
|
|
12
12
|
contents: write
|
|
13
13
|
pull-requests: write
|
|
14
14
|
issues: write
|
|
15
|
+
id-token: write
|
|
15
16
|
|
|
16
17
|
jobs:
|
|
17
18
|
release-please:
|
|
@@ -48,7 +49,8 @@ jobs:
|
|
|
48
49
|
- name: Publish to npm
|
|
49
50
|
if: ${{ steps.release.outputs.releases_created == 'true' }}
|
|
50
51
|
env:
|
|
51
|
-
|
|
52
|
+
NPM_CONFIG_PROVENANCE: true
|
|
52
53
|
run: |
|
|
54
|
+
npm install -g npm@latest
|
|
53
55
|
npm ci
|
|
54
|
-
npm publish --access
|
|
56
|
+
npm publish --access=public
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.3.1](https://github.com/onlooker-community/ecosystem/compare/v0.3.0...v0.3.1) (2026-05-22)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **ci:** use HTTPS repository URL for npm provenance ([a7e8927](https://github.com/onlooker-community/ecosystem/commit/a7e89275c5a025a8afee009853265b717091f6ca))
|
|
9
|
+
|
|
10
|
+
## [0.3.0](https://github.com/onlooker-community/ecosystem/compare/v0.2.1...v0.3.0) (2026-05-21)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
|
|
15
|
+
* **hooks:** track skill usage via skill.invoked events ([23fff0f](https://github.com/onlooker-community/ecosystem/commit/23fff0f0bfad8ab91788d8c45a0457d099d2e870))
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### Chores
|
|
19
|
+
|
|
20
|
+
* update GitHub Actions permissions to include id-token ([ca18e61](https://github.com/onlooker-community/ecosystem/commit/ca18e61571b173d1aa6e69cf9031d2daaae1ff72))
|
|
21
|
+
* update npm publish configuration in release workflow ([261fa2d](https://github.com/onlooker-community/ecosystem/commit/261fa2d5c9d656ce74f52193be615b860bc78075))
|
|
22
|
+
|
|
3
23
|
## [0.2.1](https://github.com/onlooker-community/ecosystem/compare/v0.2.0...v0.2.1) (2026-05-21)
|
|
4
24
|
|
|
5
25
|
|
package/hooks/hooks.json
CHANGED
|
@@ -18,6 +18,26 @@
|
|
|
18
18
|
"command": "\"$CLAUDE_PLUGIN_ROOT\"/scripts/hooks/agent-spawn-tracker.sh"
|
|
19
19
|
}
|
|
20
20
|
]
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"matcher": "Skill",
|
|
24
|
+
"hooks": [
|
|
25
|
+
{
|
|
26
|
+
"type": "command",
|
|
27
|
+
"command": "\"$CLAUDE_PLUGIN_ROOT\"/scripts/hooks/skill-usage-tracker.sh"
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
}
|
|
31
|
+
],
|
|
32
|
+
"UserPromptExpansion": [
|
|
33
|
+
{
|
|
34
|
+
"matcher": "",
|
|
35
|
+
"hooks": [
|
|
36
|
+
{
|
|
37
|
+
"type": "command",
|
|
38
|
+
"command": "\"$CLAUDE_PLUGIN_ROOT\"/scripts/hooks/skill-usage-tracker.sh"
|
|
39
|
+
}
|
|
40
|
+
]
|
|
21
41
|
}
|
|
22
42
|
],
|
|
23
43
|
"PostToolUse": [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onlooker-community/ecosystem",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Agents, skills, hooks, commands, rules, and MCP configurations that power [Onlooker](https://onlooker.dev)",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Onlooker Community",
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"license": "MIT",
|
|
10
10
|
"repository": {
|
|
11
11
|
"type": "git",
|
|
12
|
-
"url": "
|
|
12
|
+
"url": "https://github.com/onlooker-community/ecosystem"
|
|
13
13
|
},
|
|
14
14
|
"homepage": "https://github.com/onlooker-community/ecosystem#readme",
|
|
15
15
|
"bugs": {
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"onlooker-install": "install.sh"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@onlooker-community/schema": "^1.
|
|
22
|
+
"@onlooker-community/schema": "^1.4.0"
|
|
23
23
|
},
|
|
24
24
|
"scripts": {
|
|
25
25
|
"postinstall": "echo '\\n onlooker-ecosystem installed!\\n Run: npx onlooker-install typescript\\n Docs: https://github.com/onlooker-community/ecosystem\\n'",
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Onlooker Skill Usage Tracker
|
|
3
|
+
# Invoked by UserPromptExpansion (slash commands) and PreToolUse (matcher: Skill).
|
|
4
|
+
#
|
|
5
|
+
# Records canonical skill.invoked events to:
|
|
6
|
+
# ~/.onlooker/session-history/<session_id>.jsonl
|
|
7
|
+
# ~/.onlooker/logs/onlooker-events.jsonl
|
|
8
|
+
#
|
|
9
|
+
# Usage:
|
|
10
|
+
# echo "$INPUT" | skill-usage-tracker.sh
|
|
11
|
+
|
|
12
|
+
set -uo pipefail # No -e: never block skill invocation
|
|
13
|
+
|
|
14
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
15
|
+
source "$SCRIPT_DIR/../lib/validate-path.sh"
|
|
16
|
+
source "$SCRIPT_DIR/../lib/onlooker-schema.sh"
|
|
17
|
+
source "$SCRIPT_DIR/../lib/tool-history.sh"
|
|
18
|
+
source "$SCRIPT_DIR/../lib/skill-usage.sh"
|
|
19
|
+
|
|
20
|
+
hook_register "skill-usage-tracker" "Skill Usage Tracker" "Records skill.invoked canonical events"
|
|
21
|
+
|
|
22
|
+
INPUT=$(cat)
|
|
23
|
+
|
|
24
|
+
HOOK_EVENT=$(echo "$INPUT" | jq -r '.hook_event_name // "UserPromptExpansion"')
|
|
25
|
+
hook_set_context "$INPUT" "$HOOK_EVENT"
|
|
26
|
+
|
|
27
|
+
SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // "unknown"')
|
|
28
|
+
turn_state_export "$SESSION_ID"
|
|
29
|
+
|
|
30
|
+
# PreToolUse Skill hooks must approve the tool call
|
|
31
|
+
if [[ "$HOOK_EVENT" == "PreToolUse" ]]; then
|
|
32
|
+
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // ""')
|
|
33
|
+
if [[ "$TOOL_NAME" != "Skill" ]]; then
|
|
34
|
+
hook_success
|
|
35
|
+
exit 0
|
|
36
|
+
fi
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
RECORD=$(skill_usage_build_record "$INPUT")
|
|
40
|
+
if [[ -n "$RECORD" ]]; then
|
|
41
|
+
skill_usage_append "$SESSION_ID" "$RECORD" || hook_failure "Failed to append session history"
|
|
42
|
+
onlooker_append_event "$RECORD" || hook_failure "Failed to append global event log"
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
if [[ "$HOOK_EVENT" == "PreToolUse" ]]; then
|
|
46
|
+
SKILL_NAME=$(echo "$RECORD" | jq -r '.payload.skill_name // empty' 2>/dev/null)
|
|
47
|
+
jq -n --arg msg "Skill tracked${SKILL_NAME:+: $SKILL_NAME}" '{ "decision": "approve", "reason": $msg }'
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
hook_success
|
|
51
|
+
exit 0
|
|
@@ -8,6 +8,7 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
|
8
8
|
import { join } from 'node:path';
|
|
9
9
|
import {
|
|
10
10
|
createEvent,
|
|
11
|
+
SKILL_INVOKED,
|
|
11
12
|
TOOL_AGENT_COMPLETE,
|
|
12
13
|
TOOL_AGENT_SPAWN,
|
|
13
14
|
TOOL_FILE_EDIT,
|
|
@@ -78,11 +79,80 @@ function extractPath(toolInput, toolResponse) {
|
|
|
78
79
|
return toolInput?.file_path ?? toolInput?.path ?? toolResponse?.filePath ?? toolResponse?.path ?? undefined;
|
|
79
80
|
}
|
|
80
81
|
|
|
82
|
+
function stripUndefined(obj) {
|
|
83
|
+
return Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== undefined && v !== null && v !== ''));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function parseTurnNumber() {
|
|
87
|
+
const raw = process.env.ONLOOKER_TURN_NUMBER;
|
|
88
|
+
if (raw == null || raw === '') return undefined;
|
|
89
|
+
const n = Number.parseInt(String(raw), 10);
|
|
90
|
+
return Number.isFinite(n) && n >= 1 ? n : undefined;
|
|
91
|
+
}
|
|
92
|
+
|
|
81
93
|
/**
|
|
82
|
-
* Map
|
|
83
|
-
* Returns null when the
|
|
94
|
+
* Map UserPromptExpansion or PreToolUse (Skill) hook input to skill.invoked.
|
|
95
|
+
* Returns null when the hook input is not a skill invocation.
|
|
96
|
+
*/
|
|
97
|
+
export function mapSkillHookInput(hookInput, options) {
|
|
98
|
+
const { onlookerDir, plugin, runtime = 'claude-code', adapter_id = 'ecosystem.hooks' } = options;
|
|
99
|
+
const hookEvent = hookInput?.hook_event_name;
|
|
100
|
+
const sessionId = hookInput?.session_id ?? 'unknown';
|
|
101
|
+
|
|
102
|
+
let payload;
|
|
103
|
+
|
|
104
|
+
if (hookEvent === 'UserPromptExpansion') {
|
|
105
|
+
const skillName = hookInput?.command_name;
|
|
106
|
+
if (!skillName) return null;
|
|
107
|
+
payload = stripUndefined({
|
|
108
|
+
skill_name: skillName,
|
|
109
|
+
invocation_source: 'slash_command',
|
|
110
|
+
command_args: hookInput?.command_args,
|
|
111
|
+
command_source: hookInput?.command_source,
|
|
112
|
+
expansion_type: hookInput?.expansion_type,
|
|
113
|
+
turn_number: parseTurnNumber(),
|
|
114
|
+
});
|
|
115
|
+
} else if (hookEvent === 'PreToolUse' && hookInput?.tool_name === 'Skill') {
|
|
116
|
+
const toolInput = hookInput?.tool_input ?? {};
|
|
117
|
+
const skillName =
|
|
118
|
+
toolInput.skill ?? toolInput.skill_name ?? toolInput.name ?? toolInput.command ?? toolInput.skill_id;
|
|
119
|
+
if (!skillName) return null;
|
|
120
|
+
const args = toolInput.args ?? toolInput.command_args;
|
|
121
|
+
payload = stripUndefined({
|
|
122
|
+
skill_name: String(skillName),
|
|
123
|
+
invocation_source: 'tool',
|
|
124
|
+
command_args: typeof args === 'string' ? args : args != null ? JSON.stringify(args) : undefined,
|
|
125
|
+
turn_number: parseTurnNumber(),
|
|
126
|
+
});
|
|
127
|
+
} else {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const event = buildCanonicalEvent({
|
|
132
|
+
onlookerDir,
|
|
133
|
+
runtime,
|
|
134
|
+
adapter_id,
|
|
135
|
+
plugin,
|
|
136
|
+
session_id: sessionId,
|
|
137
|
+
event_type: SKILL_INVOKED,
|
|
138
|
+
payload,
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
const result = validate(event);
|
|
142
|
+
if (!result.valid) {
|
|
143
|
+
return { valid: false, errors: result.errors, event_type: SKILL_INVOKED };
|
|
144
|
+
}
|
|
145
|
+
return { valid: true, event: result.event };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Map Claude Code hook input to a canonical event.
|
|
150
|
+
* Returns null when the hook input is not mapped to a schema event type.
|
|
84
151
|
*/
|
|
85
152
|
export function mapHookInputToCanonical(hookInput, options) {
|
|
153
|
+
const skillMapped = mapSkillHookInput(hookInput, options);
|
|
154
|
+
if (skillMapped) return skillMapped;
|
|
155
|
+
|
|
86
156
|
const { onlookerDir, plugin, runtime = 'claude-code', adapter_id = 'ecosystem.hooks' } = options;
|
|
87
157
|
|
|
88
158
|
const toolName = hookInput?.tool_name;
|
|
@@ -25,6 +25,7 @@ onlooker_event_from_hook() {
|
|
|
25
25
|
fi
|
|
26
26
|
|
|
27
27
|
printf '%s' "$hook_input" | ONLOOKER_DIR="$ONLOOKER_DIR" ONLOOKER_PLUGIN_NAME="$ONLOOKER_PLUGIN_NAME" \
|
|
28
|
+
ONLOOKER_TURN_NUMBER="${ONLOOKER_TURN_NUMBER:-}" \
|
|
28
29
|
node "$_ONLOOKER_EVENT_JS" emit-from-hook 2>/dev/null
|
|
29
30
|
}
|
|
30
31
|
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Skill usage helpers — canonical session JSONL via @onlooker-community/schema.
|
|
3
|
+
#
|
|
4
|
+
# Source after validate-path.sh and onlooker-schema.sh:
|
|
5
|
+
# source "$CLAUDE_PLUGIN_ROOT/scripts/lib/validate-path.sh"
|
|
6
|
+
# source "$CLAUDE_PLUGIN_ROOT/scripts/lib/onlooker-schema.sh"
|
|
7
|
+
# source "$CLAUDE_PLUGIN_ROOT/scripts/lib/skill-usage.sh"
|
|
8
|
+
# source "$CLAUDE_PLUGIN_ROOT/scripts/lib/tool-history.sh"
|
|
9
|
+
|
|
10
|
+
# Build a canonical skill.invoked event from hook stdin (empty when unmapped).
|
|
11
|
+
# Usage: record=$(skill_usage_build_record "$INPUT")
|
|
12
|
+
skill_usage_build_record() {
|
|
13
|
+
local input_json="${1:-}"
|
|
14
|
+
onlooker_event_from_hook "$input_json"
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
# Append a canonical skill event to session history (reuses tool-history flock).
|
|
18
|
+
# Usage: skill_usage_append "$SESSION_ID" "$event_json"
|
|
19
|
+
skill_usage_append() {
|
|
20
|
+
tool_history_append "$1" "$2"
|
|
21
|
+
}
|
package/test/bats/config.bats
CHANGED
|
@@ -38,6 +38,29 @@ setup_file() {
|
|
|
38
38
|
[ "$status" -eq 0 ]
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
@test "hooks.json Skill matcher references skill-usage-tracker" {
|
|
42
|
+
run jq -e '.hooks.PreToolUse[2].matcher == "Skill"' "${REPO_ROOT}/hooks/hooks.json"
|
|
43
|
+
[ "$status" -eq 0 ]
|
|
44
|
+
|
|
45
|
+
local hook_cmd
|
|
46
|
+
hook_cmd=$(jq -r '.hooks.PreToolUse[2].hooks[0].command' "${REPO_ROOT}/hooks/hooks.json")
|
|
47
|
+
[[ "$hook_cmd" == *skill-usage-tracker.sh ]]
|
|
48
|
+
|
|
49
|
+
local script_path="${hook_cmd//\$CLAUDE_PLUGIN_ROOT/$REPO_ROOT}"
|
|
50
|
+
script_path="${script_path//\"/}"
|
|
51
|
+
run test -x "$script_path"
|
|
52
|
+
[ "$status" -eq 0 ]
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
@test "hooks.json UserPromptExpansion references skill-usage-tracker" {
|
|
56
|
+
run jq -e '.hooks.UserPromptExpansion[0].matcher == ""' "${REPO_ROOT}/hooks/hooks.json"
|
|
57
|
+
[ "$status" -eq 0 ]
|
|
58
|
+
|
|
59
|
+
local hook_cmd
|
|
60
|
+
hook_cmd=$(jq -r '.hooks.UserPromptExpansion[0].hooks[0].command' "${REPO_ROOT}/hooks/hooks.json")
|
|
61
|
+
[[ "$hook_cmd" == *skill-usage-tracker.sh ]]
|
|
62
|
+
}
|
|
63
|
+
|
|
41
64
|
@test "hooks.json PostToolUse references tool-history-tracker" {
|
|
42
65
|
run jq -e '.hooks.PostToolUse[0].matcher == "*"' "${REPO_ROOT}/hooks/hooks.json"
|
|
43
66
|
[ "$status" -eq 0 ]
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
#!/usr/bin/env bats
|
|
2
|
+
|
|
3
|
+
setup() {
|
|
4
|
+
# shellcheck source=../helpers/setup.bash
|
|
5
|
+
source "${BATS_TEST_DIRNAME}/../helpers/setup.bash"
|
|
6
|
+
load_validate_path
|
|
7
|
+
# shellcheck source=../../scripts/lib/onlooker-schema.sh
|
|
8
|
+
source "${REPO_ROOT}/scripts/lib/onlooker-schema.sh"
|
|
9
|
+
# shellcheck source=../../scripts/lib/tool-history.sh
|
|
10
|
+
source "${REPO_ROOT}/scripts/lib/tool-history.sh"
|
|
11
|
+
# shellcheck source=../../scripts/lib/skill-usage.sh
|
|
12
|
+
source "${REPO_ROOT}/scripts/lib/skill-usage.sh"
|
|
13
|
+
export CLAUDE_PLUGIN_ROOT="${REPO_ROOT}"
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
@test "skill_usage_build_record maps UserPromptExpansion to skill.invoked" {
|
|
17
|
+
local fixture="${REPO_ROOT}/test/fixtures/hook-inputs/user-prompt-expansion-skill.json"
|
|
18
|
+
local record
|
|
19
|
+
record=$(skill_usage_build_record "$(cat "$fixture")")
|
|
20
|
+
echo "$record" | jq -e \
|
|
21
|
+
'.schema_version == "1.0"
|
|
22
|
+
and .event_type == "skill.invoked"
|
|
23
|
+
and .payload.skill_name == "code-review"
|
|
24
|
+
and .payload.invocation_source == "slash_command"
|
|
25
|
+
and .payload.command_args == "src/main.ts"
|
|
26
|
+
and .payload.expansion_type == "slash_command"
|
|
27
|
+
and .session_id == "skill-session-001"' \
|
|
28
|
+
>/dev/null
|
|
29
|
+
echo "$record" | onlooker_validate_event
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
@test "skill_usage_build_record maps PreToolUse Skill to skill.invoked" {
|
|
33
|
+
local fixture="${REPO_ROOT}/test/fixtures/hook-inputs/pre-tool-use-skill.json"
|
|
34
|
+
local record
|
|
35
|
+
record=$(skill_usage_build_record "$(cat "$fixture")")
|
|
36
|
+
echo "$record" | jq -e \
|
|
37
|
+
'.event_type == "skill.invoked"
|
|
38
|
+
and .payload.skill_name == "code-review"
|
|
39
|
+
and .payload.invocation_source == "tool"
|
|
40
|
+
and .payload.command_args == "src/main.ts"' \
|
|
41
|
+
>/dev/null
|
|
42
|
+
echo "$record" | onlooker_validate_event
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
@test "skill-usage-tracker appends slash command skill to session JSONL" {
|
|
46
|
+
local fixture="${REPO_ROOT}/test/fixtures/hook-inputs/user-prompt-expansion-skill.json"
|
|
47
|
+
local history_file="${ONLOOKER_SESSION_HISTORY_DIR}/skill-session-001.jsonl"
|
|
48
|
+
rm -f "$history_file" "${history_file}.lock"
|
|
49
|
+
|
|
50
|
+
run bash -c "cat '${fixture}' | '${REPO_ROOT}/scripts/hooks/skill-usage-tracker.sh' 2>/dev/null"
|
|
51
|
+
[ "$status" -eq 0 ]
|
|
52
|
+
[ -f "$history_file" ]
|
|
53
|
+
tail -n 1 "$history_file" | jq -e '.event_type == "skill.invoked"' >/dev/null
|
|
54
|
+
tail -n 1 "$history_file" | onlooker_validate_event
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
@test "skill-usage-tracker approves PreToolUse Skill and records event" {
|
|
58
|
+
local fixture="${REPO_ROOT}/test/fixtures/hook-inputs/pre-tool-use-skill.json"
|
|
59
|
+
local history_file="${ONLOOKER_SESSION_HISTORY_DIR}/skill-session-001.jsonl"
|
|
60
|
+
rm -f "$history_file" "${history_file}.lock"
|
|
61
|
+
|
|
62
|
+
run bash -c "cat '${fixture}' | '${REPO_ROOT}/scripts/hooks/skill-usage-tracker.sh' 2>/dev/null"
|
|
63
|
+
[ "$status" -eq 0 ]
|
|
64
|
+
echo "$output" | jq -e '.decision == "approve"' >/dev/null
|
|
65
|
+
tail -n 1 "$history_file" | jq -e '.payload.invocation_source == "tool"' >/dev/null
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
@test "skill-usage-tracker mirrors skill.invoked to global events log" {
|
|
69
|
+
local fixture="${REPO_ROOT}/test/fixtures/hook-inputs/user-prompt-expansion-skill.json"
|
|
70
|
+
: >"$ONLOOKER_EVENTS_LOG"
|
|
71
|
+
|
|
72
|
+
cat "$fixture" | "${REPO_ROOT}/scripts/hooks/skill-usage-tracker.sh" >/dev/null 2>&1
|
|
73
|
+
|
|
74
|
+
tail -n 1 "$ONLOOKER_EVENTS_LOG" | jq -e '.event_type == "skill.invoked"' >/dev/null
|
|
75
|
+
tail -n 1 "$ONLOOKER_EVENTS_LOG" | onlooker_validate_event
|
|
76
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"session_id": "skill-session-001",
|
|
3
|
+
"cwd": "/project",
|
|
4
|
+
"hook_event_name": "UserPromptExpansion",
|
|
5
|
+
"expansion_type": "slash_command",
|
|
6
|
+
"command_name": "code-review",
|
|
7
|
+
"command_args": "src/main.ts",
|
|
8
|
+
"command_source": "plugin",
|
|
9
|
+
"prompt": "/code-review src/main.ts"
|
|
10
|
+
}
|
|
@@ -5,7 +5,7 @@ import { join } from 'node:path';
|
|
|
5
5
|
import { test } from 'node:test';
|
|
6
6
|
import { fileURLToPath } from 'node:url';
|
|
7
7
|
import { validate } from '@onlooker-community/schema';
|
|
8
|
-
import { buildCanonicalEvent, mapHookInputToCanonical } from '../../scripts/lib/onlooker-event.mjs';
|
|
8
|
+
import { buildCanonicalEvent, mapHookInputToCanonical, mapSkillHookInput } from '../../scripts/lib/onlooker-event.mjs';
|
|
9
9
|
|
|
10
10
|
const REPO_ROOT = join(fileURLToPath(new URL('../..', import.meta.url)));
|
|
11
11
|
const FIXTURES = join(REPO_ROOT, 'test/fixtures/hook-inputs');
|
|
@@ -44,6 +44,34 @@ test('mapHookInputToCanonical maps PostToolUseFailure Bash to tool.shell.exec',
|
|
|
44
44
|
assert.equal(validate(mapped.event).valid, true);
|
|
45
45
|
});
|
|
46
46
|
|
|
47
|
+
test('mapSkillHookInput maps UserPromptExpansion to skill.invoked', () => {
|
|
48
|
+
const hookInput = loadFixture('user-prompt-expansion-skill.json');
|
|
49
|
+
const tmpDir = join(REPO_ROOT, 'test/tmp-schema-events');
|
|
50
|
+
const mapped = mapSkillHookInput(hookInput, {
|
|
51
|
+
onlookerDir: tmpDir,
|
|
52
|
+
plugin: 'onlooker',
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
assert.equal(mapped.valid, true);
|
|
56
|
+
assert.equal(mapped.event.event_type, 'skill.invoked');
|
|
57
|
+
assert.equal(mapped.event.payload.skill_name, 'code-review');
|
|
58
|
+
assert.equal(mapped.event.payload.invocation_source, 'slash_command');
|
|
59
|
+
assert.equal(validate(mapped.event).valid, true);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('mapSkillHookInput maps PreToolUse Skill to skill.invoked', () => {
|
|
63
|
+
const hookInput = loadFixture('pre-tool-use-skill.json');
|
|
64
|
+
const tmpDir = join(REPO_ROOT, 'test/tmp-schema-events');
|
|
65
|
+
const mapped = mapSkillHookInput(hookInput, {
|
|
66
|
+
onlookerDir: tmpDir,
|
|
67
|
+
plugin: 'onlooker',
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
assert.equal(mapped.valid, true);
|
|
71
|
+
assert.equal(mapped.event.payload.invocation_source, 'tool');
|
|
72
|
+
assert.equal(validate(mapped.event).valid, true);
|
|
73
|
+
});
|
|
74
|
+
|
|
47
75
|
test('buildCanonicalEvent assigns monotonic file-backed sequence', () => {
|
|
48
76
|
const tmpDir = mkdtempSync(join(tmpdir(), 'onlooker-seq-'));
|
|
49
77
|
const a = buildCanonicalEvent({
|