hotsheet 0.11.0 → 0.12.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/README.md +17 -5
- package/dist/channel.js +62 -8
- package/dist/cli.js +18033 -2945
- package/dist/client/app.global.js +48 -60
- package/dist/client/styles.css +1 -1
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -104,12 +104,19 @@ The loop stays tight because the AI always knows what to work on next.
|
|
|
104
104
|
<img src="docs/demo-8.png" alt="Stats dashboard showing throughput, flow, and cycle time charts" width="900">
|
|
105
105
|
</p>
|
|
106
106
|
|
|
107
|
+
**Multi-project tabs** — open multiple projects in a single window. Tabs appear automatically when you register a second project via the Open Folder dialog (`Cmd+O`). Drag tabs to reorder, right-click for close options and "Show in Finder," and switch with `Cmd+Shift+[/]`. Each project has independent settings, sort preferences, and views.
|
|
108
|
+
|
|
109
|
+
<p align="center">
|
|
110
|
+
<img src="docs/demo-10.png" alt="Multiple project tabs showing independent ticket lists with tab context menu" width="900">
|
|
111
|
+
</p>
|
|
112
|
+
|
|
107
113
|
**Also includes:**
|
|
108
114
|
- **Tags** — free-form tags on tickets, with autocomplete and a batch tag dialog for multi-select
|
|
109
|
-
- **Custom views** — create filtered views with an interactive query builder (field + operator + value conditions, AND/OR logic)
|
|
115
|
+
- **Custom views** — create filtered views with an interactive query builder (field + operator + value conditions, AND/OR logic). Associate a tag with a view to enable drag-and-drop tagging and auto-tag on create.
|
|
116
|
+
- **Custom ticket prefix** — change the default `HS-` prefix to any project-specific prefix in Settings
|
|
110
117
|
- **Five priority levels** — Highest to Lowest, with Lucide chevron icons, sortable and filterable
|
|
111
118
|
- **Up Next flag** — star tickets to add them to the AI worklist
|
|
112
|
-
- **Drag and drop** — drag tickets onto sidebar views to change category, priority, or status; drop files onto the detail panel to attach; reorder custom views
|
|
119
|
+
- **Drag and drop** — drag tickets onto sidebar views to change category, priority, or status; drop files onto the detail panel to attach; reorder project tabs and custom views
|
|
113
120
|
- **Right-click context menus** — full context menu on tickets with category/priority/status submenus, tags, duplicate, backlog, archive, delete — all with Lucide icons
|
|
114
121
|
- **Search** — full-text search across ticket titles, details, and ticket numbers
|
|
115
122
|
- **Print** — print the dashboard, all tickets, selected tickets, or individual tickets in checklist, summary, or full-detail format
|
|
@@ -154,7 +161,7 @@ Hot Sheet automatically generates skill files for Claude Code (as well as Cursor
|
|
|
154
161
|
|
|
155
162
|
### Claude Channel Integration (Experimental)
|
|
156
163
|
|
|
157
|
-
Hot Sheet can push events directly to a running Claude Code session via MCP channels. Enable it in Settings → Experimental
|
|
164
|
+
Hot Sheet can push events directly to a running Claude Code session via MCP channels. Enable it in Settings → Experimental:
|
|
158
165
|
|
|
159
166
|
- **Play button** — appears in the sidebar. Single-click sends the worklist to Claude on demand.
|
|
160
167
|
- **Auto mode** — double-click the play button to enable automatic mode. When you star a ticket for Up Next, Claude is notified after a 5-second debounce and picks up the work automatically. Exponential backoff prevents runaway retries.
|
|
@@ -316,8 +323,9 @@ Create `.hotsheet/settings.json` to configure per-project options:
|
|
|
316
323
|
|
|
317
324
|
| Key | Description |
|
|
318
325
|
|-----|-------------|
|
|
319
|
-
| `appName` | Custom window title (defaults to the project folder name) |
|
|
326
|
+
| `appName` | Custom window title and tab name (defaults to the project folder name) |
|
|
320
327
|
| `backupDir` | Backup storage path (defaults to `.hotsheet/backups/`) |
|
|
328
|
+
| `ticketPrefix` | Custom ticket number prefix (defaults to `HS`) |
|
|
321
329
|
| `appIcon` | Icon variant (`default`, `variant-1` through `variant-9`) |
|
|
322
330
|
|
|
323
331
|
All settings can also be changed from the settings panel UI.
|
|
@@ -338,6 +346,10 @@ All settings can also be changed from the settings panel UI.
|
|
|
338
346
|
| `Cmd+P` | Print |
|
|
339
347
|
| `Cmd+F` | Focus search |
|
|
340
348
|
| `Cmd+N` / `N` | Focus new ticket input |
|
|
349
|
+
| `Cmd+O` | Open folder (add project) |
|
|
350
|
+
| `Cmd+,` | Settings |
|
|
351
|
+
| `Cmd+Shift+[` / `]` | Switch project tab |
|
|
352
|
+
| `Cmd+Alt+W` | Close active tab |
|
|
341
353
|
| `Escape` | Blur field / clear selection / close |
|
|
342
354
|
|
|
343
355
|
---
|
|
@@ -352,7 +364,7 @@ All settings can also be changed from the settings panel UI.
|
|
|
352
364
|
| Database | PGLite (embedded PostgreSQL) |
|
|
353
365
|
| UI | Custom server-side JSX (no React), vanilla client JS |
|
|
354
366
|
| Charts | Inline SVG (no external chart library) |
|
|
355
|
-
| Build | tsup (
|
|
367
|
+
| Build | tsup (server + client bundles), sass (SCSS → CSS) |
|
|
356
368
|
| Storage | `.hotsheet/` in your project directory |
|
|
357
369
|
|
|
358
370
|
Data stays local. No network calls, no accounts, no telemetry.
|
package/dist/channel.js
CHANGED
|
@@ -33472,7 +33472,7 @@ var StdioServerTransport = class {
|
|
|
33472
33472
|
};
|
|
33473
33473
|
|
|
33474
33474
|
// src/channel.ts
|
|
33475
|
-
import { unlinkSync, writeFileSync } from "fs";
|
|
33475
|
+
import { readFileSync, unlinkSync, writeFileSync } from "fs";
|
|
33476
33476
|
import { createServer } from "http";
|
|
33477
33477
|
import { join } from "path";
|
|
33478
33478
|
var dataDir = ".hotsheet";
|
|
@@ -33514,15 +33514,17 @@ var PermissionRequestSchema = external_exports3.object({
|
|
|
33514
33514
|
})
|
|
33515
33515
|
});
|
|
33516
33516
|
mcp.setNotificationHandler(PermissionRequestSchema, ({ params }) => {
|
|
33517
|
+
const t0 = Date.now();
|
|
33517
33518
|
pendingPermission = {
|
|
33518
33519
|
request_id: params.request_id,
|
|
33519
33520
|
tool_name: params.tool_name,
|
|
33520
33521
|
description: params.description,
|
|
33521
33522
|
input_preview: params.input_preview,
|
|
33522
|
-
timestamp:
|
|
33523
|
+
timestamp: t0
|
|
33523
33524
|
};
|
|
33524
|
-
process.stderr.write(`
|
|
33525
|
+
process.stderr.write(`[perm ${t0}] received: ${params.tool_name} \u2014 ${params.description}
|
|
33525
33526
|
`);
|
|
33527
|
+
void notifyMainServer();
|
|
33526
33528
|
});
|
|
33527
33529
|
var httpServer = createServer(async (req, res) => {
|
|
33528
33530
|
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
@@ -33548,7 +33550,16 @@ var httpServer = createServer(async (req, res) => {
|
|
|
33548
33550
|
}
|
|
33549
33551
|
if (req.method === "POST" && req.url === "/permission/respond") {
|
|
33550
33552
|
let body = "";
|
|
33551
|
-
|
|
33553
|
+
let bodySize = 0;
|
|
33554
|
+
for await (const chunk of req) {
|
|
33555
|
+
bodySize += chunk.length;
|
|
33556
|
+
if (bodySize > 1048576) {
|
|
33557
|
+
res.writeHead(413);
|
|
33558
|
+
res.end("Payload too large");
|
|
33559
|
+
return;
|
|
33560
|
+
}
|
|
33561
|
+
body += String(chunk);
|
|
33562
|
+
}
|
|
33552
33563
|
try {
|
|
33553
33564
|
const { request_id, behavior } = JSON.parse(body);
|
|
33554
33565
|
await mcp.notification({
|
|
@@ -33574,7 +33585,16 @@ var httpServer = createServer(async (req, res) => {
|
|
|
33574
33585
|
}
|
|
33575
33586
|
if (req.method === "POST" && req.url === "/trigger") {
|
|
33576
33587
|
let body = "";
|
|
33577
|
-
|
|
33588
|
+
let bodySize = 0;
|
|
33589
|
+
for await (const chunk of req) {
|
|
33590
|
+
bodySize += chunk.length;
|
|
33591
|
+
if (bodySize > 1048576) {
|
|
33592
|
+
res.writeHead(413);
|
|
33593
|
+
res.end("Payload too large");
|
|
33594
|
+
return;
|
|
33595
|
+
}
|
|
33596
|
+
body += String(chunk);
|
|
33597
|
+
}
|
|
33578
33598
|
try {
|
|
33579
33599
|
await mcp.notification({
|
|
33580
33600
|
method: "notifications/claude/channel",
|
|
@@ -33594,6 +33614,33 @@ var httpServer = createServer(async (req, res) => {
|
|
|
33594
33614
|
res.writeHead(404);
|
|
33595
33615
|
res.end("not found");
|
|
33596
33616
|
});
|
|
33617
|
+
function notifyMainServer(abortSignal) {
|
|
33618
|
+
try {
|
|
33619
|
+
const settingsPath = join(dataDir, "settings.json");
|
|
33620
|
+
const settings = JSON.parse(readFileSync(settingsPath, "utf-8"));
|
|
33621
|
+
if (settings.port === void 0 || settings.port === 0) {
|
|
33622
|
+
process.stderr.write(`[notify] no port in settings.json
|
|
33623
|
+
`);
|
|
33624
|
+
return Promise.resolve();
|
|
33625
|
+
}
|
|
33626
|
+
const url2 = `http://localhost:${settings.port}/api/channel/notify`;
|
|
33627
|
+
const headers = { "Content-Type": "application/json" };
|
|
33628
|
+
if (settings.secret !== void 0 && settings.secret !== "") headers["X-Hotsheet-Secret"] = settings.secret;
|
|
33629
|
+
return fetch(url2, { method: "POST", headers, signal: abortSignal }).then((res) => {
|
|
33630
|
+
if (!res.ok) {
|
|
33631
|
+
process.stderr.write(`[notify] POST ${url2} returned ${res.status}
|
|
33632
|
+
`);
|
|
33633
|
+
}
|
|
33634
|
+
}).catch((err) => {
|
|
33635
|
+
process.stderr.write(`[notify] POST ${url2} failed: ${String(err)}
|
|
33636
|
+
`);
|
|
33637
|
+
});
|
|
33638
|
+
} catch (err) {
|
|
33639
|
+
process.stderr.write(`[notify] error reading settings: ${String(err)}
|
|
33640
|
+
`);
|
|
33641
|
+
return Promise.resolve();
|
|
33642
|
+
}
|
|
33643
|
+
}
|
|
33597
33644
|
httpServer.listen(0, "127.0.0.1", () => {
|
|
33598
33645
|
const addr = httpServer.address();
|
|
33599
33646
|
if (addr !== null && typeof addr !== "string") {
|
|
@@ -33604,17 +33651,24 @@ httpServer.listen(0, "127.0.0.1", () => {
|
|
|
33604
33651
|
}
|
|
33605
33652
|
process.stderr.write(`hotsheet-channel listening on port ${port}
|
|
33606
33653
|
`);
|
|
33654
|
+
void notifyMainServer();
|
|
33607
33655
|
}
|
|
33608
33656
|
});
|
|
33609
|
-
function cleanup() {
|
|
33657
|
+
async function cleanup() {
|
|
33610
33658
|
try {
|
|
33611
33659
|
unlinkSync(portFile);
|
|
33612
33660
|
} catch {
|
|
33613
33661
|
}
|
|
33662
|
+
try {
|
|
33663
|
+
const controller = new AbortController();
|
|
33664
|
+
setTimeout(() => controller.abort(), 1e3);
|
|
33665
|
+
await notifyMainServer(controller.signal);
|
|
33666
|
+
} catch {
|
|
33667
|
+
}
|
|
33614
33668
|
process.exit(0);
|
|
33615
33669
|
}
|
|
33616
|
-
process.on("SIGTERM", cleanup);
|
|
33617
|
-
process.on("SIGINT", cleanup);
|
|
33670
|
+
process.on("SIGTERM", () => void cleanup());
|
|
33671
|
+
process.on("SIGINT", () => void cleanup());
|
|
33618
33672
|
process.on("exit", () => {
|
|
33619
33673
|
try {
|
|
33620
33674
|
unlinkSync(portFile);
|