opencode-skill-audit 0.1.2 → 0.2.0-dev.pr3.36976113543

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -12,8 +12,8 @@ Deterministic audit trail for [Claude Code](https://code.claude.com),
12
12
  observable **skills** were invoked, when, and which **files** were changed afterwards. It gives you
13
13
  a quick skill-compliance signal before you read the code.
14
14
 
15
- On opencode it also renders live in the TUI sidebar, so the audit sits next to the conversation
16
- with no command to run.
15
+ On opencode it also renders live in the TUI sidebar, and on Claude Code in a live pane, so the audit
16
+ sits next to the conversation with no command to run.
17
17
 
18
18
  ```text
19
19
  ● Skill audit — f3a91c2e · ~/Dev/Web · 14:02→16:40
@@ -90,7 +90,8 @@ Claude Code / Codex ──hooks───▶ logger.sh ──┐
90
90
  ├─▶ ~/.claude/skill-audit/<sid>.ndjson
91
91
  opencode ──tool.execute.after──▶ plugin ────┘ │
92
92
  ├─▶ skill-audit status/report/watch/list
93
- └─▶ opencode sidebar (live)
93
+ ├─▶ opencode sidebar (live)
94
+ └─▶ Claude Code pane (live)
94
95
  ```
95
96
 
96
97
  The log is the only contract between the writers and the viewers, so the CLI reads opencode
@@ -109,6 +110,22 @@ Subagent hook calls use the parent session ID, so delegated edits appear in the
109
110
 
110
111
  Restart Claude Code so the hooks load.
111
112
 
113
+ #### Live pane
114
+
115
+ The plugin also opens a **Skill audit** pane showing the current session's timeline. It refreshes
116
+ after every skill and file-edit tool call, and polls the log every two seconds.
117
+
118
+ - **Opening:** it opens by itself at session start once the terminal is 144 columns or wider.
119
+ Narrower terminals hold it back until you run `/skill-audit-pane`, which opens it at any width.
120
+ - **Placement:** in fullscreen from 110 columns it docks beside the transcript and shows the full
121
+ timeline. Press a `▼`/`▶` marker to collapse or expand a run or an hour. Elsewhere it sits above
122
+ the prompt in a compact form: the header and the latest run.
123
+ - **Other panes:** panes from other plugins become tabs beside it (click a tab, or ctrl+x tab).
124
+ Only one is shown at a time. Esc or the close mark closes it.
125
+
126
+ The pane is display only. The shell hooks keep writing the log through `logger.sh`, so recording
127
+ works the same on Claude Code builds without plugin function hooks.
128
+
112
129
  > **Migrating from a manual setup?** Remove any `logger.sh` entries from the `hooks` block of
113
130
  > `~/.claude/settings.json` first, or every event is logged twice.
114
131
 
@@ -234,6 +251,7 @@ If `list` prints `no session logs in ...`, nothing was written. Go to
234
251
  | `skill-audit --help` | Usage summary | 0 |
235
252
  | `! skill-audit status` | Run inside a Claude Code session. Queues while the model is busy | 0 |
236
253
  | opencode sidebar | Live timeline beside the conversation. No command to run | 0 |
254
+ | Claude Code pane | Live timeline in a pane. `/skill-audit-pane` opens it | 0 |
237
255
  | `/skill-audit` | Show the report inside Claude Code | Model turn |
238
256
  | `$skill-audit` | Show the report inside Codex | Model turn |
239
257
 
@@ -361,6 +379,8 @@ and sidebar are TypeScript (`src/`) built with [Bun](https://bun.sh).
361
379
  |-------------------|----------------------------------------------------------------------------|
362
380
  | `scripts/` | `logger.sh` hook target, the `skill-audit` CLI, `sync-versions.mjs` |
363
381
  | `src/` | opencode plugin (`index.ts`), sidebar (`tui.ts`), shared log and view code |
382
+ | `hooks/` | Claude Code shell hooks (`hooks.json`) and the live pane (`claude.tsx`) |
383
+ | `types/` | `$.state` contract for the Claude Code pane |
364
384
  | `test/` | Bun tests, fixtures, and `test/format-contract.sh` |
365
385
  | `.claude-plugin/` | Claude Code plugin manifest and marketplace entry |
366
386
  | `.codex-plugin/` | Codex plugin manifest |
@@ -375,17 +395,28 @@ bun run check # format:check + lint + typecheck (src and test) + tests
375
395
  bun run build # dist/index.js (plugin) and dist/tui.js (sidebar)
376
396
  ```
377
397
 
378
- | Script | What |
379
- |--------------------------|----------------------------------------------------------|
380
- | `bun run format` | Prettier, write mode |
381
- | `bun run lint` | ESLint over `src` and `test` |
382
- | `bun run typecheck` | `tsc --noEmit` for `src` |
383
- | `bun test` | Bun test suite in `test/` |
384
- | `bun run check` | Everything CI runs |
385
- | `bun run check:versions` | Assert `package.json` and both `plugin.json` files agree |
386
- | `bun run sync:versions` | Rewrite the plugin manifests from `package.json` |
387
-
388
- Tests live in `test/`. `test/format-contract.sh` pins the rendered CLI output against fixtures, so
398
+ | Script | What |
399
+ |----------------------------|----------------------------------------------------------|
400
+ | `bun run format` | Prettier, write mode |
401
+ | `bun run lint` | ESLint over `src` and `test` |
402
+ | `bun run typecheck` | `tsc --noEmit` for `src` |
403
+ | `bun test` | Bun test suite in `test/` |
404
+ | `bun run check` | Everything CI runs |
405
+ | `bun run test:claude` | Validate and test the Claude Code pane (needs `claude`) |
406
+ | `bun run typecheck:claude` | Type-check the pane (needs `.claude-plugin/types/`) |
407
+ | `bun run check:versions` | Assert `package.json` and both `plugin.json` files agree |
408
+ | `bun run sync:versions` | Rewrite the plugin manifests from `package.json` |
409
+
410
+ Tests live in `test/`. The Claude Code pane is the exception: its tests (`hooks/*.test.tsx`) run
411
+ in Claude Code's mod sandbox, which has no Node or Bun, so `bun test` is rooted at `test/`
412
+ (`bunfig.toml`). `test:claude` copies the pane's files into a scratch folder and runs
413
+ `claude plugin validate` and `claude plugin test` there. `src/core.ts` and `src/view.ts` must stay
414
+ free of `node:*` imports and `process` because the pane imports them; `test/core.test.ts` checks
415
+ this. `typecheck:claude` reads the API types the engine writes to `.claude-plugin/types/` once it
416
+ has loaded the plugin from a local folder. That folder is gitignored and not available in CI, so
417
+ neither pane check runs in CI.
418
+
419
+ `test/format-contract.sh` pins the rendered CLI output against fixtures, so
389
420
  a change to the timeline format has to be updated there deliberately. That output is the contract
390
421
  the sidebar and any third-party viewer rely on.
391
422
 
package/dist/core.d.ts ADDED
@@ -0,0 +1,84 @@
1
+ /**
2
+ * The log model with no Node dependency. The Claude Code pane runs in a
3
+ * sandbox without `node:*` modules or `process`, so everything it shares with
4
+ * the CLI and the opencode sidebar lives here; `log.ts` adds the file I/O.
5
+ */
6
+ export type SkillEvent = {
7
+ ts: string;
8
+ kind: "skill";
9
+ name: string;
10
+ args?: string;
11
+ cwd?: string;
12
+ source?: string;
13
+ };
14
+ export type FileEvent = {
15
+ ts: string;
16
+ kind: "file";
17
+ tool: string;
18
+ path: string;
19
+ cwd?: string;
20
+ };
21
+ export type AuditEvent = SkillEvent | FileEvent;
22
+ /**
23
+ * An environment variable where the host has one, `undefined` where it does
24
+ * not. Read through `globalThis` because a bare `process` throws in the Claude
25
+ * Code sandbox.
26
+ */
27
+ export declare function env(name: string): string | undefined;
28
+ /**
29
+ * Parse NDJSON log text. Malformed lines are dropped rather than thrown on: the
30
+ * sidebar reads the log while the logger is appending to it, so a truncated
31
+ * trailing line is expected, not exceptional.
32
+ */
33
+ export declare function parse(text: string): AuditEvent[];
34
+ export declare const NO_SKILL = "(no skill active)";
35
+ export type TimelineFile = {
36
+ ts: string;
37
+ tool: string;
38
+ path: string;
39
+ };
40
+ export type SkillRun = {
41
+ skill: string;
42
+ ts: string;
43
+ files: TimelineFile[];
44
+ };
45
+ /** How long a skill run may sit idle before it stops claiming later edits. */
46
+ export declare const DEFAULT_IDLE_GAP_MS: number;
47
+ /** `SKILL_AUDIT_IDLE_MINUTES` as milliseconds, or the default when unusable. */
48
+ export declare function idleGapFrom(minutes: string | undefined): number;
49
+ /**
50
+ * The idle gap, in milliseconds. Without one a single skill invoked in the
51
+ * morning claims every edit made for the rest of the day.
52
+ */
53
+ export declare function idleGapMs(): number;
54
+ /**
55
+ * Group a flat event list into skill runs, each carrying the files edited after
56
+ * it. Port of the jq reduce in scripts/skill-audit; the two must agree.
57
+ *
58
+ * A run only claims edits that keep arriving: once `gapMs` passes with no
59
+ * activity, later files fall into a synthetic run instead. An unparseable
60
+ * timestamp compares as NaN, which never exceeds the gap, so logs written
61
+ * before this rule existed group exactly as they did before.
62
+ */
63
+ export declare function group(events: AuditEvent[], gapMs?: number): SkillRun[];
64
+ export type Summary = {
65
+ runs: number;
66
+ distinct: number;
67
+ files: number;
68
+ orphan: number;
69
+ };
70
+ export declare function summarize(events: AuditEvent[], gapMs?: number): Summary;
71
+ export type SessionView = {
72
+ runs: SkillRun[];
73
+ summary: Summary;
74
+ cwd: string;
75
+ };
76
+ /** A session's view from its raw log text; empty text is an empty session. */
77
+ export declare function toView(text: string, gapMs?: number): SessionView;
78
+ /** `node:path` basename for POSIX paths: the last segment, trailing slashes ignored. */
79
+ export declare function baseName(path: string): string;
80
+ /**
81
+ * `path` relative to `cwd` when it sits under it, else `path` as given. The
82
+ * sidebar only ever shortens paths inside the session directory.
83
+ */
84
+ export declare function relativeTo(path: string, cwd: string): string;
package/dist/index.js CHANGED
@@ -3,13 +3,17 @@
3
3
  import { appendFileSync, mkdirSync, readFileSync } from "fs";
4
4
  import { homedir } from "os";
5
5
  import { isAbsolute, join } from "path";
6
+
7
+ // src/core.ts
8
+ var DEFAULT_IDLE_GAP_MS = 30 * 60000;
9
+
10
+ // src/log.ts
6
11
  function logDir() {
7
12
  return process.env.SKILL_AUDIT_DIR || join(homedir(), ".claude", "skill-audit");
8
13
  }
9
14
  function logPath(sessionID) {
10
15
  return join(logDir(), `${sessionID}.ndjson`);
11
16
  }
12
- var DEFAULT_IDLE_GAP_MS = 30 * 60000;
13
17
  function append(sessionID, event) {
14
18
  try {
15
19
  mkdirSync(logDir(), { recursive: true });
package/dist/log.d.ts CHANGED
@@ -1,63 +1,8 @@
1
+ import { type SessionView } from "./core";
2
+ export * from "./core";
1
3
  /** Directory holding the per-session NDJSON logs, shared with the bash logger. */
2
4
  export declare function logDir(): string;
3
5
  export declare function logPath(sessionID: string): string;
4
- export type SkillEvent = {
5
- ts: string;
6
- kind: "skill";
7
- name: string;
8
- args?: string;
9
- cwd?: string;
10
- source?: string;
11
- };
12
- export type FileEvent = {
13
- ts: string;
14
- kind: "file";
15
- tool: string;
16
- path: string;
17
- cwd?: string;
18
- };
19
- export type AuditEvent = SkillEvent | FileEvent;
20
- /**
21
- * Parse NDJSON log text. Malformed lines are dropped rather than thrown on: the
22
- * sidebar reads the log while the logger is appending to it, so a truncated
23
- * trailing line is expected, not exceptional.
24
- */
25
- export declare function parse(text: string): AuditEvent[];
26
- export declare const NO_SKILL = "(no skill active)";
27
- export type TimelineFile = {
28
- ts: string;
29
- tool: string;
30
- path: string;
31
- };
32
- export type SkillRun = {
33
- skill: string;
34
- ts: string;
35
- files: TimelineFile[];
36
- };
37
- /** How long a skill run may sit idle before it stops claiming later edits. */
38
- export declare const DEFAULT_IDLE_GAP_MS: number;
39
- /**
40
- * The idle gap, in milliseconds. Without one a single skill invoked in the
41
- * morning claims every edit made for the rest of the day.
42
- */
43
- export declare function idleGapMs(): number;
44
- /**
45
- * Group a flat event list into skill runs, each carrying the files edited after
46
- * it. Port of the jq reduce in scripts/skill-audit; the two must agree.
47
- *
48
- * A run only claims edits that keep arriving: once `gapMs` passes with no
49
- * activity, later files fall into a synthetic run instead. An unparseable
50
- * timestamp compares as NaN, which never exceeds the gap, so logs written
51
- * before this rule existed group exactly as they did before.
52
- */
53
- export declare function group(events: AuditEvent[], gapMs?: number): SkillRun[];
54
- export type Summary = {
55
- runs: number;
56
- distinct: number;
57
- files: number;
58
- orphan: number;
59
- };
60
- export declare function summarize(events: AuditEvent[]): Summary;
61
6
  export declare function appendSkill(sessionID: string, input: {
62
7
  ts: string;
63
8
  name: string;
@@ -77,11 +22,6 @@ export declare function appendFile(sessionID: string, input: {
77
22
  export declare function parsePatch(args: unknown): string[];
78
23
  /** Second-precision UTC timestamp, matching `date -u +%FT%TZ` in logger.sh. */
79
24
  export declare function nowTs(date?: Date): string;
80
- export type SessionView = {
81
- runs: SkillRun[];
82
- summary: Summary;
83
- cwd: string;
84
- };
85
25
  /**
86
26
  * Read one session's log. A missing or unreadable file is an empty session, not
87
27
  * an error: the sidebar renders before the first event is ever written.
package/dist/tui.js CHANGED
@@ -7,11 +7,11 @@ import { createSignal, onCleanup } from "solid-js";
7
7
  import { appendFileSync, mkdirSync, readFileSync } from "fs";
8
8
  import { homedir } from "os";
9
9
  import { isAbsolute, join } from "path";
10
- function logDir() {
11
- return process.env.SKILL_AUDIT_DIR || join(homedir(), ".claude", "skill-audit");
12
- }
13
- function logPath(sessionID) {
14
- return join(logDir(), `${sessionID}.ndjson`);
10
+
11
+ // src/core.ts
12
+ function env(name) {
13
+ const host = globalThis;
14
+ return host.process?.env?.[name];
15
15
  }
16
16
  function parse(text) {
17
17
  const events = [];
@@ -31,9 +31,12 @@ function parse(text) {
31
31
  }
32
32
  var NO_SKILL = "(no skill active)";
33
33
  var DEFAULT_IDLE_GAP_MS = 30 * 60000;
34
+ function idleGapFrom(minutes) {
35
+ const value = Number(minutes);
36
+ return Number.isFinite(value) && value > 0 ? value * 60000 : DEFAULT_IDLE_GAP_MS;
37
+ }
34
38
  function idleGapMs() {
35
- const minutes = Number(process.env.SKILL_AUDIT_IDLE_MINUTES);
36
- return Number.isFinite(minutes) && minutes > 0 ? minutes * 60000 : DEFAULT_IDLE_GAP_MS;
39
+ return idleGapFrom(env("SKILL_AUDIT_IDLE_MINUTES"));
37
40
  }
38
41
  function epoch(ts) {
39
42
  return new Date(ts).getTime();
@@ -69,7 +72,7 @@ function group(events, gapMs = idleGapMs()) {
69
72
  }
70
73
  return runs;
71
74
  }
72
- function summarize(events) {
75
+ function summarize(events, gapMs = idleGapMs()) {
73
76
  const names = [];
74
77
  const paths = new Set;
75
78
  for (const event of events) {
@@ -79,7 +82,7 @@ function summarize(events) {
79
82
  paths.add(event.path);
80
83
  }
81
84
  }
82
- const orphan = group(events).filter((run) => run.skill === NO_SKILL).reduce((total, run) => total + run.files.length, 0);
85
+ const orphan = group(events, gapMs).filter((run) => run.skill === NO_SKILL).reduce((total, run) => total + run.files.length, 0);
83
86
  return {
84
87
  runs: names.length,
85
88
  distinct: new Set(names).size,
@@ -87,34 +90,41 @@ function summarize(events) {
87
90
  orphan
88
91
  };
89
92
  }
90
- var EMPTY = {
91
- runs: [],
92
- summary: {
93
- runs: 0,
94
- distinct: 0,
95
- files: 0,
96
- orphan: 0
97
- },
98
- cwd: ""
99
- };
93
+ function toView(text, gapMs = idleGapMs()) {
94
+ const events = parse(text);
95
+ const cwd = events.find((event) => event.cwd)?.cwd ?? "";
96
+ return {
97
+ runs: group(events, gapMs),
98
+ summary: summarize(events, gapMs),
99
+ cwd
100
+ };
101
+ }
102
+ function baseName(path) {
103
+ const trimmed = path.replace(/\/+$/, "");
104
+ return trimmed.slice(trimmed.lastIndexOf("/") + 1);
105
+ }
106
+ function relativeTo(path, cwd) {
107
+ return cwd && path.startsWith(`${cwd}/`) ? path.slice(cwd.length + 1) : path;
108
+ }
109
+
110
+ // src/log.ts
111
+ function logDir() {
112
+ return process.env.SKILL_AUDIT_DIR || join(homedir(), ".claude", "skill-audit");
113
+ }
114
+ function logPath(sessionID) {
115
+ return join(logDir(), `${sessionID}.ndjson`);
116
+ }
100
117
  function readSession(sessionID) {
101
118
  let text;
102
119
  try {
103
120
  text = readFileSync(logPath(sessionID), "utf8");
104
121
  } catch {
105
- return EMPTY;
122
+ text = "";
106
123
  }
107
- const events = parse(text);
108
- const cwd = events.find((event) => event.cwd)?.cwd ?? "";
109
- return {
110
- runs: group(events),
111
- summary: summarize(events),
112
- cwd
113
- };
124
+ return toView(text);
114
125
  }
115
126
 
116
127
  // src/view.ts
117
- import { basename, relative } from "path";
118
128
  var TEXT_ICONS = {
119
129
  run: "\xB7",
120
130
  file: "\u270E",
@@ -129,8 +139,11 @@ var EMOJI_ICONS = {
129
139
  open: "\u25BC",
130
140
  closed: "\u25B6"
131
141
  };
142
+ function iconsFor(style) {
143
+ return style === "emoji" ? EMOJI_ICONS : TEXT_ICONS;
144
+ }
132
145
  function icons() {
133
- return process.env.SKILL_AUDIT_ICONS === "emoji" ? EMOJI_ICONS : TEXT_ICONS;
146
+ return iconsFor(env("SKILL_AUDIT_ICONS"));
134
147
  }
135
148
  function pad(value) {
136
149
  return String(value).padStart(2, "0");
@@ -168,30 +181,27 @@ function bucketByHour(files) {
168
181
  }
169
182
  return buckets;
170
183
  }
171
- function headerLine(summary) {
172
- const icon = icons();
184
+ function headerLine(summary, icon = icons()) {
173
185
  const counts = `${icon.run}${summary.runs} ${icon.file}${summary.files}${summary.orphan > 0 ? ` ${icon.warn}${summary.orphan}` : ""}`;
174
186
  return `Skill audit ${counts}`;
175
187
  }
176
- function runTitle(run, collapsed) {
177
- const icon = icons();
188
+ function runTitle(run, collapsed, icon = icons()) {
178
189
  const name = run.skill === NO_SKILL ? `${icon.warn} no skill` : shortName(run.skill);
179
190
  if (run.files.length === 0) {
180
191
  return ` ${name}`;
181
192
  }
182
193
  return collapsed ? `${icon.closed} ${name} (${run.files.length})` : `${icon.open} ${name}`;
183
194
  }
184
- function hourTitle(bucket, collapsed) {
185
- const icon = icons();
195
+ function hourTitle(bucket, collapsed, icon = icons()) {
186
196
  const marker = collapsed ? icon.closed : icon.open;
187
197
  return `${marker} ${bucket.hour} (${bucket.files.length})`;
188
198
  }
189
199
  function displayPath(path, cwd, width) {
190
- const rel = cwd && path.startsWith(`${cwd}/`) ? relative(cwd, path) : path;
200
+ const rel = relativeTo(path, cwd);
191
201
  if (rel.length <= width) {
192
202
  return rel;
193
203
  }
194
- const base = basename(rel);
204
+ const base = baseName(rel);
195
205
  if (base.length <= width) {
196
206
  return base;
197
207
  }
@@ -206,11 +216,11 @@ function hourKeyOf(index, bucket) {
206
216
  return `${runKey(index)}/hour:${bucket.day} ${bucket.hour}`;
207
217
  }
208
218
  function sidebarLines(view, options) {
209
- const icon = icons();
219
+ const icon = options.icons ?? icons();
210
220
  const marker = options.sectionOpen ? icon.open : icon.closed;
211
221
  const lines = [
212
222
  {
213
- text: `${marker} ${headerLine(view.summary)}`,
223
+ text: `${marker} ${headerLine(view.summary, icon)}`,
214
224
  tone: "text"
215
225
  }
216
226
  ];
@@ -228,7 +238,7 @@ function sidebarLines(view, options) {
228
238
  const key = runKey(index);
229
239
  const runCollapsed = options.collapsed.has(key);
230
240
  lines.push({
231
- text: runTitle(run, runCollapsed),
241
+ text: runTitle(run, runCollapsed, icon),
232
242
  tone: run.skill === NO_SKILL ? "warning" : "accent",
233
243
  key
234
244
  });
@@ -239,7 +249,7 @@ function sidebarLines(view, options) {
239
249
  const bucketKey = hourKeyOf(index, bucket);
240
250
  const hourCollapsed = options.collapsed.has(bucketKey);
241
251
  lines.push({
242
- text: `${HOUR_INDENT}${hourTitle(bucket, hourCollapsed)}`,
252
+ text: `${HOUR_INDENT}${hourTitle(bucket, hourCollapsed, icon)}`,
243
253
  tone: "text",
244
254
  key: bucketKey
245
255
  });
package/dist/view.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { type SessionView, type SkillRun, type Summary, type TimelineFile } from "./log";
1
+ import { type SessionView, type SkillRun, type Summary, type TimelineFile } from "./core";
2
2
  export type IconSet = {
3
3
  run: string;
4
4
  file: string;
@@ -6,6 +6,8 @@ export type IconSet = {
6
6
  open: string;
7
7
  closed: string;
8
8
  };
9
+ /** The icon set for a `SKILL_AUDIT_ICONS` value; anything but `emoji` is the text set. */
10
+ export declare function iconsFor(style: string | undefined): IconSet;
9
11
  export declare function icons(): IconSet;
10
12
  export declare function hhmm(ts: string): string;
11
13
  /** The local hour a timestamp belongs to, as a bucket label. */
@@ -24,10 +26,10 @@ export type HourBucket = {
24
26
  * folding a day's gap into one row.
25
27
  */
26
28
  export declare function bucketByHour(files: TimelineFile[]): HourBucket[];
27
- export declare function headerLine(summary: Summary): string;
29
+ export declare function headerLine(summary: Summary, icon?: IconSet): string;
28
30
  /** The skill name leads the row; its times live on the hour buckets below it. */
29
- export declare function runTitle(run: SkillRun, collapsed: boolean): string;
30
- export declare function hourTitle(bucket: HourBucket, collapsed: boolean): string;
31
+ export declare function runTitle(run: SkillRun, collapsed: boolean, icon?: IconSet): string;
32
+ export declare function hourTitle(bucket: HourBucket, collapsed: boolean, icon?: IconSet): string;
31
33
  /**
32
34
  * Fit a path into the sidebar: relative to the session directory, then the
33
35
  * basename, then a truncated basename.
@@ -47,6 +49,8 @@ export type SidebarOptions = {
47
49
  collapsed: Set<string>;
48
50
  /** Usable sidebar width, in columns. */
49
51
  width: number;
52
+ /** Icons to draw with; the environment's set when omitted. */
53
+ icons?: IconSet;
50
54
  };
51
55
  export declare function runKey(index: number): string;
52
56
  export declare function hourKeyOf(index: number, bucket: HourBucket): string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-skill-audit",
3
- "version": "0.1.2",
3
+ "version": "0.2.0-dev.pr3.36976113543",
4
4
  "description": "Audit trail of skill invocations and file changes, with a live timeline in the opencode sidebar",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -40,16 +40,18 @@
40
40
  ],
41
41
  "scripts": {
42
42
  "clean": "rm -rf dist",
43
- "format": "prettier --write src test package.json tsconfig.json tsconfig.test.json eslint.config.js .github/workflows/ci.yml",
44
- "format:check": "prettier --check src test package.json tsconfig.json tsconfig.test.json eslint.config.js .github/workflows/ci.yml",
43
+ "format": "prettier --write src test hooks/claude.tsx hooks/claude.test.tsx types package.json tsconfig.json tsconfig.test.json tsconfig.claude.json eslint.config.js .github/workflows/ci.yml",
44
+ "format:check": "prettier --check src test hooks/claude.tsx hooks/claude.test.tsx types package.json tsconfig.json tsconfig.test.json tsconfig.claude.json eslint.config.js .github/workflows/ci.yml",
45
45
  "lint": "eslint src test",
46
46
  "build:types": "tsc --emitDeclarationOnly",
47
47
  "build:index": "bun build src/index.ts --outfile dist/index.js --target bun --format esm --external @opencode-ai/plugin --external @opencode-ai/sdk",
48
48
  "build:tui": "bun build src/tui.ts --outfile dist/tui.js --target bun --format esm --external @opencode-ai/plugin --external @opencode-ai/sdk --external @opentui/core --external @opentui/solid --external solid-js",
49
49
  "build": "bun run clean && bun run build:types && bun run build:index && bun run build:tui",
50
50
  "test": "TZ=UTC bun test && TZ=UTC ./test/format-contract.sh",
51
+ "test:claude": "./test/claude-pane.sh",
51
52
  "typecheck": "tsc --noEmit",
52
53
  "typecheck:test": "tsc -p tsconfig.test.json",
54
+ "typecheck:claude": "tsc -p tsconfig.claude.json",
53
55
  "check": "bun run format:check && bun run lint && bun run typecheck && bun run typecheck:test && TZ=UTC bun test && TZ=UTC ./test/format-contract.sh",
54
56
  "check:versions": "node scripts/sync-versions.mjs --check",
55
57
  "sync:versions": "node scripts/sync-versions.mjs",