claude-threads 1.28.0 → 1.29.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/CHANGELOG.md +18 -0
- package/README.md +3 -0
- package/dist/index.js +1480 -758
- package/dist/mcp/mcp-server.js +350 -171
- package/docs/CONFIGURATION.md +89 -0
- package/package.json +1 -1
package/dist/mcp/mcp-server.js
CHANGED
|
@@ -51265,7 +51265,8 @@ class PromptExecutor extends BaseExecutor {
|
|
|
51265
51265
|
pendingContextPrompt: null,
|
|
51266
51266
|
pendingExistingWorktreePrompt: null,
|
|
51267
51267
|
pendingUpdatePrompt: null,
|
|
51268
|
-
pendingRoutinePrompt: null
|
|
51268
|
+
pendingRoutinePrompt: null,
|
|
51269
|
+
pendingWatchPrompt: null
|
|
51269
51270
|
};
|
|
51270
51271
|
}
|
|
51271
51272
|
getInitialState() {
|
|
@@ -51276,7 +51277,8 @@ class PromptExecutor extends BaseExecutor {
|
|
|
51276
51277
|
pendingContextPrompt: this.state.pendingContextPrompt ? { ...this.state.pendingContextPrompt } : null,
|
|
51277
51278
|
pendingExistingWorktreePrompt: this.state.pendingExistingWorktreePrompt ? { ...this.state.pendingExistingWorktreePrompt } : null,
|
|
51278
51279
|
pendingUpdatePrompt: this.state.pendingUpdatePrompt ? { ...this.state.pendingUpdatePrompt } : null,
|
|
51279
|
-
pendingRoutinePrompt: this.state.pendingRoutinePrompt ? { ...this.state.pendingRoutinePrompt } : null
|
|
51280
|
+
pendingRoutinePrompt: this.state.pendingRoutinePrompt ? { ...this.state.pendingRoutinePrompt } : null,
|
|
51281
|
+
pendingWatchPrompt: this.state.pendingWatchPrompt ? { ...this.state.pendingWatchPrompt } : null
|
|
51280
51282
|
};
|
|
51281
51283
|
}
|
|
51282
51284
|
hydrateState(persisted) {
|
|
@@ -51284,7 +51286,8 @@ class PromptExecutor extends BaseExecutor {
|
|
|
51284
51286
|
pendingContextPrompt: persisted.pendingContextPrompt ?? null,
|
|
51285
51287
|
pendingExistingWorktreePrompt: persisted.pendingExistingWorktreePrompt ?? null,
|
|
51286
51288
|
pendingUpdatePrompt: persisted.pendingUpdatePrompt ?? null,
|
|
51287
|
-
pendingRoutinePrompt: null
|
|
51289
|
+
pendingRoutinePrompt: null,
|
|
51290
|
+
pendingWatchPrompt: null
|
|
51288
51291
|
};
|
|
51289
51292
|
}
|
|
51290
51293
|
setPendingContextPrompt(prompt) {
|
|
@@ -51420,24 +51423,36 @@ class PromptExecutor extends BaseExecutor {
|
|
|
51420
51423
|
hasPendingRoutinePrompt() {
|
|
51421
51424
|
return this.state.pendingRoutinePrompt !== null;
|
|
51422
51425
|
}
|
|
51423
|
-
async
|
|
51424
|
-
if (!
|
|
51426
|
+
async completeCreationPrompt(pending, label, clear, emit, postId, approved, username, ctx) {
|
|
51427
|
+
if (!pending || pending.postId !== postId)
|
|
51425
51428
|
return false;
|
|
51426
|
-
|
|
51427
|
-
|
|
51428
|
-
const { parsed, requestedBy } = this.state.pendingRoutinePrompt;
|
|
51429
|
-
const statusMessage = approved ? `✅ ${ctx.formatter.formatBold(`Routine "${parsed.name}" confirmed`)} by ${ctx.formatter.formatUserMention(username)} — saving...` : `❌ ${ctx.formatter.formatBold(`Routine "${parsed.name}" discarded`)} by ${ctx.formatter.formatUserMention(username)}`;
|
|
51429
|
+
const { parsed, requestedBy } = pending;
|
|
51430
|
+
const statusMessage = approved ? `✅ ${ctx.formatter.formatBold(`${label} "${parsed.name}" confirmed`)} by ${ctx.formatter.formatUserMention(username)} — saving...` : `❌ ${ctx.formatter.formatBold(`${label} "${parsed.name}" discarded`)} by ${ctx.formatter.formatUserMention(username)}`;
|
|
51430
51431
|
try {
|
|
51431
51432
|
await ctx.platform.updatePost(postId, statusMessage);
|
|
51432
51433
|
} catch (err) {
|
|
51433
|
-
ctx.logger.debug(`Failed to update
|
|
51434
|
-
}
|
|
51435
|
-
this.state.pendingRoutinePrompt = null;
|
|
51436
|
-
if (this.events) {
|
|
51437
|
-
this.events.emit("routine-prompt:complete", { approved, parsed, requestedBy, postId });
|
|
51434
|
+
ctx.logger.debug(`Failed to update ${label.toLowerCase()} prompt post: ${err}`);
|
|
51438
51435
|
}
|
|
51436
|
+
clear();
|
|
51437
|
+
emit({ approved, parsed, requestedBy, postId });
|
|
51439
51438
|
return true;
|
|
51440
51439
|
}
|
|
51440
|
+
handleRoutinePromptResponse(postId, approved, username, ctx) {
|
|
51441
|
+
return this.completeCreationPrompt(this.state.pendingRoutinePrompt, "Routine", () => {
|
|
51442
|
+
this.state.pendingRoutinePrompt = null;
|
|
51443
|
+
}, (payload) => this.events?.emit("routine-prompt:complete", payload), postId, approved, username, ctx);
|
|
51444
|
+
}
|
|
51445
|
+
setPendingWatchPrompt(prompt) {
|
|
51446
|
+
this.state.pendingWatchPrompt = prompt;
|
|
51447
|
+
}
|
|
51448
|
+
hasPendingWatchPrompt() {
|
|
51449
|
+
return this.state.pendingWatchPrompt !== null;
|
|
51450
|
+
}
|
|
51451
|
+
handleWatchPromptResponse(postId, approved, username, ctx) {
|
|
51452
|
+
return this.completeCreationPrompt(this.state.pendingWatchPrompt, "Watch", () => {
|
|
51453
|
+
this.state.pendingWatchPrompt = null;
|
|
51454
|
+
}, (payload) => this.events?.emit("watch-prompt:complete", payload), postId, approved, username, ctx);
|
|
51455
|
+
}
|
|
51441
51456
|
async handleReaction(postId, emoji4, user, action, ctx) {
|
|
51442
51457
|
ctx.logger.debug(`PromptExecutor.handleReaction: postId=${postId.substring(0, 8)}, emoji=${emoji4}, user=${user}, action=${action}`);
|
|
51443
51458
|
if (action !== "added") {
|
|
@@ -51510,6 +51525,18 @@ class PromptExecutor extends BaseExecutor {
|
|
|
51510
51525
|
ctx.logger.debug(`PromptExecutor: emoji ${emoji4} not valid for routine prompt, ignoring`);
|
|
51511
51526
|
return false;
|
|
51512
51527
|
}
|
|
51528
|
+
if (this.state.pendingWatchPrompt?.postId === postId) {
|
|
51529
|
+
if (isApprovalEmoji(emoji4)) {
|
|
51530
|
+
ctx.logger.debug(`Watch prompt reaction from @${user}: approve`);
|
|
51531
|
+
return this.handleWatchPromptResponse(postId, true, user, ctx);
|
|
51532
|
+
}
|
|
51533
|
+
if (isDenialEmoji(emoji4)) {
|
|
51534
|
+
ctx.logger.debug(`Watch prompt reaction from @${user}: discard`);
|
|
51535
|
+
return this.handleWatchPromptResponse(postId, false, user, ctx);
|
|
51536
|
+
}
|
|
51537
|
+
ctx.logger.debug(`PromptExecutor: emoji ${emoji4} not valid for watch prompt, ignoring`);
|
|
51538
|
+
return false;
|
|
51539
|
+
}
|
|
51513
51540
|
ctx.logger.debug(`PromptExecutor: no pending prompt state matches postId=${postId.substring(0, 8)}`);
|
|
51514
51541
|
return false;
|
|
51515
51542
|
}
|
|
@@ -52057,6 +52084,9 @@ class MessageManager {
|
|
|
52057
52084
|
setPendingRoutinePrompt(prompt) {
|
|
52058
52085
|
this.promptExecutor.setPendingRoutinePrompt(prompt);
|
|
52059
52086
|
}
|
|
52087
|
+
setPendingWatchPrompt(prompt) {
|
|
52088
|
+
this.promptExecutor.setPendingWatchPrompt(prompt);
|
|
52089
|
+
}
|
|
52060
52090
|
setPendingBugReport(report) {
|
|
52061
52091
|
this.bugReportExecutor.setPendingBugReport(report);
|
|
52062
52092
|
}
|
|
@@ -56957,6 +56987,27 @@ var COMMAND_REGISTRY = [
|
|
|
56957
56987
|
{ name: "run", description: "Run a routine now, outside its schedule", args: "<n>" }
|
|
56958
56988
|
]
|
|
56959
56989
|
},
|
|
56990
|
+
{
|
|
56991
|
+
command: "watch",
|
|
56992
|
+
description: "Create an event trigger from a natural-language request (confirmed with \uD83D\uDC4D before saving)",
|
|
56993
|
+
args: "<when ..., task>",
|
|
56994
|
+
category: "settings",
|
|
56995
|
+
audience: "user",
|
|
56996
|
+
claudeNotes: "User decisions, not yours"
|
|
56997
|
+
},
|
|
56998
|
+
{
|
|
56999
|
+
command: "watches",
|
|
57000
|
+
description: "List event triggers; pause/resume/delete manage them",
|
|
57001
|
+
args: "[pause|resume|delete <n>]",
|
|
57002
|
+
category: "settings",
|
|
57003
|
+
audience: "user",
|
|
57004
|
+
claudeNotes: "User decisions, not yours",
|
|
57005
|
+
subcommands: [
|
|
57006
|
+
{ name: "pause", description: "Pause a watch", args: "<n>" },
|
|
57007
|
+
{ name: "resume", description: "Resume a paused watch", args: "<n>" },
|
|
57008
|
+
{ name: "delete", description: "Delete a watch", args: "<n>" }
|
|
57009
|
+
]
|
|
57010
|
+
},
|
|
56960
57011
|
{
|
|
56961
57012
|
command: "update",
|
|
56962
57013
|
description: "Show auto-update status",
|
|
@@ -57255,6 +57306,30 @@ var handleRoutines = async (ctx, args) => {
|
|
|
57255
57306
|
await ctx.sessionManager.manageRoutines(ctx.threadId, args, ctx.username);
|
|
57256
57307
|
return { handled: true };
|
|
57257
57308
|
};
|
|
57309
|
+
var handleWatch = async (ctx, args) => {
|
|
57310
|
+
if (ctx.commandContext === "first-message") {
|
|
57311
|
+
return { handled: false };
|
|
57312
|
+
}
|
|
57313
|
+
if (!ctx.isAllowed) {
|
|
57314
|
+
return { handled: true };
|
|
57315
|
+
}
|
|
57316
|
+
if (!args?.trim()) {
|
|
57317
|
+
await ctx.client.createPost(`⚠️ Usage: ${ctx.formatter.formatCode("!watch when <something happens>, <task>")}`, ctx.threadId);
|
|
57318
|
+
return { handled: true };
|
|
57319
|
+
}
|
|
57320
|
+
await ctx.sessionManager.createWatch(ctx.threadId, args, ctx.username);
|
|
57321
|
+
return { handled: true };
|
|
57322
|
+
};
|
|
57323
|
+
var handleWatches = async (ctx, args) => {
|
|
57324
|
+
if (ctx.commandContext === "first-message") {
|
|
57325
|
+
return { handled: false };
|
|
57326
|
+
}
|
|
57327
|
+
if (!ctx.isAllowed) {
|
|
57328
|
+
return { handled: true };
|
|
57329
|
+
}
|
|
57330
|
+
await ctx.sessionManager.manageWatches(ctx.threadId, args, ctx.username);
|
|
57331
|
+
return { handled: true };
|
|
57332
|
+
};
|
|
57258
57333
|
var handleCd = async (ctx, args) => {
|
|
57259
57334
|
if (!args) {
|
|
57260
57335
|
return { handled: false };
|
|
@@ -57448,6 +57523,8 @@ handlers.set("remember", handleRemember);
|
|
|
57448
57523
|
handlers.set("memory", handleMemory);
|
|
57449
57524
|
handlers.set("routine", handleRoutine);
|
|
57450
57525
|
handlers.set("routines", handleRoutines);
|
|
57526
|
+
handlers.set("watch", handleWatch);
|
|
57527
|
+
handlers.set("watches", handleWatches);
|
|
57451
57528
|
handlers.set("cd", handleCd);
|
|
57452
57529
|
handlers.set("permissions", handlePermissions);
|
|
57453
57530
|
handlers.set("mentions", handleMentions);
|
|
@@ -57589,16 +57666,35 @@ var contextPromptFiles = new Map;
|
|
|
57589
57666
|
// src/memory/store.ts
|
|
57590
57667
|
import { createHash } from "crypto";
|
|
57591
57668
|
import {
|
|
57592
|
-
chmodSync as chmodSync2,
|
|
57593
57669
|
existsSync as existsSync5,
|
|
57594
57670
|
mkdirSync as mkdirSync2,
|
|
57595
57671
|
readFileSync as readFileSync4,
|
|
57596
|
-
|
|
57597
|
-
realpathSync,
|
|
57598
|
-
writeFileSync as writeFileSync2
|
|
57672
|
+
realpathSync
|
|
57599
57673
|
} from "fs";
|
|
57600
57674
|
import { homedir as homedir5 } from "os";
|
|
57601
57675
|
import { basename as basename3, dirname as dirname7, join as join7, sep as sep2 } from "path";
|
|
57676
|
+
|
|
57677
|
+
// src/persistence/atomic-file.ts
|
|
57678
|
+
import { chmodSync as chmodSync2, renameSync, writeFileSync as writeFileSync2 } from "fs";
|
|
57679
|
+
|
|
57680
|
+
class SerialQueue {
|
|
57681
|
+
tail = Promise.resolve();
|
|
57682
|
+
run(fn) {
|
|
57683
|
+
const next = this.tail.then(fn, fn);
|
|
57684
|
+
this.tail = next.catch(() => {
|
|
57685
|
+
return;
|
|
57686
|
+
});
|
|
57687
|
+
return next;
|
|
57688
|
+
}
|
|
57689
|
+
}
|
|
57690
|
+
function writeFileAtomic(file2, content) {
|
|
57691
|
+
const tempFile = `${file2}.tmp`;
|
|
57692
|
+
writeFileSync2(tempFile, content, { encoding: "utf-8", mode: 384 });
|
|
57693
|
+
renameSync(tempFile, file2);
|
|
57694
|
+
chmodSync2(file2, 384);
|
|
57695
|
+
}
|
|
57696
|
+
|
|
57697
|
+
// src/memory/store.ts
|
|
57602
57698
|
var log18 = createLogger("memory");
|
|
57603
57699
|
var DEFAULT_ROOT = join7(homedir5(), ".config", "claude-threads", "memory");
|
|
57604
57700
|
var CHANNEL_BLOCK_MAX_LINES = 200;
|
|
@@ -57771,12 +57867,12 @@ class MemoryStore {
|
|
|
57771
57867
|
_(older entries omitted — \`!memory\` shows all)_` : rendered;
|
|
57772
57868
|
}
|
|
57773
57869
|
runExclusive(platformId, fn) {
|
|
57774
|
-
|
|
57775
|
-
|
|
57776
|
-
|
|
57777
|
-
|
|
57778
|
-
}
|
|
57779
|
-
return
|
|
57870
|
+
let queue = this.locks.get(platformId);
|
|
57871
|
+
if (!queue) {
|
|
57872
|
+
queue = new SerialQueue;
|
|
57873
|
+
this.locks.set(platformId, queue);
|
|
57874
|
+
}
|
|
57875
|
+
return queue.run(fn);
|
|
57780
57876
|
}
|
|
57781
57877
|
loadLines(platformId) {
|
|
57782
57878
|
const file2 = this.channelMemoryPath(platformId);
|
|
@@ -57819,10 +57915,7 @@ _(older entries omitted — \`!memory\` shows all)_` : rendered;
|
|
|
57819
57915
|
const content = [FILE_HEADER, ...lines.map((l) => l.raw)].join(`
|
|
57820
57916
|
`) + `
|
|
57821
57917
|
`;
|
|
57822
|
-
|
|
57823
|
-
writeFileSync2(tempFile, content, { encoding: "utf-8", mode: 384 });
|
|
57824
|
-
renameSync(tempFile, file2);
|
|
57825
|
-
chmodSync2(file2, 384);
|
|
57918
|
+
writeFileAtomic(file2, content);
|
|
57826
57919
|
}
|
|
57827
57920
|
ensureDir(dir) {
|
|
57828
57921
|
if (!existsSync5(dir)) {
|
|
@@ -57834,6 +57927,117 @@ _(older entries omitted — \`!memory\` shows all)_` : rendered;
|
|
|
57834
57927
|
// src/memory/distiller.ts
|
|
57835
57928
|
var log19 = createLogger("memory");
|
|
57836
57929
|
|
|
57930
|
+
// src/session/registry.ts
|
|
57931
|
+
function compositeSessionId(platformId, threadId) {
|
|
57932
|
+
return `${platformId}:${threadId}`;
|
|
57933
|
+
}
|
|
57934
|
+
|
|
57935
|
+
class SessionRegistry {
|
|
57936
|
+
sessions = new Map;
|
|
57937
|
+
postIndex = new Map;
|
|
57938
|
+
sessionStore;
|
|
57939
|
+
constructor(sessionStore) {
|
|
57940
|
+
this.sessionStore = sessionStore;
|
|
57941
|
+
}
|
|
57942
|
+
getSessionId(platformId, threadId) {
|
|
57943
|
+
return compositeSessionId(platformId, threadId);
|
|
57944
|
+
}
|
|
57945
|
+
parseSessionId(sessionId) {
|
|
57946
|
+
const colonIndex = sessionId.indexOf(":");
|
|
57947
|
+
if (colonIndex === -1)
|
|
57948
|
+
return null;
|
|
57949
|
+
return {
|
|
57950
|
+
platformId: sessionId.substring(0, colonIndex),
|
|
57951
|
+
threadId: sessionId.substring(colonIndex + 1)
|
|
57952
|
+
};
|
|
57953
|
+
}
|
|
57954
|
+
find(platformId, threadId) {
|
|
57955
|
+
return this.sessions.get(this.getSessionId(platformId, threadId));
|
|
57956
|
+
}
|
|
57957
|
+
findByThreadId(threadId) {
|
|
57958
|
+
for (const session of this.sessions.values()) {
|
|
57959
|
+
if (session.threadId === threadId) {
|
|
57960
|
+
return session;
|
|
57961
|
+
}
|
|
57962
|
+
}
|
|
57963
|
+
return;
|
|
57964
|
+
}
|
|
57965
|
+
findByPost(postId) {
|
|
57966
|
+
const threadId = this.postIndex.get(postId);
|
|
57967
|
+
if (!threadId)
|
|
57968
|
+
return;
|
|
57969
|
+
return this.findByThreadId(threadId);
|
|
57970
|
+
}
|
|
57971
|
+
get(sessionId) {
|
|
57972
|
+
return this.sessions.get(sessionId);
|
|
57973
|
+
}
|
|
57974
|
+
has(platformId, threadId) {
|
|
57975
|
+
return this.sessions.has(this.getSessionId(platformId, threadId));
|
|
57976
|
+
}
|
|
57977
|
+
isActiveThread(threadId) {
|
|
57978
|
+
return this.findByThreadId(threadId) !== undefined;
|
|
57979
|
+
}
|
|
57980
|
+
register(session) {
|
|
57981
|
+
this.sessions.set(session.sessionId, session);
|
|
57982
|
+
}
|
|
57983
|
+
unregister(sessionId) {
|
|
57984
|
+
this.sessions.delete(sessionId);
|
|
57985
|
+
}
|
|
57986
|
+
registerPost(postId, threadId) {
|
|
57987
|
+
this.postIndex.set(postId, threadId);
|
|
57988
|
+
}
|
|
57989
|
+
unregisterPost(postId) {
|
|
57990
|
+
this.postIndex.delete(postId);
|
|
57991
|
+
}
|
|
57992
|
+
clearPostsForThread(threadId) {
|
|
57993
|
+
for (const [postId, tid] of this.postIndex.entries()) {
|
|
57994
|
+
if (tid === threadId) {
|
|
57995
|
+
this.postIndex.delete(postId);
|
|
57996
|
+
}
|
|
57997
|
+
}
|
|
57998
|
+
}
|
|
57999
|
+
getAll() {
|
|
58000
|
+
return Array.from(this.sessions.values());
|
|
58001
|
+
}
|
|
58002
|
+
getActiveThreadIds() {
|
|
58003
|
+
return Array.from(this.sessions.values()).map((s) => s.threadId);
|
|
58004
|
+
}
|
|
58005
|
+
get size() {
|
|
58006
|
+
return this.sessions.size;
|
|
58007
|
+
}
|
|
58008
|
+
getForPlatform(platformId) {
|
|
58009
|
+
return Array.from(this.sessions.values()).filter((s) => s.sessionId.startsWith(`${platformId}:`));
|
|
58010
|
+
}
|
|
58011
|
+
hasPaused(platformId, threadId) {
|
|
58012
|
+
return this.sessionStore.findByThread(platformId, threadId) !== undefined;
|
|
58013
|
+
}
|
|
58014
|
+
getPersisted(platformId, threadId) {
|
|
58015
|
+
return this.sessionStore.findByThread(platformId, threadId);
|
|
58016
|
+
}
|
|
58017
|
+
getPersistedByThreadId(threadId) {
|
|
58018
|
+
return this.sessionStore.findByThreadIdAnyState(threadId);
|
|
58019
|
+
}
|
|
58020
|
+
getSessionStore() {
|
|
58021
|
+
return this.sessionStore;
|
|
58022
|
+
}
|
|
58023
|
+
hasById(sessionId) {
|
|
58024
|
+
return this.sessions.has(sessionId);
|
|
58025
|
+
}
|
|
58026
|
+
clear() {
|
|
58027
|
+
this.sessions.clear();
|
|
58028
|
+
this.postIndex.clear();
|
|
58029
|
+
}
|
|
58030
|
+
getThreadIdForPost(postId) {
|
|
58031
|
+
return this.postIndex.get(postId);
|
|
58032
|
+
}
|
|
58033
|
+
getSessions() {
|
|
58034
|
+
return this.sessions;
|
|
58035
|
+
}
|
|
58036
|
+
getPostIndex() {
|
|
58037
|
+
return this.postIndex;
|
|
58038
|
+
}
|
|
58039
|
+
}
|
|
58040
|
+
|
|
57837
58041
|
// src/session/lifecycle.ts
|
|
57838
58042
|
var log20 = createLogger("lifecycle");
|
|
57839
58043
|
var sessionLog3 = createSessionLog(log20);
|
|
@@ -57853,199 +58057,174 @@ var DEFAULT_CONFIG_DIR = join8(homedir6(), ".config", "claude-threads");
|
|
|
57853
58057
|
var DEFAULT_FILE = join8(DEFAULT_CONFIG_DIR, "github-emails.yaml");
|
|
57854
58058
|
|
|
57855
58059
|
// src/persistence/routines-store.ts
|
|
57856
|
-
import {
|
|
58060
|
+
import { join as join10 } from "path";
|
|
58061
|
+
|
|
58062
|
+
// src/persistence/platform-list-store.ts
|
|
58063
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync5, statSync as statSync2 } from "fs";
|
|
57857
58064
|
import { homedir as homedir7 } from "os";
|
|
57858
58065
|
import { join as join9 } from "path";
|
|
57859
|
-
|
|
57860
|
-
|
|
57861
|
-
// src/persistence/atomic-file.ts
|
|
57862
|
-
import { chmodSync as chmodSync3, renameSync as renameSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
57863
|
-
|
|
57864
|
-
class SerialQueue {
|
|
57865
|
-
tail = Promise.resolve();
|
|
57866
|
-
run(fn) {
|
|
57867
|
-
const next = this.tail.then(fn, fn);
|
|
57868
|
-
this.tail = next.catch(() => {
|
|
57869
|
-
return;
|
|
57870
|
-
});
|
|
57871
|
-
return next;
|
|
57872
|
-
}
|
|
57873
|
-
}
|
|
57874
|
-
function writeFileAtomic(file2, content) {
|
|
57875
|
-
const tempFile = `${file2}.tmp`;
|
|
57876
|
-
writeFileSync3(tempFile, content, { encoding: "utf-8", mode: 384 });
|
|
57877
|
-
renameSync2(tempFile, file2);
|
|
57878
|
-
chmodSync3(file2, 384);
|
|
57879
|
-
}
|
|
57880
|
-
|
|
57881
|
-
// src/persistence/routines-store.ts
|
|
57882
|
-
var log22 = createLogger("routines");
|
|
57883
|
-
var DEFAULT_CONFIG_DIR2 = join9(homedir7(), ".config", "claude-threads");
|
|
57884
|
-
var DEFAULT_FILE2 = join9(DEFAULT_CONFIG_DIR2, "routines.yaml");
|
|
58066
|
+
var STORES_CONFIG_DIR = join9(homedir7(), ".config", "claude-threads");
|
|
57885
58067
|
var STORE_VERSION = 1;
|
|
57886
|
-
|
|
57887
|
-
|
|
57888
|
-
var TIME_RE = /^([01]\d|2[0-3]):[0-5]\d$/;
|
|
57889
|
-
function isValidTimezone(tz) {
|
|
57890
|
-
if (typeof tz !== "string" || !tz)
|
|
57891
|
-
return false;
|
|
57892
|
-
try {
|
|
57893
|
-
new Intl.DateTimeFormat("en-US", { timeZone: tz });
|
|
57894
|
-
return true;
|
|
57895
|
-
} catch {
|
|
57896
|
-
return false;
|
|
57897
|
-
}
|
|
57898
|
-
}
|
|
57899
|
-
function validateSchedule(schedule) {
|
|
57900
|
-
if (!SCHEDULE_PRESETS.includes(schedule.preset)) {
|
|
57901
|
-
return `unknown preset "${String(schedule.preset)}" (expected ${SCHEDULE_PRESETS.join("/")})`;
|
|
57902
|
-
}
|
|
57903
|
-
if (!isValidTimezone(schedule.timezone)) {
|
|
57904
|
-
return `invalid timezone "${String(schedule.timezone)}"`;
|
|
57905
|
-
}
|
|
57906
|
-
if (schedule.preset === "hourly") {
|
|
57907
|
-
return null;
|
|
57908
|
-
}
|
|
57909
|
-
if (!schedule.time || !TIME_RE.test(schedule.time)) {
|
|
57910
|
-
return `invalid time "${String(schedule.time)}" (expected HH:MM, 24h)`;
|
|
57911
|
-
}
|
|
57912
|
-
if (schedule.preset === "weekly") {
|
|
57913
|
-
const weekday = schedule.weekday;
|
|
57914
|
-
if (typeof weekday !== "number" || !Number.isInteger(weekday) || weekday < 1 || weekday > 7) {
|
|
57915
|
-
return `invalid weekday "${String(weekday)}" (expected 1=Mon … 7=Sun)`;
|
|
57916
|
-
}
|
|
57917
|
-
}
|
|
57918
|
-
return null;
|
|
57919
|
-
}
|
|
57920
|
-
class RoutinesStore {
|
|
58068
|
+
|
|
58069
|
+
class PlatformListStore {
|
|
57921
58070
|
file;
|
|
57922
58071
|
configDir;
|
|
57923
58072
|
queue = new SerialQueue;
|
|
57924
|
-
|
|
57925
|
-
|
|
57926
|
-
|
|
57927
|
-
|
|
57928
|
-
|
|
58073
|
+
collectionKey;
|
|
58074
|
+
cache = null;
|
|
58075
|
+
constructor(collectionKey, defaultFile, filePath) {
|
|
58076
|
+
this.collectionKey = collectionKey;
|
|
58077
|
+
if (filePath) {
|
|
58078
|
+
this.file = filePath;
|
|
58079
|
+
this.configDir = join9(filePath, "..");
|
|
57929
58080
|
} else {
|
|
57930
|
-
this.file =
|
|
57931
|
-
this.configDir =
|
|
57932
|
-
}
|
|
57933
|
-
if (!existsSync6(this.configDir)) {
|
|
57934
|
-
mkdirSync3(this.configDir, { recursive: true, mode: 448 });
|
|
58081
|
+
this.file = defaultFile;
|
|
58082
|
+
this.configDir = STORES_CONFIG_DIR;
|
|
57935
58083
|
}
|
|
58084
|
+
mkdirSync3(this.configDir, { recursive: true, mode: 448 });
|
|
57936
58085
|
}
|
|
57937
58086
|
list(platformId) {
|
|
57938
|
-
return this.loadRaw().
|
|
58087
|
+
return structuredClone(this.loadRaw().items[platformId] ?? []);
|
|
57939
58088
|
}
|
|
57940
58089
|
get(platformId, id) {
|
|
57941
|
-
|
|
58090
|
+
const item = (this.loadRaw().items[platformId] ?? []).find((i) => i.id === id);
|
|
58091
|
+
return item === undefined ? undefined : structuredClone(item);
|
|
57942
58092
|
}
|
|
57943
|
-
|
|
58093
|
+
addItem(platformId, max, capNoun, build) {
|
|
57944
58094
|
return this.runExclusive(() => {
|
|
57945
|
-
const
|
|
57946
|
-
if (
|
|
57947
|
-
return { ok: false, error:
|
|
57948
|
-
const
|
|
57949
|
-
const
|
|
57950
|
-
if (
|
|
57951
|
-
return { ok: false, error:
|
|
57952
|
-
|
|
57953
|
-
|
|
57954
|
-
if (existing.length >= maxRoutines) {
|
|
57955
|
-
return { ok: false, error: `routine limit reached (${maxRoutines}); delete one first` };
|
|
57956
|
-
}
|
|
57957
|
-
const full = {
|
|
57958
|
-
...routine,
|
|
57959
|
-
name,
|
|
57960
|
-
prompt,
|
|
57961
|
-
id: randomUUID2().slice(0, 8),
|
|
57962
|
-
createdAt: new Date().toISOString(),
|
|
57963
|
-
enabled: true,
|
|
57964
|
-
consecutiveFailures: 0
|
|
57965
|
-
};
|
|
57966
|
-
data.routines[platformId] = [...existing, full];
|
|
58095
|
+
const built = build();
|
|
58096
|
+
if (typeof built === "string")
|
|
58097
|
+
return { ok: false, error: built };
|
|
58098
|
+
const data = this.loadRaw(true);
|
|
58099
|
+
const existing = data.items[platformId] ?? [];
|
|
58100
|
+
if (existing.length >= max) {
|
|
58101
|
+
return { ok: false, error: `${capNoun} limit reached (${max}); delete one first` };
|
|
58102
|
+
}
|
|
58103
|
+
data.items[platformId] = [...existing, built];
|
|
57967
58104
|
this.writeAtomic(data);
|
|
57968
|
-
|
|
57969
|
-
return { ok: true, routine: full };
|
|
58105
|
+
return { ok: true, item: structuredClone(built) };
|
|
57970
58106
|
});
|
|
57971
58107
|
}
|
|
57972
58108
|
update(platformId, id, patch) {
|
|
57973
58109
|
return this.runExclusive(() => {
|
|
57974
|
-
const data = this.loadRaw();
|
|
57975
|
-
const
|
|
57976
|
-
const idx =
|
|
58110
|
+
const data = this.loadRaw(true);
|
|
58111
|
+
const items = data.items[platformId] ?? [];
|
|
58112
|
+
const idx = items.findIndex((item) => item.id === id);
|
|
57977
58113
|
if (idx < 0)
|
|
57978
58114
|
return;
|
|
57979
|
-
|
|
58115
|
+
items[idx] = { ...items[idx], ...patch };
|
|
57980
58116
|
this.writeAtomic(data);
|
|
57981
|
-
return
|
|
58117
|
+
return structuredClone(items[idx]);
|
|
57982
58118
|
});
|
|
57983
58119
|
}
|
|
57984
58120
|
remove(platformId, id) {
|
|
57985
58121
|
return this.runExclusive(() => {
|
|
57986
|
-
const data = this.loadRaw();
|
|
57987
|
-
const
|
|
57988
|
-
const idx =
|
|
58122
|
+
const data = this.loadRaw(true);
|
|
58123
|
+
const items = data.items[platformId] ?? [];
|
|
58124
|
+
const idx = items.findIndex((item) => item.id === id);
|
|
57989
58125
|
if (idx < 0)
|
|
57990
58126
|
return;
|
|
57991
|
-
const [removed] =
|
|
57992
|
-
if (
|
|
57993
|
-
delete data.
|
|
58127
|
+
const [removed] = items.splice(idx, 1);
|
|
58128
|
+
if (items.length === 0)
|
|
58129
|
+
delete data.items[platformId];
|
|
57994
58130
|
this.writeAtomic(data);
|
|
57995
|
-
|
|
58131
|
+
this.onRemoved(platformId, removed);
|
|
57996
58132
|
return removed;
|
|
57997
58133
|
});
|
|
57998
58134
|
}
|
|
58135
|
+
onRemoved(_platformId, _item) {}
|
|
57999
58136
|
runExclusive(fn) {
|
|
58000
58137
|
return this.queue.run(fn);
|
|
58001
58138
|
}
|
|
58002
|
-
loadRaw() {
|
|
58139
|
+
loadRaw(forWrite = false) {
|
|
58003
58140
|
if (!existsSync6(this.file)) {
|
|
58004
|
-
|
|
58141
|
+
this.cache = null;
|
|
58142
|
+
return { version: STORE_VERSION, items: {} };
|
|
58005
58143
|
}
|
|
58006
58144
|
try {
|
|
58007
|
-
const
|
|
58008
|
-
if (
|
|
58009
|
-
return
|
|
58145
|
+
const stat = statSync2(this.file);
|
|
58146
|
+
if (this.cache && this.cache.mtimeMs === stat.mtimeMs && this.cache.size === stat.size) {
|
|
58147
|
+
return this.cache.data;
|
|
58010
58148
|
}
|
|
58011
|
-
const
|
|
58012
|
-
|
|
58013
|
-
|
|
58014
|
-
|
|
58015
|
-
|
|
58016
|
-
|
|
58017
|
-
|
|
58018
|
-
|
|
58149
|
+
const parsed = yaml.load(readFileSync5(this.file, "utf-8"));
|
|
58150
|
+
const rawItems = parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed[this.collectionKey] : undefined;
|
|
58151
|
+
if (rawItems !== null && (rawItems === undefined || typeof rawItems !== "object" || Array.isArray(rawItems))) {
|
|
58152
|
+
this.cache = null;
|
|
58153
|
+
const problem = `unexpected shape (missing or non-map '${this.collectionKey}' key)`;
|
|
58154
|
+
if (forWrite) {
|
|
58155
|
+
throw new Error(`refusing to write over unreadable ${this.file}: ${problem}`);
|
|
58156
|
+
}
|
|
58157
|
+
this.warn(`Failed to read ${this.file}: ${problem} — starting empty`);
|
|
58158
|
+
return { version: STORE_VERSION, items: {} };
|
|
58159
|
+
}
|
|
58160
|
+
const items = rawItems ?? {};
|
|
58161
|
+
for (const list of Object.values(items)) {
|
|
58162
|
+
for (const item of list)
|
|
58163
|
+
this.applyItemDefaults(item);
|
|
58164
|
+
}
|
|
58165
|
+
const data = { version: parsed?.version ?? STORE_VERSION, items };
|
|
58166
|
+
this.cache = { mtimeMs: stat.mtimeMs, size: stat.size, data };
|
|
58167
|
+
return data;
|
|
58019
58168
|
} catch (err) {
|
|
58020
|
-
|
|
58021
|
-
|
|
58169
|
+
this.cache = null;
|
|
58170
|
+
if (forWrite) {
|
|
58171
|
+
throw new Error(`refusing to write over unreadable ${this.file}: ${err.message}`, { cause: err });
|
|
58172
|
+
}
|
|
58173
|
+
this.warn(`Failed to read ${this.file}: ${err.message} — starting empty`);
|
|
58174
|
+
return { version: STORE_VERSION, items: {} };
|
|
58022
58175
|
}
|
|
58023
58176
|
}
|
|
58024
58177
|
writeAtomic(data) {
|
|
58025
|
-
|
|
58178
|
+
try {
|
|
58179
|
+
this.persistFile(yaml.dump({ version: data.version, [this.collectionKey]: data.items }, { sortKeys: true, lineWidth: -1 }));
|
|
58180
|
+
} catch (err) {
|
|
58181
|
+
this.cache = null;
|
|
58182
|
+
throw err;
|
|
58183
|
+
}
|
|
58184
|
+
try {
|
|
58185
|
+
const stat = statSync2(this.file);
|
|
58186
|
+
this.cache = { mtimeMs: stat.mtimeMs, size: stat.size, data };
|
|
58187
|
+
} catch {
|
|
58188
|
+
this.cache = null;
|
|
58189
|
+
}
|
|
58190
|
+
}
|
|
58191
|
+
persistFile(content) {
|
|
58192
|
+
writeFileAtomic(this.file, content);
|
|
58026
58193
|
}
|
|
58027
58194
|
}
|
|
58028
58195
|
|
|
58196
|
+
// src/persistence/routines-store.ts
|
|
58197
|
+
var log22 = createLogger("routines");
|
|
58198
|
+
var DEFAULT_FILE2 = join10(STORES_CONFIG_DIR, "routines.yaml");
|
|
58199
|
+
|
|
58029
58200
|
// src/routines/parser.ts
|
|
58030
58201
|
var log23 = createLogger("routines");
|
|
58031
58202
|
|
|
58203
|
+
// src/persistence/watches-store.ts
|
|
58204
|
+
import { join as join11 } from "path";
|
|
58205
|
+
var log24 = createLogger("watches");
|
|
58206
|
+
var DEFAULT_FILE3 = join11(STORES_CONFIG_DIR, "watches.yaml");
|
|
58207
|
+
|
|
58208
|
+
// src/watches/parser.ts
|
|
58209
|
+
var log25 = createLogger("watches");
|
|
58210
|
+
|
|
58032
58211
|
// src/operations/commands/handler.ts
|
|
58033
|
-
var
|
|
58034
|
-
var sessionLog4 = createSessionLog(
|
|
58212
|
+
var log26 = createLogger("commands");
|
|
58213
|
+
var sessionLog4 = createSessionLog(log26);
|
|
58035
58214
|
// src/operations/suggestions/branch.ts
|
|
58036
58215
|
import { exec as exec2 } from "child_process";
|
|
58037
58216
|
import { promisify as promisify2 } from "util";
|
|
58038
58217
|
var execAsync2 = promisify2(exec2);
|
|
58039
|
-
var
|
|
58218
|
+
var log27 = createLogger("branch");
|
|
58040
58219
|
|
|
58041
58220
|
// src/operations/worktree/handler.ts
|
|
58042
|
-
var
|
|
58043
|
-
var sessionLog5 = createSessionLog(
|
|
58221
|
+
var log28 = createLogger("worktree");
|
|
58222
|
+
var sessionLog5 = createSessionLog(log28);
|
|
58044
58223
|
// src/operations/events/handler.ts
|
|
58045
|
-
var
|
|
58046
|
-
var sessionLog6 = createSessionLog(
|
|
58224
|
+
var log29 = createLogger("events");
|
|
58225
|
+
var sessionLog6 = createSessionLog(log29);
|
|
58047
58226
|
// src/operations/monitor/handler.ts
|
|
58048
|
-
var
|
|
58227
|
+
var log30 = createLogger("monitor");
|
|
58049
58228
|
var DEFAULT_INTERVAL_MS = 60 * 1000;
|
|
58050
58229
|
// src/utils/websocket.ts
|
|
58051
58230
|
var WS;
|
|
@@ -58127,7 +58306,7 @@ ${code}
|
|
|
58127
58306
|
|
|
58128
58307
|
// src/platform/mattermost/upload.ts
|
|
58129
58308
|
import { readFile } from "fs/promises";
|
|
58130
|
-
var
|
|
58309
|
+
var log31 = createLogger("mm-upload");
|
|
58131
58310
|
async function uploadFileMattermost(args) {
|
|
58132
58311
|
const { url: url2, token, channelId, threadId, filePath, filename, caption } = args;
|
|
58133
58312
|
const buffer = await readFile(filePath);
|
|
@@ -58135,7 +58314,7 @@ async function uploadFileMattermost(args) {
|
|
|
58135
58314
|
const arrayBuffer = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
|
|
58136
58315
|
const formData = new FormData;
|
|
58137
58316
|
formData.append("files", new Blob([arrayBuffer]), filename);
|
|
58138
|
-
|
|
58317
|
+
log31.debug(`POST /files (${buffer.length} bytes, ${filename})`);
|
|
58139
58318
|
const uploadResponse = await fetch(uploadUrl, {
|
|
58140
58319
|
method: "POST",
|
|
58141
58320
|
headers: {
|
|
@@ -58159,7 +58338,7 @@ async function uploadFileMattermost(args) {
|
|
|
58159
58338
|
root_id: resolvePostThreadId(threadId),
|
|
58160
58339
|
file_ids: [fileInfo.id]
|
|
58161
58340
|
};
|
|
58162
|
-
|
|
58341
|
+
log31.debug(`POST /posts (file_ids=[${fileInfo.id}])`);
|
|
58163
58342
|
const postResponse = await fetch(postUrl, {
|
|
58164
58343
|
method: "POST",
|
|
58165
58344
|
headers: {
|
|
@@ -58653,7 +58832,7 @@ ${code}
|
|
|
58653
58832
|
|
|
58654
58833
|
// src/platform/slack/upload.ts
|
|
58655
58834
|
import { readFile as readFile2 } from "fs/promises";
|
|
58656
|
-
var
|
|
58835
|
+
var log32 = createLogger("slack-upload");
|
|
58657
58836
|
var DEFAULT_API_URL = "https://slack.com/api";
|
|
58658
58837
|
async function uploadFileSlack(args) {
|
|
58659
58838
|
const { botToken, channelId, threadTs, filePath, filename, caption } = args;
|
|
@@ -58661,7 +58840,7 @@ async function uploadFileSlack(args) {
|
|
|
58661
58840
|
const buffer = await readFile2(filePath);
|
|
58662
58841
|
const params = new URLSearchParams({ filename, length: String(buffer.length) });
|
|
58663
58842
|
const step1Url = `${apiUrl}/files.getUploadURLExternal?${params.toString()}`;
|
|
58664
|
-
|
|
58843
|
+
log32.debug(`GET files.getUploadURLExternal (${buffer.length} bytes, ${filename})`);
|
|
58665
58844
|
const step1Response = await fetch(step1Url, {
|
|
58666
58845
|
method: "GET",
|
|
58667
58846
|
headers: {
|
|
@@ -58679,7 +58858,7 @@ async function uploadFileSlack(args) {
|
|
|
58679
58858
|
const uploadUrl = step1Data.upload_url;
|
|
58680
58859
|
const fileId = step1Data.file_id;
|
|
58681
58860
|
const arrayBuffer = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
|
|
58682
|
-
|
|
58861
|
+
log32.debug(`POST <upload_url>`);
|
|
58683
58862
|
const step2Response = await fetch(uploadUrl, {
|
|
58684
58863
|
method: "POST",
|
|
58685
58864
|
headers: {
|
|
@@ -58699,7 +58878,7 @@ async function uploadFileSlack(args) {
|
|
|
58699
58878
|
if (caption !== undefined) {
|
|
58700
58879
|
step3Body.initial_comment = caption;
|
|
58701
58880
|
}
|
|
58702
|
-
|
|
58881
|
+
log32.debug(`POST files.completeUploadExternal (file_id=${fileId}, thread_ts=${threadTs})`);
|
|
58703
58882
|
const step3Response = await fetch(`${apiUrl}/files.completeUploadExternal`, {
|
|
58704
58883
|
method: "POST",
|
|
58705
58884
|
headers: {
|
|
@@ -58717,7 +58896,7 @@ async function uploadFileSlack(args) {
|
|
|
58717
58896
|
throw new Error(`Slack completeUploadExternal error: ${step3Data.error || "unknown"}`);
|
|
58718
58897
|
}
|
|
58719
58898
|
if (!step3Data.ts) {
|
|
58720
|
-
|
|
58899
|
+
log32.warn(`Slack completeUploadExternal returned no ts; using fileId ${fileId} as postId. ` + `Do not use this id for updatePost/addReaction.`);
|
|
58721
58900
|
}
|
|
58722
58901
|
return { fileId, postId: step3Data.ts ?? fileId };
|
|
58723
58902
|
}
|