@blockrun/franklin 3.2.4 → 3.3.1
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 +216 -233
- package/dist/agent/commands.js +54 -13
- package/dist/agent/context.js +31 -1
- package/dist/agent/loop.js +48 -19
- package/dist/agent/permissions.js +3 -3
- package/dist/commands/migrate.d.ts +13 -0
- package/dist/commands/migrate.js +389 -0
- package/dist/commands/panel.d.ts +6 -0
- package/dist/commands/panel.js +29 -0
- package/dist/commands/start.js +41 -2
- package/dist/events/bridge.d.ts +1 -0
- package/dist/events/bridge.js +24 -0
- package/dist/events/bus.d.ts +17 -0
- package/dist/events/bus.js +55 -0
- package/dist/events/types.d.ts +49 -0
- package/dist/events/types.js +8 -0
- package/dist/index.js +15 -0
- package/dist/learnings/extractor.d.ts +16 -0
- package/dist/learnings/extractor.js +234 -0
- package/dist/learnings/index.d.ts +3 -0
- package/dist/learnings/index.js +2 -0
- package/dist/learnings/store.d.ts +15 -0
- package/dist/learnings/store.js +130 -0
- package/dist/learnings/types.d.ts +24 -0
- package/dist/learnings/types.js +7 -0
- package/dist/mcp/client.js +9 -2
- package/dist/narrative/state.d.ts +30 -0
- package/dist/narrative/state.js +69 -0
- package/dist/panel/html.d.ts +5 -0
- package/dist/panel/html.js +341 -0
- package/dist/panel/server.d.ts +7 -0
- package/dist/panel/server.js +152 -0
- package/dist/session/storage.js +4 -2
- package/dist/social/browser-pool.d.ts +29 -0
- package/dist/social/browser-pool.js +57 -0
- package/dist/social/preflight.d.ts +14 -0
- package/dist/social/preflight.js +26 -0
- package/dist/social/x.d.ts +8 -0
- package/dist/social/x.js +9 -1
- package/dist/stats/tracker.d.ts +1 -0
- package/dist/stats/tracker.js +59 -13
- package/dist/tools/bash.js +6 -1
- package/dist/tools/index.js +3 -0
- package/dist/tools/posttox.d.ts +7 -0
- package/dist/tools/posttox.js +137 -0
- package/dist/tools/searchx.d.ts +7 -0
- package/dist/tools/searchx.js +111 -0
- package/dist/tools/trading.d.ts +3 -0
- package/dist/tools/trading.js +168 -0
- package/dist/tools/webfetch.js +19 -9
- package/dist/tools/write.js +2 -0
- package/dist/trading/config.d.ts +23 -0
- package/dist/trading/config.js +45 -0
- package/dist/trading/data.d.ts +30 -0
- package/dist/trading/data.js +112 -0
- package/dist/trading/metrics.d.ts +29 -0
- package/dist/trading/metrics.js +105 -0
- package/dist/ui/app.js +73 -44
- package/dist/ui/markdown.d.ts +9 -0
- package/dist/ui/markdown.js +86 -0
- package/package.json +1 -1
package/dist/agent/commands.js
CHANGED
|
@@ -196,9 +196,9 @@ const DIRECT_COMMANDS = {
|
|
|
196
196
|
` **Coding:** /commit /review /test /fix /debug /explain /search /find /refactor /scaffold\n` +
|
|
197
197
|
` **Git:** /push /pr /undo /status /diff /log /branch /stash /unstash\n` +
|
|
198
198
|
` **Analysis:** /security /lint /optimize /todo /deps /clean /migrate /doc\n` +
|
|
199
|
-
` **Session:** /plan /ultraplan /execute /compact /retry /sessions /resume /context /tasks\n` +
|
|
199
|
+
` **Session:** /plan /ultraplan /execute /compact /retry /sessions /resume /session-search /context /tasks\n` +
|
|
200
200
|
` **Power:** /ultrathink [query] /ultraplan /dump\n` +
|
|
201
|
-
` **Info:** /model /wallet /cost /tokens /mcp /doctor /version /bug /help\n` +
|
|
201
|
+
` **Info:** /model /wallet /cost /tokens /learnings /mcp /doctor /version /bug /help\n` +
|
|
202
202
|
` **UI:** /clear /exit\n` +
|
|
203
203
|
(ultrathinkOn ? `\n Ultrathink: ON\n` : '')
|
|
204
204
|
});
|
|
@@ -350,11 +350,12 @@ const DIRECT_COMMANDS = {
|
|
|
350
350
|
for (const s of sessions.slice(0, 10)) {
|
|
351
351
|
const date = new Date(s.updatedAt).toLocaleString();
|
|
352
352
|
const dir = s.workDir ? ` — ${s.workDir.split('/').pop()}` : '';
|
|
353
|
-
|
|
353
|
+
const current = s.id === ctx.sessionId ? ' (current)' : '';
|
|
354
|
+
text += ` ${s.id} ${s.model} ${s.turnCount} turns ${date}${dir}${current}\n`;
|
|
354
355
|
}
|
|
355
356
|
if (sessions.length > 10)
|
|
356
357
|
text += ` ... and ${sessions.length - 10} more\n`;
|
|
357
|
-
text += '\nUse /resume <session-id>
|
|
358
|
+
text += '\nUse /resume to restore the latest session, or /resume <session-id> for a specific one.\n';
|
|
358
359
|
ctx.onEvent({ kind: 'text_delta', text });
|
|
359
360
|
}
|
|
360
361
|
emitDone(ctx);
|
|
@@ -486,13 +487,17 @@ export async function handleSlashCommand(input, ctx) {
|
|
|
486
487
|
await DIRECT_COMMANDS[input](ctx);
|
|
487
488
|
return { handled: true };
|
|
488
489
|
}
|
|
489
|
-
// /search <query> — full-text search past sessions
|
|
490
|
-
if (input === '/search' ||
|
|
491
|
-
|
|
490
|
+
// /session-search <query> — full-text search past sessions
|
|
491
|
+
if (input === '/session-search' ||
|
|
492
|
+
input.startsWith('/session-search ') ||
|
|
493
|
+
input === '/ssearch' ||
|
|
494
|
+
input.startsWith('/ssearch ')) {
|
|
495
|
+
const prefix = input.startsWith('/ssearch') ? '/ssearch' : '/session-search';
|
|
496
|
+
const query = input === prefix ? '' : input.slice(prefix.length + 1).trim();
|
|
492
497
|
if (!query) {
|
|
493
|
-
ctx.onEvent({ kind: 'text_delta', text: 'Usage: /search <query>\n' +
|
|
498
|
+
ctx.onEvent({ kind: 'text_delta', text: 'Usage: /session-search <query>\n' +
|
|
494
499
|
'Finds past sessions whose messages match the query.\n' +
|
|
495
|
-
'Use quotes for phrase search: /search "payment loop"\n'
|
|
500
|
+
'Use quotes for phrase search: /session-search "payment loop"\n'
|
|
496
501
|
});
|
|
497
502
|
emitDone(ctx);
|
|
498
503
|
return { handled: true };
|
|
@@ -513,6 +518,34 @@ export async function handleSlashCommand(input, ctx) {
|
|
|
513
518
|
emitDone(ctx);
|
|
514
519
|
return { handled: true };
|
|
515
520
|
}
|
|
521
|
+
// /learnings — view or clear per-user learnings
|
|
522
|
+
if (input === '/learnings' || input.startsWith('/learnings ')) {
|
|
523
|
+
const { loadLearnings, decayLearnings, saveLearnings } = await import('../learnings/store.js');
|
|
524
|
+
const arg = input.slice('/learnings'.length).trim();
|
|
525
|
+
if (arg === 'clear') {
|
|
526
|
+
saveLearnings([]);
|
|
527
|
+
ctx.onEvent({ kind: 'text_delta', text: 'All learnings cleared.\n' });
|
|
528
|
+
}
|
|
529
|
+
else {
|
|
530
|
+
let learnings = loadLearnings();
|
|
531
|
+
if (learnings.length === 0) {
|
|
532
|
+
ctx.onEvent({ kind: 'text_delta', text: 'No learnings yet. Franklin learns your preferences over time.\n' });
|
|
533
|
+
}
|
|
534
|
+
else {
|
|
535
|
+
learnings = decayLearnings(learnings);
|
|
536
|
+
const sorted = [...learnings].sort((a, b) => (b.confidence * b.times_confirmed) - (a.confidence * a.times_confirmed));
|
|
537
|
+
let text = `**Personal Learnings** (${sorted.length})\n\n`;
|
|
538
|
+
for (const l of sorted) {
|
|
539
|
+
const conf = l.confidence >= 0.8 ? 'high' : l.confidence >= 0.5 ? 'mid' : 'low';
|
|
540
|
+
text += ` [${conf}] ${l.learning} (×${l.times_confirmed})\n`;
|
|
541
|
+
}
|
|
542
|
+
text += '\nUse `/learnings clear` to reset.\n';
|
|
543
|
+
ctx.onEvent({ kind: 'text_delta', text });
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
emitDone(ctx);
|
|
547
|
+
return { handled: true };
|
|
548
|
+
}
|
|
516
549
|
// /model — show current model or switch with /model <name>
|
|
517
550
|
if (input === '/model' || input.startsWith('/model ')) {
|
|
518
551
|
if (input === '/model') {
|
|
@@ -523,6 +556,7 @@ export async function handleSlashCommand(input, ctx) {
|
|
|
523
556
|
else {
|
|
524
557
|
const newModel = resolveModel(input.slice(7).trim());
|
|
525
558
|
ctx.config.model = newModel;
|
|
559
|
+
ctx.config.onModelChange?.(newModel);
|
|
526
560
|
ctx.onEvent({ kind: 'text_delta', text: `Model → **${newModel}**\n` });
|
|
527
561
|
}
|
|
528
562
|
emitDone(ctx);
|
|
@@ -604,9 +638,16 @@ export async function handleSlashCommand(input, ctx) {
|
|
|
604
638
|
emitDone(ctx);
|
|
605
639
|
return { handled: true };
|
|
606
640
|
}
|
|
607
|
-
// /resume <id>
|
|
608
|
-
if (input.startsWith('/resume ')) {
|
|
609
|
-
const targetId = input
|
|
641
|
+
// /resume or /resume <id>
|
|
642
|
+
if (input === '/resume' || input.startsWith('/resume ')) {
|
|
643
|
+
const targetId = input === '/resume'
|
|
644
|
+
? listSessions().find((session) => session.id !== ctx.sessionId)?.id ?? ''
|
|
645
|
+
: input.slice(8).trim();
|
|
646
|
+
if (!targetId) {
|
|
647
|
+
ctx.onEvent({ kind: 'text_delta', text: 'No previous session available to resume.\n' });
|
|
648
|
+
emitDone(ctx);
|
|
649
|
+
return { handled: true };
|
|
650
|
+
}
|
|
610
651
|
const restored = loadSessionHistory(targetId);
|
|
611
652
|
if (restored.length === 0) {
|
|
612
653
|
ctx.onEvent({ kind: 'text_delta', text: `Session "${targetId}" not found or empty.\n` });
|
|
@@ -636,7 +677,7 @@ export async function handleSlashCommand(input, ctx) {
|
|
|
636
677
|
...Object.keys(DIRECT_COMMANDS),
|
|
637
678
|
...Object.keys(REWRITE_COMMANDS),
|
|
638
679
|
...ARG_COMMANDS.map(c => c.prefix.trim()),
|
|
639
|
-
'/branch', '/resume', '/model', '/wallet', '/cost', '/help', '/clear', '/retry', '/exit',
|
|
680
|
+
'/branch', '/resume', '/model', '/wallet', '/cost', '/help', '/clear', '/retry', '/exit', '/session-search', '/ssearch',
|
|
640
681
|
];
|
|
641
682
|
const cmd = input.split(/\s/)[0];
|
|
642
683
|
const close = allCommands.filter(c => {
|
package/dist/agent/context.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import fs from 'node:fs';
|
|
6
6
|
import path from 'node:path';
|
|
7
7
|
import { execSync } from 'node:child_process';
|
|
8
|
+
import { loadLearnings, decayLearnings, saveLearnings, formatForPrompt } from '../learnings/store.js';
|
|
8
9
|
// ─── System Instructions Assembly ──────────────────────────────────────────
|
|
9
10
|
const BASE_INSTRUCTIONS = `You are runcode, an AI coding agent that helps users with software engineering tasks.
|
|
10
11
|
You have access to tools for reading, writing, editing files, running shell commands, searching codebases, web browsing, and more.
|
|
@@ -32,7 +33,24 @@ You have access to tools for reading, writing, editing files, running shell comm
|
|
|
32
33
|
- **Batch bash**: combine sequential shell commands into one Bash call with && or a script. Only split when you need to inspect intermediate output.
|
|
33
34
|
- **AskUser**: Only use AskUser when you are about to perform a destructive action (deleting files, dropping databases) and need explicit confirmation. NEVER use AskUser to ask what the user wants — just answer their message directly. If their request is vague, make a reasonable assumption and proceed.
|
|
34
35
|
- Never write to /etc, /usr, ~/.ssh, ~/.aws. Don't commit secrets.
|
|
35
|
-
- Type /help to see all slash commands
|
|
36
|
+
- Type /help to see all slash commands.
|
|
37
|
+
|
|
38
|
+
# Missing Access
|
|
39
|
+
Always deliver results first using whatever tools work (WebSearch, WebFetch, etc.). Never let missing access block you.
|
|
40
|
+
After delivering results, if a better data source exists, add one line at the end:
|
|
41
|
+
"Tip: run franklin social setup && franklin social login x for live X data."
|
|
42
|
+
Do NOT check access before acting. Do NOT explain what you tried. Just deliver, then tip.
|
|
43
|
+
|
|
44
|
+
# X / Social Marketing
|
|
45
|
+
When the user asks about X posts, trending topics, or social growth:
|
|
46
|
+
1. Search and find relevant posts immediately (WebSearch or SearchX).
|
|
47
|
+
2. Present results as a numbered list of SUGGESTIONS, not actions. Each item must include:
|
|
48
|
+
- The post author and a short snippet
|
|
49
|
+
- A clickable link (https://x.com/...)
|
|
50
|
+
- A suggested reply draft (2-3 sentences, natural tone, not salesy)
|
|
51
|
+
3. End with: "Reply to any of these? Give me the number."
|
|
52
|
+
4. Do NOT auto-post. Do NOT explain how the social system works. Do NOT dump config JSON.
|
|
53
|
+
5. If the user asks to set up X access, ask them simple questions one at a time (handle? product? keywords?) and write the config yourself. Never show raw JSON to the user.`;
|
|
36
54
|
// Cache assembled instructions per workingDir — avoids re-running git commands
|
|
37
55
|
// when sub-agents are spawned (common in parallel tool use patterns).
|
|
38
56
|
const _instructionCache = new Map();
|
|
@@ -57,6 +75,18 @@ export function assembleInstructions(workingDir) {
|
|
|
57
75
|
if (gitInfo) {
|
|
58
76
|
parts.push(`# Git Context\n\n${gitInfo}`);
|
|
59
77
|
}
|
|
78
|
+
// Inject per-user learnings from self-evolution system
|
|
79
|
+
try {
|
|
80
|
+
let learnings = loadLearnings();
|
|
81
|
+
if (learnings.length > 0) {
|
|
82
|
+
learnings = decayLearnings(learnings);
|
|
83
|
+
saveLearnings(learnings);
|
|
84
|
+
const personalContext = formatForPrompt(learnings);
|
|
85
|
+
if (personalContext)
|
|
86
|
+
parts.push(personalContext);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
catch { /* learnings are optional — never block startup */ }
|
|
60
90
|
_instructionCache.set(workingDir, parts);
|
|
61
91
|
return parts;
|
|
62
92
|
}
|
package/dist/agent/loop.js
CHANGED
|
@@ -42,7 +42,21 @@ export async function interactiveSession(config, getUserInput, onEvent, onAbortR
|
|
|
42
42
|
const sessionId = createSessionId();
|
|
43
43
|
let turnCount = 0;
|
|
44
44
|
let tokenBudgetWarned = false; // Emit token budget warning at most once per session
|
|
45
|
+
let lastSessionActivity = Date.now();
|
|
46
|
+
const persistSessionMeta = () => {
|
|
47
|
+
updateSessionMeta(sessionId, {
|
|
48
|
+
model: config.model,
|
|
49
|
+
workDir,
|
|
50
|
+
turnCount,
|
|
51
|
+
messageCount: history.length,
|
|
52
|
+
});
|
|
53
|
+
};
|
|
54
|
+
const persistSessionMessage = (message) => {
|
|
55
|
+
appendToSession(sessionId, message);
|
|
56
|
+
persistSessionMeta();
|
|
57
|
+
};
|
|
45
58
|
pruneOldSessions(sessionId); // Cleanup old sessions on start, protect current
|
|
59
|
+
persistSessionMeta();
|
|
46
60
|
while (true) {
|
|
47
61
|
let input = await getUserInput();
|
|
48
62
|
if (input === null)
|
|
@@ -72,23 +86,28 @@ export async function interactiveSession(config, getUserInput, onEvent, onAbortR
|
|
|
72
86
|
}
|
|
73
87
|
lastUserInput = input;
|
|
74
88
|
history.push({ role: 'user', content: input });
|
|
75
|
-
appendToSession(sessionId, { role: 'user', content: input });
|
|
76
89
|
turnCount++;
|
|
90
|
+
persistSessionMessage({ role: 'user', content: input });
|
|
77
91
|
const abort = new AbortController();
|
|
78
92
|
onAbortReady?.(() => abort.abort());
|
|
79
93
|
let loopCount = 0;
|
|
80
94
|
let recoveryAttempts = 0;
|
|
81
95
|
let compactFailures = 0;
|
|
82
96
|
let maxTokensOverride;
|
|
83
|
-
|
|
97
|
+
const turnIdleReference = lastSessionActivity;
|
|
98
|
+
lastSessionActivity = Date.now();
|
|
84
99
|
// Agent loop for this user message
|
|
85
100
|
while (loopCount < maxTurns) {
|
|
86
101
|
loopCount++;
|
|
102
|
+
// Signal UI that a new LLM round is starting (shows spinner between tool results and next response)
|
|
103
|
+
if (loopCount > 1) {
|
|
104
|
+
onEvent({ kind: 'thinking_delta', text: '' });
|
|
105
|
+
}
|
|
87
106
|
// ── Token optimization pipeline ──
|
|
88
107
|
// 1. Strip thinking, budget tool results, time-based cleanup (always — cheap)
|
|
89
108
|
const optimized = optimizeHistory(history, {
|
|
90
109
|
debug: config.debug,
|
|
91
|
-
lastActivityTimestamp:
|
|
110
|
+
lastActivityTimestamp: loopCount === 1 ? turnIdleReference : lastSessionActivity,
|
|
92
111
|
});
|
|
93
112
|
if (optimized !== history) {
|
|
94
113
|
history.length = 0;
|
|
@@ -185,9 +204,12 @@ export async function interactiveSession(config, getUserInput, onEvent, onAbortR
|
|
|
185
204
|
if (err.name === 'AbortError' || abort.signal.aborted) {
|
|
186
205
|
// Save any partial response that was streamed before abort
|
|
187
206
|
if (responseParts && responseParts.length > 0) {
|
|
188
|
-
|
|
189
|
-
|
|
207
|
+
const partialAssistant = { role: 'assistant', content: responseParts };
|
|
208
|
+
history.push(partialAssistant);
|
|
209
|
+
persistSessionMessage(partialAssistant);
|
|
190
210
|
}
|
|
211
|
+
lastSessionActivity = Date.now();
|
|
212
|
+
persistSessionMeta();
|
|
191
213
|
onEvent({ kind: 'turn_done', reason: 'aborted' });
|
|
192
214
|
break;
|
|
193
215
|
}
|
|
@@ -249,6 +271,8 @@ export async function interactiveSession(config, getUserInput, onEvent, onAbortR
|
|
|
249
271
|
reason: 'error',
|
|
250
272
|
error: `[${classified.label}] ${errMsg}${suggestion}`,
|
|
251
273
|
});
|
|
274
|
+
lastSessionActivity = Date.now();
|
|
275
|
+
persistSessionMeta();
|
|
252
276
|
break;
|
|
253
277
|
}
|
|
254
278
|
// When API doesn't return input tokens (some models return 0), estimate from history
|
|
@@ -278,11 +302,16 @@ export async function interactiveSession(config, getUserInput, onEvent, onAbortR
|
|
|
278
302
|
}
|
|
279
303
|
}
|
|
280
304
|
// Append what we got + a continuation prompt (text already streamed)
|
|
281
|
-
|
|
282
|
-
|
|
305
|
+
const partialAssistant = { role: 'assistant', content: responseParts };
|
|
306
|
+
const continuationPrompt = {
|
|
283
307
|
role: 'user',
|
|
284
308
|
content: 'Continue where you left off. Do not repeat what you already said.',
|
|
285
|
-
}
|
|
309
|
+
};
|
|
310
|
+
history.push(partialAssistant);
|
|
311
|
+
persistSessionMessage(partialAssistant);
|
|
312
|
+
history.push(continuationPrompt);
|
|
313
|
+
persistSessionMessage(continuationPrompt);
|
|
314
|
+
lastSessionActivity = Date.now();
|
|
286
315
|
continue; // Retry with higher limit
|
|
287
316
|
}
|
|
288
317
|
// Reset recovery counter on successful completion
|
|
@@ -294,17 +323,13 @@ export async function interactiveSession(config, getUserInput, onEvent, onAbortR
|
|
|
294
323
|
invocations.push(part);
|
|
295
324
|
}
|
|
296
325
|
}
|
|
297
|
-
|
|
326
|
+
const assistantMessage = { role: 'assistant', content: responseParts };
|
|
327
|
+
history.push(assistantMessage);
|
|
328
|
+
persistSessionMessage(assistantMessage);
|
|
298
329
|
// No more capabilities → done with this user message
|
|
299
330
|
if (invocations.length === 0) {
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
updateSessionMeta(sessionId, {
|
|
303
|
-
model: config.model,
|
|
304
|
-
workDir: config.workingDir || process.cwd(),
|
|
305
|
-
turnCount,
|
|
306
|
-
messageCount: history.length,
|
|
307
|
-
});
|
|
331
|
+
lastSessionActivity = Date.now();
|
|
332
|
+
persistSessionMeta();
|
|
308
333
|
// Token budget warning — emit once per session when crossing 70%
|
|
309
334
|
if (!tokenBudgetWarned) {
|
|
310
335
|
const { estimated } = getAnchoredTokenCount(history);
|
|
@@ -327,7 +352,7 @@ export async function interactiveSession(config, getUserInput, onEvent, onAbortR
|
|
|
327
352
|
onEvent({ kind: 'capability_done', id: inv.id, result });
|
|
328
353
|
}
|
|
329
354
|
// Refresh activity timestamp after tool execution
|
|
330
|
-
|
|
355
|
+
lastSessionActivity = Date.now();
|
|
331
356
|
// Append outcomes
|
|
332
357
|
const outcomeContent = results.map(([inv, result]) => ({
|
|
333
358
|
type: 'tool_result',
|
|
@@ -335,9 +360,13 @@ export async function interactiveSession(config, getUserInput, onEvent, onAbortR
|
|
|
335
360
|
content: result.output,
|
|
336
361
|
is_error: result.isError,
|
|
337
362
|
}));
|
|
338
|
-
|
|
363
|
+
const toolResultMessage = { role: 'user', content: outcomeContent };
|
|
364
|
+
history.push(toolResultMessage);
|
|
365
|
+
persistSessionMessage(toolResultMessage);
|
|
339
366
|
}
|
|
340
367
|
if (loopCount >= maxTurns) {
|
|
368
|
+
lastSessionActivity = Date.now();
|
|
369
|
+
persistSessionMeta();
|
|
341
370
|
onEvent({ kind: 'turn_done', reason: 'max_turns' });
|
|
342
371
|
}
|
|
343
372
|
}
|
|
@@ -8,12 +8,12 @@ import readline from 'node:readline';
|
|
|
8
8
|
import chalk from 'chalk';
|
|
9
9
|
import { BLOCKRUN_DIR } from '../config.js';
|
|
10
10
|
// ─── Default Rules ─────────────────────────────────────────────────────────
|
|
11
|
-
const READ_ONLY_TOOLS = new Set(['Read', 'Glob', 'Grep', 'WebSearch', 'Task', 'AskUser', 'ImageGen']);
|
|
11
|
+
const READ_ONLY_TOOLS = new Set(['Read', 'Glob', 'Grep', 'WebSearch', 'Task', 'AskUser', 'ImageGen', 'TradingSignal', 'TradingMarket', 'SearchX']);
|
|
12
12
|
const DESTRUCTIVE_TOOLS = new Set(['Write', 'Edit', 'Bash']);
|
|
13
13
|
const DEFAULT_RULES = {
|
|
14
|
-
allow: ['Read', 'Glob', 'Grep', 'WebSearch', 'WebFetch', 'Task', 'AskUser', 'ImageGen'],
|
|
14
|
+
allow: ['Read', 'Glob', 'Grep', 'WebSearch', 'WebFetch', 'Task', 'AskUser', 'ImageGen', 'TradingSignal', 'TradingMarket', 'SearchX'],
|
|
15
15
|
deny: [],
|
|
16
|
-
ask: ['Write', 'Edit', 'Bash', 'Agent'],
|
|
16
|
+
ask: ['Write', 'Edit', 'Bash', 'Agent', 'PostToX'],
|
|
17
17
|
};
|
|
18
18
|
// ─── Permission Manager ────────────────────────────────────────────────────
|
|
19
19
|
export class PermissionManager {
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* franklin migrate — one-click import from other AI coding agents.
|
|
3
|
+
*
|
|
4
|
+
* Detects installed tools (Claude Code, Cline, Cursor, etc.),
|
|
5
|
+
* shows what can be migrated, and imports with user confirmation.
|
|
6
|
+
*/
|
|
7
|
+
export declare function migrateCommand(): Promise<void>;
|
|
8
|
+
/**
|
|
9
|
+
* Check if other AI tools are installed and suggest migration.
|
|
10
|
+
* Only runs once — writes a marker file after first check.
|
|
11
|
+
* Returns true if the user chose to migrate (caller should re-run start after).
|
|
12
|
+
*/
|
|
13
|
+
export declare function checkAndSuggestMigration(): Promise<boolean>;
|