himotoku 0.1.1 → 0.1.2
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/bin/himotoku.mjs +65 -40
- package/package.json +20 -7
- package/packages/client/dist/assets/index-BJ-wo9zf.js +83 -0
- package/packages/client/dist/assets/index-DHKIuFuW.css +11 -0
- package/packages/client/dist/index.html +2 -2
- package/packages/client/dist/assets/index-Be0EWei7.css +0 -1
- package/packages/client/dist/assets/index-CK2CVDCQ.js +0 -49
package/bin/himotoku.mjs
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// src/cli.ts
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
import path2 from "node:path";
|
|
6
|
-
import { existsSync as existsSync2,
|
|
6
|
+
import { existsSync as existsSync2, readFileSync, statSync } from "node:fs";
|
|
7
7
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
8
8
|
|
|
9
9
|
// packages/server/dist/index.js
|
|
@@ -35,9 +35,7 @@ Respond in the user's language (detect from their message).`;
|
|
|
35
35
|
options: {
|
|
36
36
|
systemPrompt,
|
|
37
37
|
cwd,
|
|
38
|
-
allowedTools: ["Read", "Grep", "Glob"
|
|
39
|
-
permissionMode: "bypassPermissions",
|
|
40
|
-
allowDangerouslySkipPermissions: true,
|
|
38
|
+
allowedTools: ["Read", "Grep", "Glob"],
|
|
41
39
|
includePartialMessages: true,
|
|
42
40
|
maxTurns: 25,
|
|
43
41
|
abortController,
|
|
@@ -45,8 +43,9 @@ Respond in the user's language (detect from their message).`;
|
|
|
45
43
|
}
|
|
46
44
|
});
|
|
47
45
|
let fullContent = "";
|
|
46
|
+
const toolNameMap = /* @__PURE__ */ new Map();
|
|
48
47
|
for await (const message of q) {
|
|
49
|
-
for (const out of mapMessage(message,
|
|
48
|
+
for (const out of mapMessage(message, toolNameMap)) {
|
|
50
49
|
if (out.type === "chat_stream") {
|
|
51
50
|
fullContent += out.content;
|
|
52
51
|
}
|
|
@@ -54,7 +53,7 @@ Respond in the user's language (detect from their message).`;
|
|
|
54
53
|
}
|
|
55
54
|
}
|
|
56
55
|
}
|
|
57
|
-
function* mapMessage(message,
|
|
56
|
+
function* mapMessage(message, toolNameMap) {
|
|
58
57
|
switch (message.type) {
|
|
59
58
|
case "stream_event": {
|
|
60
59
|
const event = message.event;
|
|
@@ -67,6 +66,7 @@ function* mapMessage(message, _fullContent) {
|
|
|
67
66
|
for (const block of message.message.content) {
|
|
68
67
|
if (typeof block === "object" && "type" in block) {
|
|
69
68
|
if (block.type === "tool_use") {
|
|
69
|
+
toolNameMap.set(block.id, block.name);
|
|
70
70
|
yield {
|
|
71
71
|
type: "tool_start",
|
|
72
72
|
tool: block.name,
|
|
@@ -84,12 +84,14 @@ function* mapMessage(message, _fullContent) {
|
|
|
84
84
|
const blocks = Array.isArray(content) ? content : [];
|
|
85
85
|
for (const block of blocks) {
|
|
86
86
|
if (typeof block === "object" && "type" in block && block.type === "tool_result") {
|
|
87
|
+
const toolUseId = "tool_use_id" in block ? block.tool_use_id : "";
|
|
87
88
|
const output = typeof block.content === "string" ? block.content : JSON.stringify(block.content);
|
|
88
89
|
yield {
|
|
89
90
|
type: "tool_result",
|
|
90
|
-
|
|
91
|
+
// #3: Look up actual tool name
|
|
92
|
+
tool: toolNameMap.get(toolUseId) ?? "unknown",
|
|
91
93
|
output: output.slice(0, 2e3),
|
|
92
|
-
toolUseId
|
|
94
|
+
toolUseId
|
|
93
95
|
};
|
|
94
96
|
}
|
|
95
97
|
}
|
|
@@ -102,7 +104,9 @@ function* mapMessage(message, _fullContent) {
|
|
|
102
104
|
yield {
|
|
103
105
|
type: "chat_done",
|
|
104
106
|
fullContent: success.result,
|
|
105
|
-
costUsd: success.total_cost_usd
|
|
107
|
+
costUsd: success.total_cost_usd,
|
|
108
|
+
// #4: Return session_id for conversation continuity
|
|
109
|
+
sessionId: success.session_id
|
|
106
110
|
};
|
|
107
111
|
} else {
|
|
108
112
|
const error = message;
|
|
@@ -118,17 +122,24 @@ function* mapMessage(message, _fullContent) {
|
|
|
118
122
|
|
|
119
123
|
// packages/server/dist/index.js
|
|
120
124
|
function createServer({ port, cwd, clientDistPath: clientDistOverride }) {
|
|
125
|
+
if (!process.env.ANTHROPIC_API_KEY) {
|
|
126
|
+
console.error("Error: ANTHROPIC_API_KEY environment variable is not set.");
|
|
127
|
+
console.error(" export ANTHROPIC_API_KEY=sk-ant-...");
|
|
128
|
+
process.exit(1);
|
|
129
|
+
}
|
|
121
130
|
const app = new Hono();
|
|
122
131
|
const { injectWebSocket, upgradeWebSocket } = createNodeWebSocket({ app });
|
|
123
132
|
app.get("/ws", upgradeWebSocket(() => {
|
|
124
133
|
let abortController = null;
|
|
134
|
+
let isProcessing = false;
|
|
135
|
+
let sessionId;
|
|
125
136
|
return {
|
|
126
137
|
onMessage: async (event, ws) => {
|
|
127
138
|
let data;
|
|
128
139
|
try {
|
|
129
140
|
data = JSON.parse(typeof event.data === "string" ? event.data : event.data.toString());
|
|
130
141
|
} catch {
|
|
131
|
-
ws
|
|
142
|
+
safeSend(ws, { type: "error", message: "Invalid JSON" });
|
|
132
143
|
return;
|
|
133
144
|
}
|
|
134
145
|
if (data.type === "cancel") {
|
|
@@ -136,41 +147,55 @@ function createServer({ port, cwd, clientDistPath: clientDistOverride }) {
|
|
|
136
147
|
return;
|
|
137
148
|
}
|
|
138
149
|
if (data.type === "chat") {
|
|
150
|
+
if (isProcessing) {
|
|
151
|
+
safeSend(ws, {
|
|
152
|
+
type: "error",
|
|
153
|
+
message: "A request is already in progress. Please wait or cancel it first."
|
|
154
|
+
});
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
isProcessing = true;
|
|
139
158
|
abortController = new AbortController();
|
|
140
159
|
try {
|
|
141
160
|
for await (const msg of runAgent({
|
|
142
161
|
prompt: data.content,
|
|
143
162
|
cwd,
|
|
163
|
+
sessionId,
|
|
144
164
|
abortController
|
|
145
165
|
})) {
|
|
146
|
-
|
|
166
|
+
if (msg.type === "chat_done" && msg.sessionId) {
|
|
167
|
+
sessionId = msg.sessionId;
|
|
168
|
+
}
|
|
169
|
+
safeSend(ws, msg);
|
|
147
170
|
}
|
|
148
171
|
} catch (err) {
|
|
149
172
|
if (err.name !== "AbortError") {
|
|
150
|
-
ws
|
|
173
|
+
safeSend(ws, {
|
|
151
174
|
type: "error",
|
|
152
175
|
message: String(err)
|
|
153
|
-
})
|
|
176
|
+
});
|
|
154
177
|
}
|
|
155
178
|
} finally {
|
|
156
179
|
abortController = null;
|
|
180
|
+
isProcessing = false;
|
|
157
181
|
}
|
|
158
182
|
}
|
|
159
183
|
}
|
|
160
184
|
};
|
|
161
185
|
}));
|
|
162
|
-
const
|
|
163
|
-
const clientDistPath = clientDistOverride ?? path.resolve(
|
|
186
|
+
const __dirname2 = path.dirname(fileURLToPath(import.meta.url));
|
|
187
|
+
const clientDistPath = clientDistOverride ?? path.resolve(__dirname2, "../../client/dist");
|
|
164
188
|
if (existsSync(clientDistPath)) {
|
|
165
189
|
app.use("/*", serveStatic({
|
|
166
|
-
root:
|
|
190
|
+
root: clientDistPath,
|
|
191
|
+
rewriteRequestPath: (p) => p
|
|
167
192
|
}));
|
|
168
193
|
app.get("*", serveStatic({
|
|
169
|
-
root:
|
|
194
|
+
root: clientDistPath,
|
|
170
195
|
rewriteRequestPath: () => "/index.html"
|
|
171
196
|
}));
|
|
172
197
|
}
|
|
173
|
-
const server = serve({ fetch: app.fetch, port }, () => {
|
|
198
|
+
const server = serve({ fetch: app.fetch, port, hostname: "127.0.0.1" }, () => {
|
|
174
199
|
console.log(` \u{1F310} http://localhost:${port}`);
|
|
175
200
|
console.log(` Ready! Ask me anything about this codebase.
|
|
176
201
|
`);
|
|
@@ -178,41 +203,41 @@ function createServer({ port, cwd, clientDistPath: clientDistOverride }) {
|
|
|
178
203
|
injectWebSocket(server);
|
|
179
204
|
return server;
|
|
180
205
|
}
|
|
206
|
+
function safeSend(ws, data) {
|
|
207
|
+
if (ws.readyState === void 0 || ws.readyState === 1) {
|
|
208
|
+
try {
|
|
209
|
+
ws.send(JSON.stringify(data));
|
|
210
|
+
} catch {
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
181
214
|
|
|
182
215
|
// src/cli.ts
|
|
216
|
+
var __dirname = path2.dirname(fileURLToPath2(import.meta.url));
|
|
217
|
+
var pkg = JSON.parse(
|
|
218
|
+
readFileSync(path2.resolve(__dirname, "../package.json"), "utf-8")
|
|
219
|
+
);
|
|
183
220
|
var program = new Command();
|
|
184
|
-
program.name("himotoku").version(
|
|
221
|
+
program.name("himotoku").version(pkg.version).description("Unravel any codebase \u2014 AI-powered code exploration via chat").argument("<repo-path>", "Path to the repository to analyze").option("-p, --port <number>", "Port to listen on", "4890").action((repoPath, opts) => {
|
|
185
222
|
const cwd = path2.resolve(repoPath);
|
|
186
223
|
if (!existsSync2(cwd)) {
|
|
187
224
|
console.error(`Error: ${cwd} does not exist`);
|
|
188
225
|
process.exit(1);
|
|
189
226
|
}
|
|
190
|
-
|
|
227
|
+
if (!statSync(cwd).isDirectory()) {
|
|
228
|
+
console.error(`Error: ${cwd} is not a directory`);
|
|
229
|
+
process.exit(1);
|
|
230
|
+
}
|
|
191
231
|
const port = parseInt(opts.port, 10);
|
|
232
|
+
if (isNaN(port) || port < 1 || port > 65535) {
|
|
233
|
+
console.error(`Error: Invalid port number "${opts.port}". Must be 1-65535.`);
|
|
234
|
+
process.exit(1);
|
|
235
|
+
}
|
|
192
236
|
console.log();
|
|
193
|
-
console.log(
|
|
237
|
+
console.log(`\u{1F4DC} himotoku v${pkg.version}`);
|
|
194
238
|
console.log(` Analyzing: ${cwd}`);
|
|
195
|
-
console.log(` ${fileCount} files found`);
|
|
196
239
|
console.log();
|
|
197
|
-
const __dirname = path2.dirname(fileURLToPath2(import.meta.url));
|
|
198
240
|
const clientDistPath = path2.resolve(__dirname, "../packages/client/dist");
|
|
199
241
|
createServer({ port, cwd, clientDistPath });
|
|
200
242
|
});
|
|
201
|
-
function countFiles(dir, depth = 0) {
|
|
202
|
-
if (depth > 3) return 0;
|
|
203
|
-
try {
|
|
204
|
-
const entries = readdirSync(dir, { withFileTypes: true });
|
|
205
|
-
let count = 0;
|
|
206
|
-
for (const entry of entries) {
|
|
207
|
-
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
208
|
-
if (entry.isFile()) count++;
|
|
209
|
-
else if (entry.isDirectory()) {
|
|
210
|
-
count += countFiles(path2.join(dir, entry.name), depth + 1);
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
return count;
|
|
214
|
-
} catch {
|
|
215
|
-
return 0;
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
243
|
program.parse();
|
package/package.json
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "himotoku",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Unveil any codebase — AI-powered code exploration via chat",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"workspaces": [
|
|
7
|
+
".",
|
|
7
8
|
"packages/*"
|
|
8
9
|
],
|
|
9
10
|
"scripts": {
|
|
10
|
-
"build": "
|
|
11
|
-
"
|
|
12
|
-
"
|
|
11
|
+
"build": "node scripts/bundle.mjs",
|
|
12
|
+
"changeset": "changeset",
|
|
13
|
+
"version-packages": "changeset version",
|
|
14
|
+
"release": "changeset publish",
|
|
15
|
+
"prepublishOnly": "npx turbo build"
|
|
13
16
|
},
|
|
14
17
|
"bin": {
|
|
15
18
|
"himotoku": "bin/himotoku.mjs"
|
|
@@ -28,6 +31,14 @@
|
|
|
28
31
|
"claude",
|
|
29
32
|
"agent"
|
|
30
33
|
],
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "https://github.com/gipcompany/himotoku.git"
|
|
37
|
+
},
|
|
38
|
+
"homepage": "https://github.com/gipcompany/himotoku",
|
|
39
|
+
"bugs": {
|
|
40
|
+
"url": "https://github.com/gipcompany/himotoku/issues"
|
|
41
|
+
},
|
|
31
42
|
"packageManager": "npm@11.6.2",
|
|
32
43
|
"license": "MIT",
|
|
33
44
|
"dependencies": {
|
|
@@ -35,12 +46,14 @@
|
|
|
35
46
|
"@hono/node-server": "^1.14.0",
|
|
36
47
|
"@hono/node-ws": "^1.1.0",
|
|
37
48
|
"commander": "^13.0.0",
|
|
38
|
-
"hono": "^4.7.0"
|
|
39
|
-
},
|
|
40
|
-
"peerDependencies": {
|
|
49
|
+
"hono": "^4.7.0",
|
|
41
50
|
"zod": "^4.0.0"
|
|
42
51
|
},
|
|
43
52
|
"devDependencies": {
|
|
53
|
+
"@himotoku/core": "*",
|
|
54
|
+
"@himotoku/server": "*",
|
|
55
|
+
"@changesets/changelog-github": "^0.6.0",
|
|
56
|
+
"@changesets/cli": "^2.30.0",
|
|
44
57
|
"@types/node": "^22.0.0",
|
|
45
58
|
"esbuild": "^0.27.4",
|
|
46
59
|
"turbo": "^2.5.0",
|