create-walle 0.7.1 → 0.9.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/bin/create-walle.js +10 -1
- package/package.json +1 -1
- package/template/README.md +123 -43
- package/template/bin/dev.sh +72 -0
- package/template/claude-task-manager/db.js +2 -2
- package/template/claude-task-manager/public/css/walle.css +23 -2
- package/template/claude-task-manager/public/index.html +236 -70
- package/template/claude-task-manager/public/js/walle.js +255 -48
- package/template/docs/ux-improvement-plan.md +84 -0
- package/template/package.json +5 -2
- package/template/wall-e/agent.js +4 -58
- package/template/wall-e/api-walle.js +131 -8
- package/template/wall-e/chat.js +23 -3
- package/template/wall-e/core-tasks.js +53 -0
- package/template/wall-e/skills/_bundled/file-ingest/SKILL.md +56 -0
- package/template/wall-e/skills/_bundled/file-ingest/run.js +142 -0
- package/template/wall-e/skills/_bundled/mcp-scan/SKILL.md +14 -0
- package/template/wall-e/skills/_bundled/mcp-scan/run.js +86 -0
- package/template/wall-e/skills/_bundled/morning-briefing/run.js +2 -2
- package/template/wall-e/skills/mcp-client.js +241 -13
- package/template/wall-e/tools/local-tools.js +93 -0
|
@@ -2226,6 +2226,9 @@
|
|
|
2226
2226
|
<button class="svc-btn" onclick="svcAction('walle','restart')" id="svc-walle-restart" style="display:none">Restart</button>
|
|
2227
2227
|
</div>
|
|
2228
2228
|
</div>
|
|
2229
|
+
<div style="margin-top:8px;padding-top:8px;border-top:1px solid var(--border);">
|
|
2230
|
+
<button class="svc-btn" onclick="restartAll()" style="width:100%;">Restart All</button>
|
|
2231
|
+
</div>
|
|
2229
2232
|
</div>
|
|
2230
2233
|
</div>
|
|
2231
2234
|
</div>
|
|
@@ -2234,12 +2237,17 @@
|
|
|
2234
2237
|
<nav class="topbar-nav" id="topbar-nav">
|
|
2235
2238
|
<button class="nav-pill active" data-nav="sessions" onclick="navTo('sessions')" title="Terminal sessions">Sessions</button>
|
|
2236
2239
|
<button class="nav-pill" data-nav="prompts" onclick="navTo('prompts')" title="Prompt Editor">Prompts</button>
|
|
2237
|
-
<button class="nav-pill" data-nav="insights" onclick="navTo('insights')" title="Session analysis">Insights</button>
|
|
2238
|
-
<button class="nav-pill" data-nav="permissions" onclick="navTo('permissions')" title="Tool permissions">Permissions</button>
|
|
2239
|
-
<button class="nav-pill" data-nav="codereview" onclick="navTo('codereview')" title="Code Review">Review</button>
|
|
2240
2240
|
<button class="nav-pill" data-nav="walle" onclick="navTo('walle')" title="WALL-E Agent">WALL-E</button>
|
|
2241
|
-
<
|
|
2242
|
-
|
|
2241
|
+
<div style="position:relative;display:inline-block;" id="nav-more-wrap">
|
|
2242
|
+
<button class="nav-pill" onclick="toggleNavMore()" title="More pages">More <span style="font-size:10px;">▾</span></button>
|
|
2243
|
+
<div id="nav-more-dropdown" style="display:none;position:absolute;top:100%;left:0;z-index:999;background:var(--bg-light);border:1px solid var(--border);border-radius:6px;padding:4px 0;min-width:140px;box-shadow:0 4px 12px rgba(0,0,0,0.3);">
|
|
2244
|
+
<button class="nav-pill" data-nav="insights" onclick="navTo('insights');closeNavMore()" style="display:block;width:100%;text-align:left;border-radius:0;padding:6px 14px;">Insights</button>
|
|
2245
|
+
<button class="nav-pill" data-nav="permissions" onclick="navTo('permissions');closeNavMore()" style="display:block;width:100%;text-align:left;border-radius:0;padding:6px 14px;">Permissions</button>
|
|
2246
|
+
<button class="nav-pill" data-nav="codereview" onclick="navTo('codereview');closeNavMore()" style="display:block;width:100%;text-align:left;border-radius:0;padding:6px 14px;">Review</button>
|
|
2247
|
+
<button class="nav-pill" data-nav="rules" onclick="navTo('rules');closeNavMore()" style="display:block;width:100%;text-align:left;border-radius:0;padding:6px 14px;">Rules</button>
|
|
2248
|
+
<button class="nav-pill" data-nav="backups" onclick="navTo('backups');closeNavMore()" style="display:block;width:100%;text-align:left;border-radius:0;padding:6px 14px;">Backups</button>
|
|
2249
|
+
</div>
|
|
2250
|
+
</div>
|
|
2243
2251
|
<a href="/setup.html" class="nav-pill" title="Setup & Settings" style="text-decoration:none;margin-left:auto;font-size:16px;padding:4px 8px;">⚙</a>
|
|
2244
2252
|
</nav>
|
|
2245
2253
|
<div class="topbar-right">
|
|
@@ -2307,14 +2315,22 @@
|
|
|
2307
2315
|
</div>
|
|
2308
2316
|
<div id="terminal-area">
|
|
2309
2317
|
<div id="welcome">
|
|
2310
|
-
<h2>Welcome to
|
|
2311
|
-
<p>
|
|
2312
|
-
<
|
|
2313
|
-
|
|
2314
|
-
<
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
+
<h2 style="font-size:24px;margin-bottom:4px;">Welcome to CTM</h2>
|
|
2319
|
+
<p style="color:var(--fg-dim);margin-bottom:20px;">Manage Claude Code sessions, prompts, and your AI assistant Wall-E.</p>
|
|
2320
|
+
<button class="btn primary" onclick="createSession()" style="font-size:15px;padding:8px 24px;margin-bottom:28px;">New Claude Session</button>
|
|
2321
|
+
<div style="display:flex;gap:16px;max-width:680px;">
|
|
2322
|
+
<div onclick="navTo('sessions')" style="flex:1;padding:14px;background:rgba(255,255,255,0.03);border:1px solid var(--border);border-radius:8px;cursor:pointer;">
|
|
2323
|
+
<div style="font-weight:600;margin-bottom:4px;color:var(--fg);">Sessions</div>
|
|
2324
|
+
<div style="font-size:12px;color:var(--fg-dim);">Run and manage Claude Code terminal sessions</div>
|
|
2325
|
+
</div>
|
|
2326
|
+
<div onclick="navTo('prompts')" style="flex:1;padding:14px;background:rgba(255,255,255,0.03);border:1px solid var(--border);border-radius:8px;cursor:pointer;">
|
|
2327
|
+
<div style="font-weight:600;margin-bottom:4px;color:var(--fg);">Prompts</div>
|
|
2328
|
+
<div style="font-size:12px;color:var(--fg-dim);">Save, organize, and send prompts to Claude</div>
|
|
2329
|
+
</div>
|
|
2330
|
+
<div onclick="navTo('walle')" style="flex:1;padding:14px;background:rgba(255,255,255,0.03);border:1px solid var(--border);border-radius:8px;cursor:pointer;">
|
|
2331
|
+
<div style="font-weight:600;margin-bottom:4px;color:var(--fg);">WALL-E</div>
|
|
2332
|
+
<div style="font-size:12px;color:var(--fg-dim);">Your personal AI assistant — chat, tasks, and insights</div>
|
|
2333
|
+
</div>
|
|
2318
2334
|
</div>
|
|
2319
2335
|
</div>
|
|
2320
2336
|
<div id="insights-panel">
|
|
@@ -2379,12 +2395,18 @@
|
|
|
2379
2395
|
<div class="walle-subnav">
|
|
2380
2396
|
<button class="walle-subnav-btn active" data-view="chat" onclick="WE.showView('chat')">Chat</button>
|
|
2381
2397
|
<button class="walle-subnav-btn" data-view="tasks" onclick="WE.showView('tasks')">Tasks</button>
|
|
2382
|
-
<button class="walle-subnav-btn" data-view="brain" onclick="WE.showView('brain')">Brain</button>
|
|
2383
|
-
<button class="walle-subnav-btn" data-view="actions" onclick="WE.showView('actions')">Actions</button>
|
|
2384
2398
|
<button class="walle-subnav-btn" data-view="skills" onclick="WE.showView('skills')">Skills</button>
|
|
2385
|
-
<button class="walle-subnav-btn" data-view="
|
|
2386
|
-
<
|
|
2387
|
-
|
|
2399
|
+
<button class="walle-subnav-btn" data-view="mcp" onclick="WE.showView('mcp')">MCP</button>
|
|
2400
|
+
<div style="position:relative;display:inline-block;" id="we-more-wrap">
|
|
2401
|
+
<button class="walle-subnav-btn" onclick="WE.toggleMoreTabs()" id="we-more-btn">More <span style="font-size:10px;">▾</span></button>
|
|
2402
|
+
<div id="we-more-dropdown" style="display:none;position:absolute;top:100%;left:0;z-index:999;background:var(--bg-light);border:1px solid var(--border);border-radius:6px;padding:4px 0;min-width:120px;box-shadow:0 4px 12px rgba(0,0,0,0.3);">
|
|
2403
|
+
<button class="walle-subnav-btn" data-view="brain" onclick="WE.showView('brain');WE.closeMoreTabs()" style="display:block;width:100%;text-align:left;border-radius:0;padding:6px 14px;">Brain</button>
|
|
2404
|
+
<button class="walle-subnav-btn" data-view="actions" onclick="WE.showView('actions');WE.closeMoreTabs()" style="display:block;width:100%;text-align:left;border-radius:0;padding:6px 14px;">Actions</button>
|
|
2405
|
+
<button class="walle-subnav-btn" data-view="timeline" onclick="WE.showView('timeline');WE.closeMoreTabs()" style="display:block;width:100%;text-align:left;border-radius:0;padding:6px 14px;">Timeline</button>
|
|
2406
|
+
<button class="walle-subnav-btn" data-view="questions" onclick="WE.showView('questions');WE.closeMoreTabs()" style="display:block;width:100%;text-align:left;border-radius:0;padding:6px 14px;">Questions</button>
|
|
2407
|
+
<button class="walle-subnav-btn" data-view="status" onclick="WE.showView('status');WE.closeMoreTabs()" style="display:block;width:100%;text-align:left;border-radius:0;padding:6px 14px;">Status</button>
|
|
2408
|
+
</div>
|
|
2409
|
+
</div>
|
|
2388
2410
|
</div>
|
|
2389
2411
|
</div>
|
|
2390
2412
|
<div id="walle-body" class="walle-body"></div>
|
|
@@ -2420,6 +2442,11 @@
|
|
|
2420
2442
|
<div id="permissions-panel">
|
|
2421
2443
|
<div class="perm-toolbar">
|
|
2422
2444
|
<h3>Permission Manager</h3>
|
|
2445
|
+
<div id="perm-explainer" style="background:rgba(255,255,255,0.03);border-left:3px solid var(--accent);padding:8px 12px;font-size:12px;color:var(--fg-dim);margin:0 0 8px;border-radius:0 4px 4px 0;display:flex;align-items:center;gap:8px;">
|
|
2446
|
+
<span style="flex:1;">Permissions control what Claude Code can do automatically. Rules you approve here apply across all sessions.</span>
|
|
2447
|
+
<button onclick="this.parentElement.style.display='none';localStorage.setItem('perm_explainer_dismissed','1')" style="background:none;border:none;color:var(--fg-dim);cursor:pointer;font-size:14px;padding:2px 6px;">×</button>
|
|
2448
|
+
</div>
|
|
2449
|
+
<script>if(localStorage.getItem('perm_explainer_dismissed')==='1'){document.getElementById('perm-explainer').style.display='none';}</script>
|
|
2423
2450
|
<select id="perm-scope-filter" onchange="renderPermissions()" style="background:var(--bg-lighter);color:var(--fg);border:1px solid var(--border);padding:5px 8px;border-radius:5px;font-size:12px;">
|
|
2424
2451
|
<option value="__global__">All Projects (Global)</option>
|
|
2425
2452
|
</select>
|
|
@@ -2493,11 +2520,16 @@
|
|
|
2493
2520
|
<!-- Prompts Panel (merged from prompts.html) -->
|
|
2494
2521
|
<div id="prompts-panel">
|
|
2495
2522
|
<div class="prompts-topbar">
|
|
2496
|
-
<
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2523
|
+
<div style="position:relative;display:inline-block;" id="pe-power-wrap">
|
|
2524
|
+
<button class="btn" onclick="(function(){ var dd=document.getElementById('pe-power-dd'); var open=dd.style.display==='none'; dd.style.display=open?'block':'none'; if(open){setTimeout(function(){document.addEventListener('click',function h(e){if(!document.getElementById('pe-power-wrap').contains(e.target)){dd.style.display='none';document.removeEventListener('click',h,true);}},true);},0);} })()">Power Tools <span style="font-size:10px;">▾</span></button>
|
|
2525
|
+
<div id="pe-power-dd" style="display:none;position:absolute;top:100%;left:0;z-index:999;background:var(--bg-light);border:1px solid var(--border);border-radius:6px;padding:4px 0;min-width:130px;box-shadow:0 4px 12px rgba(0,0,0,0.3);">
|
|
2526
|
+
<button class="btn" onclick="PE.showView('chains');document.getElementById('pe-power-dd').style.display='none'" style="display:block;width:100%;text-align:left;border-radius:0;padding:6px 14px;border:none;">Chains</button>
|
|
2527
|
+
<button class="btn" onclick="PE.showView('templates');document.getElementById('pe-power-dd').style.display='none'" style="display:block;width:100%;text-align:left;border-radius:0;padding:6px 14px;border:none;">Templates</button>
|
|
2528
|
+
<button class="btn" onclick="PE.showView('patterns');document.getElementById('pe-power-dd').style.display='none'" style="display:block;width:100%;text-align:left;border-radius:0;padding:6px 14px;border:none;">Patterns</button>
|
|
2529
|
+
<button class="btn" onclick="PE.openHarvestModal();document.getElementById('pe-power-dd').style.display='none'" style="display:block;width:100%;text-align:left;border-radius:0;padding:6px 14px;border:none;">Harvest</button>
|
|
2530
|
+
<button class="btn" onclick="PE.toggleCopilotPanel();document.getElementById('pe-power-dd').style.display='none'" style="display:block;width:100%;text-align:left;border-radius:0;padding:6px 14px;border:none;">Copilot</button>
|
|
2531
|
+
</div>
|
|
2532
|
+
</div>
|
|
2501
2533
|
</div>
|
|
2502
2534
|
<div id="prompts-panel-inner">
|
|
2503
2535
|
<div id="prompt-sidebar">
|
|
@@ -2793,7 +2825,7 @@
|
|
|
2793
2825
|
</div>
|
|
2794
2826
|
<!-- Queue Builder Panel (right side) -->
|
|
2795
2827
|
<div id="queue-panel-resize" style="width:4px;cursor:col-resize;flex-shrink:0;background:var(--border);display:none;transition:background 0.15s;z-index:2;" onmousedown="startQueuePanelResize(event)" onmouseenter="this.style.background='var(--accent)'" onmouseleave="this.style.background=''"></div>
|
|
2796
|
-
<div id="queue-panel" style="display:
|
|
2828
|
+
<div id="queue-panel" style="display:none;width:320px;background:var(--bg-light);border-left:none;flex-direction:column;flex-shrink:0;overflow:hidden;">
|
|
2797
2829
|
<div style="padding:10px;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:8px;">
|
|
2798
2830
|
<span style="flex:1;font-weight:600;font-size:13px;">Queue Builder</span>
|
|
2799
2831
|
<button class="btn" style="padding:2px 8px;font-size:11px;" onclick="toggleQueuePanel()">Close</button>
|
|
@@ -3022,7 +3054,7 @@ const state = window._ctmState = {
|
|
|
3022
3054
|
sidebarManuallyHidden: false,
|
|
3023
3055
|
rulesFiles: [],
|
|
3024
3056
|
currentRule: null,
|
|
3025
|
-
queuePanelOpen:
|
|
3057
|
+
queuePanelOpen: false,
|
|
3026
3058
|
};
|
|
3027
3059
|
|
|
3028
3060
|
// --- WebSocket ---
|
|
@@ -3168,6 +3200,22 @@ async function pollServiceStatus() {
|
|
|
3168
3200
|
}
|
|
3169
3201
|
}
|
|
3170
3202
|
|
|
3203
|
+
async function restartAll() {
|
|
3204
|
+
if (!confirm('Restart CTM server and Wall-E?')) return;
|
|
3205
|
+
hideAppMenu();
|
|
3206
|
+
// Restart Wall-E first (while CTM is still up to handle the API call)
|
|
3207
|
+
try {
|
|
3208
|
+
await fetch(`/api/restart/walle?token=${state.token}`, { method: 'POST' });
|
|
3209
|
+
toast('Wall-E restarting...', { type: 'info' });
|
|
3210
|
+
} catch {}
|
|
3211
|
+
// Brief pause to let Wall-E restart begin, then restart CTM
|
|
3212
|
+
await new Promise(r => setTimeout(r, 500));
|
|
3213
|
+
state._restarting = true;
|
|
3214
|
+
try {
|
|
3215
|
+
await fetch(`/api/restart/ctm?token=${state.token}`, { method: 'POST' });
|
|
3216
|
+
} catch { /* expected — server dying */ }
|
|
3217
|
+
}
|
|
3218
|
+
|
|
3171
3219
|
async function svcAction(service, action) {
|
|
3172
3220
|
const label = service === 'ctm' ? 'CTM server' : 'Wall-E';
|
|
3173
3221
|
if (action === 'stop' && !confirm(`Stop ${label}?`)) return;
|
|
@@ -3262,39 +3310,26 @@ function createTerminal(id) {
|
|
|
3262
3310
|
term.onData((data) => {
|
|
3263
3311
|
send({ type: 'input', id, data });
|
|
3264
3312
|
clearWaitingState(id);
|
|
3265
|
-
// User typed something —
|
|
3313
|
+
// User typed something — scroll to bottom so they see the response
|
|
3266
3314
|
const s = state.sessions.get(id);
|
|
3267
|
-
if (s) s.
|
|
3315
|
+
if (s) s.term.scrollToBottom();
|
|
3268
3316
|
});
|
|
3269
3317
|
|
|
3270
3318
|
term.onResize(({ cols, rows }) => {
|
|
3271
3319
|
send({ type: 'resize', id, cols, rows });
|
|
3272
3320
|
});
|
|
3273
3321
|
|
|
3274
|
-
//
|
|
3275
|
-
//
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
});
|
|
3284
|
-
// Mouse wheel on the terminal should always work — reset stuck userScrolling
|
|
3285
|
-
// if user scrolls to the bottom manually
|
|
3286
|
-
viewport.addEventListener('wheel', () => {
|
|
3287
|
-
const s = state.sessions.get(id);
|
|
3288
|
-
if (!s || !s.userScrolling) return;
|
|
3289
|
-
// Check after the wheel event is processed
|
|
3290
|
-
requestAnimationFrame(() => {
|
|
3291
|
-
const atBottom = viewport.scrollTop + viewport.clientHeight >= viewport.scrollHeight - 10;
|
|
3292
|
-
if (atBottom) s.userScrolling = false;
|
|
3293
|
-
});
|
|
3294
|
-
});
|
|
3295
|
-
}
|
|
3322
|
+
// Auto-scroll is handled in onOutput using xterm.js buffer API (viewportY vs baseY).
|
|
3323
|
+
// No manual tracking needed — xterm.js buffer is the source of truth.
|
|
3324
|
+
//
|
|
3325
|
+
// Safety: clicking the terminal forces a fit() recalculation, fixing any scroll desync
|
|
3326
|
+
// that may occur after Claude Code's TUI exits or terminal state changes.
|
|
3327
|
+
container.addEventListener('click', () => {
|
|
3328
|
+
const s = state.sessions.get(id);
|
|
3329
|
+
if (s) s.fitAddon.fit();
|
|
3330
|
+
});
|
|
3296
3331
|
|
|
3297
|
-
state.sessions.set(id, { term, fitAddon, container,
|
|
3332
|
+
state.sessions.set(id, { term, fitAddon, container, needsFontRefresh: true });
|
|
3298
3333
|
return { term, fitAddon, container };
|
|
3299
3334
|
}
|
|
3300
3335
|
|
|
@@ -3434,7 +3469,6 @@ function updateTopbarContext(activeId) {
|
|
|
3434
3469
|
if (activeId === 'prompts') {
|
|
3435
3470
|
ctxBtns.innerHTML = `
|
|
3436
3471
|
<button class="topbar-util-btn" onclick="PE.showView('conversations')" title="Browse prompt conversations">Conversations</button>
|
|
3437
|
-
<button class="topbar-util-btn" onclick="PE.showView('backups')" title="Prompt backups" style="color:var(--green)">Backups</button>
|
|
3438
3472
|
`;
|
|
3439
3473
|
divider.style.display = '';
|
|
3440
3474
|
newSessionBtn.innerHTML = '+ New Prompt';
|
|
@@ -3449,6 +3483,21 @@ function updateTopbarContext(activeId) {
|
|
|
3449
3483
|
|
|
3450
3484
|
let _prevNav = null; // track previous nav section for Alt+Tab swap
|
|
3451
3485
|
|
|
3486
|
+
function toggleNavMore() {
|
|
3487
|
+
const dd = document.getElementById('nav-more-dropdown');
|
|
3488
|
+
dd.style.display = dd.style.display === 'none' ? 'block' : 'none';
|
|
3489
|
+
if (dd.style.display === 'block') {
|
|
3490
|
+
setTimeout(() => document.addEventListener('click', _closeNavMoreOutside, true), 0);
|
|
3491
|
+
}
|
|
3492
|
+
}
|
|
3493
|
+
function closeNavMore() {
|
|
3494
|
+
document.getElementById('nav-more-dropdown').style.display = 'none';
|
|
3495
|
+
document.removeEventListener('click', _closeNavMoreOutside, true);
|
|
3496
|
+
}
|
|
3497
|
+
function _closeNavMoreOutside(e) {
|
|
3498
|
+
if (!document.getElementById('nav-more-wrap').contains(e.target)) closeNavMore();
|
|
3499
|
+
}
|
|
3500
|
+
|
|
3452
3501
|
function navTo(target, opts) {
|
|
3453
3502
|
// Track previous nav for Alt+Tab toggle
|
|
3454
3503
|
const currentNav = document.querySelector('.nav-pill.active')?.dataset?.nav || 'sessions';
|
|
@@ -3617,11 +3666,30 @@ function _copyablePath(label, p) {
|
|
|
3617
3666
|
return row;
|
|
3618
3667
|
}
|
|
3619
3668
|
|
|
3669
|
+
var _backupSelected = { ctm: new Set(), brain: new Set() };
|
|
3670
|
+
|
|
3620
3671
|
function _appendBackupTable(container, title, backups, type, showRestore) {
|
|
3672
|
+
var selected = _backupSelected[type];
|
|
3673
|
+
// Header row with title and batch delete
|
|
3674
|
+
var header = document.createElement('div');
|
|
3675
|
+
header.style.cssText = 'display:flex;align-items:center;gap:8px;margin:20px 0 8px;';
|
|
3621
3676
|
var h3 = document.createElement('h3');
|
|
3622
|
-
h3.style.cssText = 'font-size:13px;color:var(--fg-dim);margin:
|
|
3677
|
+
h3.style.cssText = 'font-size:13px;color:var(--fg-dim);flex:1;margin:0;';
|
|
3623
3678
|
h3.textContent = title + ' (' + backups.length + ')';
|
|
3624
|
-
|
|
3679
|
+
header.appendChild(h3);
|
|
3680
|
+
if (selected.size > 0) {
|
|
3681
|
+
var countLabel = document.createElement('span');
|
|
3682
|
+
countLabel.style.cssText = 'font-size:11px;color:var(--fg-dim);';
|
|
3683
|
+
countLabel.textContent = selected.size + ' selected';
|
|
3684
|
+
header.appendChild(countLabel);
|
|
3685
|
+
var batchBtn = document.createElement('button');
|
|
3686
|
+
batchBtn.className = 'btn small';
|
|
3687
|
+
batchBtn.style.color = 'var(--red)';
|
|
3688
|
+
batchBtn.textContent = 'Delete selected';
|
|
3689
|
+
batchBtn.onclick = function() { _batchDeleteBackups(type); };
|
|
3690
|
+
header.appendChild(batchBtn);
|
|
3691
|
+
}
|
|
3692
|
+
container.appendChild(header);
|
|
3625
3693
|
if (backups.length === 0) {
|
|
3626
3694
|
var p = document.createElement('p'); p.style.cssText = 'font-size:12px;color:var(--fg-dim)'; p.textContent = 'No backups yet.';
|
|
3627
3695
|
container.appendChild(p); return;
|
|
@@ -3630,10 +3698,27 @@ function _appendBackupTable(container, title, backups, type, showRestore) {
|
|
|
3630
3698
|
table.style.cssText = 'width:100%;font-size:12px;border-collapse:collapse;';
|
|
3631
3699
|
var thead = document.createElement('tr');
|
|
3632
3700
|
thead.style.cssText = 'color:var(--fg-dim);text-align:left;';
|
|
3701
|
+
// Select-all checkbox
|
|
3702
|
+
var thCb = document.createElement('th'); thCb.style.cssText = 'padding:4px 8px;width:28px;';
|
|
3703
|
+
var selectAll = document.createElement('input'); selectAll.type = 'checkbox';
|
|
3704
|
+
selectAll.checked = backups.length > 0 && backups.slice(0, 20).every(function(b) { return selected.has(b.name); });
|
|
3705
|
+
selectAll.onchange = function() {
|
|
3706
|
+
backups.slice(0, 20).forEach(function(b) { if (selectAll.checked) selected.add(b.name); else selected.delete(b.name); });
|
|
3707
|
+
loadBackupsData();
|
|
3708
|
+
};
|
|
3709
|
+
thCb.appendChild(selectAll);
|
|
3710
|
+
thead.appendChild(thCb);
|
|
3633
3711
|
['Name','Size','Date',''].forEach(function(t) { var th = document.createElement('th'); th.style.padding = '4px 8px'; th.textContent = t; thead.appendChild(th); });
|
|
3634
3712
|
table.appendChild(thead);
|
|
3635
3713
|
backups.slice(0, 20).forEach(function(b) {
|
|
3636
|
-
var
|
|
3714
|
+
var isChecked = selected.has(b.name);
|
|
3715
|
+
var tr = document.createElement('tr');
|
|
3716
|
+
tr.style.cssText = 'border-top:1px solid var(--border);' + (isChecked ? 'background:rgba(88,166,255,0.06);' : '');
|
|
3717
|
+
// Checkbox
|
|
3718
|
+
var tdCb = document.createElement('td'); tdCb.style.cssText = 'padding:6px 8px;width:28px;';
|
|
3719
|
+
var cb = document.createElement('input'); cb.type = 'checkbox'; cb.checked = isChecked;
|
|
3720
|
+
cb.onchange = (function(name) { return function() { if (cb.checked) selected.add(name); else selected.delete(name); loadBackupsData(); }; })(b.name);
|
|
3721
|
+
tdCb.appendChild(cb);
|
|
3637
3722
|
var td1 = document.createElement('td'); td1.style.cssText = 'padding:6px 8px;color:var(--fg)'; td1.textContent = b.name + (b.hasImages ? ' + images' : '');
|
|
3638
3723
|
var td2 = document.createElement('td'); td2.style.cssText = 'padding:6px 8px;color:var(--fg-dim)'; td2.textContent = _fmtSize(b.size);
|
|
3639
3724
|
var td3 = document.createElement('td'); td3.style.cssText = 'padding:6px 8px;color:var(--fg-dim)'; td3.textContent = _fmtDate(b.createdAt);
|
|
@@ -3647,12 +3732,29 @@ function _appendBackupTable(container, title, backups, type, showRestore) {
|
|
|
3647
3732
|
var db = document.createElement('button'); db.className = 'btn small'; db.style.color = 'var(--red)'; db.textContent = 'Delete';
|
|
3648
3733
|
db.onclick = (function(n) { return function() { _deleteBackup(type, n); }; })(b.name);
|
|
3649
3734
|
td4.appendChild(db);
|
|
3650
|
-
tr.appendChild(td1); tr.appendChild(td2); tr.appendChild(td3); tr.appendChild(td4);
|
|
3735
|
+
tr.appendChild(tdCb); tr.appendChild(td1); tr.appendChild(td2); tr.appendChild(td3); tr.appendChild(td4);
|
|
3651
3736
|
table.appendChild(tr);
|
|
3652
3737
|
});
|
|
3653
3738
|
container.appendChild(table);
|
|
3654
3739
|
}
|
|
3655
3740
|
|
|
3741
|
+
function _batchDeleteBackups(type) {
|
|
3742
|
+
var selected = _backupSelected[type];
|
|
3743
|
+
if (selected.size === 0) return;
|
|
3744
|
+
if (!confirm('Delete ' + selected.size + ' ' + (type === 'ctm' ? 'CTM' : 'Brain') + ' backup(s)?')) return;
|
|
3745
|
+
var token = window._ctmState ? window._ctmState.token : '';
|
|
3746
|
+
var promises = [];
|
|
3747
|
+
selected.forEach(function(name) {
|
|
3748
|
+
var url = type === 'ctm' ? '/api/backups/' + encodeURIComponent(name) + '?token=' + token : '/api/wall-e/backups/' + encodeURIComponent(name) + '?token=' + token;
|
|
3749
|
+
promises.push(fetch(url, { method: 'DELETE' }));
|
|
3750
|
+
});
|
|
3751
|
+
Promise.all(promises).then(function() {
|
|
3752
|
+
selected.clear();
|
|
3753
|
+
if (typeof showToast === 'function') showToast('Deleted ' + promises.length + ' backup(s)');
|
|
3754
|
+
loadBackupsData();
|
|
3755
|
+
});
|
|
3756
|
+
}
|
|
3757
|
+
|
|
3656
3758
|
function _fmtSize(bytes) {
|
|
3657
3759
|
if (!bytes) return '0 B';
|
|
3658
3760
|
if (bytes < 1024) return bytes + ' B';
|
|
@@ -3726,19 +3828,28 @@ function onOutput(msg) {
|
|
|
3726
3828
|
// Only strip \e[3J (Erase Scrollback) — preserves scroll history.
|
|
3727
3829
|
// Do NOT strip \e[2J or \e[?1049h/l — needed for Claude Code's TUI.
|
|
3728
3830
|
const data = msg.data.replace(/\x1b\[3J/g, '');
|
|
3729
|
-
// Auto-scroll:
|
|
3730
|
-
//
|
|
3731
|
-
//
|
|
3732
|
-
|
|
3733
|
-
|
|
3831
|
+
// Auto-scroll using xterm.js buffer API: if viewport is at the bottom of the
|
|
3832
|
+
// scrollback, keep it there. If user has scrolled up, don't interrupt.
|
|
3833
|
+
// buffer.active.viewportY === buffer.active.baseY means "at bottom".
|
|
3834
|
+
const buf = s.term.buffer.active;
|
|
3835
|
+
const wasAtBottom = buf.viewportY >= buf.baseY;
|
|
3836
|
+
if (state.activeTab === msg.id && wasAtBottom) {
|
|
3734
3837
|
s.term.write(data, () => {
|
|
3735
3838
|
s.term.scrollToBottom();
|
|
3736
|
-
// Keep _programmaticScroll true briefly after scroll settles
|
|
3737
|
-
requestAnimationFrame(() => { s._programmaticScroll = false; });
|
|
3738
3839
|
});
|
|
3739
3840
|
} else {
|
|
3740
3841
|
s.term.write(data);
|
|
3741
3842
|
}
|
|
3843
|
+
// After output stops for 500ms, force xterm to recalculate viewport dimensions.
|
|
3844
|
+
// This fixes a desync between xterm's internal buffer and the DOM viewport that
|
|
3845
|
+
// can occur when Claude Code's TUI exits (Ink unmount, alt screen buffer exit).
|
|
3846
|
+
// Without this, the viewport may become unscrollable after output finishes.
|
|
3847
|
+
clearTimeout(s._outputIdleTimer);
|
|
3848
|
+
s._outputIdleTimer = setTimeout(() => {
|
|
3849
|
+
if (state.activeTab === msg.id) {
|
|
3850
|
+
s.fitAddon.fit();
|
|
3851
|
+
}
|
|
3852
|
+
}, 500);
|
|
3742
3853
|
// Remove compact banner on new activity
|
|
3743
3854
|
const banner = s.container.querySelector('.compact-banner');
|
|
3744
3855
|
if (banner) { banner.remove(); requestAnimationFrame(() => s.fitAddon.fit()); }
|
|
@@ -3760,7 +3871,7 @@ function onScrollback(msg) {
|
|
|
3760
3871
|
// \e[3J = Erase Scrollback, \e[2J = Erase Display, \e[?1049h/l = Alt Screen Buffer
|
|
3761
3872
|
// These are fine during live output but destroy content when replayed on refresh.
|
|
3762
3873
|
msg.data = msg.data.replace(/\x1b\[3J/g, '').replace(/\x1b\[2J/g, '').replace(/\x1b\[\?1049[hl]/g, '');
|
|
3763
|
-
|
|
3874
|
+
// Fresh attach — scroll to bottom after scrollback is loaded (via write callback below)
|
|
3764
3875
|
// Fit terminal to get correct local dimensions
|
|
3765
3876
|
s.fitAddon.fit();
|
|
3766
3877
|
const localCols = s.term.cols;
|
|
@@ -4442,6 +4553,7 @@ cwdInput.addEventListener('focus', () => {
|
|
|
4442
4553
|
|
|
4443
4554
|
// --- Recent Sessions ---
|
|
4444
4555
|
let allRecentSessions = [];
|
|
4556
|
+
let _convSearchPending = false;
|
|
4445
4557
|
let currentFilter = 'all';
|
|
4446
4558
|
let aiSearchMode = false;
|
|
4447
4559
|
let aiSearchDebounce = null;
|
|
@@ -4670,13 +4782,36 @@ function renderFilteredSessions() {
|
|
|
4670
4782
|
const q = document.getElementById('recent-search').value.toLowerCase();
|
|
4671
4783
|
let sessions = getFilteredSessions();
|
|
4672
4784
|
if (q && !aiSearchMode) {
|
|
4673
|
-
|
|
4674
|
-
|
|
4675
|
-
|
|
4676
|
-
s.
|
|
4677
|
-
|
|
4678
|
-
|
|
4679
|
-
|
|
4785
|
+
// First filter local metadata
|
|
4786
|
+
const metaMatches = new Set();
|
|
4787
|
+
sessions = sessions.filter(s => {
|
|
4788
|
+
const match = (s.firstMessage || '').toLowerCase().includes(q) ||
|
|
4789
|
+
(s.aiTitle || '').toLowerCase().includes(q) ||
|
|
4790
|
+
s.project.toLowerCase().includes(q) ||
|
|
4791
|
+
s.sessionId.toLowerCase().includes(q) ||
|
|
4792
|
+
(s.gitBranch || '').toLowerCase().includes(q);
|
|
4793
|
+
if (match) metaMatches.add(s.sessionId);
|
|
4794
|
+
return match;
|
|
4795
|
+
});
|
|
4796
|
+
// Also search imported conversations in DB (async, will re-render when results arrive)
|
|
4797
|
+
if (sessions.length === 0 && q.length >= 3 && !_convSearchPending) {
|
|
4798
|
+
_convSearchPending = true;
|
|
4799
|
+
fetch('/api/conversations?search=' + encodeURIComponent(q) + '&limit=20&all_devices=1&token=' + (state.token || ''))
|
|
4800
|
+
.then(r => r.json())
|
|
4801
|
+
.then(data => {
|
|
4802
|
+
_convSearchPending = false;
|
|
4803
|
+
const convResults = (data.backups ? [] : (Array.isArray(data) ? data : []));
|
|
4804
|
+
if (convResults.length === 0) return;
|
|
4805
|
+
// Merge conversation matches into session list
|
|
4806
|
+
for (const c of convResults) {
|
|
4807
|
+
if (metaMatches.has(c.session_id)) continue;
|
|
4808
|
+
const existing = allRecentSessions.find(s => s.sessionId === c.session_id);
|
|
4809
|
+
if (existing && !sessions.includes(existing)) sessions.push(existing);
|
|
4810
|
+
}
|
|
4811
|
+
if (sessions.length > 0) renderRecentSessions(sessions);
|
|
4812
|
+
})
|
|
4813
|
+
.catch(() => { _convSearchPending = false; });
|
|
4814
|
+
}
|
|
4680
4815
|
}
|
|
4681
4816
|
|
|
4682
4817
|
// Sort: pinned first (in user-defined order), then by modifiedAt
|
|
@@ -7348,6 +7483,27 @@ function handleHashRoute() {
|
|
|
7348
7483
|
return;
|
|
7349
7484
|
}
|
|
7350
7485
|
|
|
7486
|
+
// #walle/task/:id — open WALL-E tasks and scroll to specific task
|
|
7487
|
+
if (firstPart.startsWith('walle/task/')) {
|
|
7488
|
+
const taskId = firstPart.slice('walle/task/'.length);
|
|
7489
|
+
navTo('walle', { skipHash: true });
|
|
7490
|
+
setTimeout(() => {
|
|
7491
|
+
if (typeof WE !== 'undefined') {
|
|
7492
|
+
WE.showView('tasks');
|
|
7493
|
+
// Wait for tasks to render, then scroll to the task and expand output
|
|
7494
|
+
setTimeout(() => {
|
|
7495
|
+
const el = document.getElementById('task-' + taskId);
|
|
7496
|
+
if (el) {
|
|
7497
|
+
el.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
7498
|
+
const output = el.querySelector('.we-task-output-panel');
|
|
7499
|
+
if (output && !output.classList.contains('expanded')) output.classList.add('expanded');
|
|
7500
|
+
}
|
|
7501
|
+
}, 500);
|
|
7502
|
+
}
|
|
7503
|
+
}, 200);
|
|
7504
|
+
return;
|
|
7505
|
+
}
|
|
7506
|
+
|
|
7351
7507
|
// #walle — open WALL-E panel
|
|
7352
7508
|
if (isNav && firstPart === 'walle') {
|
|
7353
7509
|
navTo('walle', { skipHash: true });
|
|
@@ -7928,6 +8084,15 @@ function removeQpItem(idx) {
|
|
|
7928
8084
|
renderQpItems();
|
|
7929
8085
|
}
|
|
7930
8086
|
|
|
8087
|
+
function resendQpItem(idx) {
|
|
8088
|
+
if (idx >= 0 && idx < qpItems.length) {
|
|
8089
|
+
qpItems[idx].status = 'pending';
|
|
8090
|
+
saveQpDraft();
|
|
8091
|
+
renderQpItems();
|
|
8092
|
+
toast('Item reset to pending — will be sent next', { type: 'info' });
|
|
8093
|
+
}
|
|
8094
|
+
}
|
|
8095
|
+
|
|
7931
8096
|
function toggleQpMode() {
|
|
7932
8097
|
qpMode = qpMode === 'auto' ? 'manual' : 'auto';
|
|
7933
8098
|
updateQpModeBtn();
|
|
@@ -8251,6 +8416,7 @@ function renderQpItems() {
|
|
|
8251
8416
|
</div>
|
|
8252
8417
|
<div style="display:flex;align-items:center;gap:2px;padding-top:2px;flex-shrink:0;">
|
|
8253
8418
|
<span style="font-size:9px;color:var(--fg-dim);background:var(--bg-lighter);padding:1px 5px;border-radius:8px;">${item.type === 'prompt' ? '#' + item.promptId : 'inline'}</span>
|
|
8419
|
+
${isSent && !isEditing ? `<button onclick="resendQpItem(${idx})" style="background:none;border:none;color:var(--fg-dim);cursor:pointer;font-size:10px;padding:0 2px;line-height:1;" onmouseover="this.style.color='var(--accent)'" onmouseout="this.style.color='var(--fg-dim)'" title="Re-send this item">↻</button>` : ''}
|
|
8254
8420
|
${!isEditing && item.type === 'inline' ? `<button onclick="saveQpItemAsPrompt(${idx})" style="background:none;border:none;color:var(--fg-dim);cursor:pointer;font-size:10px;padding:0 2px;line-height:1;" onmouseover="this.style.color='var(--green)'" onmouseout="this.style.color='var(--fg-dim)'" title="Save as prompt">💾</button>` : ''}
|
|
8255
8421
|
${!isEditing ? `<button onclick="startQpEdit(${idx})" style="background:none;border:none;color:var(--fg-dim);cursor:pointer;font-size:11px;padding:0 2px;line-height:1;" onmouseover="this.style.color='var(--accent)'" onmouseout="this.style.color='var(--fg-dim)'" title="Edit">✎</button>` : ''}
|
|
8256
8422
|
<button onclick="removeQpItem(${idx})" style="background:none;border:none;color:var(--fg-dim);cursor:pointer;font-size:14px;padding:0 2px;line-height:1;" onmouseover="this.style.color='var(--red)'" onmouseout="this.style.color='var(--fg-dim)'">×</button>
|