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
package/src/setup.ts ADDED
@@ -0,0 +1,402 @@
1
+ #!/usr/bin/env node
2
+
3
+ import * as fs from 'node:fs';
4
+ import * as path from 'node:path';
5
+ import * as crypto from 'node:crypto';
6
+ import { execSync } from 'node:child_process';
7
+ import * as readline from 'node:readline';
8
+
9
+ // ── Constants ──────────────────────────────────────────────────────────
10
+
11
+ const MIN_OPENCLAW_VERSION = '2026.1.0';
12
+ const INCLUDE_FILENAME = 'telegram-manager.generated.groups.json5';
13
+ const REGISTRY_FILENAME = 'topics.json';
14
+
15
+ // ── Main ──────────────────────────────────────────────────────────────
16
+
17
+ async function main(): Promise<void> {
18
+ console.log('openclaw-telegram-manager setup');
19
+ console.log('================================\n');
20
+
21
+ // Step 1: Check OpenClaw version
22
+ const version = checkOpenClawVersion();
23
+ console.log(`[1/11] OpenClaw version: ${version}`);
24
+
25
+ // Step 2: Locate config directory
26
+ const configDir = locateConfigDir();
27
+ console.log(`[2/11] Config directory: ${configDir}`);
28
+
29
+ // Step 3: Check directory permissions
30
+ checkDirPermissions(configDir);
31
+ console.log('[3/11] Directory permissions checked');
32
+
33
+ // Step 4: Install plugin
34
+ installPlugin();
35
+ console.log('[4/11] Plugin installation checked');
36
+
37
+ // Step 5: Patch openclaw.json with $include reference
38
+ patchConfig(configDir);
39
+ console.log('[5/11] Config patched with $include reference');
40
+
41
+ // Step 6: Create workspace directory structure
42
+ const workspaceDir = path.join(configDir, 'workspace');
43
+ const projectsDir = path.join(workspaceDir, 'projects');
44
+ ensureDir(projectsDir);
45
+ console.log(`[6/11] Workspace directory: ${projectsDir}`);
46
+
47
+ // Step 7: Initialize empty registry
48
+ initRegistry(projectsDir);
49
+ console.log('[7/11] Registry initialized');
50
+
51
+ // Step 8: Create empty generated include
52
+ createEmptyInclude(configDir);
53
+ console.log('[8/11] Empty include file created');
54
+
55
+ // Step 9: Optional cron setup
56
+ const isInteractive = process.stdin.isTTY === true;
57
+ const setupCron = isInteractive ? await promptYesNo('Set up daily doctor cron job? [Y/n] ') : true;
58
+ if (setupCron) {
59
+ const groupId = isInteractive
60
+ ? await promptInput('Enter your Telegram group ID (e.g., -1003731538650): ')
61
+ : '';
62
+ setupDoctorCron(configDir, groupId);
63
+ console.log('[9/11] Doctor cron job configured');
64
+ } else {
65
+ console.log('[9/11] Skipped cron setup');
66
+ }
67
+
68
+ // Step 10: Trigger gateway restart
69
+ triggerRestart(configDir);
70
+ console.log('[10/11] Gateway restart triggered');
71
+
72
+ // Step 11: Print summary
73
+ printSummary(configDir, projectsDir);
74
+ console.log('[11/11] Setup complete!\n');
75
+ }
76
+
77
+ // ── Step implementations ──────────────────────────────────────────────
78
+
79
+ function checkOpenClawVersion(): string {
80
+ let version: string;
81
+ try {
82
+ version = execSync('openclaw --version', { encoding: 'utf-8' }).trim();
83
+ } catch {
84
+ console.error(
85
+ 'Error: OpenClaw not found. Please install OpenClaw (>=2026.1.0) first.',
86
+ );
87
+ process.exit(1);
88
+ }
89
+
90
+ // Extract version number (e.g., "openclaw 2026.2.0" -> "2026.2.0")
91
+ const match = version.match(/(\d+\.\d+\.\d+)/);
92
+ if (!match) {
93
+ console.warn(`Warning: Could not parse OpenClaw version from "${version}". Proceeding anyway.`);
94
+ return version;
95
+ }
96
+
97
+ const versionStr = match[1]!;
98
+ if (compareVersions(versionStr, MIN_OPENCLAW_VERSION) < 0) {
99
+ console.error(
100
+ `Error: OpenClaw ${versionStr} found, but openclaw-telegram-manager requires >=${MIN_OPENCLAW_VERSION}. Please upgrade.`,
101
+ );
102
+ process.exit(1);
103
+ }
104
+
105
+ return versionStr;
106
+ }
107
+
108
+ function locateConfigDir(): string {
109
+ // Check environment variable
110
+ const envDir = process.env['OPENCLAW_CONFIG_DIR'];
111
+ if (envDir && fs.existsSync(envDir)) {
112
+ return path.resolve(envDir);
113
+ }
114
+
115
+ // Check default location
116
+ const homeDir = process.env['HOME'] ?? process.env['USERPROFILE'] ?? '';
117
+ const defaultDir = path.join(homeDir, '.openclaw');
118
+ if (fs.existsSync(defaultDir)) {
119
+ return defaultDir;
120
+ }
121
+
122
+ // Walk up from cwd looking for openclaw.json
123
+ let dir = process.cwd();
124
+ while (dir !== path.dirname(dir)) {
125
+ if (fs.existsSync(path.join(dir, 'openclaw.json'))) {
126
+ return dir;
127
+ }
128
+ dir = path.dirname(dir);
129
+ }
130
+
131
+ console.error(
132
+ 'Error: Could not find OpenClaw config directory. Set $OPENCLAW_CONFIG_DIR or ensure ~/.openclaw/ exists.',
133
+ );
134
+ process.exit(1);
135
+ }
136
+
137
+ function checkDirPermissions(dir: string): void {
138
+ try {
139
+ const stat = fs.statSync(dir);
140
+ const mode = stat.mode;
141
+ const permissions = (mode & 0o777).toString(8);
142
+
143
+ // Check for world-writable or group-writable
144
+ if (mode & 0o002) {
145
+ console.warn(
146
+ `Warning: ${dir} is world-writable (${permissions}). Consider restricting to owner-only (chmod 700).`,
147
+ );
148
+ } else if (mode & 0o020) {
149
+ console.warn(
150
+ `Warning: ${dir} is group-writable (${permissions}). Consider restricting to owner-only (chmod 700).`,
151
+ );
152
+ }
153
+ } catch {
154
+ console.warn(`Warning: Could not check permissions for ${dir}.`);
155
+ }
156
+ }
157
+
158
+ function installPlugin(): void {
159
+ try {
160
+ // Check if already installed
161
+ const result = execSync('openclaw plugins list', { encoding: 'utf-8' });
162
+ if (result.includes('openclaw-telegram-manager')) {
163
+ console.log(' Plugin already installed, skipping.');
164
+ return;
165
+ }
166
+ } catch {
167
+ // plugins list might not be available, try installing anyway
168
+ }
169
+
170
+ try {
171
+ execSync('openclaw plugins install openclaw-telegram-manager', {
172
+ encoding: 'utf-8',
173
+ stdio: 'inherit',
174
+ });
175
+ } catch {
176
+ console.warn(
177
+ ' Warning: Could not install plugin via `openclaw plugins install`. You may need to install manually.',
178
+ );
179
+ }
180
+ }
181
+
182
+ function patchConfig(configDir: string): void {
183
+ const configPath = path.join(configDir, 'openclaw.json');
184
+
185
+ if (!fs.existsSync(configPath)) {
186
+ console.warn(` Warning: ${configPath} not found. Skipping config patch.`);
187
+ return;
188
+ }
189
+
190
+ let content: string;
191
+ try {
192
+ content = fs.readFileSync(configPath, 'utf-8');
193
+ } catch (err) {
194
+ console.warn(` Warning: Could not read ${configPath}. Skipping config patch.`);
195
+ return;
196
+ }
197
+
198
+ // Check if $include reference already exists
199
+ if (content.includes(INCLUDE_FILENAME)) {
200
+ console.log(' $include reference already present, skipping.');
201
+ return;
202
+ }
203
+
204
+ // Parse as JSON (OpenClaw config may be JSON or JSON5)
205
+ let config: Record<string, unknown>;
206
+ try {
207
+ config = JSON.parse(content) as Record<string, unknown>;
208
+ } catch {
209
+ // Try a more lenient approach: just check for the string and warn
210
+ console.warn(
211
+ ' Warning: Could not parse openclaw.json as JSON. Please manually add the $include reference.',
212
+ );
213
+ console.warn(` Add to channels.telegram.groups: { "$include": "./${INCLUDE_FILENAME}" }`);
214
+ return;
215
+ }
216
+
217
+ // Ensure path exists: channels.telegram.groups
218
+ if (!config['channels']) config['channels'] = {};
219
+ const channels = config['channels'] as Record<string, unknown>;
220
+
221
+ if (!channels['telegram']) channels['telegram'] = {};
222
+ const telegram = channels['telegram'] as Record<string, unknown>;
223
+
224
+ // Set groups to $include
225
+ telegram['groups'] = { $include: `./${INCLUDE_FILENAME}` };
226
+
227
+ // Backup and write
228
+ const bakPath = configPath + '.bak';
229
+ fs.copyFileSync(configPath, bakPath);
230
+
231
+ const newContent = JSON.stringify(config, null, 2) + '\n';
232
+ fs.writeFileSync(configPath, newContent, { mode: 0o600 });
233
+ }
234
+
235
+ function ensureDir(dir: string): void {
236
+ if (!fs.existsSync(dir)) {
237
+ fs.mkdirSync(dir, { recursive: true });
238
+ }
239
+ }
240
+
241
+ function initRegistry(projectsDir: string): void {
242
+ const registryPath = path.join(projectsDir, REGISTRY_FILENAME);
243
+
244
+ if (fs.existsSync(registryPath)) {
245
+ console.log(' Registry already exists, skipping initialization.');
246
+ return;
247
+ }
248
+
249
+ const callbackSecret = crypto.randomBytes(32).toString('hex');
250
+ const registry = {
251
+ version: 1,
252
+ topicManagerAdmins: [],
253
+ callbackSecret,
254
+ lastDoctorAllRunAt: null,
255
+ maxTopics: 100,
256
+ topics: {},
257
+ };
258
+
259
+ fs.writeFileSync(registryPath, JSON.stringify(registry, null, 2) + '\n', {
260
+ mode: 0o600,
261
+ });
262
+ }
263
+
264
+ function createEmptyInclude(configDir: string): void {
265
+ const includePath = path.join(configDir, INCLUDE_FILENAME);
266
+
267
+ if (fs.existsSync(includePath)) {
268
+ console.log(' Include file already exists, skipping.');
269
+ return;
270
+ }
271
+
272
+ const content = [
273
+ '// This file is generated by telegram-manager. Do not hand-edit.',
274
+ '{}',
275
+ '',
276
+ ].join('\n');
277
+
278
+ fs.writeFileSync(includePath, content, { mode: 0o600 });
279
+ }
280
+
281
+ function setupDoctorCron(configDir: string, groupId: string): void {
282
+ const cronDir = path.join(configDir, 'cron');
283
+ ensureDir(cronDir);
284
+
285
+ const cronJobPath = path.join(cronDir, 'topic-doctor-daily.json');
286
+
287
+ if (fs.existsSync(cronJobPath)) {
288
+ console.log(' Cron job already exists, skipping.');
289
+ return;
290
+ }
291
+
292
+ const target = groupId
293
+ ? `${groupId}:topic:1`
294
+ : '-100XXXXXXXXXX:topic:1';
295
+
296
+ const cronJob = {
297
+ name: 'topic-doctor-daily',
298
+ schedule: { kind: 'cron', expr: '0 9 * * *', tz: 'UTC' },
299
+ sessionTarget: 'isolated',
300
+ payload: {
301
+ kind: 'agentTurn',
302
+ message:
303
+ 'Run topic doctor health checks on all registered topics. Check the registry at projects/topics.json and evaluate each eligible topic. Post per-topic reports with inline keyboards. If any topic\'s thread returns an API error (deleted/migrated), log it and continue to the next topic.',
304
+ timeoutSeconds: 300,
305
+ },
306
+ delivery: {
307
+ mode: 'announce',
308
+ channel: 'telegram',
309
+ to: target,
310
+ bestEffort: true,
311
+ },
312
+ enabled: true,
313
+ deleteAfterRun: false,
314
+ };
315
+
316
+ fs.writeFileSync(cronJobPath, JSON.stringify(cronJob, null, 2) + '\n', {
317
+ mode: 0o600,
318
+ });
319
+
320
+ if (!groupId) {
321
+ console.warn(
322
+ ' Warning: No group ID provided. Edit the cron job at:',
323
+ );
324
+ console.warn(` ${cronJobPath}`);
325
+ console.warn(' Replace -100XXXXXXXXXX with your actual group ID.');
326
+ }
327
+ }
328
+
329
+ function triggerRestart(configDir: string): void {
330
+ try {
331
+ execSync('openclaw gateway restart', {
332
+ encoding: 'utf-8',
333
+ timeout: 10_000,
334
+ });
335
+ } catch {
336
+ console.warn(
337
+ ' Warning: Could not restart gateway. Run `openclaw gateway restart` manually.',
338
+ );
339
+ }
340
+ }
341
+
342
+ function printSummary(configDir: string, projectsDir: string): void {
343
+ console.log('\n================================');
344
+ console.log('Setup complete!\n');
345
+ console.log('What was done:');
346
+ console.log(` - Config directory: ${configDir}`);
347
+ console.log(` - Projects directory: ${projectsDir}`);
348
+ console.log(` - Registry: ${path.join(projectsDir, REGISTRY_FILENAME)}`);
349
+ console.log(` - Include: ${path.join(configDir, INCLUDE_FILENAME)}`);
350
+ console.log('\nNext steps:');
351
+ console.log(' 1. Go to any Telegram forum topic');
352
+ console.log(' 2. Type /topic init');
353
+ console.log(' 3. The topic will be registered and a capsule created');
354
+ console.log('\nFor help: /topic help');
355
+ }
356
+
357
+ // ── Helpers ───────────────────────────────────────────────────────────
358
+
359
+ function compareVersions(a: string, b: string): number {
360
+ const aParts = a.split('.').map(Number);
361
+ const bParts = b.split('.').map(Number);
362
+ for (let i = 0; i < Math.max(aParts.length, bParts.length); i++) {
363
+ const aVal = aParts[i] ?? 0;
364
+ const bVal = bParts[i] ?? 0;
365
+ if (aVal !== bVal) return aVal - bVal;
366
+ }
367
+ return 0;
368
+ }
369
+
370
+ function promptYesNo(question: string): Promise<boolean> {
371
+ return new Promise((resolve) => {
372
+ const rl = readline.createInterface({
373
+ input: process.stdin,
374
+ output: process.stdout,
375
+ });
376
+ rl.question(question, (answer) => {
377
+ rl.close();
378
+ const normalized = answer.trim().toLowerCase();
379
+ resolve(normalized === '' || normalized === 'y' || normalized === 'yes');
380
+ });
381
+ });
382
+ }
383
+
384
+ function promptInput(question: string): Promise<string> {
385
+ return new Promise((resolve) => {
386
+ const rl = readline.createInterface({
387
+ input: process.stdin,
388
+ output: process.stdout,
389
+ });
390
+ rl.question(question, (answer) => {
391
+ rl.close();
392
+ resolve(answer.trim());
393
+ });
394
+ });
395
+ }
396
+
397
+ // ── Entry point ───────────────────────────────────────────────────────
398
+
399
+ main().catch((err) => {
400
+ console.error('Setup failed:', err instanceof Error ? err.message : String(err));
401
+ process.exit(1);
402
+ });
@@ -0,0 +1,3 @@
1
+ # Commands: {{slug}}
2
+
3
+ _Build, deploy, test, and other commands for this topic. Kept here so they're not lost on reset._
@@ -0,0 +1,3 @@
1
+ # Cron: {{slug}}
2
+
3
+ _Cron job IDs and schedules for this topic._
@@ -0,0 +1,3 @@
1
+ # Links: {{slug}}
2
+
3
+ _URLs, paths, and service endpoints for this topic._
@@ -0,0 +1,3 @@
1
+ # Notes: {{slug}}
2
+
3
+ _Anything worth remembering about this topic._
@@ -0,0 +1,3 @@
1
+ # {{slug}}
2
+
3
+ {{description}}
@@ -0,0 +1,13 @@
1
+ # Status: {{slug}}
2
+
3
+ ## Last done (UTC)
4
+
5
+ {{timestamp}}
6
+
7
+ _No work recorded yet._
8
+
9
+ ## Next 3 actions
10
+
11
+ 1. [T-1] _Define first task in TODO.md_
12
+ 2. [T-2] _Define second task in TODO.md_
13
+ 3. [T-3] _Define third task in TODO.md_
@@ -0,0 +1,11 @@
1
+ # TODO: {{slug}}
2
+
3
+ ## Backlog
4
+
5
+ - [T-1] _First task placeholder — replace with actual task_
6
+ - [T-2] _Second task placeholder — replace with actual task_
7
+ - [T-3] _Third task placeholder — replace with actual task_
8
+
9
+ ## Completed
10
+
11
+ _None yet._
@@ -0,0 +1,3 @@
1
+ # Architecture: {{slug}}
2
+
3
+ _Components, data flow, dependencies, and design decisions._
@@ -0,0 +1,3 @@
1
+ # Deployment: {{slug}}
2
+
3
+ _Environments, deployment steps, rollback procedures, and infra details._
@@ -0,0 +1,3 @@
1
+ # Campaigns: {{slug}}
2
+
3
+ _Active campaigns, target audiences, channels, timelines, and budgets._
@@ -0,0 +1,3 @@
1
+ # Metrics: {{slug}}
2
+
3
+ _KPIs, conversion rates, engagement stats, and performance data._
@@ -0,0 +1,3 @@
1
+ # Findings: {{slug}}
2
+
3
+ _Conclusions, insights, data summaries, and recommendations._
@@ -0,0 +1,3 @@
1
+ # Sources: {{slug}}
2
+
3
+ _Papers, articles, datasets, APIs, and other reference material._