openclaw-telegram-manager 1.0.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.
Files changed (138) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +110 -0
  3. package/dist/commands/archive.d.ts +4 -0
  4. package/dist/commands/archive.d.ts.map +1 -0
  5. package/dist/commands/archive.js +71 -0
  6. package/dist/commands/archive.js.map +1 -0
  7. package/dist/commands/doctor-all.d.ts +3 -0
  8. package/dist/commands/doctor-all.d.ts.map +1 -0
  9. package/dist/commands/doctor-all.js +193 -0
  10. package/dist/commands/doctor-all.js.map +1 -0
  11. package/dist/commands/doctor.d.ts +3 -0
  12. package/dist/commands/doctor.d.ts.map +1 -0
  13. package/dist/commands/doctor.js +74 -0
  14. package/dist/commands/doctor.js.map +1 -0
  15. package/dist/commands/help.d.ts +4 -0
  16. package/dist/commands/help.d.ts.map +1 -0
  17. package/dist/commands/help.js +8 -0
  18. package/dist/commands/help.js.map +1 -0
  19. package/dist/commands/init.d.ts +17 -0
  20. package/dist/commands/init.d.ts.map +1 -0
  21. package/dist/commands/init.js +304 -0
  22. package/dist/commands/init.js.map +1 -0
  23. package/dist/commands/list.d.ts +3 -0
  24. package/dist/commands/list.d.ts.map +1 -0
  25. package/dist/commands/list.js +22 -0
  26. package/dist/commands/list.js.map +1 -0
  27. package/dist/commands/rename.d.ts +3 -0
  28. package/dist/commands/rename.d.ts.map +1 -0
  29. package/dist/commands/rename.js +115 -0
  30. package/dist/commands/rename.js.map +1 -0
  31. package/dist/commands/snooze.d.ts +3 -0
  32. package/dist/commands/snooze.d.ts.map +1 -0
  33. package/dist/commands/snooze.js +52 -0
  34. package/dist/commands/snooze.js.map +1 -0
  35. package/dist/commands/status.d.ts +3 -0
  36. package/dist/commands/status.d.ts.map +1 -0
  37. package/dist/commands/status.js +48 -0
  38. package/dist/commands/status.js.map +1 -0
  39. package/dist/commands/sync.d.ts +3 -0
  40. package/dist/commands/sync.d.ts.map +1 -0
  41. package/dist/commands/sync.js +38 -0
  42. package/dist/commands/sync.js.map +1 -0
  43. package/dist/commands/upgrade.d.ts +3 -0
  44. package/dist/commands/upgrade.d.ts.map +1 -0
  45. package/dist/commands/upgrade.js +52 -0
  46. package/dist/commands/upgrade.js.map +1 -0
  47. package/dist/index.d.ts +25 -0
  48. package/dist/index.d.ts.map +1 -0
  49. package/dist/index.js +30 -0
  50. package/dist/index.js.map +1 -0
  51. package/dist/lib/audit.d.ts +12 -0
  52. package/dist/lib/audit.d.ts.map +1 -0
  53. package/dist/lib/audit.js +35 -0
  54. package/dist/lib/audit.js.map +1 -0
  55. package/dist/lib/auth.d.ts +26 -0
  56. package/dist/lib/auth.d.ts.map +1 -0
  57. package/dist/lib/auth.js +73 -0
  58. package/dist/lib/auth.js.map +1 -0
  59. package/dist/lib/capsule.d.ts +27 -0
  60. package/dist/lib/capsule.d.ts.map +1 -0
  61. package/dist/lib/capsule.js +130 -0
  62. package/dist/lib/capsule.js.map +1 -0
  63. package/dist/lib/config-restart.d.ts +23 -0
  64. package/dist/lib/config-restart.d.ts.map +1 -0
  65. package/dist/lib/config-restart.js +129 -0
  66. package/dist/lib/config-restart.js.map +1 -0
  67. package/dist/lib/doctor-checks.d.ts +50 -0
  68. package/dist/lib/doctor-checks.d.ts.map +1 -0
  69. package/dist/lib/doctor-checks.js +421 -0
  70. package/dist/lib/doctor-checks.js.map +1 -0
  71. package/dist/lib/include-generator.d.ts +35 -0
  72. package/dist/lib/include-generator.d.ts.map +1 -0
  73. package/dist/lib/include-generator.js +140 -0
  74. package/dist/lib/include-generator.js.map +1 -0
  75. package/dist/lib/registry.d.ts +27 -0
  76. package/dist/lib/registry.d.ts.map +1 -0
  77. package/dist/lib/registry.js +154 -0
  78. package/dist/lib/registry.js.map +1 -0
  79. package/dist/lib/security.d.ts +57 -0
  80. package/dist/lib/security.d.ts.map +1 -0
  81. package/dist/lib/security.js +133 -0
  82. package/dist/lib/security.js.map +1 -0
  83. package/dist/lib/telegram.d.ts +55 -0
  84. package/dist/lib/telegram.d.ts.map +1 -0
  85. package/dist/lib/telegram.js +254 -0
  86. package/dist/lib/telegram.js.map +1 -0
  87. package/dist/lib/types.d.ts +120 -0
  88. package/dist/lib/types.d.ts.map +1 -0
  89. package/dist/lib/types.js +85 -0
  90. package/dist/lib/types.js.map +1 -0
  91. package/dist/setup.d.ts +3 -0
  92. package/dist/setup.d.ts.map +1 -0
  93. package/dist/setup.js +333 -0
  94. package/dist/setup.js.map +1 -0
  95. package/dist/tool.d.ts +15 -0
  96. package/dist/tool.d.ts.map +1 -0
  97. package/dist/tool.js +201 -0
  98. package/dist/tool.js.map +1 -0
  99. package/openclaw.plugin.json +9 -0
  100. package/package.json +48 -0
  101. package/skills/topic/SKILL.md +35 -0
  102. package/src/commands/archive.ts +89 -0
  103. package/src/commands/doctor-all.ts +243 -0
  104. package/src/commands/doctor.ts +100 -0
  105. package/src/commands/help.ts +11 -0
  106. package/src/commands/init.ts +376 -0
  107. package/src/commands/list.ts +28 -0
  108. package/src/commands/rename.ts +140 -0
  109. package/src/commands/snooze.ts +69 -0
  110. package/src/commands/status.ts +59 -0
  111. package/src/commands/sync.ts +46 -0
  112. package/src/commands/upgrade.ts +64 -0
  113. package/src/index.ts +54 -0
  114. package/src/lib/audit.ts +44 -0
  115. package/src/lib/auth.ts +96 -0
  116. package/src/lib/capsule.ts +206 -0
  117. package/src/lib/config-restart.ts +167 -0
  118. package/src/lib/doctor-checks.ts +639 -0
  119. package/src/lib/include-generator.ts +174 -0
  120. package/src/lib/registry.ts +197 -0
  121. package/src/lib/security.ts +174 -0
  122. package/src/lib/telegram.ts +311 -0
  123. package/src/lib/types.ts +172 -0
  124. package/src/setup.ts +402 -0
  125. package/src/templates/base/COMMANDS.md +3 -0
  126. package/src/templates/base/CRON.md +3 -0
  127. package/src/templates/base/LINKS.md +3 -0
  128. package/src/templates/base/NOTES.md +3 -0
  129. package/src/templates/base/README.md +3 -0
  130. package/src/templates/base/STATUS.md +13 -0
  131. package/src/templates/base/TODO.md +11 -0
  132. package/src/templates/overlays/coding/ARCHITECTURE.md +3 -0
  133. package/src/templates/overlays/coding/DEPLOY.md +3 -0
  134. package/src/templates/overlays/marketing/CAMPAIGNS.md +3 -0
  135. package/src/templates/overlays/marketing/METRICS.md +3 -0
  136. package/src/templates/overlays/research/FINDINGS.md +3 -0
  137. package/src/templates/overlays/research/SOURCES.md +3 -0
  138. package/src/tool.ts +282 -0
