hotsheet 0.7.0 → 0.9.0
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/dist/cli.js +58 -25
- package/dist/client/app.global.js +45 -45
- package/dist/client/assets/icon-default.png +0 -0
- package/dist/client/assets/icon-variant-1.png +0 -0
- package/dist/client/assets/icon-variant-2.png +0 -0
- package/dist/client/assets/icon-variant-3.png +0 -0
- package/dist/client/assets/icon-variant-4.png +0 -0
- package/dist/client/assets/icon-variant-5.png +0 -0
- package/dist/client/assets/icon-variant-6.png +0 -0
- package/dist/client/assets/icon-variant-7.png +0 -0
- package/dist/client/assets/icon-variant-8.png +0 -0
- package/dist/client/assets/icon-variant-9.png +0 -0
- package/dist/client/styles.css +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -519,10 +519,17 @@ async function isChannelAlive(dataDir2) {
|
|
|
519
519
|
async function triggerChannel(dataDir2, serverPort, message) {
|
|
520
520
|
const port2 = getChannelPort(dataDir2);
|
|
521
521
|
if (!port2) return false;
|
|
522
|
+
let secretHeader = "";
|
|
523
|
+
try {
|
|
524
|
+
const { readFileSettings: readFileSettings2 } = await Promise.resolve().then(() => (init_file_settings(), file_settings_exports));
|
|
525
|
+
const settings = readFileSettings2(dataDir2);
|
|
526
|
+
if (settings.secret) secretHeader = ` -H "X-Hotsheet-Secret: ${settings.secret}"`;
|
|
527
|
+
} catch {
|
|
528
|
+
}
|
|
522
529
|
const doneSignal = `
|
|
523
530
|
|
|
524
531
|
When you are completely finished (or if there was nothing to do), signal completion by running:
|
|
525
|
-
curl -s -X POST http://localhost:${serverPort}/api/channel/done`;
|
|
532
|
+
curl -s -X POST http://localhost:${serverPort}/api/channel/done${secretHeader}`;
|
|
526
533
|
const content = message ? message + doneSignal : "Process the Hot Sheet worklist. Run /hotsheet to work through the current Up Next items." + doneSignal;
|
|
527
534
|
try {
|
|
528
535
|
const res = await fetch(`http://127.0.0.1:${port2}/trigger`, {
|
|
@@ -545,7 +552,7 @@ var init_channel_config = __esm({
|
|
|
545
552
|
// src/cli.ts
|
|
546
553
|
import { mkdirSync as mkdirSync6 } from "fs";
|
|
547
554
|
import { tmpdir } from "os";
|
|
548
|
-
import { join as join12, resolve as
|
|
555
|
+
import { join as join12, resolve as resolve4 } from "path";
|
|
549
556
|
|
|
550
557
|
// src/backup.ts
|
|
551
558
|
init_connection();
|
|
@@ -2387,7 +2394,7 @@ import { fileURLToPath as fileURLToPath2 } from "url";
|
|
|
2387
2394
|
// src/routes/api.ts
|
|
2388
2395
|
import { existsSync as existsSync7, mkdirSync as mkdirSync4, rmSync as rmSync5 } from "fs";
|
|
2389
2396
|
import { Hono } from "hono";
|
|
2390
|
-
import { basename, extname, join as join9, relative as relative2 } from "path";
|
|
2397
|
+
import { basename, extname, join as join9, relative as relative2, resolve as resolve3 } from "path";
|
|
2391
2398
|
|
|
2392
2399
|
// src/skills.ts
|
|
2393
2400
|
init_file_settings();
|
|
@@ -2879,8 +2886,8 @@ function notifyChange() {
|
|
|
2879
2886
|
changeVersion++;
|
|
2880
2887
|
const waiters = pollWaiters;
|
|
2881
2888
|
pollWaiters = [];
|
|
2882
|
-
for (const
|
|
2883
|
-
|
|
2889
|
+
for (const resolve5 of waiters) {
|
|
2890
|
+
resolve5(changeVersion);
|
|
2884
2891
|
}
|
|
2885
2892
|
}
|
|
2886
2893
|
apiRoutes.get("/poll", async (c) => {
|
|
@@ -2889,11 +2896,11 @@ apiRoutes.get("/poll", async (c) => {
|
|
|
2889
2896
|
return c.json({ version: changeVersion });
|
|
2890
2897
|
}
|
|
2891
2898
|
const version = await Promise.race([
|
|
2892
|
-
new Promise((
|
|
2893
|
-
pollWaiters.push(
|
|
2899
|
+
new Promise((resolve5) => {
|
|
2900
|
+
pollWaiters.push(resolve5);
|
|
2894
2901
|
}),
|
|
2895
|
-
new Promise((
|
|
2896
|
-
setTimeout(() =>
|
|
2902
|
+
new Promise((resolve5) => {
|
|
2903
|
+
setTimeout(() => resolve5(changeVersion), 3e4);
|
|
2897
2904
|
})
|
|
2898
2905
|
]);
|
|
2899
2906
|
return c.json({ version });
|
|
@@ -3133,7 +3140,11 @@ apiRoutes.post("/attachments/:id/reveal", async (c) => {
|
|
|
3133
3140
|
apiRoutes.get("/attachments/file/*", async (c) => {
|
|
3134
3141
|
const filePath = c.req.path.replace("/api/attachments/file/", "");
|
|
3135
3142
|
const dataDir2 = c.get("dataDir");
|
|
3136
|
-
const
|
|
3143
|
+
const attachDir = resolve3(join9(dataDir2, "attachments"));
|
|
3144
|
+
const fullPath = resolve3(join9(attachDir, filePath));
|
|
3145
|
+
if (!fullPath.startsWith(attachDir + "/") && fullPath !== attachDir) {
|
|
3146
|
+
return c.json({ error: "Invalid path" }, 403);
|
|
3147
|
+
}
|
|
3137
3148
|
if (!existsSync7(fullPath)) {
|
|
3138
3149
|
return c.json({ error: "File not found" }, 404);
|
|
3139
3150
|
}
|
|
@@ -3206,7 +3217,8 @@ apiRoutes.patch("/settings", async (c) => {
|
|
|
3206
3217
|
apiRoutes.get("/file-settings", async (c) => {
|
|
3207
3218
|
const { readFileSettings: readFileSettings2 } = await Promise.resolve().then(() => (init_file_settings(), file_settings_exports));
|
|
3208
3219
|
const dataDir2 = c.get("dataDir");
|
|
3209
|
-
|
|
3220
|
+
const { secret, secretPathHash, port: port2, ...safe } = readFileSettings2(dataDir2);
|
|
3221
|
+
return c.json(safe);
|
|
3210
3222
|
});
|
|
3211
3223
|
apiRoutes.patch("/file-settings", async (c) => {
|
|
3212
3224
|
const { writeFileSettings: writeFileSettings2 } = await Promise.resolve().then(() => (init_file_settings(), file_settings_exports));
|
|
@@ -3837,7 +3849,7 @@ pageRoutes.get("/", (c) => {
|
|
|
3837
3849
|
/* @__PURE__ */ jsx("div", { id: "detail-attachments", className: "detail-attachments" }),
|
|
3838
3850
|
/* @__PURE__ */ jsx("label", { className: "btn btn-sm upload-btn", children: [
|
|
3839
3851
|
"Attach File",
|
|
3840
|
-
/* @__PURE__ */ jsx("input", { type: "file", id: "detail-file-input", style: "display:none" })
|
|
3852
|
+
/* @__PURE__ */ jsx("input", { type: "file", id: "detail-file-input", style: "display:none", multiple: true })
|
|
3841
3853
|
] })
|
|
3842
3854
|
] }),
|
|
3843
3855
|
/* @__PURE__ */ jsx("div", { className: "detail-field detail-field-full", id: "detail-notes-section", children: [
|
|
@@ -3862,7 +3874,7 @@ pageRoutes.get("/", (c) => {
|
|
|
3862
3874
|
/* @__PURE__ */ jsx("kbd", { children: "Enter" }),
|
|
3863
3875
|
" new ticket"
|
|
3864
3876
|
] }),
|
|
3865
|
-
/* @__PURE__ */ jsx("span", { children: [
|
|
3877
|
+
/* @__PURE__ */ jsx("span", { "data-hint": "category", children: [
|
|
3866
3878
|
/* @__PURE__ */ jsx("kbd", { children: [
|
|
3867
3879
|
"\u2318",
|
|
3868
3880
|
"I/B/F/R/K/G"
|
|
@@ -3958,7 +3970,10 @@ pageRoutes.get("/", (c) => {
|
|
|
3958
3970
|
/* @__PURE__ */ jsx("div", { className: "settings-tab-panel active", "data-panel": "general", children: [
|
|
3959
3971
|
/* @__PURE__ */ jsx("div", { className: "settings-field", children: [
|
|
3960
3972
|
/* @__PURE__ */ jsx("label", { children: "App name" }),
|
|
3961
|
-
/* @__PURE__ */ jsx("
|
|
3973
|
+
/* @__PURE__ */ jsx("div", { className: "settings-app-name-row", children: [
|
|
3974
|
+
/* @__PURE__ */ jsx("button", { className: "app-icon-picker-btn", id: "app-icon-picker-btn", title: "Change app icon", children: /* @__PURE__ */ jsx("img", { id: "app-icon-preview", src: "/static/assets/icon-default.png", width: "28", height: "28" }) }),
|
|
3975
|
+
/* @__PURE__ */ jsx("input", { type: "text", id: "settings-app-name", placeholder: "Hot Sheet" })
|
|
3976
|
+
] }),
|
|
3962
3977
|
/* @__PURE__ */ jsx("span", { className: "settings-hint", id: "settings-app-name-hint", children: "Custom name shown in the title bar. Leave empty for default." })
|
|
3963
3978
|
] }),
|
|
3964
3979
|
/* @__PURE__ */ jsx("div", { className: "settings-field", children: [
|
|
@@ -4062,10 +4077,10 @@ pageRoutes.get("/", (c) => {
|
|
|
4062
4077
|
|
|
4063
4078
|
// src/server.ts
|
|
4064
4079
|
function tryServe(fetch2, port2) {
|
|
4065
|
-
return new Promise((
|
|
4080
|
+
return new Promise((resolve5, reject) => {
|
|
4066
4081
|
const server = serve({ fetch: fetch2, port: port2 });
|
|
4067
4082
|
server.on("listening", () => {
|
|
4068
|
-
|
|
4083
|
+
resolve5(port2);
|
|
4069
4084
|
});
|
|
4070
4085
|
server.on("error", (err) => {
|
|
4071
4086
|
reject(err);
|
|
@@ -4098,15 +4113,33 @@ async function startServer(port2, dataDir2, options) {
|
|
|
4098
4113
|
return new Response(content, { headers: { "Content-Type": mimeTypes[ext || ""] || "application/octet-stream", "Cache-Control": "max-age=86400" } });
|
|
4099
4114
|
});
|
|
4100
4115
|
app.use("/api/*", async (c, next) => {
|
|
4116
|
+
const settings = readFileSettings(dataDir2);
|
|
4117
|
+
const expectedSecret = settings.secret;
|
|
4118
|
+
if (!expectedSecret) {
|
|
4119
|
+
await next();
|
|
4120
|
+
return;
|
|
4121
|
+
}
|
|
4101
4122
|
const headerSecret = c.req.header("X-Hotsheet-Secret");
|
|
4123
|
+
const method = c.req.method;
|
|
4124
|
+
const isMutation = method === "POST" || method === "PATCH" || method === "PUT" || method === "DELETE";
|
|
4102
4125
|
if (headerSecret) {
|
|
4103
|
-
|
|
4104
|
-
if (settings.secret && headerSecret !== settings.secret) {
|
|
4126
|
+
if (headerSecret !== expectedSecret) {
|
|
4105
4127
|
return c.json({
|
|
4106
4128
|
error: "Secret mismatch \u2014 you may be connecting to the wrong Hot Sheet instance.",
|
|
4107
4129
|
recovery: "Re-read .hotsheet/settings.json to get the correct port and secret, and re-read your skill files (e.g. .claude/skills/hotsheet/SKILL.md) for updated instructions."
|
|
4108
4130
|
}, 403);
|
|
4109
4131
|
}
|
|
4132
|
+
} else if (isMutation) {
|
|
4133
|
+
const origin = c.req.header("Origin");
|
|
4134
|
+
const referer = c.req.header("Referer");
|
|
4135
|
+
const localhostPattern = /^https?:\/\/(localhost|127\.0\.0\.1)(:\d+)?(\/|$)/;
|
|
4136
|
+
const isSameOrigin = origin && localhostPattern.test(origin) || referer && localhostPattern.test(referer);
|
|
4137
|
+
if (!isSameOrigin) {
|
|
4138
|
+
return c.json({
|
|
4139
|
+
error: "Missing X-Hotsheet-Secret header. Read .hotsheet/settings.json for the correct port and secret.",
|
|
4140
|
+
recovery: "Re-read .hotsheet/settings.json to get the correct port and secret, and re-read your skill files for updated instructions."
|
|
4141
|
+
}, 403);
|
|
4142
|
+
}
|
|
4110
4143
|
}
|
|
4111
4144
|
await next();
|
|
4112
4145
|
});
|
|
@@ -4186,10 +4219,10 @@ function isFirstUseToday() {
|
|
|
4186
4219
|
return last !== today;
|
|
4187
4220
|
}
|
|
4188
4221
|
function fetchLatestVersion() {
|
|
4189
|
-
return new Promise((
|
|
4222
|
+
return new Promise((resolve5) => {
|
|
4190
4223
|
const req = get(`https://registry.npmjs.org/${PACKAGE_NAME}/latest`, { timeout: 5e3 }, (res) => {
|
|
4191
4224
|
if (res.statusCode !== 200) {
|
|
4192
|
-
|
|
4225
|
+
resolve5(null);
|
|
4193
4226
|
return;
|
|
4194
4227
|
}
|
|
4195
4228
|
let data = "";
|
|
@@ -4198,18 +4231,18 @@ function fetchLatestVersion() {
|
|
|
4198
4231
|
});
|
|
4199
4232
|
res.on("end", () => {
|
|
4200
4233
|
try {
|
|
4201
|
-
|
|
4234
|
+
resolve5(JSON.parse(data).version);
|
|
4202
4235
|
} catch {
|
|
4203
|
-
|
|
4236
|
+
resolve5(null);
|
|
4204
4237
|
}
|
|
4205
4238
|
});
|
|
4206
4239
|
});
|
|
4207
4240
|
req.on("error", () => {
|
|
4208
|
-
|
|
4241
|
+
resolve5(null);
|
|
4209
4242
|
});
|
|
4210
4243
|
req.on("timeout", () => {
|
|
4211
4244
|
req.destroy();
|
|
4212
|
-
|
|
4245
|
+
resolve5(null);
|
|
4213
4246
|
});
|
|
4214
4247
|
});
|
|
4215
4248
|
}
|
|
@@ -4311,7 +4344,7 @@ function parseArgs(argv) {
|
|
|
4311
4344
|
}
|
|
4312
4345
|
break;
|
|
4313
4346
|
case "--data-dir":
|
|
4314
|
-
dataDir2 =
|
|
4347
|
+
dataDir2 = resolve4(args[++i]);
|
|
4315
4348
|
break;
|
|
4316
4349
|
case "--check-for-updates":
|
|
4317
4350
|
forceUpdateCheck = true;
|