hotsheet 0.15.4 → 0.16.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 +553 -271
- package/dist/client/app.global.js +54 -53
- package/dist/client/styles.css +1 -1
- package/package.json +2 -1
package/dist/cli.js
CHANGED
|
@@ -15053,7 +15053,8 @@ function buildLogWhereClause(filters) {
|
|
|
15053
15053
|
}
|
|
15054
15054
|
if (filters?.search !== void 0 && filters.search !== "") {
|
|
15055
15055
|
conditions.push(`(summary ILIKE $${paramIdx} OR detail ILIKE $${paramIdx})`);
|
|
15056
|
-
|
|
15056
|
+
const escaped = filters.search.replace(/[%_\\]/g, "\\$&");
|
|
15057
|
+
params.push(`%${escaped}%`);
|
|
15057
15058
|
paramIdx++;
|
|
15058
15059
|
}
|
|
15059
15060
|
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
@@ -15337,6 +15338,9 @@ var init_tags = __esm({
|
|
|
15337
15338
|
});
|
|
15338
15339
|
|
|
15339
15340
|
// src/db/tickets.ts
|
|
15341
|
+
function escapeIlike(value) {
|
|
15342
|
+
return value.replace(/[%_\\]/g, "\\$&");
|
|
15343
|
+
}
|
|
15340
15344
|
async function nextTicketNumber(prefix = "HS") {
|
|
15341
15345
|
const db = await getDb();
|
|
15342
15346
|
const result = await db.query("SELECT nextval('ticket_seq')");
|
|
@@ -15482,7 +15486,7 @@ function buildTicketWhereClause(filters) {
|
|
|
15482
15486
|
}
|
|
15483
15487
|
if (filters.search !== void 0 && filters.search !== "") {
|
|
15484
15488
|
conditions.push(`(title ILIKE $${paramIdx} OR details ILIKE $${paramIdx} OR ticket_number ILIKE $${paramIdx} OR tags ILIKE $${paramIdx})`);
|
|
15485
|
-
values.push(`%${filters.search}%`);
|
|
15489
|
+
values.push(`%${escapeIlike(filters.search)}%`);
|
|
15486
15490
|
paramIdx++;
|
|
15487
15491
|
}
|
|
15488
15492
|
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
@@ -15625,12 +15629,12 @@ async function queryTickets(logic, conditions, sortBy, sortDir, requiredTag, inc
|
|
|
15625
15629
|
break;
|
|
15626
15630
|
case "contains":
|
|
15627
15631
|
userWhere.push(`${field} ILIKE $${paramIdx}`);
|
|
15628
|
-
values.push(`%${cond.value}%`);
|
|
15632
|
+
values.push(`%${escapeIlike(cond.value)}%`);
|
|
15629
15633
|
paramIdx++;
|
|
15630
15634
|
break;
|
|
15631
15635
|
case "not_contains":
|
|
15632
15636
|
userWhere.push(`${field} NOT ILIKE $${paramIdx}`);
|
|
15633
|
-
values.push(`%${cond.value}%`);
|
|
15637
|
+
values.push(`%${escapeIlike(cond.value)}%`);
|
|
15634
15638
|
paramIdx++;
|
|
15635
15639
|
break;
|
|
15636
15640
|
}
|
|
@@ -16340,7 +16344,7 @@ var init_skills = __esm({
|
|
|
16340
16344
|
"use strict";
|
|
16341
16345
|
init_file_settings();
|
|
16342
16346
|
init_types();
|
|
16343
|
-
SKILL_VERSION =
|
|
16347
|
+
SKILL_VERSION = 8;
|
|
16344
16348
|
skillCategories = DEFAULT_CATEGORIES;
|
|
16345
16349
|
HOTSHEET_ALLOW_PATTERNS = [
|
|
16346
16350
|
"Bash(curl * http://localhost:417*/api/*)",
|
|
@@ -16534,13 +16538,15 @@ async function buildWorkflowInstructions(port, secretHeader) {
|
|
|
16534
16538
|
sections.push("");
|
|
16535
16539
|
sections.push("## Requesting User Feedback");
|
|
16536
16540
|
sections.push("");
|
|
16537
|
-
sections.push("When you need input from the user before continuing, add a note with
|
|
16541
|
+
sections.push("When you need input from the user before continuing, add a note where the **entire note text begins** with one of these exact prefixes:");
|
|
16538
16542
|
sections.push("");
|
|
16539
|
-
sections.push("- **Standard feedback**:
|
|
16543
|
+
sections.push("- **Standard feedback**: `FEEDBACK NEEDED: Your question here`");
|
|
16540
16544
|
sections.push(` \`curl -s -X PATCH http://localhost:${port}/api/tickets/{id} -H "Content-Type: application/json"${secretHeader} -d '{"notes": "FEEDBACK NEEDED: Your question here"}'\``);
|
|
16541
|
-
sections.push("- **Urgent feedback** (auto-selects the ticket in the UI):
|
|
16545
|
+
sections.push("- **Urgent feedback** (auto-selects the ticket in the UI): `IMMEDIATE FEEDBACK NEEDED: Your question here`");
|
|
16542
16546
|
sections.push(` \`curl -s -X PATCH http://localhost:${port}/api/tickets/{id} -H "Content-Type: application/json"${secretHeader} -d '{"notes": "IMMEDIATE FEEDBACK NEEDED: Your urgent question"}'\``);
|
|
16543
16547
|
sections.push("");
|
|
16548
|
+
sections.push('**IMPORTANT:** The prefix must be the very first characters of the note \u2014 do not add any text before it. The note text sent in the `"notes"` field must start with `FEEDBACK NEEDED:` or `IMMEDIATE FEEDBACK NEEDED:` exactly.');
|
|
16549
|
+
sections.push("");
|
|
16544
16550
|
sections.push("After adding a feedback note, signal done and wait to be re-triggered. The user will see a dialog prompting them to respond. When they submit feedback, you will be re-triggered with a message indicating the ticket was updated.");
|
|
16545
16551
|
sections.push("");
|
|
16546
16552
|
sections.push('Only the most recent note is checked for feedback prefixes. Once the user responds (or clicks "No Response Needed"), the feedback state clears automatically.');
|
|
@@ -18121,6 +18127,150 @@ var init_notify = __esm({
|
|
|
18121
18127
|
}
|
|
18122
18128
|
});
|
|
18123
18129
|
|
|
18130
|
+
// src/routes/validation.ts
|
|
18131
|
+
function parseBody(schema, data) {
|
|
18132
|
+
const result = schema.safeParse(data);
|
|
18133
|
+
if (!result.success) {
|
|
18134
|
+
const messages = result.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).filter((m) => m !== ": ");
|
|
18135
|
+
return { success: false, error: messages.join("; ") || "Invalid request body" };
|
|
18136
|
+
}
|
|
18137
|
+
return { success: true, data: result.data };
|
|
18138
|
+
}
|
|
18139
|
+
var TicketPrioritySchema, TicketStatusSchema, SortBySchema, SortDirSchema, CreateTicketSchema, UpdateTicketSchema, BatchActionSchema, DuplicateSchema, NotesEditSchema, NotesBulkSchema, QueryTicketsSchema, UpdateSettingsSchema, BackupTierSchema, CreateBackupSchema, RestoreBackupSchema, ShellExecSchema, ShellKillSchema, ChannelTriggerSchema, PermissionRespondSchema, RegisterProjectSchema, ReorderProjectsSchema, CategoryDefSchema, UpdateCategoriesSchema, PrintSchema, GlobalConfigSchema, PluginActionSchema, PluginValidateSchema, PluginSyncScheduleSchema, PluginConflictResolveSchema, PluginInstallSchema, PluginGlobalConfigSchema, ChannelHeartbeatSchema;
|
|
18140
|
+
var init_validation = __esm({
|
|
18141
|
+
"src/routes/validation.ts"() {
|
|
18142
|
+
"use strict";
|
|
18143
|
+
init_zod();
|
|
18144
|
+
TicketPrioritySchema = external_exports.enum(["highest", "high", "default", "low", "lowest"]);
|
|
18145
|
+
TicketStatusSchema = external_exports.enum(["not_started", "started", "completed", "verified", "backlog", "archive", "deleted"]);
|
|
18146
|
+
SortBySchema = external_exports.enum(["created", "priority", "category", "status"]);
|
|
18147
|
+
SortDirSchema = external_exports.enum(["asc", "desc"]);
|
|
18148
|
+
CreateTicketSchema = external_exports.object({
|
|
18149
|
+
title: external_exports.string().optional().default(""),
|
|
18150
|
+
defaults: external_exports.object({
|
|
18151
|
+
category: external_exports.string().optional(),
|
|
18152
|
+
priority: TicketPrioritySchema.or(external_exports.literal("")).optional(),
|
|
18153
|
+
status: TicketStatusSchema.or(external_exports.literal("")).optional(),
|
|
18154
|
+
up_next: external_exports.boolean().optional(),
|
|
18155
|
+
details: external_exports.string().optional(),
|
|
18156
|
+
tags: external_exports.string().optional()
|
|
18157
|
+
}).optional()
|
|
18158
|
+
});
|
|
18159
|
+
UpdateTicketSchema = external_exports.object({
|
|
18160
|
+
title: external_exports.string().optional(),
|
|
18161
|
+
details: external_exports.string().optional(),
|
|
18162
|
+
notes: external_exports.string().optional(),
|
|
18163
|
+
tags: external_exports.string().optional(),
|
|
18164
|
+
category: external_exports.string().optional(),
|
|
18165
|
+
priority: TicketPrioritySchema.optional(),
|
|
18166
|
+
status: TicketStatusSchema.optional(),
|
|
18167
|
+
up_next: external_exports.boolean().optional(),
|
|
18168
|
+
last_read_at: external_exports.string().nullable().optional()
|
|
18169
|
+
});
|
|
18170
|
+
BatchActionSchema = external_exports.object({
|
|
18171
|
+
ids: external_exports.array(external_exports.number().int()),
|
|
18172
|
+
action: external_exports.enum(["delete", "restore", "category", "priority", "status", "up_next", "mark_read", "mark_unread"]),
|
|
18173
|
+
value: external_exports.union([external_exports.string(), external_exports.boolean()]).optional()
|
|
18174
|
+
});
|
|
18175
|
+
DuplicateSchema = external_exports.object({
|
|
18176
|
+
ids: external_exports.array(external_exports.number().int())
|
|
18177
|
+
});
|
|
18178
|
+
NotesEditSchema = external_exports.object({
|
|
18179
|
+
text: external_exports.string()
|
|
18180
|
+
});
|
|
18181
|
+
NotesBulkSchema = external_exports.object({
|
|
18182
|
+
notes: external_exports.string()
|
|
18183
|
+
});
|
|
18184
|
+
QueryTicketsSchema = external_exports.object({
|
|
18185
|
+
logic: external_exports.enum(["all", "any"]),
|
|
18186
|
+
conditions: external_exports.array(external_exports.object({
|
|
18187
|
+
field: external_exports.enum(["category", "priority", "status", "title", "details", "up_next", "tags"]),
|
|
18188
|
+
operator: external_exports.enum(["equals", "not_equals", "contains", "not_contains", "lt", "lte", "gt", "gte"]),
|
|
18189
|
+
value: external_exports.string()
|
|
18190
|
+
})),
|
|
18191
|
+
sort_by: external_exports.string().optional(),
|
|
18192
|
+
sort_dir: SortDirSchema.optional(),
|
|
18193
|
+
required_tag: external_exports.string().optional(),
|
|
18194
|
+
include_archived: external_exports.boolean().optional()
|
|
18195
|
+
});
|
|
18196
|
+
UpdateSettingsSchema = external_exports.record(external_exports.string(), external_exports.string());
|
|
18197
|
+
BackupTierSchema = external_exports.enum(["5min", "hourly", "daily"]);
|
|
18198
|
+
CreateBackupSchema = external_exports.object({
|
|
18199
|
+
tier: BackupTierSchema
|
|
18200
|
+
});
|
|
18201
|
+
RestoreBackupSchema = external_exports.object({
|
|
18202
|
+
tier: external_exports.string().min(1),
|
|
18203
|
+
filename: external_exports.string().min(1)
|
|
18204
|
+
});
|
|
18205
|
+
ShellExecSchema = external_exports.object({
|
|
18206
|
+
command: external_exports.string().min(1, "Command cannot be empty"),
|
|
18207
|
+
name: external_exports.string().optional()
|
|
18208
|
+
});
|
|
18209
|
+
ShellKillSchema = external_exports.object({
|
|
18210
|
+
id: external_exports.number().int()
|
|
18211
|
+
});
|
|
18212
|
+
ChannelTriggerSchema = external_exports.object({
|
|
18213
|
+
message: external_exports.string().optional()
|
|
18214
|
+
});
|
|
18215
|
+
PermissionRespondSchema = external_exports.object({
|
|
18216
|
+
request_id: external_exports.string(),
|
|
18217
|
+
behavior: external_exports.enum(["allow", "deny"]),
|
|
18218
|
+
tool_name: external_exports.string().optional()
|
|
18219
|
+
});
|
|
18220
|
+
RegisterProjectSchema = external_exports.object({
|
|
18221
|
+
dataDir: external_exports.string().min(1, "dataDir is required")
|
|
18222
|
+
});
|
|
18223
|
+
ReorderProjectsSchema = external_exports.object({
|
|
18224
|
+
secrets: external_exports.array(external_exports.string())
|
|
18225
|
+
});
|
|
18226
|
+
CategoryDefSchema = external_exports.object({
|
|
18227
|
+
id: external_exports.string().min(1),
|
|
18228
|
+
label: external_exports.string().min(1),
|
|
18229
|
+
shortLabel: external_exports.string().min(1),
|
|
18230
|
+
color: external_exports.string().min(1),
|
|
18231
|
+
shortcutKey: external_exports.string(),
|
|
18232
|
+
description: external_exports.string()
|
|
18233
|
+
}).loose();
|
|
18234
|
+
UpdateCategoriesSchema = external_exports.array(CategoryDefSchema).min(1);
|
|
18235
|
+
PrintSchema = external_exports.object({
|
|
18236
|
+
html: external_exports.string()
|
|
18237
|
+
});
|
|
18238
|
+
GlobalConfigSchema = external_exports.object({
|
|
18239
|
+
channelEnabled: external_exports.boolean().optional(),
|
|
18240
|
+
shareTotalSeconds: external_exports.number().optional(),
|
|
18241
|
+
shareLastPrompted: external_exports.string().optional(),
|
|
18242
|
+
shareAccepted: external_exports.boolean().optional()
|
|
18243
|
+
}).strict();
|
|
18244
|
+
PluginActionSchema = external_exports.object({
|
|
18245
|
+
actionId: external_exports.string(),
|
|
18246
|
+
ticketIds: external_exports.array(external_exports.number().int()).optional(),
|
|
18247
|
+
value: external_exports.unknown().optional()
|
|
18248
|
+
});
|
|
18249
|
+
PluginValidateSchema = external_exports.object({
|
|
18250
|
+
key: external_exports.string(),
|
|
18251
|
+
value: external_exports.string()
|
|
18252
|
+
});
|
|
18253
|
+
PluginSyncScheduleSchema = external_exports.object({
|
|
18254
|
+
interval_minutes: external_exports.number().nullable()
|
|
18255
|
+
});
|
|
18256
|
+
PluginConflictResolveSchema = external_exports.object({
|
|
18257
|
+
plugin_id: external_exports.string().min(1, "plugin_id is required"),
|
|
18258
|
+
resolution: external_exports.enum(["keep_local", "keep_remote"])
|
|
18259
|
+
});
|
|
18260
|
+
PluginInstallSchema = external_exports.object({
|
|
18261
|
+
path: external_exports.string().min(1, "path is required")
|
|
18262
|
+
});
|
|
18263
|
+
PluginGlobalConfigSchema = external_exports.object({
|
|
18264
|
+
key: external_exports.string().min(1, "key is required"),
|
|
18265
|
+
value: external_exports.string()
|
|
18266
|
+
});
|
|
18267
|
+
ChannelHeartbeatSchema = external_exports.object({
|
|
18268
|
+
projectDir: external_exports.string().optional(),
|
|
18269
|
+
state: external_exports.enum(["busy", "idle", "heartbeat"]).optional()
|
|
18270
|
+
});
|
|
18271
|
+
}
|
|
18272
|
+
});
|
|
18273
|
+
|
|
18124
18274
|
// src/channel-config.ts
|
|
18125
18275
|
var channel_config_exports = {};
|
|
18126
18276
|
__export(channel_config_exports, {
|
|
@@ -18339,6 +18489,129 @@ var init_global_config = __esm({
|
|
|
18339
18489
|
}
|
|
18340
18490
|
});
|
|
18341
18491
|
|
|
18492
|
+
// src/claude-hooks.ts
|
|
18493
|
+
var claude_hooks_exports = {};
|
|
18494
|
+
__export(claude_hooks_exports, {
|
|
18495
|
+
installHeartbeatHook: () => installHeartbeatHook,
|
|
18496
|
+
isHeartbeatHookInstalled: () => isHeartbeatHookInstalled,
|
|
18497
|
+
removeHeartbeatHook: () => removeHeartbeatHook
|
|
18498
|
+
});
|
|
18499
|
+
import { copyFileSync, existsSync as existsSync11, mkdirSync as mkdirSync8, readFileSync as readFileSync10, writeFileSync as writeFileSync10 } from "fs";
|
|
18500
|
+
import { homedir as homedir4 } from "os";
|
|
18501
|
+
import { join as join13 } from "path";
|
|
18502
|
+
function getClaudeSettingsPath() {
|
|
18503
|
+
return join13(homedir4(), ".claude", "settings.json");
|
|
18504
|
+
}
|
|
18505
|
+
function readClaudeSettings() {
|
|
18506
|
+
const path = getClaudeSettingsPath();
|
|
18507
|
+
if (!existsSync11(path)) return {};
|
|
18508
|
+
try {
|
|
18509
|
+
return JSON.parse(readFileSync10(path, "utf-8"));
|
|
18510
|
+
} catch {
|
|
18511
|
+
return {};
|
|
18512
|
+
}
|
|
18513
|
+
}
|
|
18514
|
+
function writeClaudeSettings(settings) {
|
|
18515
|
+
const path = getClaudeSettingsPath();
|
|
18516
|
+
mkdirSync8(join13(homedir4(), ".claude"), { recursive: true });
|
|
18517
|
+
if (existsSync11(path)) {
|
|
18518
|
+
copyFileSync(path, path + ".bak");
|
|
18519
|
+
}
|
|
18520
|
+
writeFileSync10(path, JSON.stringify(settings, null, 2) + "\n", "utf-8");
|
|
18521
|
+
}
|
|
18522
|
+
function isHeartbeatHookInstalled() {
|
|
18523
|
+
const settings = readClaudeSettings();
|
|
18524
|
+
if (!settings.hooks) return false;
|
|
18525
|
+
for (const groups of Object.values(settings.hooks)) {
|
|
18526
|
+
if (!Array.isArray(groups)) continue;
|
|
18527
|
+
for (const group of groups) {
|
|
18528
|
+
if (group.hooks.some((h) => h.command.includes(HOOK_MARKER))) return true;
|
|
18529
|
+
}
|
|
18530
|
+
}
|
|
18531
|
+
return false;
|
|
18532
|
+
}
|
|
18533
|
+
function makeCommand(port, state) {
|
|
18534
|
+
return `curl -s -X POST http://localhost:${port}/api/channel/heartbeat -H "Content-Type: application/json" -d '{"projectDir":"'$CLAUDE_PROJECT_DIR'","state":"${state}"}' >/dev/null 2>&1 & # ${HOOK_MARKER}`;
|
|
18535
|
+
}
|
|
18536
|
+
function installHeartbeatHook(port) {
|
|
18537
|
+
if (isHeartbeatHookInstalled()) {
|
|
18538
|
+
updateHeartbeatHookPort(port);
|
|
18539
|
+
return;
|
|
18540
|
+
}
|
|
18541
|
+
const settings = readClaudeSettings();
|
|
18542
|
+
if (!settings.hooks) settings.hooks = {};
|
|
18543
|
+
const hookDefs = [
|
|
18544
|
+
{ event: "PostToolUse", state: "heartbeat" },
|
|
18545
|
+
{ event: "UserPromptSubmit", state: "busy" },
|
|
18546
|
+
{ event: "Stop", state: "idle" }
|
|
18547
|
+
];
|
|
18548
|
+
for (const def of hookDefs) {
|
|
18549
|
+
if (!Array.isArray(settings.hooks[def.event])) settings.hooks[def.event] = [];
|
|
18550
|
+
settings.hooks[def.event].push({
|
|
18551
|
+
hooks: [{ "//": "Hot Sheet", type: "command", command: makeCommand(port, def.state), timeout: 5 }]
|
|
18552
|
+
});
|
|
18553
|
+
}
|
|
18554
|
+
writeClaudeSettings(settings);
|
|
18555
|
+
console.log("[hooks] Installed Claude Code hooks (PostToolUse, UserPromptSubmit, Stop)");
|
|
18556
|
+
}
|
|
18557
|
+
function updateHeartbeatHookPort(port) {
|
|
18558
|
+
const settings = readClaudeSettings();
|
|
18559
|
+
if (!settings.hooks) return;
|
|
18560
|
+
let changed = false;
|
|
18561
|
+
for (const groups of Object.values(settings.hooks)) {
|
|
18562
|
+
if (!Array.isArray(groups)) continue;
|
|
18563
|
+
for (const group of groups) {
|
|
18564
|
+
for (const hook of group.hooks) {
|
|
18565
|
+
if (hook.command.includes(HOOK_MARKER)) {
|
|
18566
|
+
const updated = hook.command.replace(/localhost:\d+/, `localhost:${port}`);
|
|
18567
|
+
if (updated !== hook.command) {
|
|
18568
|
+
hook.command = updated;
|
|
18569
|
+
changed = true;
|
|
18570
|
+
}
|
|
18571
|
+
if (hook["//"] === void 0) {
|
|
18572
|
+
hook["//"] = "Hot Sheet";
|
|
18573
|
+
changed = true;
|
|
18574
|
+
}
|
|
18575
|
+
}
|
|
18576
|
+
}
|
|
18577
|
+
}
|
|
18578
|
+
}
|
|
18579
|
+
if (changed) {
|
|
18580
|
+
writeClaudeSettings(settings);
|
|
18581
|
+
console.log(`[hooks] Updated heartbeat hook port to ${port}`);
|
|
18582
|
+
}
|
|
18583
|
+
}
|
|
18584
|
+
function removeHeartbeatHook() {
|
|
18585
|
+
const settings = readClaudeSettings();
|
|
18586
|
+
if (!settings.hooks) return;
|
|
18587
|
+
let changed = false;
|
|
18588
|
+
for (const [event, groups] of Object.entries(settings.hooks)) {
|
|
18589
|
+
if (!Array.isArray(groups)) continue;
|
|
18590
|
+
const filtered = groups.filter(
|
|
18591
|
+
(group) => !group.hooks.some((h) => h.command.includes(HOOK_MARKER))
|
|
18592
|
+
);
|
|
18593
|
+
if (filtered.length !== groups.length) {
|
|
18594
|
+
changed = true;
|
|
18595
|
+
if (filtered.length === 0) {
|
|
18596
|
+
Reflect.deleteProperty(settings.hooks, event);
|
|
18597
|
+
} else {
|
|
18598
|
+
settings.hooks[event] = filtered;
|
|
18599
|
+
}
|
|
18600
|
+
}
|
|
18601
|
+
}
|
|
18602
|
+
if (!changed) return;
|
|
18603
|
+
if (Object.keys(settings.hooks).length === 0) delete settings.hooks;
|
|
18604
|
+
writeClaudeSettings(settings);
|
|
18605
|
+
console.log("[hooks] Removed Claude Code heartbeat hooks");
|
|
18606
|
+
}
|
|
18607
|
+
var HOOK_MARKER;
|
|
18608
|
+
var init_claude_hooks = __esm({
|
|
18609
|
+
"src/claude-hooks.ts"() {
|
|
18610
|
+
"use strict";
|
|
18611
|
+
HOOK_MARKER = "hotsheet-heartbeat";
|
|
18612
|
+
}
|
|
18613
|
+
});
|
|
18614
|
+
|
|
18342
18615
|
// src/db/sync.ts
|
|
18343
18616
|
async function getSyncRecord(ticketId, pluginId) {
|
|
18344
18617
|
const db = await getDb();
|
|
@@ -18629,6 +18902,7 @@ var init_keychain = __esm({
|
|
|
18629
18902
|
// src/plugins/loader.ts
|
|
18630
18903
|
var loader_exports = {};
|
|
18631
18904
|
__export(loader_exports, {
|
|
18905
|
+
compareSemver: () => compareSemver,
|
|
18632
18906
|
disablePlugin: () => disablePlugin,
|
|
18633
18907
|
discoverPlugins: () => discoverPlugins,
|
|
18634
18908
|
dismissBundledPlugin: () => dismissBundledPlugin,
|
|
@@ -18651,10 +18925,19 @@ __export(loader_exports, {
|
|
|
18651
18925
|
unloadAllPlugins: () => unloadAllPlugins,
|
|
18652
18926
|
unregisterPlugin: () => unregisterPlugin
|
|
18653
18927
|
});
|
|
18654
|
-
import { cpSync, existsSync as
|
|
18655
|
-
import { homedir as
|
|
18656
|
-
import { dirname as dirname3, join as
|
|
18928
|
+
import { cpSync, existsSync as existsSync13, mkdirSync as mkdirSync9, readdirSync as readdirSync3, readFileSync as readFileSync11, rmSync as rmSync7, statSync as statSync2, writeFileSync as writeFileSync12 } from "fs";
|
|
18929
|
+
import { homedir as homedir6 } from "os";
|
|
18930
|
+
import { dirname as dirname3, join as join15 } from "path";
|
|
18657
18931
|
import { fileURLToPath as fileURLToPath2, pathToFileURL } from "url";
|
|
18932
|
+
function compareSemver(a, b) {
|
|
18933
|
+
const pa = a.split(".").map(Number);
|
|
18934
|
+
const pb = b.split(".").map(Number);
|
|
18935
|
+
for (let i = 0; i < 3; i++) {
|
|
18936
|
+
if ((pa[i] || 0) < (pb[i] || 0)) return -1;
|
|
18937
|
+
if ((pa[i] || 0) > (pb[i] || 0)) return 1;
|
|
18938
|
+
}
|
|
18939
|
+
return 0;
|
|
18940
|
+
}
|
|
18658
18941
|
function getConfigLabelOverride(pluginId, labelId) {
|
|
18659
18942
|
return configLabelOverrides.get(`${pluginId}:${labelId}`);
|
|
18660
18943
|
}
|
|
@@ -18686,15 +18969,15 @@ function getAllBackends() {
|
|
|
18686
18969
|
return getLoadedPlugins().filter((p) => p.backend !== null && p.enabled).map((p) => p.backend);
|
|
18687
18970
|
}
|
|
18688
18971
|
function getPluginDir() {
|
|
18689
|
-
return
|
|
18972
|
+
return join15(homedir6(), ".hotsheet", "plugins");
|
|
18690
18973
|
}
|
|
18691
18974
|
function discoverPlugins() {
|
|
18692
18975
|
const pluginDir = getPluginDir();
|
|
18693
|
-
if (!
|
|
18976
|
+
if (!existsSync13(pluginDir)) return [];
|
|
18694
18977
|
const results = [];
|
|
18695
18978
|
for (const entry of readdirSync3(pluginDir, { withFileTypes: true })) {
|
|
18696
18979
|
if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
|
|
18697
|
-
const pluginPath =
|
|
18980
|
+
const pluginPath = join15(pluginDir, entry.name);
|
|
18698
18981
|
if (entry.isSymbolicLink()) {
|
|
18699
18982
|
try {
|
|
18700
18983
|
if (!statSync2(pluginPath).isDirectory()) continue;
|
|
@@ -18708,20 +18991,20 @@ function discoverPlugins() {
|
|
|
18708
18991
|
return results;
|
|
18709
18992
|
}
|
|
18710
18993
|
function readManifest(pluginPath) {
|
|
18711
|
-
const manifestPath =
|
|
18712
|
-
if (
|
|
18994
|
+
const manifestPath = join15(pluginPath, "manifest.json");
|
|
18995
|
+
if (existsSync13(manifestPath)) {
|
|
18713
18996
|
try {
|
|
18714
|
-
const raw = JSON.parse(
|
|
18997
|
+
const raw = JSON.parse(readFileSync11(manifestPath, "utf-8"));
|
|
18715
18998
|
return validateManifest(raw);
|
|
18716
18999
|
} catch (e) {
|
|
18717
19000
|
console.warn(`[plugins] Invalid manifest.json in ${pluginPath}: ${getErrorMessage(e)}`);
|
|
18718
19001
|
return null;
|
|
18719
19002
|
}
|
|
18720
19003
|
}
|
|
18721
|
-
const pkgPath =
|
|
18722
|
-
if (
|
|
19004
|
+
const pkgPath = join15(pluginPath, "package.json");
|
|
19005
|
+
if (existsSync13(pkgPath)) {
|
|
18723
19006
|
try {
|
|
18724
|
-
const pkg = JSON.parse(
|
|
19007
|
+
const pkg = JSON.parse(readFileSync11(pkgPath, "utf-8"));
|
|
18725
19008
|
const hotsheet = pkg.hotsheet;
|
|
18726
19009
|
if (hotsheet != null) {
|
|
18727
19010
|
const author = pkg.author;
|
|
@@ -18747,13 +19030,13 @@ function validateManifest(raw) {
|
|
|
18747
19030
|
return result.data;
|
|
18748
19031
|
}
|
|
18749
19032
|
function getDismissedPluginsPath() {
|
|
18750
|
-
return
|
|
19033
|
+
return join15(homedir6(), ".hotsheet", "dismissed-plugins.json");
|
|
18751
19034
|
}
|
|
18752
19035
|
function getDismissedPlugins() {
|
|
18753
19036
|
const path = getDismissedPluginsPath();
|
|
18754
|
-
if (!
|
|
19037
|
+
if (!existsSync13(path)) return /* @__PURE__ */ new Set();
|
|
18755
19038
|
try {
|
|
18756
|
-
const result = external_exports.array(external_exports.string()).safeParse(JSON.parse(
|
|
19039
|
+
const result = external_exports.array(external_exports.string()).safeParse(JSON.parse(readFileSync11(path, "utf-8")));
|
|
18757
19040
|
return result.success ? new Set(result.data) : /* @__PURE__ */ new Set();
|
|
18758
19041
|
} catch {
|
|
18759
19042
|
return /* @__PURE__ */ new Set();
|
|
@@ -18763,19 +19046,19 @@ function dismissBundledPlugin(pluginId) {
|
|
|
18763
19046
|
const dismissed = getDismissedPlugins();
|
|
18764
19047
|
dismissed.add(pluginId);
|
|
18765
19048
|
const path = getDismissedPluginsPath();
|
|
18766
|
-
|
|
18767
|
-
|
|
19049
|
+
mkdirSync9(dirname3(path), { recursive: true });
|
|
19050
|
+
writeFileSync12(path, JSON.stringify([...dismissed]));
|
|
18768
19051
|
}
|
|
18769
19052
|
function undismissBundledPlugin(pluginId) {
|
|
18770
19053
|
const dismissed = getDismissedPlugins();
|
|
18771
19054
|
dismissed.delete(pluginId);
|
|
18772
|
-
|
|
19055
|
+
writeFileSync12(getDismissedPluginsPath(), JSON.stringify([...dismissed]));
|
|
18773
19056
|
}
|
|
18774
19057
|
function getBundledDir() {
|
|
18775
19058
|
const selfDir = dirname3(fileURLToPath2(import.meta.url));
|
|
18776
|
-
let bundledDir =
|
|
18777
|
-
if (!
|
|
18778
|
-
return
|
|
19059
|
+
let bundledDir = join15(selfDir, "plugins");
|
|
19060
|
+
if (!existsSync13(bundledDir)) bundledDir = join15(process.cwd(), "dist", "plugins");
|
|
19061
|
+
return existsSync13(bundledDir) ? bundledDir : null;
|
|
18779
19062
|
}
|
|
18780
19063
|
function listBundledPlugins() {
|
|
18781
19064
|
const bundledDir = getBundledDir();
|
|
@@ -18784,7 +19067,7 @@ function listBundledPlugins() {
|
|
|
18784
19067
|
const results = [];
|
|
18785
19068
|
for (const entry of readdirSync3(bundledDir, { withFileTypes: true })) {
|
|
18786
19069
|
if (!entry.isDirectory()) continue;
|
|
18787
|
-
const manifest = readManifest(
|
|
19070
|
+
const manifest = readManifest(join15(bundledDir, entry.name));
|
|
18788
19071
|
if (!manifest) continue;
|
|
18789
19072
|
results.push({
|
|
18790
19073
|
manifest,
|
|
@@ -18798,15 +19081,15 @@ function installBundledPlugin(pluginId) {
|
|
|
18798
19081
|
const bundledDir = getBundledDir();
|
|
18799
19082
|
if (bundledDir == null) return false;
|
|
18800
19083
|
const pluginDir = getPluginDir();
|
|
18801
|
-
|
|
19084
|
+
mkdirSync9(pluginDir, { recursive: true });
|
|
18802
19085
|
for (const entry of readdirSync3(bundledDir, { withFileTypes: true })) {
|
|
18803
19086
|
if (!entry.isDirectory()) continue;
|
|
18804
|
-
const manifest = readManifest(
|
|
19087
|
+
const manifest = readManifest(join15(bundledDir, entry.name));
|
|
18805
19088
|
if (!manifest || manifest.id !== pluginId) continue;
|
|
18806
|
-
const targetPath =
|
|
19089
|
+
const targetPath = join15(pluginDir, entry.name);
|
|
18807
19090
|
try {
|
|
18808
|
-
if (
|
|
18809
|
-
cpSync(
|
|
19091
|
+
if (existsSync13(targetPath)) rmSync7(targetPath, { recursive: true, force: true });
|
|
19092
|
+
cpSync(join15(bundledDir, entry.name), targetPath, { recursive: true, force: true });
|
|
18810
19093
|
undismissBundledPlugin(pluginId);
|
|
18811
19094
|
console.log(`[plugins] Installed bundled plugin: ${manifest.name}`);
|
|
18812
19095
|
return true;
|
|
@@ -18821,21 +19104,21 @@ function installBundledPlugins() {
|
|
|
18821
19104
|
const bundledDir = getBundledDir();
|
|
18822
19105
|
if (bundledDir == null) return;
|
|
18823
19106
|
const pluginDir = getPluginDir();
|
|
18824
|
-
|
|
19107
|
+
mkdirSync9(pluginDir, { recursive: true });
|
|
18825
19108
|
const dismissed = getDismissedPlugins();
|
|
18826
19109
|
for (const entry of readdirSync3(bundledDir, { withFileTypes: true })) {
|
|
18827
19110
|
if (!entry.isDirectory()) continue;
|
|
18828
|
-
const bundledManifestCheck = readManifest(
|
|
19111
|
+
const bundledManifestCheck = readManifest(join15(bundledDir, entry.name));
|
|
18829
19112
|
if (bundledManifestCheck && dismissed.has(bundledManifestCheck.id)) continue;
|
|
18830
|
-
const targetPath =
|
|
18831
|
-
const sourcePath =
|
|
18832
|
-
if (
|
|
19113
|
+
const targetPath = join15(pluginDir, entry.name);
|
|
19114
|
+
const sourcePath = join15(bundledDir, entry.name);
|
|
19115
|
+
if (existsSync13(targetPath)) {
|
|
18833
19116
|
const installedManifest = readManifest(targetPath);
|
|
18834
19117
|
const bundledManifest = readManifest(sourcePath);
|
|
18835
19118
|
if (installedManifest && bundledManifest) {
|
|
18836
19119
|
const entryFile = installedManifest.entry ?? "index.js";
|
|
18837
|
-
const entryExists =
|
|
18838
|
-
if (entryExists && installedManifest.version
|
|
19120
|
+
const entryExists = existsSync13(join15(targetPath, entryFile));
|
|
19121
|
+
if (entryExists && compareSemver(installedManifest.version, bundledManifest.version) >= 0) {
|
|
18839
19122
|
continue;
|
|
18840
19123
|
}
|
|
18841
19124
|
}
|
|
@@ -18864,8 +19147,8 @@ async function loadAllPlugins(enabledPlugins) {
|
|
|
18864
19147
|
}
|
|
18865
19148
|
async function loadPlugin(pluginPath, manifest, enabled) {
|
|
18866
19149
|
const entry = manifest.entry ?? "index.js";
|
|
18867
|
-
const entryPath =
|
|
18868
|
-
if (!
|
|
19150
|
+
const entryPath = join15(pluginPath, entry);
|
|
19151
|
+
if (!existsSync13(entryPath)) {
|
|
18869
19152
|
console.warn(`[plugins] Plugin ${manifest.id}: entry point ${entry} not found`);
|
|
18870
19153
|
loadedPlugins.set(manifest.id, {
|
|
18871
19154
|
manifest,
|
|
@@ -18916,13 +19199,13 @@ async function loadPlugin(pluginPath, manifest, enabled) {
|
|
|
18916
19199
|
}
|
|
18917
19200
|
}
|
|
18918
19201
|
function getGlobalConfigPath() {
|
|
18919
|
-
return
|
|
19202
|
+
return join15(homedir6(), ".hotsheet", "plugin-config.json");
|
|
18920
19203
|
}
|
|
18921
19204
|
function readGlobalConfig2() {
|
|
18922
19205
|
const configPath = getGlobalConfigPath();
|
|
18923
|
-
if (!
|
|
19206
|
+
if (!existsSync13(configPath)) return {};
|
|
18924
19207
|
try {
|
|
18925
|
-
const result = external_exports.record(external_exports.string(), external_exports.record(external_exports.string(), external_exports.string()).optional()).safeParse(JSON.parse(
|
|
19208
|
+
const result = external_exports.record(external_exports.string(), external_exports.record(external_exports.string(), external_exports.string()).optional()).safeParse(JSON.parse(readFileSync11(configPath, "utf-8")));
|
|
18926
19209
|
return result.success ? result.data : {};
|
|
18927
19210
|
} catch {
|
|
18928
19211
|
return {};
|
|
@@ -18930,8 +19213,8 @@ function readGlobalConfig2() {
|
|
|
18930
19213
|
}
|
|
18931
19214
|
function writeGlobalConfig2(config2) {
|
|
18932
19215
|
const configPath = getGlobalConfigPath();
|
|
18933
|
-
|
|
18934
|
-
|
|
19216
|
+
mkdirSync9(dirname3(configPath), { recursive: true });
|
|
19217
|
+
writeFileSync12(configPath, JSON.stringify(config2, null, 2));
|
|
18935
19218
|
}
|
|
18936
19219
|
function getGlobalPluginSetting(pluginId, key) {
|
|
18937
19220
|
const config2 = readGlobalConfig2();
|
|
@@ -19401,29 +19684,11 @@ async function syncSingleTicketContent(backend, ticketId, remoteId) {
|
|
|
19401
19684
|
}
|
|
19402
19685
|
}
|
|
19403
19686
|
}
|
|
19404
|
-
async function
|
|
19405
|
-
|
|
19406
|
-
const
|
|
19407
|
-
if (!ticket) return;
|
|
19408
|
-
const localNotes = parseNotes(ticket.notes);
|
|
19409
|
-
let remoteComments;
|
|
19410
|
-
try {
|
|
19411
|
-
remoteComments = await backend.getComments(remoteId);
|
|
19412
|
-
} catch (e) {
|
|
19413
|
-
const msg = getErrorMessage(e);
|
|
19414
|
-
if (msg.includes("404") || msg.includes("410") || msg.includes("Not Found")) {
|
|
19415
|
-
throw e;
|
|
19416
|
-
}
|
|
19417
|
-
return;
|
|
19418
|
-
}
|
|
19419
|
-
const mappings = await getNoteSyncRecords(ticketId, backend.id);
|
|
19420
|
-
const noteIdToMapping = new Map(mappings.map((m) => [m.note_id, m]));
|
|
19421
|
-
const isAttMapping = (noteId2) => noteId2.startsWith("att_");
|
|
19422
|
-
let changed = false;
|
|
19423
|
-
const localNoteById = new Map(localNotes.map((n) => [n.id, n]));
|
|
19424
|
-
const remoteCommentById = new Map(remoteComments.map((c) => [c.id, c]));
|
|
19687
|
+
async function reconcileExistingMappings(ctx) {
|
|
19688
|
+
const { backend, ticketId, remoteId, localNotes, localNoteById, mappings } = ctx;
|
|
19689
|
+
const remoteCommentById = new Map(ctx.remoteComments.map((c) => [c.id, c]));
|
|
19425
19690
|
for (const mapping of mappings) {
|
|
19426
|
-
if (
|
|
19691
|
+
if (mapping.note_id.startsWith("att_")) continue;
|
|
19427
19692
|
const localNote = localNoteById.get(mapping.note_id);
|
|
19428
19693
|
const remoteComment = remoteCommentById.get(mapping.remote_comment_id);
|
|
19429
19694
|
const base = mapping.last_synced_text ?? null;
|
|
@@ -19447,7 +19712,7 @@ async function syncTicketComments(backend, ticketId, remoteId) {
|
|
|
19447
19712
|
}
|
|
19448
19713
|
} else if (remoteChanged && !localChanged) {
|
|
19449
19714
|
localNote.text = remoteText;
|
|
19450
|
-
changed = true;
|
|
19715
|
+
ctx.changed = true;
|
|
19451
19716
|
await upsertNoteSyncRecord(ticketId, mapping.note_id, backend.id, mapping.remote_comment_id, remoteText);
|
|
19452
19717
|
} else if (localChanged && remoteChanged) {
|
|
19453
19718
|
if (backend.updateComment) {
|
|
@@ -19479,21 +19744,22 @@ async function syncTicketComments(backend, ticketId, remoteId) {
|
|
|
19479
19744
|
if (idx >= 0) {
|
|
19480
19745
|
localNotes.splice(idx, 1);
|
|
19481
19746
|
localNoteById.delete(mapping.note_id);
|
|
19482
|
-
changed = true;
|
|
19747
|
+
ctx.changed = true;
|
|
19483
19748
|
}
|
|
19484
19749
|
await deleteNoteSyncRecord(ticketId, mapping.note_id, backend.id);
|
|
19485
19750
|
continue;
|
|
19486
19751
|
}
|
|
19487
19752
|
await deleteNoteSyncRecord(ticketId, mapping.note_id, backend.id);
|
|
19488
19753
|
}
|
|
19754
|
+
}
|
|
19755
|
+
async function pullNewRemoteComments(ctx) {
|
|
19756
|
+
const { backend, ticketId, localNotes, localNoteById, remoteComments, mappings, noteIdToMapping } = ctx;
|
|
19489
19757
|
const mappedRemoteIds = new Set(mappings.map((m) => m.remote_comment_id));
|
|
19490
19758
|
const localTexts = new Set(localNotes.map((n) => n.text.trim()));
|
|
19491
19759
|
for (const comment of remoteComments) {
|
|
19492
19760
|
if (mappedRemoteIds.has(comment.id)) continue;
|
|
19493
19761
|
if (localTexts.has(comment.text.trim())) {
|
|
19494
|
-
const existing = localNotes.find(
|
|
19495
|
-
(n) => n.text.trim() === comment.text.trim() && !noteIdToMapping.has(n.id)
|
|
19496
|
-
);
|
|
19762
|
+
const existing = localNotes.find((n) => n.text.trim() === comment.text.trim() && !noteIdToMapping.has(n.id));
|
|
19497
19763
|
if (existing) {
|
|
19498
19764
|
await upsertNoteSyncRecord(ticketId, existing.id, backend.id, comment.id, existing.text);
|
|
19499
19765
|
noteIdToMapping.set(existing.id, {
|
|
@@ -19513,21 +19779,22 @@ async function syncTicketComments(backend, ticketId, remoteId) {
|
|
|
19513
19779
|
localNoteById.set(noteId2, localNotes[localNotes.length - 1]);
|
|
19514
19780
|
await upsertNoteSyncRecord(ticketId, noteId2, backend.id, comment.id, comment.text);
|
|
19515
19781
|
localTexts.add(comment.text.trim());
|
|
19516
|
-
changed = true;
|
|
19782
|
+
ctx.changed = true;
|
|
19517
19783
|
}
|
|
19784
|
+
}
|
|
19785
|
+
async function pushNewLocalNotes(ctx) {
|
|
19786
|
+
const { backend, ticketId, remoteId, localNotes, remoteComments, mappings, noteIdToMapping } = ctx;
|
|
19518
19787
|
const remoteTexts = new Set(remoteComments.map((c) => c.text.trim()));
|
|
19519
19788
|
const mappedRemoteIdsAfterPull = /* @__PURE__ */ new Set([
|
|
19520
|
-
...
|
|
19789
|
+
...mappings.map((m) => m.remote_comment_id),
|
|
19521
19790
|
...Array.from(noteIdToMapping.values()).map((m) => m.remote_comment_id)
|
|
19522
19791
|
]);
|
|
19523
19792
|
for (const note of localNotes) {
|
|
19524
|
-
if (
|
|
19793
|
+
if (note.id.startsWith("att_")) continue;
|
|
19525
19794
|
if (noteIdToMapping.has(note.id)) continue;
|
|
19526
19795
|
if (!backend.createComment) continue;
|
|
19527
19796
|
if (remoteTexts.has(note.text.trim())) {
|
|
19528
|
-
const existing = remoteComments.find(
|
|
19529
|
-
(c) => c.text.trim() === note.text.trim() && !mappedRemoteIdsAfterPull.has(c.id)
|
|
19530
|
-
);
|
|
19797
|
+
const existing = remoteComments.find((c) => c.text.trim() === note.text.trim() && !mappedRemoteIdsAfterPull.has(c.id));
|
|
19531
19798
|
if (existing) {
|
|
19532
19799
|
await upsertNoteSyncRecord(ticketId, note.id, backend.id, existing.id, note.text);
|
|
19533
19800
|
mappedRemoteIdsAfterPull.add(existing.id);
|
|
@@ -19543,7 +19810,36 @@ async function syncTicketComments(backend, ticketId, remoteId) {
|
|
|
19543
19810
|
console.warn(`[sync] Failed to push note ${note.id} for ticket ${ticketId}: ${getErrorMessage(e)}`);
|
|
19544
19811
|
}
|
|
19545
19812
|
}
|
|
19546
|
-
|
|
19813
|
+
}
|
|
19814
|
+
async function syncTicketComments(backend, ticketId, remoteId) {
|
|
19815
|
+
if (!backend.getComments) return;
|
|
19816
|
+
const ticket = await getTicket(ticketId);
|
|
19817
|
+
if (!ticket) return;
|
|
19818
|
+
const localNotes = parseNotes(ticket.notes);
|
|
19819
|
+
let remoteComments;
|
|
19820
|
+
try {
|
|
19821
|
+
remoteComments = await backend.getComments(remoteId);
|
|
19822
|
+
} catch (e) {
|
|
19823
|
+
const msg = getErrorMessage(e);
|
|
19824
|
+
if (msg.includes("404") || msg.includes("410") || msg.includes("Not Found")) throw e;
|
|
19825
|
+
return;
|
|
19826
|
+
}
|
|
19827
|
+
const mappings = await getNoteSyncRecords(ticketId, backend.id);
|
|
19828
|
+
const ctx = {
|
|
19829
|
+
backend,
|
|
19830
|
+
ticketId,
|
|
19831
|
+
remoteId,
|
|
19832
|
+
localNotes,
|
|
19833
|
+
remoteComments,
|
|
19834
|
+
mappings,
|
|
19835
|
+
localNoteById: new Map(localNotes.map((n) => [n.id, n])),
|
|
19836
|
+
noteIdToMapping: new Map(mappings.map((m) => [m.note_id, m])),
|
|
19837
|
+
changed: false
|
|
19838
|
+
};
|
|
19839
|
+
await reconcileExistingMappings(ctx);
|
|
19840
|
+
await pullNewRemoteComments(ctx);
|
|
19841
|
+
await pushNewLocalNotes(ctx);
|
|
19842
|
+
if (ctx.changed) {
|
|
19547
19843
|
const { getDb: getDbForNotes } = await Promise.resolve().then(() => (init_connection(), connection_exports));
|
|
19548
19844
|
const db = await getDbForNotes();
|
|
19549
19845
|
await db.query("UPDATE tickets SET notes = $1 WHERE id = $2", [JSON.stringify(localNotes), ticketId]);
|
|
@@ -19585,8 +19881,8 @@ async function syncTicketAttachments(backend, ticketId, remoteId) {
|
|
|
19585
19881
|
const attSyncId = `att_${att.id}`;
|
|
19586
19882
|
if (syncedAttIds.has(attSyncId)) continue;
|
|
19587
19883
|
try {
|
|
19588
|
-
const { readFileSync:
|
|
19589
|
-
const content =
|
|
19884
|
+
const { readFileSync: readFileSync15 } = await import("fs");
|
|
19885
|
+
const content = readFileSync15(att.stored_path);
|
|
19590
19886
|
const ext = att.original_filename.split(".").pop()?.toLowerCase() ?? "";
|
|
19591
19887
|
const { getMimeType: getMimeType2 } = await Promise.resolve().then(() => (init_mime_types(), mime_types_exports));
|
|
19592
19888
|
const mimeType = getMimeType2(ext);
|
|
@@ -19649,10 +19945,10 @@ __export(plugins_exports, {
|
|
|
19649
19945
|
isPluginEnabledForProject: () => isPluginEnabledForProject,
|
|
19650
19946
|
pluginRoutes: () => pluginRoutes
|
|
19651
19947
|
});
|
|
19652
|
-
import { existsSync as
|
|
19948
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync10, readFileSync as readFileSync12, rmSync as rmSync8, symlinkSync } from "fs";
|
|
19653
19949
|
import { Hono as Hono5 } from "hono";
|
|
19654
|
-
import { homedir as
|
|
19655
|
-
import { basename as basename2, join as
|
|
19950
|
+
import { homedir as homedir7 } from "os";
|
|
19951
|
+
import { basename as basename2, join as join16 } from "path";
|
|
19656
19952
|
async function getActivatedBackend(pluginId) {
|
|
19657
19953
|
await reactivatePlugin(pluginId);
|
|
19658
19954
|
const plugin = getPluginById(pluginId);
|
|
@@ -19746,6 +20042,7 @@ var init_plugins = __esm({
|
|
|
19746
20042
|
init_errorMessage();
|
|
19747
20043
|
init_helpers();
|
|
19748
20044
|
init_notify();
|
|
20045
|
+
init_validation();
|
|
19749
20046
|
pluginRoutes = new Hono5();
|
|
19750
20047
|
pluginRoutes.get("/plugins", async (c) => {
|
|
19751
20048
|
const loaded = getLoadedPlugins();
|
|
@@ -19784,7 +20081,10 @@ var init_plugins = __esm({
|
|
|
19784
20081
|
let plugin = getPluginById(pluginId);
|
|
19785
20082
|
if (!plugin) return c.json({ error: "Plugin not found" }, 404);
|
|
19786
20083
|
if (!plugin.instance.onAction) return c.json({ error: "Plugin does not handle actions" }, 400);
|
|
19787
|
-
const
|
|
20084
|
+
const raw = await c.req.json();
|
|
20085
|
+
const parsed = parseBody(PluginActionSchema, raw);
|
|
20086
|
+
if (!parsed.success) return c.json({ error: parsed.error }, 400);
|
|
20087
|
+
const body = parsed.data;
|
|
19788
20088
|
await reactivatePlugin(pluginId);
|
|
19789
20089
|
plugin = getPluginById(pluginId);
|
|
19790
20090
|
if (!plugin.instance.onAction) return c.json({ error: "Plugin does not handle actions" }, 400);
|
|
@@ -19802,7 +20102,10 @@ var init_plugins = __esm({
|
|
|
19802
20102
|
pluginRoutes.post("/plugins/validate/:id", async (c) => {
|
|
19803
20103
|
const plugin = getPluginById(c.req.param("id"));
|
|
19804
20104
|
if (!plugin?.instance.validateField) return c.json(null);
|
|
19805
|
-
const
|
|
20105
|
+
const raw = await c.req.json();
|
|
20106
|
+
const parsed = parseBody(PluginValidateSchema, raw);
|
|
20107
|
+
if (!parsed.success) return c.json({ error: parsed.error }, 400);
|
|
20108
|
+
const body = parsed.data;
|
|
19806
20109
|
try {
|
|
19807
20110
|
const result = await plugin.instance.validateField(body.key, body.value);
|
|
19808
20111
|
return c.json(result);
|
|
@@ -19976,7 +20279,10 @@ var init_plugins = __esm({
|
|
|
19976
20279
|
const pluginId = c.req.param("id");
|
|
19977
20280
|
const plugin = getPluginById(pluginId);
|
|
19978
20281
|
if (!plugin) return c.json({ error: "Plugin not found" }, 404);
|
|
19979
|
-
const
|
|
20282
|
+
const raw = await c.req.json();
|
|
20283
|
+
const parsed = parseBody(PluginSyncScheduleSchema, raw);
|
|
20284
|
+
if (!parsed.success) return c.json({ error: parsed.error }, 400);
|
|
20285
|
+
const body = parsed.data;
|
|
19980
20286
|
if (body.interval_minutes === null || body.interval_minutes === 0) {
|
|
19981
20287
|
stopScheduledSync(pluginId);
|
|
19982
20288
|
return c.json({ ok: true, scheduled: false });
|
|
@@ -20019,26 +20325,27 @@ var init_plugins = __esm({
|
|
|
20019
20325
|
pluginRoutes.post("/sync/conflicts/:ticketId/resolve", async (c) => {
|
|
20020
20326
|
const ticketId = parseIntParam(c, "ticketId");
|
|
20021
20327
|
if (ticketId === null) return c.json({ error: "Invalid ticket ID" }, 400);
|
|
20022
|
-
const
|
|
20023
|
-
|
|
20024
|
-
|
|
20025
|
-
|
|
20026
|
-
await resolveConflict(ticketId, body.plugin_id, body.resolution);
|
|
20328
|
+
const raw = await c.req.json();
|
|
20329
|
+
const parsed = parseBody(PluginConflictResolveSchema, raw);
|
|
20330
|
+
if (!parsed.success) return c.json({ error: parsed.error }, 400);
|
|
20331
|
+
await resolveConflict(ticketId, parsed.data.plugin_id, parsed.data.resolution);
|
|
20027
20332
|
return c.json({ ok: true });
|
|
20028
20333
|
});
|
|
20029
20334
|
pluginRoutes.post("/plugins/install", async (c) => {
|
|
20030
|
-
const
|
|
20031
|
-
|
|
20335
|
+
const raw = await c.req.json();
|
|
20336
|
+
const parsed = parseBody(PluginInstallSchema, raw);
|
|
20337
|
+
if (!parsed.success) return c.json({ error: parsed.error }, 400);
|
|
20338
|
+
const body = parsed.data;
|
|
20032
20339
|
const sourcePath = body.path;
|
|
20033
|
-
if (!
|
|
20340
|
+
if (!existsSync14(sourcePath)) {
|
|
20034
20341
|
return c.json({ error: `Path does not exist: ${sourcePath}` }, 400);
|
|
20035
20342
|
}
|
|
20036
|
-
const hasManifest =
|
|
20343
|
+
const hasManifest = existsSync14(join16(sourcePath, "manifest.json"));
|
|
20037
20344
|
const hasPkgHotsheet = (() => {
|
|
20038
|
-
const pkgPath =
|
|
20039
|
-
if (!
|
|
20345
|
+
const pkgPath = join16(sourcePath, "package.json");
|
|
20346
|
+
if (!existsSync14(pkgPath)) return false;
|
|
20040
20347
|
try {
|
|
20041
|
-
const pkg = JSON.parse(
|
|
20348
|
+
const pkg = JSON.parse(readFileSync12(pkgPath, "utf-8"));
|
|
20042
20349
|
return pkg.hotsheet !== void 0;
|
|
20043
20350
|
} catch {
|
|
20044
20351
|
return false;
|
|
@@ -20047,11 +20354,11 @@ var init_plugins = __esm({
|
|
|
20047
20354
|
if (!hasManifest && !hasPkgHotsheet) {
|
|
20048
20355
|
return c.json({ error: "Directory must contain manifest.json or package.json with hotsheet field" }, 400);
|
|
20049
20356
|
}
|
|
20050
|
-
const pluginsDir =
|
|
20051
|
-
|
|
20357
|
+
const pluginsDir = join16(homedir7(), ".hotsheet", "plugins");
|
|
20358
|
+
mkdirSync10(pluginsDir, { recursive: true });
|
|
20052
20359
|
const linkName = basename2(sourcePath);
|
|
20053
|
-
const linkPath =
|
|
20054
|
-
if (
|
|
20360
|
+
const linkPath = join16(pluginsDir, linkName);
|
|
20361
|
+
if (existsSync14(linkPath)) {
|
|
20055
20362
|
return c.json({ error: `Plugin already exists at ${linkPath}` }, 400);
|
|
20056
20363
|
}
|
|
20057
20364
|
try {
|
|
@@ -20069,14 +20376,14 @@ var init_plugins = __esm({
|
|
|
20069
20376
|
if (plugin?.enabled === true) {
|
|
20070
20377
|
await disablePlugin(pluginId);
|
|
20071
20378
|
}
|
|
20072
|
-
const pluginsDir =
|
|
20379
|
+
const pluginsDir = join16(homedir7(), ".hotsheet", "plugins");
|
|
20073
20380
|
const candidates = [
|
|
20074
20381
|
plugin?.path,
|
|
20075
|
-
|
|
20382
|
+
join16(pluginsDir, pluginId)
|
|
20076
20383
|
].filter((p) => p !== void 0);
|
|
20077
20384
|
let removed = false;
|
|
20078
20385
|
for (const candidate of candidates) {
|
|
20079
|
-
if (
|
|
20386
|
+
if (existsSync14(candidate)) {
|
|
20080
20387
|
try {
|
|
20081
20388
|
rmSync8(candidate, { recursive: true, force: true });
|
|
20082
20389
|
removed = true;
|
|
@@ -20108,8 +20415,10 @@ var init_plugins = __esm({
|
|
|
20108
20415
|
});
|
|
20109
20416
|
pluginRoutes.post("/plugins/:id/global-config", async (c) => {
|
|
20110
20417
|
const pluginId = c.req.param("id");
|
|
20111
|
-
const
|
|
20112
|
-
|
|
20418
|
+
const raw = await c.req.json();
|
|
20419
|
+
const parsed = parseBody(PluginGlobalConfigSchema, raw);
|
|
20420
|
+
if (!parsed.success) return c.json({ error: parsed.error }, 400);
|
|
20421
|
+
const body = parsed.data;
|
|
20113
20422
|
const plugin = getPluginById(pluginId);
|
|
20114
20423
|
const isSecret = plugin?.manifest.preferences?.find((p) => p.key === body.key)?.secret === true;
|
|
20115
20424
|
if (isSecret) {
|
|
@@ -20178,9 +20487,9 @@ var init_plugins = __esm({
|
|
|
20178
20487
|
// src/cli.ts
|
|
20179
20488
|
init_backup();
|
|
20180
20489
|
import { execFile as execFile3 } from "child_process";
|
|
20181
|
-
import { existsSync as
|
|
20490
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync12 } from "fs";
|
|
20182
20491
|
import { tmpdir as tmpdir2 } from "os";
|
|
20183
|
-
import { join as
|
|
20492
|
+
import { join as join19, resolve as resolve8 } from "path";
|
|
20184
20493
|
|
|
20185
20494
|
// src/cleanup.ts
|
|
20186
20495
|
init_queries();
|
|
@@ -20338,9 +20647,9 @@ init_mime_types();
|
|
|
20338
20647
|
init_projects();
|
|
20339
20648
|
import { serve } from "@hono/node-server";
|
|
20340
20649
|
import { execFile as execFile2 } from "child_process";
|
|
20341
|
-
import { existsSync as
|
|
20650
|
+
import { existsSync as existsSync16, readFileSync as readFileSync13 } from "fs";
|
|
20342
20651
|
import { Hono as Hono13 } from "hono";
|
|
20343
|
-
import { basename as basename3, dirname as dirname4, join as
|
|
20652
|
+
import { basename as basename3, dirname as dirname4, join as join17 } from "path";
|
|
20344
20653
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
20345
20654
|
|
|
20346
20655
|
// src/routes/api.ts
|
|
@@ -20375,8 +20684,8 @@ attachmentRoutes.post("/tickets/:id/attachments", async (c) => {
|
|
|
20375
20684
|
mkdirSync6(attachDir, { recursive: true });
|
|
20376
20685
|
const storedPath = join10(attachDir, storedName);
|
|
20377
20686
|
const buffer = Buffer.from(await file2.arrayBuffer());
|
|
20378
|
-
const { writeFileSync:
|
|
20379
|
-
|
|
20687
|
+
const { writeFileSync: writeFileSync14 } = await import("fs");
|
|
20688
|
+
writeFileSync14(storedPath, buffer);
|
|
20380
20689
|
const attachment = await addAttachment(id, originalName, storedPath);
|
|
20381
20690
|
notifyMutation(c.get("dataDir"));
|
|
20382
20691
|
return c.json(attachment, 201);
|
|
@@ -20413,8 +20722,8 @@ attachmentRoutes.get("/attachments/file/*", async (c) => {
|
|
|
20413
20722
|
if (!existsSync8(fullPath)) {
|
|
20414
20723
|
return c.json({ error: "File not found" }, 404);
|
|
20415
20724
|
}
|
|
20416
|
-
const { readFileSync:
|
|
20417
|
-
const content =
|
|
20725
|
+
const { readFileSync: readFileSync15 } = await import("fs");
|
|
20726
|
+
const content = readFileSync15(fullPath);
|
|
20418
20727
|
const ext = extname(fullPath).toLowerCase();
|
|
20419
20728
|
const contentType = getMimeType(ext);
|
|
20420
20729
|
return new Response(content, {
|
|
@@ -20426,120 +20735,8 @@ attachmentRoutes.get("/attachments/file/*", async (c) => {
|
|
|
20426
20735
|
init_commandLog();
|
|
20427
20736
|
init_settings();
|
|
20428
20737
|
init_notify();
|
|
20738
|
+
init_validation();
|
|
20429
20739
|
import { Hono as Hono2 } from "hono";
|
|
20430
|
-
|
|
20431
|
-
// src/routes/validation.ts
|
|
20432
|
-
init_zod();
|
|
20433
|
-
var TicketPrioritySchema = external_exports.enum(["highest", "high", "default", "low", "lowest"]);
|
|
20434
|
-
var TicketStatusSchema = external_exports.enum(["not_started", "started", "completed", "verified", "backlog", "archive", "deleted"]);
|
|
20435
|
-
var SortBySchema = external_exports.enum(["created", "priority", "category", "status"]);
|
|
20436
|
-
var SortDirSchema = external_exports.enum(["asc", "desc"]);
|
|
20437
|
-
var CreateTicketSchema = external_exports.object({
|
|
20438
|
-
title: external_exports.string().optional().default(""),
|
|
20439
|
-
defaults: external_exports.object({
|
|
20440
|
-
category: external_exports.string().optional(),
|
|
20441
|
-
priority: TicketPrioritySchema.or(external_exports.literal("")).optional(),
|
|
20442
|
-
status: TicketStatusSchema.or(external_exports.literal("")).optional(),
|
|
20443
|
-
up_next: external_exports.boolean().optional(),
|
|
20444
|
-
details: external_exports.string().optional(),
|
|
20445
|
-
tags: external_exports.string().optional()
|
|
20446
|
-
}).optional()
|
|
20447
|
-
});
|
|
20448
|
-
var UpdateTicketSchema = external_exports.object({
|
|
20449
|
-
title: external_exports.string().optional(),
|
|
20450
|
-
details: external_exports.string().optional(),
|
|
20451
|
-
notes: external_exports.string().optional(),
|
|
20452
|
-
tags: external_exports.string().optional(),
|
|
20453
|
-
category: external_exports.string().optional(),
|
|
20454
|
-
priority: TicketPrioritySchema.optional(),
|
|
20455
|
-
status: TicketStatusSchema.optional(),
|
|
20456
|
-
up_next: external_exports.boolean().optional(),
|
|
20457
|
-
last_read_at: external_exports.string().nullable().optional()
|
|
20458
|
-
});
|
|
20459
|
-
var BatchActionSchema = external_exports.object({
|
|
20460
|
-
ids: external_exports.array(external_exports.number().int()),
|
|
20461
|
-
action: external_exports.enum(["delete", "restore", "category", "priority", "status", "up_next", "mark_read", "mark_unread"]),
|
|
20462
|
-
value: external_exports.union([external_exports.string(), external_exports.boolean()]).optional()
|
|
20463
|
-
});
|
|
20464
|
-
var DuplicateSchema = external_exports.object({
|
|
20465
|
-
ids: external_exports.array(external_exports.number().int())
|
|
20466
|
-
});
|
|
20467
|
-
var NotesEditSchema = external_exports.object({
|
|
20468
|
-
text: external_exports.string()
|
|
20469
|
-
});
|
|
20470
|
-
var NotesBulkSchema = external_exports.object({
|
|
20471
|
-
notes: external_exports.string()
|
|
20472
|
-
});
|
|
20473
|
-
var QueryTicketsSchema = external_exports.object({
|
|
20474
|
-
logic: external_exports.enum(["all", "any"]),
|
|
20475
|
-
conditions: external_exports.array(external_exports.object({
|
|
20476
|
-
field: external_exports.enum(["category", "priority", "status", "title", "details", "up_next", "tags"]),
|
|
20477
|
-
operator: external_exports.enum(["equals", "not_equals", "contains", "not_contains", "lt", "lte", "gt", "gte"]),
|
|
20478
|
-
value: external_exports.string()
|
|
20479
|
-
})),
|
|
20480
|
-
sort_by: external_exports.string().optional(),
|
|
20481
|
-
sort_dir: SortDirSchema.optional(),
|
|
20482
|
-
required_tag: external_exports.string().optional(),
|
|
20483
|
-
include_archived: external_exports.boolean().optional()
|
|
20484
|
-
});
|
|
20485
|
-
var UpdateSettingsSchema = external_exports.record(external_exports.string(), external_exports.string());
|
|
20486
|
-
var BackupTierSchema = external_exports.enum(["5min", "hourly", "daily"]);
|
|
20487
|
-
var CreateBackupSchema = external_exports.object({
|
|
20488
|
-
tier: BackupTierSchema
|
|
20489
|
-
});
|
|
20490
|
-
var RestoreBackupSchema = external_exports.object({
|
|
20491
|
-
tier: external_exports.string().min(1),
|
|
20492
|
-
filename: external_exports.string().min(1)
|
|
20493
|
-
});
|
|
20494
|
-
var ShellExecSchema = external_exports.object({
|
|
20495
|
-
command: external_exports.string().min(1, "Command cannot be empty"),
|
|
20496
|
-
name: external_exports.string().optional()
|
|
20497
|
-
});
|
|
20498
|
-
var ShellKillSchema = external_exports.object({
|
|
20499
|
-
id: external_exports.number().int()
|
|
20500
|
-
});
|
|
20501
|
-
var ChannelTriggerSchema = external_exports.object({
|
|
20502
|
-
message: external_exports.string().optional()
|
|
20503
|
-
});
|
|
20504
|
-
var PermissionRespondSchema = external_exports.object({
|
|
20505
|
-
request_id: external_exports.string(),
|
|
20506
|
-
behavior: external_exports.enum(["allow", "deny"]),
|
|
20507
|
-
tool_name: external_exports.string().optional()
|
|
20508
|
-
});
|
|
20509
|
-
var RegisterProjectSchema = external_exports.object({
|
|
20510
|
-
dataDir: external_exports.string().min(1, "dataDir is required")
|
|
20511
|
-
});
|
|
20512
|
-
var ReorderProjectsSchema = external_exports.object({
|
|
20513
|
-
secrets: external_exports.array(external_exports.string())
|
|
20514
|
-
});
|
|
20515
|
-
var CategoryDefSchema = external_exports.object({
|
|
20516
|
-
id: external_exports.string().min(1),
|
|
20517
|
-
label: external_exports.string().min(1),
|
|
20518
|
-
shortLabel: external_exports.string().min(1),
|
|
20519
|
-
color: external_exports.string().min(1),
|
|
20520
|
-
shortcutKey: external_exports.string(),
|
|
20521
|
-
description: external_exports.string()
|
|
20522
|
-
}).loose();
|
|
20523
|
-
var UpdateCategoriesSchema = external_exports.array(CategoryDefSchema).min(1);
|
|
20524
|
-
var PrintSchema = external_exports.object({
|
|
20525
|
-
html: external_exports.string()
|
|
20526
|
-
});
|
|
20527
|
-
var GlobalConfigSchema = external_exports.object({
|
|
20528
|
-
channelEnabled: external_exports.boolean().optional(),
|
|
20529
|
-
shareTotalSeconds: external_exports.number().optional(),
|
|
20530
|
-
shareLastPrompted: external_exports.string().optional(),
|
|
20531
|
-
shareAccepted: external_exports.boolean().optional()
|
|
20532
|
-
}).strict();
|
|
20533
|
-
function parseBody(schema, data) {
|
|
20534
|
-
const result = schema.safeParse(data);
|
|
20535
|
-
if (!result.success) {
|
|
20536
|
-
const messages = result.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).filter((m) => m !== ": ");
|
|
20537
|
-
return { success: false, error: messages.join("; ") || "Invalid request body" };
|
|
20538
|
-
}
|
|
20539
|
-
return { success: true, data: result.data };
|
|
20540
|
-
}
|
|
20541
|
-
|
|
20542
|
-
// src/routes/channel.ts
|
|
20543
20740
|
var channelRoutes = new Hono2();
|
|
20544
20741
|
var channelDoneFlags = /* @__PURE__ */ new Map();
|
|
20545
20742
|
var loggedPermissionRequests = /* @__PURE__ */ new Map();
|
|
@@ -20690,6 +20887,7 @@ channelRoutes.post("/channel/enable", async (c) => {
|
|
|
20690
20887
|
const { writeGlobalConfig: writeGlobalConfig3 } = await Promise.resolve().then(() => (init_global_config(), global_config_exports));
|
|
20691
20888
|
const dataDir = c.get("dataDir");
|
|
20692
20889
|
writeGlobalConfig3({ channelEnabled: true });
|
|
20890
|
+
const serverPort = parseInt(new URL(c.req.url).port || "4174", 10);
|
|
20693
20891
|
try {
|
|
20694
20892
|
const { getAllProjects: getAllProjects2 } = await Promise.resolve().then(() => (init_projects(), projects_exports));
|
|
20695
20893
|
const { ensureSkillsForDir: ensureSkillsForDir2 } = await Promise.resolve().then(() => (init_skills(), skills_exports));
|
|
@@ -20701,6 +20899,8 @@ channelRoutes.post("/channel/enable", async (c) => {
|
|
|
20701
20899
|
} catch {
|
|
20702
20900
|
registerChannel2(dataDir);
|
|
20703
20901
|
}
|
|
20902
|
+
const { installHeartbeatHook: installHeartbeatHook2 } = await Promise.resolve().then(() => (init_claude_hooks(), claude_hooks_exports));
|
|
20903
|
+
installHeartbeatHook2(serverPort);
|
|
20704
20904
|
notifyChange();
|
|
20705
20905
|
return c.json({ ok: true });
|
|
20706
20906
|
});
|
|
@@ -20720,9 +20920,35 @@ channelRoutes.post("/channel/disable", async (c) => {
|
|
|
20720
20920
|
unregisterChannel2(dataDir);
|
|
20721
20921
|
await shutdownChannel2(dataDir);
|
|
20722
20922
|
}
|
|
20923
|
+
const { removeHeartbeatHook: removeHeartbeatHook2 } = await Promise.resolve().then(() => (init_claude_hooks(), claude_hooks_exports));
|
|
20924
|
+
removeHeartbeatHook2();
|
|
20723
20925
|
notifyChange();
|
|
20724
20926
|
return c.json({ ok: true });
|
|
20725
20927
|
});
|
|
20928
|
+
channelRoutes.post("/channel/heartbeat", async (c) => {
|
|
20929
|
+
const { getAllProjects: getAllProjects2 } = await Promise.resolve().then(() => (init_projects(), projects_exports));
|
|
20930
|
+
const raw = await c.req.json().catch(() => ({}));
|
|
20931
|
+
const parsed = parseBody(ChannelHeartbeatSchema, raw);
|
|
20932
|
+
if (!parsed.success) return c.json({ ok: false });
|
|
20933
|
+
const projectDir = parsed.data.projectDir;
|
|
20934
|
+
const hookState = parsed.data.state ?? "heartbeat";
|
|
20935
|
+
if (projectDir === void 0 || projectDir === "") return c.json({ ok: false });
|
|
20936
|
+
const projects2 = getAllProjects2();
|
|
20937
|
+
const match = projects2.find((p) => {
|
|
20938
|
+
const rootDir = p.dataDir.replace(/\/.hotsheet\/?$/, "");
|
|
20939
|
+
return rootDir === projectDir || projectDir.startsWith(rootDir + "/");
|
|
20940
|
+
});
|
|
20941
|
+
if (!match) return c.json({ ok: false });
|
|
20942
|
+
heartbeatUpdates.push({ secret: match.secret, state: hookState });
|
|
20943
|
+
notifyChange();
|
|
20944
|
+
return c.json({ ok: true, project: match.name });
|
|
20945
|
+
});
|
|
20946
|
+
var heartbeatUpdates = [];
|
|
20947
|
+
channelRoutes.get("/channel/heartbeat-status", (c) => {
|
|
20948
|
+
const updates = [...heartbeatUpdates];
|
|
20949
|
+
heartbeatUpdates.length = 0;
|
|
20950
|
+
return c.json({ updates });
|
|
20951
|
+
});
|
|
20726
20952
|
channelRoutes.post("/channel/notify", (c) => {
|
|
20727
20953
|
notifyChange();
|
|
20728
20954
|
return c.json({ ok: true });
|
|
@@ -20763,10 +20989,11 @@ init_open_in_file_manager();
|
|
|
20763
20989
|
init_projects();
|
|
20764
20990
|
init_skills();
|
|
20765
20991
|
init_notify();
|
|
20766
|
-
|
|
20992
|
+
init_validation();
|
|
20993
|
+
import { existsSync as existsSync12, readdirSync as readdirSync2, writeFileSync as writeFileSync11 } from "fs";
|
|
20767
20994
|
import { Hono as Hono4 } from "hono";
|
|
20768
|
-
import { homedir as
|
|
20769
|
-
import { join as
|
|
20995
|
+
import { homedir as homedir5, tmpdir } from "os";
|
|
20996
|
+
import { join as join14, relative as relative2, resolve as resolve6 } from "path";
|
|
20770
20997
|
var dashboardRoutes = new Hono4();
|
|
20771
20998
|
dashboardRoutes.get("/poll", async (c) => {
|
|
20772
20999
|
const clientVersion = Math.max(0, parseInt(c.req.query("version") ?? "0", 10) || 0);
|
|
@@ -20800,7 +21027,7 @@ dashboardRoutes.get("/dashboard", async (c) => {
|
|
|
20800
21027
|
dashboardRoutes.get("/worklist-info", (c) => {
|
|
20801
21028
|
const dataDir = c.get("dataDir");
|
|
20802
21029
|
const cwd = process.cwd();
|
|
20803
|
-
const worklistRel = relative2(cwd,
|
|
21030
|
+
const worklistRel = relative2(cwd, join14(dataDir, "worklist.md"));
|
|
20804
21031
|
const prompt = `Read ${worklistRel} for current work items.`;
|
|
20805
21032
|
for (const p of getAllProjects()) {
|
|
20806
21033
|
ensureSkillsForDir(p.dataDir.replace(/\/.hotsheet\/?$/, ""));
|
|
@@ -20809,23 +21036,23 @@ dashboardRoutes.get("/worklist-info", (c) => {
|
|
|
20809
21036
|
return c.json({ prompt, skillCreated });
|
|
20810
21037
|
});
|
|
20811
21038
|
dashboardRoutes.get("/browse", (c) => {
|
|
20812
|
-
const requestedPath = c.req.query("path") ??
|
|
21039
|
+
const requestedPath = c.req.query("path") ?? homedir5();
|
|
20813
21040
|
const absPath = resolve6(requestedPath);
|
|
20814
|
-
if (!
|
|
21041
|
+
if (!existsSync12(absPath)) {
|
|
20815
21042
|
return c.json({ error: "Path does not exist", path: absPath }, 404);
|
|
20816
21043
|
}
|
|
20817
21044
|
try {
|
|
20818
21045
|
const entries = readdirSync2(absPath, { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith(".")).sort((a, b) => a.name.localeCompare(b.name)).map((e) => ({
|
|
20819
21046
|
name: e.name,
|
|
20820
|
-
path:
|
|
20821
|
-
hasHotsheet:
|
|
21047
|
+
path: join14(absPath, e.name),
|
|
21048
|
+
hasHotsheet: existsSync12(join14(absPath, e.name, ".hotsheet"))
|
|
20822
21049
|
}));
|
|
20823
21050
|
const parentPath = resolve6(absPath, "..");
|
|
20824
21051
|
return c.json({
|
|
20825
21052
|
path: absPath,
|
|
20826
21053
|
parent: parentPath !== absPath ? parentPath : null,
|
|
20827
21054
|
entries,
|
|
20828
|
-
hasHotsheet:
|
|
21055
|
+
hasHotsheet: existsSync12(join14(absPath, ".hotsheet"))
|
|
20829
21056
|
});
|
|
20830
21057
|
} catch {
|
|
20831
21058
|
return c.json({ error: "Cannot read directory", path: absPath }, 403);
|
|
@@ -20891,8 +21118,8 @@ dashboardRoutes.post("/print", async (c) => {
|
|
|
20891
21118
|
const raw = await c.req.json();
|
|
20892
21119
|
const parsed = parseBody(PrintSchema, raw);
|
|
20893
21120
|
if (!parsed.success) return c.json({ error: parsed.error }, 400);
|
|
20894
|
-
const tmpPath =
|
|
20895
|
-
|
|
21121
|
+
const tmpPath = join14(tmpdir(), `hotsheet-print-${Date.now()}.html`);
|
|
21122
|
+
writeFileSync11(tmpPath, parsed.data.html, "utf-8");
|
|
20896
21123
|
await openInFileManager(tmpPath);
|
|
20897
21124
|
return c.json({ ok: true, path: tmpPath });
|
|
20898
21125
|
});
|
|
@@ -20904,6 +21131,7 @@ init_plugins();
|
|
|
20904
21131
|
init_queries();
|
|
20905
21132
|
init_types();
|
|
20906
21133
|
init_notify();
|
|
21134
|
+
init_validation();
|
|
20907
21135
|
import { Hono as Hono6 } from "hono";
|
|
20908
21136
|
var settingsRoutes = new Hono6();
|
|
20909
21137
|
settingsRoutes.get("/tags", async (c) => {
|
|
@@ -20971,6 +21199,7 @@ settingsRoutes.patch("/file-settings", async (c) => {
|
|
|
20971
21199
|
|
|
20972
21200
|
// src/routes/shell.ts
|
|
20973
21201
|
init_commandLog();
|
|
21202
|
+
init_validation();
|
|
20974
21203
|
import { Hono as Hono7 } from "hono";
|
|
20975
21204
|
var shellRoutes = new Hono7();
|
|
20976
21205
|
var runningProcesses = /* @__PURE__ */ new Map();
|
|
@@ -21059,6 +21288,7 @@ init_syncEngine();
|
|
|
21059
21288
|
init_helpers();
|
|
21060
21289
|
init_notify();
|
|
21061
21290
|
init_plugins();
|
|
21291
|
+
init_validation();
|
|
21062
21292
|
import { rmSync as rmSync9 } from "fs";
|
|
21063
21293
|
import { Hono as Hono8 } from "hono";
|
|
21064
21294
|
var VALID_STATUS_FILTERS = /* @__PURE__ */ new Set([
|
|
@@ -21361,6 +21591,7 @@ if (PLUGINS_ENABLED) apiRoutes.route("/", pluginRoutes);
|
|
|
21361
21591
|
// src/routes/backups.ts
|
|
21362
21592
|
init_backup();
|
|
21363
21593
|
init_markdown();
|
|
21594
|
+
init_validation();
|
|
21364
21595
|
import { Hono as Hono10 } from "hono";
|
|
21365
21596
|
var backupRoutes = new Hono10();
|
|
21366
21597
|
backupRoutes.get("/", (c) => {
|
|
@@ -22236,13 +22467,14 @@ init_open_in_file_manager();
|
|
|
22236
22467
|
init_project_list();
|
|
22237
22468
|
init_projects();
|
|
22238
22469
|
init_notify();
|
|
22239
|
-
|
|
22470
|
+
init_validation();
|
|
22471
|
+
import { existsSync as existsSync15 } from "fs";
|
|
22240
22472
|
import { Hono as Hono12 } from "hono";
|
|
22241
22473
|
import { resolve as resolve7 } from "path";
|
|
22242
22474
|
var projectRoutes = new Hono12();
|
|
22243
22475
|
projectRoutes.get("/", async (c) => {
|
|
22244
22476
|
const projects2 = getAllProjects();
|
|
22245
|
-
const stale = projects2.filter((p) => !
|
|
22477
|
+
const stale = projects2.filter((p) => !existsSync15(p.dataDir));
|
|
22246
22478
|
for (const p of stale) {
|
|
22247
22479
|
removeFromProjectList(p.dataDir);
|
|
22248
22480
|
unregisterProject(p.secret);
|
|
@@ -22250,7 +22482,7 @@ projectRoutes.get("/", async (c) => {
|
|
|
22250
22482
|
const persisted = readProjectList();
|
|
22251
22483
|
const inMemoryDirs = new Set(getAllProjects().map((p) => p.dataDir));
|
|
22252
22484
|
for (const dir of persisted) {
|
|
22253
|
-
if (!inMemoryDirs.has(dir) && !
|
|
22485
|
+
if (!inMemoryDirs.has(dir) && !existsSync15(dir)) {
|
|
22254
22486
|
removeFromProjectList(dir);
|
|
22255
22487
|
}
|
|
22256
22488
|
}
|
|
@@ -22379,7 +22611,7 @@ projectRoutes.post("/:secret/reveal", async (c) => {
|
|
|
22379
22611
|
const project = getProjectBySecret(secret);
|
|
22380
22612
|
if (!project) return c.json({ error: "Project not found" }, 404);
|
|
22381
22613
|
const projectRoot2 = resolve7(project.dataDir, "..");
|
|
22382
|
-
if (!
|
|
22614
|
+
if (!existsSync15(projectRoot2)) return c.json({ error: "Folder not found on disk" }, 404);
|
|
22383
22615
|
await openInFileManager(projectRoot2);
|
|
22384
22616
|
return c.json({ ok: true });
|
|
22385
22617
|
});
|
|
@@ -22429,25 +22661,25 @@ async function startServer(port, dataDir, options) {
|
|
|
22429
22661
|
await runWithDataDir(resolvedDataDir, () => next());
|
|
22430
22662
|
});
|
|
22431
22663
|
const selfDir = dirname4(fileURLToPath3(import.meta.url));
|
|
22432
|
-
const distDir =
|
|
22664
|
+
const distDir = existsSync16(join17(selfDir, "client", "styles.css")) ? join17(selfDir, "client") : join17(selfDir, "..", "dist", "client");
|
|
22433
22665
|
app.get("/static/styles.css", (c) => {
|
|
22434
|
-
const css =
|
|
22666
|
+
const css = readFileSync13(join17(distDir, "styles.css"), "utf-8");
|
|
22435
22667
|
return c.text(css, 200, { "Content-Type": "text/css", "Cache-Control": "no-cache" });
|
|
22436
22668
|
});
|
|
22437
22669
|
app.get("/static/app.js", (c) => {
|
|
22438
|
-
const js =
|
|
22670
|
+
const js = readFileSync13(join17(distDir, "app.global.js"), "utf-8");
|
|
22439
22671
|
return c.text(js, 200, { "Content-Type": "application/javascript", "Cache-Control": "no-cache" });
|
|
22440
22672
|
});
|
|
22441
22673
|
app.get("/static/assets/:filename", (c) => {
|
|
22442
22674
|
const filename = basename3(c.req.param("filename"));
|
|
22443
|
-
const filePath =
|
|
22444
|
-
if (!
|
|
22445
|
-
const content =
|
|
22675
|
+
const filePath = join17(distDir, "assets", filename);
|
|
22676
|
+
if (!existsSync16(filePath)) return c.notFound();
|
|
22677
|
+
const content = readFileSync13(filePath);
|
|
22446
22678
|
const ext = filename.split(".").pop() ?? "";
|
|
22447
22679
|
return new Response(content, { headers: { "Content-Type": getMimeType(ext), "Cache-Control": "max-age=86400" } });
|
|
22448
22680
|
});
|
|
22449
22681
|
app.use("/api/*", async (c, next) => {
|
|
22450
|
-
if (c.req.path.startsWith("/api/projects")) {
|
|
22682
|
+
if (c.req.path.startsWith("/api/projects") || c.req.path === "/api/channel/heartbeat") {
|
|
22451
22683
|
await next();
|
|
22452
22684
|
return;
|
|
22453
22685
|
}
|
|
@@ -22536,18 +22768,18 @@ init_skills();
|
|
|
22536
22768
|
init_markdown();
|
|
22537
22769
|
|
|
22538
22770
|
// src/update-check.ts
|
|
22539
|
-
import { existsSync as
|
|
22771
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync11, readFileSync as readFileSync14, writeFileSync as writeFileSync13 } from "fs";
|
|
22540
22772
|
import { get } from "https";
|
|
22541
|
-
import { homedir as
|
|
22542
|
-
import { dirname as dirname5, join as
|
|
22773
|
+
import { homedir as homedir8 } from "os";
|
|
22774
|
+
import { dirname as dirname5, join as join18 } from "path";
|
|
22543
22775
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
22544
|
-
var DATA_DIR =
|
|
22545
|
-
var CHECK_FILE =
|
|
22776
|
+
var DATA_DIR = join18(homedir8(), ".hotsheet");
|
|
22777
|
+
var CHECK_FILE = join18(DATA_DIR, "last-update-check");
|
|
22546
22778
|
var PACKAGE_NAME = "hotsheet";
|
|
22547
22779
|
function getCurrentVersion() {
|
|
22548
22780
|
try {
|
|
22549
22781
|
const dir = dirname5(fileURLToPath4(import.meta.url));
|
|
22550
|
-
const pkg = JSON.parse(
|
|
22782
|
+
const pkg = JSON.parse(readFileSync14(join18(dir, "..", "package.json"), "utf-8"));
|
|
22551
22783
|
return pkg.version;
|
|
22552
22784
|
} catch {
|
|
22553
22785
|
return "0.0.0";
|
|
@@ -22555,16 +22787,16 @@ function getCurrentVersion() {
|
|
|
22555
22787
|
}
|
|
22556
22788
|
function getLastCheckDate() {
|
|
22557
22789
|
try {
|
|
22558
|
-
if (
|
|
22559
|
-
return
|
|
22790
|
+
if (existsSync17(CHECK_FILE)) {
|
|
22791
|
+
return readFileSync14(CHECK_FILE, "utf-8").trim();
|
|
22560
22792
|
}
|
|
22561
22793
|
} catch {
|
|
22562
22794
|
}
|
|
22563
22795
|
return null;
|
|
22564
22796
|
}
|
|
22565
22797
|
function saveCheckDate() {
|
|
22566
|
-
|
|
22567
|
-
|
|
22798
|
+
mkdirSync11(DATA_DIR, { recursive: true });
|
|
22799
|
+
writeFileSync13(CHECK_FILE, (/* @__PURE__ */ new Date()).toISOString().slice(0, 10), "utf-8");
|
|
22568
22800
|
}
|
|
22569
22801
|
function isFirstUseToday() {
|
|
22570
22802
|
const last = getLastCheckDate();
|
|
@@ -22676,7 +22908,7 @@ Examples:
|
|
|
22676
22908
|
function parseArgs(argv) {
|
|
22677
22909
|
const args = argv.slice(2);
|
|
22678
22910
|
let port = 4174;
|
|
22679
|
-
let dataDir =
|
|
22911
|
+
let dataDir = join19(process.cwd(), ".hotsheet");
|
|
22680
22912
|
let demo = null;
|
|
22681
22913
|
let forceUpdateCheck = false;
|
|
22682
22914
|
let noOpen = false;
|
|
@@ -22824,21 +23056,28 @@ async function handleEarlyFlags(args) {
|
|
|
22824
23056
|
return false;
|
|
22825
23057
|
}
|
|
22826
23058
|
async function initializeProject(dataDir, demo) {
|
|
22827
|
-
|
|
23059
|
+
const t0 = Date.now();
|
|
23060
|
+
const elapsed = () => `${Date.now() - t0}ms`;
|
|
23061
|
+
mkdirSync12(dataDir, { recursive: true });
|
|
22828
23062
|
if (demo === null) {
|
|
22829
23063
|
acquireLock(dataDir);
|
|
22830
23064
|
ensureGitignore(process.cwd());
|
|
22831
23065
|
}
|
|
23066
|
+
console.error(`[init-project ${elapsed()}] initializing DB...`);
|
|
22832
23067
|
setDataDir(dataDir);
|
|
22833
23068
|
const db = await getDb();
|
|
23069
|
+
console.error(`[init-project ${elapsed()}] DB ready`);
|
|
22834
23070
|
if (demo !== null) {
|
|
22835
23071
|
await seedDemoData(demo);
|
|
22836
23072
|
}
|
|
22837
23073
|
if (demo === null) {
|
|
22838
23074
|
const { runWithDataDir: runWithDataDir2 } = await Promise.resolve().then(() => (init_connection(), connection_exports));
|
|
23075
|
+
console.error(`[init-project ${elapsed()}] migrating settings...`);
|
|
22839
23076
|
const { migrateDbSettingsToFile: migrateDbSettingsToFile2 } = await Promise.resolve().then(() => (init_migrate_settings(), migrate_settings_exports));
|
|
22840
23077
|
await runWithDataDir2(dataDir, () => migrateDbSettingsToFile2(dataDir));
|
|
23078
|
+
console.error(`[init-project ${elapsed()}] cleaning up attachments...`);
|
|
22841
23079
|
await runWithDataDir2(dataDir, () => cleanupAttachments());
|
|
23080
|
+
console.error(`[init-project ${elapsed()}] done`);
|
|
22842
23081
|
}
|
|
22843
23082
|
console.log(` Data directory: ${dataDir}`);
|
|
22844
23083
|
return db;
|
|
@@ -22870,14 +23109,22 @@ async function startAndConfigure(port, dataDir, strictPort) {
|
|
|
22870
23109
|
return { actualPort, secret };
|
|
22871
23110
|
}
|
|
22872
23111
|
async function postStartup(dataDir, actualPort, demo, noOpen) {
|
|
23112
|
+
const t0 = Date.now();
|
|
23113
|
+
const elapsed = () => `${Date.now() - t0}ms`;
|
|
22873
23114
|
if (demo === null) {
|
|
22874
23115
|
initBackupScheduler(dataDir);
|
|
22875
23116
|
addToProjectList(dataDir);
|
|
23117
|
+
console.error(`[post-startup ${elapsed()}] restoring previous projects...`);
|
|
22876
23118
|
await restorePreviousProjects(dataDir, actualPort);
|
|
23119
|
+
console.error(`[post-startup ${elapsed()}] migrating global config...`);
|
|
22877
23120
|
await migrateGlobalConfig();
|
|
23121
|
+
console.error(`[post-startup ${elapsed()}] cleaning up stale channels...`);
|
|
22878
23122
|
await cleanupStaleChannels();
|
|
22879
|
-
|
|
23123
|
+
console.error(`[post-startup ${elapsed()}] setting up skills and channels...`);
|
|
23124
|
+
await setupSkillsAndChannels(actualPort);
|
|
23125
|
+
console.error(`[post-startup ${elapsed()}] setting up instance lifecycle...`);
|
|
22880
23126
|
setupInstanceLifecycle(actualPort);
|
|
23127
|
+
console.error(`[post-startup ${elapsed()}] done`);
|
|
22881
23128
|
}
|
|
22882
23129
|
if (!noOpen) {
|
|
22883
23130
|
const url2 = `http://localhost:${actualPort}`;
|
|
@@ -22894,7 +23141,7 @@ async function restorePreviousProjects(dataDir, actualPort) {
|
|
|
22894
23141
|
validProjects.push(prevDir);
|
|
22895
23142
|
continue;
|
|
22896
23143
|
}
|
|
22897
|
-
if (!
|
|
23144
|
+
if (!existsSync18(prevDir)) continue;
|
|
22898
23145
|
try {
|
|
22899
23146
|
await registerProject(prevDir, actualPort);
|
|
22900
23147
|
validProjects.push(prevDir);
|
|
@@ -22931,7 +23178,7 @@ async function cleanupStaleChannels() {
|
|
|
22931
23178
|
await cleanupStaleChannel2(p.dataDir);
|
|
22932
23179
|
}
|
|
22933
23180
|
}
|
|
22934
|
-
async function setupSkillsAndChannels() {
|
|
23181
|
+
async function setupSkillsAndChannels(port) {
|
|
22935
23182
|
const { getAllProjects: getAllProjects2 } = await Promise.resolve().then(() => (init_projects(), projects_exports));
|
|
22936
23183
|
const { ensureSkillsForDir: ensureSkillsForDir2 } = await Promise.resolve().then(() => (init_skills(), skills_exports));
|
|
22937
23184
|
for (const p of getAllProjects2()) {
|
|
@@ -22942,6 +23189,18 @@ async function setupSkillsAndChannels() {
|
|
|
22942
23189
|
if (readGlobalConfig3().channelEnabled === true) {
|
|
22943
23190
|
const { registerChannelForAll: registerChannelForAll2 } = await Promise.resolve().then(() => (init_channel_config(), channel_config_exports));
|
|
22944
23191
|
registerChannelForAll2(getAllProjects2().map((p) => p.dataDir));
|
|
23192
|
+
const { installHeartbeatHook: installHeartbeatHook2 } = await Promise.resolve().then(() => (init_claude_hooks(), claude_hooks_exports));
|
|
23193
|
+
installHeartbeatHook2(port);
|
|
23194
|
+
}
|
|
23195
|
+
}
|
|
23196
|
+
async function ensureHooksForRunningInstance(port) {
|
|
23197
|
+
try {
|
|
23198
|
+
const { readGlobalConfig: readGlobalConfig3 } = await Promise.resolve().then(() => (init_global_config(), global_config_exports));
|
|
23199
|
+
if (readGlobalConfig3().channelEnabled === true) {
|
|
23200
|
+
const { installHeartbeatHook: installHeartbeatHook2 } = await Promise.resolve().then(() => (init_claude_hooks(), claude_hooks_exports));
|
|
23201
|
+
installHeartbeatHook2(port);
|
|
23202
|
+
}
|
|
23203
|
+
} catch {
|
|
22945
23204
|
}
|
|
22946
23205
|
}
|
|
22947
23206
|
function setupInstanceLifecycle(actualPort) {
|
|
@@ -22958,6 +23217,13 @@ function setupInstanceLifecycle(actualPort) {
|
|
|
22958
23217
|
});
|
|
22959
23218
|
}
|
|
22960
23219
|
async function main() {
|
|
23220
|
+
const t0 = Date.now();
|
|
23221
|
+
const elapsed = () => `${Date.now() - t0}ms`;
|
|
23222
|
+
const watchdog = setTimeout(() => {
|
|
23223
|
+
console.error(`[startup] WARNING: startup has taken ${elapsed()} \u2014 still not ready`);
|
|
23224
|
+
console.error("[startup] This may indicate a hang in DB init, network check, or project restore.");
|
|
23225
|
+
console.error("[startup] Check the timing logs above to identify the stuck phase.");
|
|
23226
|
+
}, 1e4);
|
|
22961
23227
|
const parsed = parseArgs(process.argv);
|
|
22962
23228
|
if (!parsed) {
|
|
22963
23229
|
printUsage();
|
|
@@ -22965,8 +23231,11 @@ async function main() {
|
|
|
22965
23231
|
}
|
|
22966
23232
|
const { port, demo, forceUpdateCheck, noOpen, strictPort } = parsed;
|
|
22967
23233
|
let { dataDir } = parsed;
|
|
23234
|
+
console.error(`[startup ${elapsed()}] parsed args`);
|
|
22968
23235
|
await handleEarlyFlags(parsed);
|
|
23236
|
+
console.error(`[startup ${elapsed()}] checking for updates...`);
|
|
22969
23237
|
await checkForUpdates(forceUpdateCheck);
|
|
23238
|
+
console.error(`[startup ${elapsed()}] update check done`);
|
|
22970
23239
|
if (demo !== null) {
|
|
22971
23240
|
const scenario = DEMO_SCENARIOS.find((s) => s.id === demo);
|
|
22972
23241
|
if (!scenario) {
|
|
@@ -22977,17 +23246,22 @@ async function main() {
|
|
|
22977
23246
|
}
|
|
22978
23247
|
process.exit(1);
|
|
22979
23248
|
}
|
|
22980
|
-
dataDir =
|
|
23249
|
+
dataDir = join19(tmpdir2(), `hotsheet-demo-${Date.now()}`);
|
|
22981
23250
|
console.log(`
|
|
22982
23251
|
DEMO MODE: ${scenario.label}
|
|
22983
23252
|
`);
|
|
22984
23253
|
}
|
|
22985
23254
|
if (demo === null) {
|
|
23255
|
+
console.error(`[startup ${elapsed()}] cleaning up stale instances...`);
|
|
22986
23256
|
await cleanupStaleInstance();
|
|
23257
|
+
console.error(`[startup ${elapsed()}] stale cleanup done`);
|
|
22987
23258
|
const instance = readInstanceFile();
|
|
22988
23259
|
if (instance !== null) {
|
|
23260
|
+
console.error(`[startup ${elapsed()}] checking if instance on port ${instance.port} is running...`);
|
|
22989
23261
|
const running = await isInstanceRunning(instance.port);
|
|
23262
|
+
console.error(`[startup ${elapsed()}] instance check: running=${running}`);
|
|
22990
23263
|
if (running) {
|
|
23264
|
+
await ensureHooksForRunningInstance(instance.port);
|
|
22991
23265
|
if (!noOpen) {
|
|
22992
23266
|
await joinRunningInstance(instance.port, dataDir);
|
|
22993
23267
|
} else {
|
|
@@ -23010,13 +23284,21 @@ async function main() {
|
|
|
23010
23284
|
}
|
|
23011
23285
|
}
|
|
23012
23286
|
}
|
|
23287
|
+
console.error(`[startup ${elapsed()}] initializing project...`);
|
|
23013
23288
|
const db = await initializeProject(dataDir, demo);
|
|
23289
|
+
console.error(`[startup ${elapsed()}] project initialized`);
|
|
23014
23290
|
if (demo !== null) {
|
|
23015
23291
|
writeFileSettings(dataDir, { appName: "Hot Sheet Demo" });
|
|
23016
23292
|
}
|
|
23293
|
+
console.error(`[startup ${elapsed()}] starting server...`);
|
|
23017
23294
|
const { actualPort, secret } = await startAndConfigure(port, dataDir, strictPort);
|
|
23295
|
+
console.error(`[startup ${elapsed()}] server started on port ${actualPort}`);
|
|
23018
23296
|
registerExistingProject(dataDir, secret, db);
|
|
23297
|
+
console.error(`[startup ${elapsed()}] running post-startup tasks...`);
|
|
23019
23298
|
await postStartup(dataDir, actualPort, demo, noOpen);
|
|
23299
|
+
console.error(`[startup ${elapsed()}] post-startup complete`);
|
|
23300
|
+
clearTimeout(watchdog);
|
|
23301
|
+
console.error(`[startup ${elapsed()}] startup finished`);
|
|
23020
23302
|
if (demo !== null) {
|
|
23021
23303
|
const { seedDemoExtraProjects: seedDemoExtraProjects2 } = await Promise.resolve().then(() => (init_demo(), demo_exports));
|
|
23022
23304
|
await seedDemoExtraProjects2(demo, dataDir, actualPort);
|