@@ -0,0 +1,304 @@
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+ import { readRegistry, withRegistry } from '../lib/registry.js';
4
+ import { checkAuthorization } from '../lib/auth.js';
5
+ import { topicKey, CAPSULE_VERSION, } from '../lib/types.js';
6
+ import { validateSlug, sanitizeSlug, jailCheck, rejectSymlink, htmlEscape, validateGroupId, validateThreadId, buildCallbackData, } from '../lib/security.js';
7
+ import { scaffoldCapsule } from '../lib/capsule.js';
8
+ import { buildTopicCard, buildInitSlugButtons, buildInitTypeButtons } from '../lib/telegram.js';
9
+ import { generateInclude } from '../lib/include-generator.js';
10
+ import { triggerRestart, getConfigWrites } from '../lib/config-restart.js';
11
+ import { appendAudit, buildAuditEntry } from '../lib/audit.js';
12
+ const VALID_TYPES = new Set(['coding', 'research', 'marketing', 'custom']);
13
+ export async function handleInit(ctx, args) {
14
+ const { workspaceDir, configDir, userId, groupId, threadId, rpc, logger, messageContext } = ctx;
15
+ if (!userId || !groupId || !threadId) {
16
+ return { text: 'Missing context: groupId, threadId, or userId not available. Run this command inside a Telegram forum topic.' };
17
+ }
18
+ // Validate IDs
19
+ if (!validateGroupId(groupId)) {
20
+ return { text: 'Invalid groupId format.' };
21
+ }
22
+ if (!validateThreadId(threadId)) {
23
+ return { text: 'Invalid threadId format.' };
24
+ }
25
+ const registry = readRegistry(workspaceDir);
26
+ // Auth check (user tier, with first-user bootstrap)
27
+ const auth = checkAuthorization(userId, 'init', registry);
28
+ if (!auth.authorized) {
29
+ return { text: auth.message ?? 'Not authorized.' };
30
+ }
31
+ // Max topics check
32
+ const topicCount = Object.keys(registry.topics).length;
33
+ if (topicCount >= registry.maxTopics) {
34
+ return {
35
+ text: `Maximum number of topics (${registry.maxTopics}) reached. Archive or remove existing topics first.`,
36
+ };
37
+ }
38
+ // Check if topic already registered
39
+ const key = topicKey(groupId, threadId);
40
+ if (registry.topics[key]) {
41
+ return {
42
+ text: `This topic is already registered as <code>${htmlEscape(registry.topics[key].slug)}</code>.`,
43
+ parseMode: 'HTML',
44
+ };
45
+ }
46
+ // Parse args: [slug] [type]
47
+ const parts = args.trim().split(/\s+/);
48
+ let slugArg = parts[0] ?? '';
49
+ let typeArg = parts[1] ?? '';
50
+ // Derive slug from topic title or args
51
+ const topicTitle = messageContext?.['topicTitle'] ?? '';
52
+ let slug;
53
+ if (slugArg) {
54
+ slug = slugArg;
55
+ }
56
+ else if (topicTitle) {
57
+ slug = sanitizeSlug(topicTitle);
58
+ }
59
+ else {
60
+ slug = `topic-${threadId}`;
61
+ }
62
+ // Ensure slug starts with a letter (sanitizeSlug may produce something starting with a digit)
63
+ if (slug && !/^[a-z]/.test(slug)) {
64
+ slug = 't-' + slug;
65
+ }
66
+ // Validate slug
67
+ if (!validateSlug(slug)) {
68
+ return {
69
+ text: `Invalid slug "${htmlEscape(slug)}". Must start with a letter, lowercase alphanumeric + hyphens, max 50 chars.`,
70
+ };
71
+ }
72
+ // Determine type
73
+ let topicType = 'coding';
74
+ if (typeArg && VALID_TYPES.has(typeArg.toLowerCase())) {
75
+ topicType = typeArg.toLowerCase();
76
+ }
77
+ const projectsBase = path.join(workspaceDir, 'projects');
78
+ // Path jail check
79
+ if (!jailCheck(projectsBase, slug)) {
80
+ return { text: 'Path safety check failed. Slug may escape the projects directory.' };
81
+ }
82
+ // Symlink check on projects base
83
+ if (rejectSymlink(projectsBase)) {
84
+ return { text: 'Projects base is a symlink. Aborting for security.' };
85
+ }
86
+ // Collision detection (registry)
87
+ const slugInUse = Object.values(registry.topics).some((t) => t.slug === slug);
88
+ const diskExists = fs.existsSync(path.join(projectsBase, slug));
89
+ let finalSlug = slug;
90
+ if (slugInUse || diskExists) {
91
+ // Append last 4 chars of groupId for uniqueness
92
+ const suffix = groupId.replace(/^-/, '').slice(-4);
93
+ finalSlug = `${slug}-${suffix}`.slice(0, 50);
94
+ if (!validateSlug(finalSlug)) {
95
+ return { text: `Could not generate a unique slug. Please provide one: /topic init &lt;slug&gt; [type]` };
96
+ }
97
+ // Check the fallback slug too
98
+ const fallbackInUse = Object.values(registry.topics).some((t) => t.slug === finalSlug);
99
+ if (fallbackInUse) {
100
+ return {
101
+ text: `Both <code>${htmlEscape(slug)}</code> and <code>${htmlEscape(finalSlug)}</code> are taken. Please provide a unique slug: /topic init &lt;slug&gt; [type]`,
102
+ parseMode: 'HTML',
103
+ };
104
+ }
105
+ // Re-check jail for fallback slug
106
+ if (!jailCheck(projectsBase, finalSlug)) {
107
+ return { text: 'Path safety check failed for fallback slug.' };
108
+ }
109
+ }
110
+ // Symlink check on target path
111
+ const targetPath = path.join(projectsBase, finalSlug);
112
+ if (rejectSymlink(targetPath)) {
113
+ return { text: 'Target path is a symlink. Aborting for security.' };
114
+ }
115
+ // Scaffold capsule and write registry entry atomically under lock
116
+ const isFirstUser = registry.topicManagerAdmins.length === 0;
117
+ try {
118
+ await withRegistry(workspaceDir, (data) => {
119
+ scaffoldCapsule(projectsBase, finalSlug, topicType);
120
+ const newEntry = {
121
+ groupId,
122
+ threadId,
123
+ slug: finalSlug,
124
+ type: topicType,
125
+ status: 'active',
126
+ capsuleVersion: CAPSULE_VERSION,
127
+ lastMessageAt: new Date().toISOString(),
128
+ lastDoctorReportAt: null,
129
+ lastDoctorRunAt: null,
130
+ snoozeUntil: null,
131
+ ignoreChecks: [],
132
+ consecutiveSilentDoctors: 0,
133
+ lastPostError: null,
134
+ extras: {},
135
+ };
136
+ data.topics[key] = newEntry;
137
+ // First-user bootstrap: add as admin
138
+ if (isFirstUser) {
139
+ data.topicManagerAdmins.push(userId);
140
+ }
141
+ });
142
+ }
143
+ catch (err) {
144
+ const msg = err instanceof Error ? err.message : String(err);
145
+ return { text: `Failed to initialize topic: ${htmlEscape(msg)}` };
146
+ }
147
+ // If configWrites enabled: regenerate include + trigger restart
148
+ let restartMsg = '';
149
+ const configWritesEnabled = await getConfigWrites(ctx.rpc);
150
+ if (configWritesEnabled) {
151
+ try {
152
+ const updatedRegistry = readRegistry(workspaceDir);
153
+ generateInclude(workspaceDir, updatedRegistry, configDir);
154
+ const result = await triggerRestart(rpc, logger);
155
+ if (!result.success && result.fallbackMessage) {
156
+ restartMsg = '\n' + result.fallbackMessage;
157
+ }
158
+ }
159
+ catch (err) {
160
+ const msg = err instanceof Error ? err.message : String(err);
161
+ restartMsg = `\nWarning: include generation failed: ${htmlEscape(msg)}`;
162
+ }
163
+ }
164
+ // Audit log
165
+ appendAudit(workspaceDir, buildAuditEntry(userId, 'init', finalSlug, `Initialized topic type=${topicType} group=${groupId} thread=${threadId}`));
166
+ // Build topic card
167
+ const topicCard = buildTopicCard(finalSlug, topicType, CAPSULE_VERSION);
168
+ let adminNote = '';
169
+ if (isFirstUser) {
170
+ adminNote = '\n\nYou are the first user and have been added as a telegram-manager admin.';
171
+ }
172
+ let slugNote = '';
173
+ if (finalSlug !== slug) {
174
+ slugNote = `\n\nNote: slug <code>${htmlEscape(slug)}</code> was taken, using <code>${htmlEscape(finalSlug)}</code> instead.`;
175
+ }
176
+ return {
177
+ text: `${topicCard}${slugNote}${adminNote}${restartMsg}`,
178
+ parseMode: 'HTML',
179
+ pin: true,
180
+ };
181
+ }
182
+ // ── Callback data byte limit ──────────────────────────────────────────
183
+ const CALLBACK_BYTE_LIMIT = 64;
184
+ /**
185
+ * Check whether a callback payload for the given slug fits within
186
+ * Telegram's 64-byte callback_data limit.
187
+ */
188
+ function fitsCallbackLimit(action, slug, groupId, threadId, secret) {
189
+ const data = buildCallbackData(action, slug, groupId, threadId, secret);
190
+ return Buffer.byteLength(data, 'utf-8') <= CALLBACK_BYTE_LIMIT;
191
+ }
192
+ // ── Interactive init flow ─────────────────────────────────────────────
193
+ const INIT_TYPE_MAP = {
194
+ ic: 'coding',
195
+ ir: 'research',
196
+ im: 'marketing',
197
+ ix: 'custom',
198
+ };
199
+ /**
200
+ * Entry point for `/topic init`. If args are provided, delegates straight
201
+ * to `handleInit`. Otherwise starts the interactive two-step flow.
202
+ */
203
+ export async function handleInitInteractive(ctx, args) {
204
+ if (args.trim()) {
205
+ return handleInit(ctx, args);
206
+ }
207
+ return buildSlugConfirmation(ctx);
208
+ }
209
+ /**
210
+ * Step 1: derive slug and present a [Confirm] inline button.
211
+ * Falls back to text instructions if the callback data would exceed 64 bytes.
212
+ */
213
+ async function buildSlugConfirmation(ctx) {
214
+ const { workspaceDir, userId, groupId, threadId, messageContext } = ctx;
215
+ if (!userId || !groupId || !threadId) {
216
+ return { text: 'Missing context: groupId, threadId, or userId not available. Run this command inside a Telegram forum topic.' };
217
+ }
218
+ if (!validateGroupId(groupId)) {
219
+ return { text: 'Invalid groupId format.' };
220
+ }
221
+ if (!validateThreadId(threadId)) {
222
+ return { text: 'Invalid threadId format.' };
223
+ }
224
+ const registry = readRegistry(workspaceDir);
225
+ const auth = checkAuthorization(userId, 'init', registry);
226
+ if (!auth.authorized) {
227
+ return { text: auth.message ?? 'Not authorized.' };
228
+ }
229
+ const topicCount = Object.keys(registry.topics).length;
230
+ if (topicCount >= registry.maxTopics) {
231
+ return { text: `Maximum number of topics (${registry.maxTopics}) reached. Archive or remove existing topics first.` };
232
+ }
233
+ const key = topicKey(groupId, threadId);
234
+ if (registry.topics[key]) {
235
+ return {
236
+ text: `This topic is already registered as <code>${htmlEscape(registry.topics[key].slug)}</code>.`,
237
+ parseMode: 'HTML',
238
+ };
239
+ }
240
+ // Derive slug (same logic as handleInit lines 74–96)
241
+ const topicTitle = messageContext?.['topicTitle'] ?? '';
242
+ let slug;
243
+ if (topicTitle) {
244
+ slug = sanitizeSlug(topicTitle);
245
+ }
246
+ else {
247
+ slug = `topic-${threadId}`;
248
+ }
249
+ if (slug && !/^[a-z]/.test(slug)) {
250
+ slug = 't-' + slug;
251
+ }
252
+ if (!validateSlug(slug)) {
253
+ return {
254
+ text: `Invalid derived slug "${htmlEscape(slug)}". Please provide one: /topic init &lt;slug&gt; [type]`,
255
+ };
256
+ }
257
+ // Check callback byte limit — if slug is too long, fall back to text
258
+ if (!fitsCallbackLimit('is', slug, groupId, threadId, registry.callbackSecret)) {
259
+ return {
260
+ text: `Suggested slug: <code>${htmlEscape(slug)}</code>\n\nSlug is too long for inline buttons. Please run:\n<code>/topic init ${htmlEscape(slug)} [type]</code>`,
261
+ parseMode: 'HTML',
262
+ };
263
+ }
264
+ const keyboard = buildInitSlugButtons(slug, groupId, threadId, registry.callbackSecret);
265
+ return {
266
+ text: `Initialize this topic as <code>${htmlEscape(slug)}</code>?`,
267
+ parseMode: 'HTML',
268
+ inlineKeyboard: keyboard,
269
+ };
270
+ }
271
+ /**
272
+ * Step 2 (callback `is`): re-validate, then show the type picker.
273
+ */
274
+ export async function handleInitSlugConfirm(ctx, slug) {
275
+ const { workspaceDir, userId, groupId, threadId } = ctx;
276
+ if (!userId || !groupId || !threadId) {
277
+ return { text: 'Missing context.' };
278
+ }
279
+ const registry = readRegistry(workspaceDir);
280
+ const auth = checkAuthorization(userId, 'init', registry);
281
+ if (!auth.authorized) {
282
+ return { text: auth.message ?? 'Not authorized.' };
283
+ }
284
+ const key = topicKey(groupId, threadId);
285
+ if (registry.topics[key]) {
286
+ return {
287
+ text: `This topic is already registered as <code>${htmlEscape(registry.topics[key].slug)}</code>.`,
288
+ parseMode: 'HTML',
289
+ };
290
+ }
291
+ const keyboard = buildInitTypeButtons(slug, groupId, threadId, registry.callbackSecret);
292
+ return {
293
+ text: `Slug: <code>${htmlEscape(slug)}</code>\n\nPick a topic type:`,
294
+ parseMode: 'HTML',
295
+ inlineKeyboard: keyboard,
296
+ };
297
+ }
298
+ /**
299
+ * Step 3 (callbacks `ic`/`ir`/`im`/`ix`): complete init with chosen type.
300
+ */
301
+ export async function handleInitTypeSelect(ctx, slug, type) {
302
+ return handleInit(ctx, `${slug} ${type}`);
303
+ }
304
+ //# sourceMappingURL=init.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { CommandContext, CommandResult } from './help.js';
2
+ export declare function handleList(ctx: CommandContext): Promise<CommandResult>;
3
+ //# sourceMappingURL=list.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list.d.ts","sourceRoot":"","sources":["../../src/commands/list.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAE/D,wBAAsB,UAAU,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC,CAsB5E"}
@@ -0,0 +1,22 @@
1
+ import { readRegistry } from '../lib/registry.js';
2
+ import { checkAuthorization } from '../lib/auth.js';
3
+ import { buildListMessage } from '../lib/telegram.js';
4
+ export async function handleList(ctx) {
5
+ const { workspaceDir, userId } = ctx;
6
+ if (!userId) {
7
+ return { text: 'Missing context: userId not available.' };
8
+ }
9
+ const registry = readRegistry(workspaceDir);
10
+ // Auth check (admin tier)
11
+ const auth = checkAuthorization(userId, 'list', registry);
12
+ if (!auth.authorized) {
13
+ return { text: auth.message ?? 'Not authorized.' };
14
+ }
15
+ const topics = Object.values(registry.topics);
16
+ const text = buildListMessage(topics);
17
+ return {
18
+ text,
19
+ parseMode: 'HTML',
20
+ };
21
+ }
22
+ //# sourceMappingURL=list.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list.js","sourceRoot":"","sources":["../../src/commands/list.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAGtD,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,GAAmB;IAClD,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,GAAG,GAAG,CAAC;IAErC,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO,EAAE,IAAI,EAAE,wCAAwC,EAAE,CAAC;IAC5D,CAAC;IAED,MAAM,QAAQ,GAAG,YAAY,CAAC,YAAY,CAAC,CAAC;IAE5C,0BAA0B;IAC1B,MAAM,IAAI,GAAG,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC;IAC1D,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC;QACrB,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,OAAO,IAAI,iBAAiB,EAAE,CAAC;IACrD,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC9C,MAAM,IAAI,GAAG,gBAAgB,CAAC,MAAM,CAAC,CAAC;IAEtC,OAAO;QACL,IAAI;QACJ,SAAS,EAAE,MAAM;KAClB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { CommandContext, CommandResult } from './help.js';
2
+ export declare function handleRename(ctx: CommandContext, newSlug: string): Promise<CommandResult>;
3
+ //# sourceMappingURL=rename.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rename.d.ts","sourceRoot":"","sources":["../../src/commands/rename.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAE/D,wBAAsB,YAAY,CAAC,GAAG,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CA8H/F"}
@@ -0,0 +1,115 @@
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+ import { readRegistry, withRegistry } from '../lib/registry.js';
4
+ import { checkAuthorization } from '../lib/auth.js';
5
+ import { topicKey } from '../lib/types.js';
6
+ import { validateSlug, jailCheck, rejectSymlink, htmlEscape } from '../lib/security.js';
7
+ import { buildTopicCard } from '../lib/telegram.js';
8
+ import { generateInclude } from '../lib/include-generator.js';
9
+ import { triggerRestart, getConfigWrites } from '../lib/config-restart.js';
10
+ import { appendAudit, buildAuditEntry } from '../lib/audit.js';
11
+ export async function handleRename(ctx, newSlug) {
12
+ const { workspaceDir, configDir, userId, groupId, threadId, rpc, logger } = ctx;
13
+ if (!userId || !groupId || !threadId) {
14
+ return { text: 'Missing context: userId, groupId, or threadId not available.' };
15
+ }
16
+ const trimmedSlug = newSlug.trim();
17
+ if (!trimmedSlug) {
18
+ return { text: 'Usage: /topic rename &lt;new-slug&gt;' };
19
+ }
20
+ // Validate new slug
21
+ if (!validateSlug(trimmedSlug)) {
22
+ return {
23
+ text: `Invalid slug "${htmlEscape(trimmedSlug)}". Must match: lowercase letter start, alphanumeric + hyphens, max 50 chars.`,
24
+ };
25
+ }
26
+ const registry = readRegistry(workspaceDir);
27
+ // Auth check (admin tier)
28
+ const auth = checkAuthorization(userId, 'rename', registry);
29
+ if (!auth.authorized) {
30
+ return { text: auth.message ?? 'Not authorized.' };
31
+ }
32
+ const key = topicKey(groupId, threadId);
33
+ const entry = registry.topics[key];
34
+ if (!entry) {
35
+ return { text: 'This topic is not registered. Run /topic init first.' };
36
+ }
37
+ const oldSlug = entry.slug;
38
+ if (oldSlug === trimmedSlug) {
39
+ return { text: `Topic is already named <code>${htmlEscape(oldSlug)}</code>.` };
40
+ }
41
+ const projectsBase = path.join(workspaceDir, 'projects');
42
+ // Path jail check for both old and new paths
43
+ if (!jailCheck(projectsBase, oldSlug)) {
44
+ return { text: 'Path safety check failed for current slug.' };
45
+ }
46
+ if (!jailCheck(projectsBase, trimmedSlug)) {
47
+ return { text: 'Path safety check failed for new slug.' };
48
+ }
49
+ const oldPath = path.join(projectsBase, oldSlug);
50
+ const newPath = path.join(projectsBase, trimmedSlug);
51
+ // Symlink check on both paths
52
+ if (rejectSymlink(oldPath)) {
53
+ return { text: 'Current capsule directory is a symlink. Aborting for security.' };
54
+ }
55
+ if (rejectSymlink(newPath)) {
56
+ return { text: 'Target capsule directory is a symlink. Aborting for security.' };
57
+ }
58
+ // Collision check: new slug in registry
59
+ const collisionInRegistry = Object.values(registry.topics).some((t) => t.slug === trimmedSlug);
60
+ if (collisionInRegistry) {
61
+ return {
62
+ text: `Slug <code>${htmlEscape(trimmedSlug)}</code> is already used by another topic.`,
63
+ parseMode: 'HTML',
64
+ };
65
+ }
66
+ // Collision check: new path on disk
67
+ if (fs.existsSync(newPath)) {
68
+ return {
69
+ text: `Directory projects/${htmlEscape(trimmedSlug)}/ already exists on disk.`,
70
+ parseMode: 'HTML',
71
+ };
72
+ }
73
+ // Rename folder on disk
74
+ if (!fs.existsSync(oldPath)) {
75
+ return { text: `Source capsule directory not found: projects/${htmlEscape(oldSlug)}/` };
76
+ }
77
+ // Rename folder and update registry atomically under lock
78
+ try {
79
+ await withRegistry(workspaceDir, (data) => {
80
+ const topic = data.topics[key];
81
+ if (topic) {
82
+ fs.renameSync(oldPath, newPath);
83
+ topic.slug = trimmedSlug;
84
+ }
85
+ });
86
+ }
87
+ catch (err) {
88
+ const msg = err instanceof Error ? err.message : String(err);
89
+ return { text: `Failed to rename capsule: ${htmlEscape(msg)}` };
90
+ }
91
+ // Regenerate include if configWrites enabled
92
+ let restartMsg = '';
93
+ const configWritesEnabled = await getConfigWrites(ctx.rpc);
94
+ if (configWritesEnabled) {
95
+ try {
96
+ const updatedRegistry = readRegistry(workspaceDir);
97
+ generateInclude(workspaceDir, updatedRegistry, configDir);
98
+ }
99
+ catch (err) {
100
+ const msg = err instanceof Error ? err.message : String(err);
101
+ restartMsg = `\nWarning: include generation failed: ${htmlEscape(msg)}`;
102
+ }
103
+ const result = await triggerRestart(rpc, logger);
104
+ if (!result.success && result.fallbackMessage) {
105
+ restartMsg += '\n' + result.fallbackMessage;
106
+ }
107
+ }
108
+ appendAudit(workspaceDir, buildAuditEntry(userId, 'rename', trimmedSlug, `Renamed from ${oldSlug} to ${trimmedSlug}`));
109
+ const topicCard = buildTopicCard(trimmedSlug, entry.type, entry.capsuleVersion);
110
+ return {
111
+ text: `Topic renamed from <code>${htmlEscape(oldSlug)}</code> to <code>${htmlEscape(trimmedSlug)}</code>.\n\n${topicCard}${restartMsg}`,
112
+ parseMode: 'HTML',
113
+ };
114
+ }
115
+ //# sourceMappingURL=rename.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rename.js","sourceRoot":"","sources":["../../src/commands/rename.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { CommandContext, CommandResult } from './help.js';
2
+ export declare function handleSnooze(ctx: CommandContext, args: string): Promise<CommandResult>;
3
+ //# sourceMappingURL=snooze.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"snooze.d.ts","sourceRoot":"","sources":["../../src/commands/snooze.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAI/D,wBAAsB,YAAY,CAAC,GAAG,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CA0D5F"}
@@ -0,0 +1,52 @@
1
+ import { withRegistry } from '../lib/registry.js';
2
+ import { readRegistry } from '../lib/registry.js';
3
+ import { checkAuthorization } from '../lib/auth.js';
4
+ import { topicKey } from '../lib/types.js';
5
+ import { htmlEscape } from '../lib/security.js';
6
+ import { appendAudit, buildAuditEntry } from '../lib/audit.js';
7
+ const DURATION_RE = /^(\d+)d$/;
8
+ export async function handleSnooze(ctx, args) {
9
+ const { workspaceDir, userId, groupId, threadId } = ctx;
10
+ if (!userId || !groupId || !threadId) {
11
+ return { text: 'Missing context: userId, groupId, or threadId not available.' };
12
+ }
13
+ // Parse duration
14
+ const trimmed = args.trim();
15
+ if (!trimmed) {
16
+ return { text: 'Usage: /topic snooze &lt;Nd&gt; (e.g., 7d, 30d)' };
17
+ }
18
+ const match = DURATION_RE.exec(trimmed);
19
+ if (!match) {
20
+ return { text: `Invalid duration "${htmlEscape(trimmed)}". Use format: 7d, 30d, etc.` };
21
+ }
22
+ const days = parseInt(match[1], 10);
23
+ if (days <= 0 || days > 365) {
24
+ return { text: 'Duration must be between 1 and 365 days.' };
25
+ }
26
+ const registry = readRegistry(workspaceDir);
27
+ // Auth check (user tier)
28
+ const auth = checkAuthorization(userId, 'snooze', registry);
29
+ if (!auth.authorized) {
30
+ return { text: auth.message ?? 'Not authorized.' };
31
+ }
32
+ const key = topicKey(groupId, threadId);
33
+ const entry = registry.topics[key];
34
+ if (!entry) {
35
+ return { text: 'This topic is not registered. Run /topic init first.' };
36
+ }
37
+ const snoozeUntil = new Date(Date.now() + days * 24 * 60 * 60 * 1000).toISOString();
38
+ await withRegistry(workspaceDir, (data) => {
39
+ const topic = data.topics[key];
40
+ if (topic) {
41
+ topic.snoozeUntil = snoozeUntil;
42
+ topic.consecutiveSilentDoctors = 0;
43
+ topic.status = 'snoozed';
44
+ }
45
+ });
46
+ appendAudit(workspaceDir, buildAuditEntry(userId, 'snooze', entry.slug, `Snoozed for ${days} days until ${snoozeUntil}`));
47
+ return {
48
+ text: `Topic <code>${htmlEscape(entry.slug)}</code> snoozed for ${days} days (until ${htmlEscape(snoozeUntil)}).`,
49
+ parseMode: 'HTML',
50
+ };
51
+ }
52
+ //# sourceMappingURL=snooze.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"snooze.js","sourceRoot":"","sources":["../../src/commands/snooze.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { CommandContext, CommandResult } from './help.js';
2
+ export declare function handleStatus(ctx: CommandContext): Promise<CommandResult>;
3
+ //# sourceMappingURL=status.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/commands/status.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAE/D,wBAAsB,YAAY,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC,CAiD9E"}
@@ -0,0 +1,48 @@
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+ import { readRegistry } from '../lib/registry.js';
4
+ import { checkAuthorization } from '../lib/auth.js';
5
+ import { topicKey } from '../lib/types.js';
6
+ import { jailCheck, rejectSymlink, htmlEscape } from '../lib/security.js';
7
+ import { truncateMessage } from '../lib/telegram.js';
8
+ export async function handleStatus(ctx) {
9
+ const { workspaceDir, userId, groupId, threadId } = ctx;
10
+ if (!userId || !groupId || !threadId) {
11
+ return { text: 'Missing context: userId, groupId, or threadId not available.' };
12
+ }
13
+ const registry = readRegistry(workspaceDir);
14
+ // Auth check (user tier)
15
+ const auth = checkAuthorization(userId, 'status', registry);
16
+ if (!auth.authorized) {
17
+ return { text: auth.message ?? 'Not authorized.' };
18
+ }
19
+ const key = topicKey(groupId, threadId);
20
+ const entry = registry.topics[key];
21
+ if (!entry) {
22
+ return { text: 'This topic is not registered. Run /topic init first.' };
23
+ }
24
+ const projectsBase = path.join(workspaceDir, 'projects');
25
+ const capsuleDir = path.join(projectsBase, entry.slug);
26
+ // Path safety
27
+ if (!jailCheck(projectsBase, entry.slug)) {
28
+ return { text: 'Path safety check failed.' };
29
+ }
30
+ if (rejectSymlink(capsuleDir)) {
31
+ return { text: 'Capsule directory is a symlink. Aborting for security.' };
32
+ }
33
+ const statusPath = path.join(capsuleDir, 'STATUS.md');
34
+ if (!fs.existsSync(statusPath)) {
35
+ return { text: 'STATUS.md not found in capsule. Run /topic doctor to diagnose.' };
36
+ }
37
+ try {
38
+ const content = fs.readFileSync(statusPath, 'utf-8');
39
+ return {
40
+ text: truncateMessage(content),
41
+ };
42
+ }
43
+ catch (err) {
44
+ const msg = err instanceof Error ? err.message : String(err);
45
+ return { text: `Failed to read STATUS.md: ${htmlEscape(msg)}` };
46
+ }
47
+ }
48
+ //# sourceMappingURL=status.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.js","sourceRoot":"","sources":["../../src/commands/status.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAC3C,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAC1E,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAGrD,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,GAAmB;IACpD,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,GAAG,CAAC;IAExD,IAAI,CAAC,MAAM,IAAI,CAAC,OAAO,IAAI,CAAC,QAAQ,EAAE,CAAC;QACrC,OAAO,EAAE,IAAI,EAAE,8DAA8D,EAAE,CAAC;IAClF,CAAC;IAED,MAAM,QAAQ,GAAG,YAAY,CAAC,YAAY,CAAC,CAAC;IAE5C,yBAAyB;IACzB,MAAM,IAAI,GAAG,kBAAkB,CAAC,MAAM,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAC5D,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC;QACrB,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,OAAO,IAAI,iBAAiB,EAAE,CAAC;IACrD,CAAC;IAED,MAAM,GAAG,GAAG,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IACxC,MAAM,KAAK,GAAG,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAEnC,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,EAAE,IAAI,EAAE,sDAAsD,EAAE,CAAC;IAC1E,CAAC;IAED,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;IACzD,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;IAEvD,cAAc;IACd,IAAI,CAAC,SAAS,CAAC,YAAY,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACzC,OAAO,EAAE,IAAI,EAAE,2BAA2B,EAAE,CAAC;IAC/C,CAAC;IAED,IAAI,aAAa,CAAC,UAAU,CAAC,EAAE,CAAC;QAC9B,OAAO,EAAE,IAAI,EAAE,wDAAwD,EAAE,CAAC;IAC5E,CAAC;IAED,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC;IAEtD,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC/B,OAAO,EAAE,IAAI,EAAE,gEAAgE,EAAE,CAAC;IACpF,CAAC;IAED,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;QACrD,OAAO;YACL,IAAI,EAAE,eAAe,CAAC,OAAO,CAAC;SAC/B,CAAC;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC7D,OAAO,EAAE,IAAI,EAAE,6BAA6B,UAAU,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;IAClE,CAAC;AACH,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { CommandContext, CommandResult } from './help.js';
2
+ export declare function handleSync(ctx: CommandContext): Promise<CommandResult>;
3
+ //# sourceMappingURL=sync.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../src/commands/sync.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAE/D,wBAAsB,UAAU,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC,CAsC5E"}