himotoku 0.1.1 → 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/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, readdirSync } from "node:fs";
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", "Bash"],
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, fullContent)) {
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, _fullContent) {
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
- tool: "unknown",
91
+ // #3: Look up actual tool name
92
+ tool: toolNameMap.get(toolUseId) ?? "unknown",
91
93
  output: output.slice(0, 2e3),
92
- toolUseId: "tool_use_id" in block ? block.tool_use_id : ""
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;
@@ -122,13 +126,15 @@ function createServer({ port, cwd, clientDistPath: clientDistOverride }) {
122
126
  const { injectWebSocket, upgradeWebSocket } = createNodeWebSocket({ app });
123
127
  app.get("/ws", upgradeWebSocket(() => {
124
128
  let abortController = null;
129
+ let isProcessing = false;
130
+ let sessionId;
125
131
  return {
126
132
  onMessage: async (event, ws) => {
127
133
  let data;
128
134
  try {
129
135
  data = JSON.parse(typeof event.data === "string" ? event.data : event.data.toString());
130
136
  } catch {
131
- ws.send(JSON.stringify({ type: "error", message: "Invalid JSON" }));
137
+ safeSend(ws, { type: "error", message: "Invalid JSON" });
132
138
  return;
133
139
  }
134
140
  if (data.type === "cancel") {
@@ -136,41 +142,55 @@ function createServer({ port, cwd, clientDistPath: clientDistOverride }) {
136
142
  return;
137
143
  }
138
144
  if (data.type === "chat") {
145
+ if (isProcessing) {
146
+ safeSend(ws, {
147
+ type: "error",
148
+ message: "A request is already in progress. Please wait or cancel it first."
149
+ });
150
+ return;
151
+ }
152
+ isProcessing = true;
139
153
  abortController = new AbortController();
140
154
  try {
141
155
  for await (const msg of runAgent({
142
156
  prompt: data.content,
143
157
  cwd,
158
+ sessionId,
144
159
  abortController
145
160
  })) {
146
- ws.send(JSON.stringify(msg));
161
+ if (msg.type === "chat_done" && msg.sessionId) {
162
+ sessionId = msg.sessionId;
163
+ }
164
+ safeSend(ws, msg);
147
165
  }
148
166
  } catch (err) {
149
167
  if (err.name !== "AbortError") {
150
- ws.send(JSON.stringify({
168
+ safeSend(ws, {
151
169
  type: "error",
152
170
  message: String(err)
153
- }));
171
+ });
154
172
  }
155
173
  } finally {
156
174
  abortController = null;
175
+ isProcessing = false;
157
176
  }
158
177
  }
159
178
  }
160
179
  };
161
180
  }));
162
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
163
- const clientDistPath = clientDistOverride ?? path.resolve(__dirname, "../../client/dist");
181
+ const __dirname2 = path.dirname(fileURLToPath(import.meta.url));
182
+ const clientDistPath = clientDistOverride ?? path.resolve(__dirname2, "../../client/dist");
164
183
  if (existsSync(clientDistPath)) {
165
184
  app.use("/*", serveStatic({
166
- root: path.relative(process.cwd(), clientDistPath)
185
+ root: clientDistPath,
186
+ rewriteRequestPath: (p) => p
167
187
  }));
168
188
  app.get("*", serveStatic({
169
- root: path.relative(process.cwd(), clientDistPath),
189
+ root: clientDistPath,
170
190
  rewriteRequestPath: () => "/index.html"
171
191
  }));
172
192
  }
173
- const server = serve({ fetch: app.fetch, port }, () => {
193
+ const server = serve({ fetch: app.fetch, port, hostname: "127.0.0.1" }, () => {
174
194
  console.log(` \u{1F310} http://localhost:${port}`);
175
195
  console.log(` Ready! Ask me anything about this codebase.
176
196
  `);
@@ -178,41 +198,41 @@ function createServer({ port, cwd, clientDistPath: clientDistOverride }) {
178
198
  injectWebSocket(server);
179
199
  return server;
180
200
  }
201
+ function safeSend(ws, data) {
202
+ if (ws.readyState === void 0 || ws.readyState === 1) {
203
+ try {
204
+ ws.send(JSON.stringify(data));
205
+ } catch {
206
+ }
207
+ }
208
+ }
181
209
 
182
210
  // src/cli.ts
211
+ var __dirname = path2.dirname(fileURLToPath2(import.meta.url));
212
+ var pkg = JSON.parse(
213
+ readFileSync(path2.resolve(__dirname, "../package.json"), "utf-8")
214
+ );
183
215
  var program = new Command();
184
- program.name("himotoku").version("0.1.0").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) => {
216
+ 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
217
  const cwd = path2.resolve(repoPath);
186
218
  if (!existsSync2(cwd)) {
187
219
  console.error(`Error: ${cwd} does not exist`);
188
220
  process.exit(1);
189
221
  }
190
- const fileCount = countFiles(cwd);
222
+ if (!statSync(cwd).isDirectory()) {
223
+ console.error(`Error: ${cwd} is not a directory`);
224
+ process.exit(1);
225
+ }
191
226
  const port = parseInt(opts.port, 10);
227
+ if (isNaN(port) || port < 1 || port > 65535) {
228
+ console.error(`Error: Invalid port number "${opts.port}". Must be 1-65535.`);
229
+ process.exit(1);
230
+ }
192
231
  console.log();
193
- console.log("\u{1F4DC} himotoku v0.1.0");
232
+ console.log(`\u{1F4DC} himotoku v${pkg.version}`);
194
233
  console.log(` Analyzing: ${cwd}`);
195
- console.log(` ${fileCount} files found`);
196
234
  console.log();
197
- const __dirname = path2.dirname(fileURLToPath2(import.meta.url));
198
235
  const clientDistPath = path2.resolve(__dirname, "../packages/client/dist");
199
236
  createServer({ port, cwd, clientDistPath });
200
237
  });
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
238
  program.parse();
package/package.json CHANGED
@@ -1,15 +1,18 @@
1
1
  {
2
2
  "name": "himotoku",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
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": "turbo build && node scripts/bundle.mjs",
11
- "dev": "turbo dev",
12
- "clean": "turbo clean"
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",