openhacker 0.1.2 → 0.1.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openhacker",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Scaffold a self-hosted OpenHacker security agent",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
@@ -4,7 +4,6 @@ You are OpenHacker, an application security agent.
4
4
 
5
5
  The user gives you a GitHub repository (as `owner/name` or a github.com URL). Analyze it for security vulnerabilities and report your findings.
6
6
 
7
- - Briefly narrate what you are checking as you go (dependencies, auth, input handling, secrets, etc.).
8
7
  - Use the shell to clone and explore the repo. Use `ls`/`find` to list directories; only use `read_file` on actual file paths, never on a directory.
9
8
  - Call out concrete risks with severity and, where possible, how to fix them.
10
9
  - If you cannot access the repository, say so plainly.
@@ -95,13 +95,35 @@ h1 span {
95
95
  margin-bottom: 0;
96
96
  }
97
97
  .reply .reasoning {
98
- white-space: pre-wrap;
99
- color: var(--muted);
100
- font-size: 13px;
101
98
  margin: 0 0 12px;
102
99
  padding-left: 12px;
103
100
  border-left: 2px solid var(--border);
104
101
  }
102
+ .reply .reasoning > summary {
103
+ cursor: pointer;
104
+ color: var(--muted);
105
+ font-size: 12px;
106
+ text-transform: uppercase;
107
+ letter-spacing: 0.5px;
108
+ list-style: none;
109
+ user-select: none;
110
+ }
111
+ .reply .reasoning > summary::-webkit-details-marker {
112
+ display: none;
113
+ }
114
+ .reply .reasoning > summary::before {
115
+ content: "› ";
116
+ display: inline-block;
117
+ }
118
+ .reply .reasoning[open] > summary::before {
119
+ content: "⌄ ";
120
+ }
121
+ .reply .reasoning > p {
122
+ white-space: pre-wrap;
123
+ color: var(--muted);
124
+ font-size: 13px;
125
+ margin: 8px 0 0;
126
+ }
105
127
 
106
128
  .tool {
107
129
  display: flex;
@@ -123,6 +145,33 @@ h1 span {
123
145
  padding: 0 8px;
124
146
  }
125
147
 
148
+ .hacking {
149
+ color: var(--muted);
150
+ font-size: 13px;
151
+ margin: 0;
152
+ }
153
+ .reply .text + .hacking {
154
+ margin-top: 12px;
155
+ }
156
+ .dots::after {
157
+ content: "";
158
+ animation: dots 1.2s steps(4, end) infinite;
159
+ }
160
+ @keyframes dots {
161
+ 0% {
162
+ content: "";
163
+ }
164
+ 25% {
165
+ content: ".";
166
+ }
167
+ 50% {
168
+ content: "..";
169
+ }
170
+ 75% {
171
+ content: "...";
172
+ }
173
+ }
174
+
126
175
  .cursor {
127
176
  display: inline-block;
128
177
  width: 8px;
@@ -15,7 +15,7 @@ export default function Home() {
15
15
  if (!value || busy) return;
16
16
  agent.reset();
17
17
  agent.send({
18
- message: `Analyze the GitHub repository "${value}" for security vulnerabilities. Walk through what you check and report what you find.`,
18
+ message: `Analyze the GitHub repository "${value}" for security vulnerabilities. Reply with only the final report.`,
19
19
  });
20
20
  }
21
21
 
@@ -23,6 +23,20 @@ export default function Home() {
23
23
  .reverse()
24
24
  .find((m) => m.role === "assistant");
25
25
 
26
+ const parts = reply?.parts ?? [];
27
+ const lastStep = parts.reduce<number | undefined>((max, p) => {
28
+ const idx = "stepIndex" in p ? p.stepIndex : undefined;
29
+ if (typeof idx !== "number") return max;
30
+ return max === undefined ? idx : Math.max(max, idx);
31
+ }, undefined);
32
+
33
+ let result = "";
34
+ for (const p of parts) {
35
+ if (p.type !== "text") continue;
36
+ if (lastStep !== undefined && p.stepIndex !== lastStep) continue;
37
+ result += p.text;
38
+ }
39
+
26
40
  return (
27
41
  <main className="container">
28
42
  <h1>
@@ -45,41 +59,16 @@ export default function Home() {
45
59
  </button>
46
60
  </form>
47
61
 
48
- {reply ? (
62
+ {reply || busy ? (
49
63
  <section className="reply">
50
- {reply.parts.map((part, i) => {
51
- if (part.type === "reasoning") {
52
- return (
53
- <p key={i} className="reasoning">
54
- {part.text}
55
- </p>
56
- );
57
- }
58
- if (part.type === "text") {
59
- return (
60
- <p key={i} className="text">
61
- {part.text}
62
- </p>
63
- );
64
- }
65
- if (part.type === "dynamic-tool") {
66
- return (
67
- <div key={i} className="tool">
68
- <span className="tool-name">{part.toolName}</span>
69
- <span className="tool-state">{part.state}</span>
70
- </div>
71
- );
72
- }
73
- return null;
74
- })}
75
- {agent.status === "streaming" ? (
76
- <span className="cursor" aria-hidden />
64
+ {result ? <p className="text">{result}</p> : null}
65
+ {busy && !result ? (
66
+ <p className="hacking">
67
+ hacking
68
+ <span className="dots" aria-hidden />
69
+ </p>
77
70
  ) : null}
78
71
  </section>
79
- ) : busy ? (
80
- <section className="reply">
81
- <span className="cursor" aria-hidden />
82
- </section>
83
72
  ) : null}
84
73
 
85
74
  {agent.status === "error" ? (