@aion0/forge 0.2.1 → 0.2.3
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/README.md +166 -175
- package/app/api/code/route.ts +31 -4
- package/app/api/docs/route.ts +48 -3
- package/app/api/git/route.ts +131 -0
- package/app/api/online/route.ts +40 -0
- package/app/api/pipelines/[id]/route.ts +28 -0
- package/app/api/pipelines/route.ts +52 -0
- package/app/api/preview/[...path]/route.ts +64 -0
- package/app/api/preview/route.ts +135 -0
- package/app/api/tasks/[id]/route.ts +8 -2
- package/components/CodeViewer.tsx +205 -4
- package/components/Dashboard.tsx +85 -1
- package/components/DocsViewer.tsx +64 -6
- package/components/NewTaskModal.tsx +7 -7
- package/components/PipelineEditor.tsx +399 -0
- package/components/PipelineView.tsx +435 -0
- package/components/PreviewPanel.tsx +154 -0
- package/components/ProjectManager.tsx +410 -0
- package/components/SettingsModal.tsx +4 -1
- package/components/TaskDetail.tsx +1 -1
- package/components/WebTerminal.tsx +109 -9
- package/lib/pipeline.ts +514 -0
- package/lib/task-manager.ts +70 -12
- package/lib/telegram-bot.ts +99 -1
- package/package.json +2 -1
package/lib/telegram-bot.ts
CHANGED
|
@@ -39,6 +39,9 @@ const chatListMode = new Map<number, 'tasks' | 'projects' | 'sessions' | 'task-c
|
|
|
39
39
|
// Pending task creation: waiting for prompt text
|
|
40
40
|
const pendingTaskProject = new Map<number, { name: string; path: string }>(); // chatId → project
|
|
41
41
|
|
|
42
|
+
// Pending note: waiting for content
|
|
43
|
+
const pendingNote = new Set<number>(); // chatIds waiting for note content
|
|
44
|
+
|
|
42
45
|
// Buffer for streaming logs
|
|
43
46
|
const logBuffers = new Map<string, { entries: string[]; timer: ReturnType<typeof setTimeout> | null }>();
|
|
44
47
|
|
|
@@ -112,10 +115,25 @@ async function poll() {
|
|
|
112
115
|
|
|
113
116
|
async function handleMessage(msg: any) {
|
|
114
117
|
const chatId = msg.chat.id;
|
|
118
|
+
|
|
119
|
+
// Whitelist check — only allow configured chat IDs, block all if not configured
|
|
120
|
+
const settings = loadSettings();
|
|
121
|
+
const allowedIds = settings.telegramChatId.split(',').map((s: string) => s.trim()).filter(Boolean);
|
|
122
|
+
if (allowedIds.length === 0 || !allowedIds.includes(String(chatId))) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
115
126
|
// Message received (logged silently)
|
|
116
127
|
const text: string = msg.text.trim();
|
|
117
128
|
const replyTo = msg.reply_to_message?.message_id;
|
|
118
129
|
|
|
130
|
+
// Check if waiting for note content
|
|
131
|
+
if (pendingNote.has(chatId) && !text.startsWith('/')) {
|
|
132
|
+
pendingNote.delete(chatId);
|
|
133
|
+
await sendNoteToDocsClaude(chatId, text);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
119
137
|
// Check if waiting for task prompt
|
|
120
138
|
const pending = pendingTaskProject.get(chatId);
|
|
121
139
|
if (pending && !text.startsWith('/')) {
|
|
@@ -186,6 +204,7 @@ async function handleMessage(msg: any) {
|
|
|
186
204
|
if (text.startsWith('/')) {
|
|
187
205
|
// Any new command cancels pending states
|
|
188
206
|
pendingTaskProject.delete(chatId);
|
|
207
|
+
pendingNote.delete(chatId);
|
|
189
208
|
|
|
190
209
|
const [cmd, ...args] = text.split(/\s+/);
|
|
191
210
|
switch (cmd) {
|
|
@@ -238,6 +257,10 @@ async function handleMessage(msg: any) {
|
|
|
238
257
|
case '/doc':
|
|
239
258
|
await handleDocs(chatId, args.join(' '));
|
|
240
259
|
break;
|
|
260
|
+
case '/note':
|
|
261
|
+
case '/docs_write':
|
|
262
|
+
await handleDocsWrite(chatId, args.join(' '));
|
|
263
|
+
break;
|
|
241
264
|
case '/cancel':
|
|
242
265
|
await handleCancel(chatId, args[0]);
|
|
243
266
|
break;
|
|
@@ -293,7 +316,8 @@ async function sendHelp(chatId: number) {
|
|
|
293
316
|
`📝 Submit task:\nproject-name: your instructions\n\n` +
|
|
294
317
|
`👀 /peek [project] [sessionId] — session summary\n` +
|
|
295
318
|
`📖 /docs — docs session summary\n` +
|
|
296
|
-
`/docs <filename> — view doc file\n
|
|
319
|
+
`/docs <filename> — view doc file\n` +
|
|
320
|
+
`📝 /note — quick note to docs claude\n\n` +
|
|
297
321
|
`🔧 /cancel <id> /retry <id>\n` +
|
|
298
322
|
`/projects — list projects\n\n` +
|
|
299
323
|
`🌐 /tunnel — tunnel status\n` +
|
|
@@ -1151,6 +1175,79 @@ async function handleDocs(chatId: number, input: string) {
|
|
|
1151
1175
|
}
|
|
1152
1176
|
}
|
|
1153
1177
|
|
|
1178
|
+
// ─── Docs Write (Quick Notes) ────────────────────────────────
|
|
1179
|
+
|
|
1180
|
+
async function handleDocsWrite(chatId: number, content: string) {
|
|
1181
|
+
const settings = loadSettings();
|
|
1182
|
+
if (String(chatId) !== settings.telegramChatId) { await send(chatId, '⛔ Unauthorized'); return; }
|
|
1183
|
+
|
|
1184
|
+
if (!content) {
|
|
1185
|
+
pendingNote.add(chatId);
|
|
1186
|
+
await send(chatId, '📝 Send your note content:');
|
|
1187
|
+
return;
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
await sendNoteToDocsClaude(chatId, content);
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
async function sendNoteToDocsClaude(chatId: number, content: string) {
|
|
1194
|
+
const settings = loadSettings();
|
|
1195
|
+
const docRoots = (settings.docRoots || []).map((r: string) => r.replace(/^~/, require('os').homedir()));
|
|
1196
|
+
|
|
1197
|
+
if (docRoots.length === 0) {
|
|
1198
|
+
await send(chatId, '⚠️ No document directories configured.');
|
|
1199
|
+
return;
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
const { execSync, spawnSync } = require('child_process');
|
|
1203
|
+
const { writeFileSync, unlinkSync } = require('fs');
|
|
1204
|
+
const { join } = require('path');
|
|
1205
|
+
const { homedir } = require('os');
|
|
1206
|
+
const SESSION_NAME = 'mw-docs-claude';
|
|
1207
|
+
|
|
1208
|
+
// Check if the docs tmux session exists
|
|
1209
|
+
let sessionExists = false;
|
|
1210
|
+
try {
|
|
1211
|
+
execSync(`tmux has-session -t ${SESSION_NAME} 2>/dev/null`);
|
|
1212
|
+
sessionExists = true;
|
|
1213
|
+
} catch {}
|
|
1214
|
+
|
|
1215
|
+
if (!sessionExists) {
|
|
1216
|
+
await send(chatId, '⚠️ Docs Claude session not running. Open the Docs tab first to start it.');
|
|
1217
|
+
return;
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
// Check if Claude is the active process (not shell)
|
|
1221
|
+
let paneCmd = '';
|
|
1222
|
+
try {
|
|
1223
|
+
paneCmd = execSync(`tmux display-message -p -t ${SESSION_NAME} '#{pane_current_command}'`, { encoding: 'utf-8', timeout: 2000 }).trim();
|
|
1224
|
+
} catch {}
|
|
1225
|
+
|
|
1226
|
+
if (paneCmd === 'zsh' || paneCmd === 'bash' || paneCmd === 'fish' || !paneCmd) {
|
|
1227
|
+
await send(chatId, '⚠️ Claude is not running in the Docs session. Open the Docs tab and start Claude first.');
|
|
1228
|
+
return;
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
// Write content to a temp file, then use tmux to send a prompt referencing it
|
|
1232
|
+
const tmpFile = join(homedir(), '.forge', '.note-tmp.txt');
|
|
1233
|
+
try {
|
|
1234
|
+
writeFileSync(tmpFile, content, 'utf-8');
|
|
1235
|
+
|
|
1236
|
+
// Send a single-line prompt to Claude via tmux send-keys using the temp file
|
|
1237
|
+
const prompt = `Please read the file ${tmpFile} and save its content as a note in the appropriate location in my docs. Analyze the content to determine the best file and location. After saving, delete the temp file.`;
|
|
1238
|
+
|
|
1239
|
+
// Use tmux send-keys with literal flag to avoid interpretation issues
|
|
1240
|
+
spawnSync('tmux', ['send-keys', '-t', SESSION_NAME, '-l', prompt], { timeout: 5000 });
|
|
1241
|
+
// Send Enter separately
|
|
1242
|
+
spawnSync('tmux', ['send-keys', '-t', SESSION_NAME, 'Enter'], { timeout: 2000 });
|
|
1243
|
+
|
|
1244
|
+
await send(chatId, `📝 Note sent to Docs Claude:\n\n${content.slice(0, 200)}${content.length > 200 ? '...' : ''}`);
|
|
1245
|
+
} catch (err) {
|
|
1246
|
+
try { unlinkSync(tmpFile); } catch {}
|
|
1247
|
+
await send(chatId, '❌ Failed to send note to Claude session');
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1154
1251
|
// ─── Real-time Streaming ─────────────────────────────────────
|
|
1155
1252
|
|
|
1156
1253
|
function bufferLogEntry(taskId: string, chatId: number, entry: TaskLogEntry) {
|
|
@@ -1283,6 +1380,7 @@ async function setBotCommands(token: string) {
|
|
|
1283
1380
|
{ command: 'tunnel_password', description: 'Get login password' },
|
|
1284
1381
|
{ command: 'peek', description: 'Session summary (AI + recent)' },
|
|
1285
1382
|
{ command: 'docs', description: 'Docs session summary / view file' },
|
|
1383
|
+
{ command: 'note', description: 'Quick note to docs Claude' },
|
|
1286
1384
|
{ command: 'watch', description: 'Monitor session' },
|
|
1287
1385
|
{ command: 'watchers', description: 'List watchers' },
|
|
1288
1386
|
{ command: 'help', description: 'Show help' },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aion0/forge",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "Unified AI workflow platform — multi-model task orchestration, persistent sessions, web terminal, remote access",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
"@auth/core": "^0.34.3",
|
|
35
35
|
"@xterm/addon-fit": "^0.11.0",
|
|
36
36
|
"@xterm/xterm": "^6.0.0",
|
|
37
|
+
"@xyflow/react": "^12.10.1",
|
|
37
38
|
"ai": "^6.0.116",
|
|
38
39
|
"better-sqlite3": "^12.6.2",
|
|
39
40
|
"next": "^16.1.6",
|