@co0ontty/wand 1.1.2 → 1.1.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.
@@ -434,11 +434,13 @@ export class ClaudePtyBridge extends EventEmitter {
434
434
  return (/\bdo you want to\b/i.test(normalized) ||
435
435
  /\bgrant\b.*\bpermission\b/i.test(normalized) ||
436
436
  /\bhaven't granted\b/i.test(normalized) ||
437
- /\benter to confirm\b/i.test(normalized));
437
+ /\benter to confirm\b/i.test(normalized) ||
438
+ /\bwould you like to proceed\b/i.test(normalized) ||
439
+ /❯/.test(normalized));
438
440
  }
439
441
  extractPromptText(normalized) {
440
442
  // Return a snippet around the permission prompt
441
- const match = normalized.match(/.{0,100}(?:do you want to|permission|grant|enter to confirm).{0,100}/i);
443
+ const match = normalized.match(/.{0,100}(?:do you want to|permission|grant|enter to confirm|would you like to proceed|❯).{0,100}/i);
442
444
  return match?.[0] ?? normalized.slice(-100);
443
445
  }
444
446
  extractPermissionTarget(normalized) {
package/dist/cli.js CHANGED
@@ -6,7 +6,7 @@ import { ensureDatabaseFile, resolveDatabasePath } from "./storage.js";
6
6
  async function main() {
7
7
  const args = process.argv.slice(2);
8
8
  const command = args[0] || "help";
9
- const configPath = resolveConfigPath(readFlagValue(args, "--config"));
9
+ const configPath = resolveConfigPath(readFlagValue(args, "-c") || readFlagValue(args, "--config"));
10
10
  switch (command) {
11
11
  case "init": {
12
12
  await ensureRequiredFiles(configPath);
@@ -63,7 +63,7 @@ Commands:
63
63
  wand config:set Update a simple config value
64
64
 
65
65
  Options:
66
- --config <path> Use a custom config file path
66
+ -c, --config <path> Use a custom config file (default: ~/.wand/config.json)
67
67
  `);
68
68
  }
69
69
  async function ensureRequiredFiles(configPath) {
@@ -52,6 +52,10 @@ export declare class ProcessManager extends EventEmitter {
52
52
  private claudeHistoryCache;
53
53
  private static readonly HISTORY_CACHE_TTL_MS;
54
54
  listClaudeHistorySessions(): ClaudeHistorySession[];
55
+ deleteClaudeHistoryFiles(sessions: {
56
+ claudeSessionId: string;
57
+ cwd: string;
58
+ }[]): number;
55
59
  get(id: string): SessionSnapshot | null;
56
60
  sendInput(id: string, input: string, view?: "chat" | "terminal", shortcutKey?: string): SessionSnapshot;
57
61
  /** Emit a task event for a session, debounced to avoid flooding */
@@ -40,7 +40,8 @@ const PROMPT_PATTERNS = [
40
40
  /\bwould you like to\b/i,
41
41
  /\bshall i\b/i,
42
42
  /\bcan i\b/i,
43
- /\bgrant\b.*\bpermission\b/i
43
+ /\bgrant\b.*\bpermission\b/i,
44
+ /❯/
44
45
  ];
45
46
  const REAL_CONVERSATION_MIN_LINES = 2;
46
47
  const REAL_CONVERSATION_MIN_MESSAGES = 2;
@@ -1135,6 +1136,27 @@ export class ProcessManager extends EventEmitter {
1135
1136
  this.claudeHistoryCache = { data: allSessions, expiresAt: now + ProcessManager.HISTORY_CACHE_TTL_MS };
1136
1137
  return allSessions;
1137
1138
  }
1139
+ deleteClaudeHistoryFiles(sessions) {
1140
+ let deleted = 0;
1141
+ for (const { claudeSessionId, cwd } of sessions) {
1142
+ if (!UUID_V4_PATTERN.test(claudeSessionId))
1143
+ continue;
1144
+ const jsonlPath = path.join(getClaudeProjectDir(cwd), `${claudeSessionId}.jsonl`);
1145
+ try {
1146
+ if (existsSync(jsonlPath)) {
1147
+ unlinkSync(jsonlPath);
1148
+ deleted++;
1149
+ }
1150
+ }
1151
+ catch {
1152
+ // Best-effort — Claude cache cleanup is non-critical
1153
+ }
1154
+ }
1155
+ if (deleted > 0) {
1156
+ this.claudeHistoryCache = null;
1157
+ }
1158
+ return deleted;
1159
+ }
1138
1160
  get(id) {
1139
1161
  this.archiveExpiredSessions();
1140
1162
  const record = this.sessions.get(id);
package/dist/server.js CHANGED
@@ -255,6 +255,16 @@ function getHiddenClaudeSessionIds(storage) {
255
255
  function saveHiddenClaudeSessionIds(storage, hidden) {
256
256
  storage.setConfigValue(HIDDEN_CLAUDE_SESSIONS_KEY, JSON.stringify(Array.from(hidden)));
257
257
  }
258
+ function removeFromHiddenClaudeSessionIds(storage, idsToRemove) {
259
+ const hidden = getHiddenClaudeSessionIds(storage);
260
+ let changed = false;
261
+ for (const id of idsToRemove) {
262
+ if (hidden.delete(id))
263
+ changed = true;
264
+ }
265
+ if (changed)
266
+ saveHiddenClaudeSessionIds(storage, hidden);
267
+ }
258
268
  const MAX_RECENT_PATHS = 10;
259
269
  // ── File language detection ──
260
270
  function getLanguageFromExt(ext, filePath) {
@@ -508,10 +518,18 @@ export async function startServer(config, configPath) {
508
518
  res.status(400).json({ error: "会话 ID 不能为空。" });
509
519
  return;
510
520
  }
511
- const hidden = getHiddenClaudeSessionIds(storage);
512
- if (!hidden.has(claudeSessionId)) {
513
- hidden.add(claudeSessionId);
514
- saveHiddenClaudeSessionIds(storage, hidden);
521
+ const session = processes.listClaudeHistorySessions()
522
+ .find((s) => s.claudeSessionId === claudeSessionId);
523
+ if (session) {
524
+ processes.deleteClaudeHistoryFiles([{ claudeSessionId, cwd: session.cwd }]);
525
+ removeFromHiddenClaudeSessionIds(storage, [claudeSessionId]);
526
+ }
527
+ else {
528
+ const hidden = getHiddenClaudeSessionIds(storage);
529
+ if (!hidden.has(claudeSessionId)) {
530
+ hidden.add(claudeSessionId);
531
+ saveHiddenClaudeSessionIds(storage, hidden);
532
+ }
515
533
  }
516
534
  res.json({ ok: true });
517
535
  });
@@ -523,22 +541,15 @@ export async function startServer(config, configPath) {
523
541
  }
524
542
  try {
525
543
  const sessions = processes.listClaudeHistorySessions();
526
- const hidden = getHiddenClaudeSessionIds(storage);
527
- let added = 0;
544
+ const toDelete = [];
528
545
  for (const session of sessions) {
529
- if (!session.claudeSessionId || session.cwd !== cwd) {
530
- continue;
531
- }
532
- if (hidden.has(session.claudeSessionId)) {
533
- continue;
546
+ if (session.claudeSessionId && session.cwd === cwd) {
547
+ toDelete.push({ claudeSessionId: session.claudeSessionId, cwd: session.cwd });
534
548
  }
535
- hidden.add(session.claudeSessionId);
536
- added += 1;
537
549
  }
538
- if (added > 0) {
539
- saveHiddenClaudeSessionIds(storage, hidden);
540
- }
541
- res.json({ ok: true, deleted: added });
550
+ const deleted = processes.deleteClaudeHistoryFiles(toDelete);
551
+ removeFromHiddenClaudeSessionIds(storage, toDelete.map((s) => s.claudeSessionId));
552
+ res.json({ ok: true, deleted });
542
553
  }
543
554
  catch (error) {
544
555
  res.status(500).json({ error: getErrorMessage(error, "无法删除该目录下的历史会话。") });
@@ -553,19 +564,38 @@ export async function startServer(config, configPath) {
553
564
  return;
554
565
  }
555
566
  try {
556
- const hidden = getHiddenClaudeSessionIds(storage);
557
- let added = 0;
558
- for (const claudeSessionId of claudeSessionIds) {
559
- if (hidden.has(claudeSessionId)) {
560
- continue;
567
+ const allSessions = processes.listClaudeHistorySessions();
568
+ const sessionMap = new Map();
569
+ for (const s of allSessions) {
570
+ if (s.claudeSessionId)
571
+ sessionMap.set(s.claudeSessionId, s.cwd);
572
+ }
573
+ const toDelete = [];
574
+ const toHide = [];
575
+ for (const id of claudeSessionIds) {
576
+ const cwd = sessionMap.get(id);
577
+ if (cwd) {
578
+ toDelete.push({ claudeSessionId: id, cwd });
579
+ }
580
+ else {
581
+ toHide.push(id);
561
582
  }
562
- hidden.add(claudeSessionId);
563
- added += 1;
564
583
  }
565
- if (added > 0) {
566
- saveHiddenClaudeSessionIds(storage, hidden);
584
+ const deleted = processes.deleteClaudeHistoryFiles(toDelete);
585
+ removeFromHiddenClaudeSessionIds(storage, toDelete.map((s) => s.claudeSessionId));
586
+ if (toHide.length > 0) {
587
+ const hidden = getHiddenClaudeSessionIds(storage);
588
+ let added = 0;
589
+ for (const id of toHide) {
590
+ if (!hidden.has(id)) {
591
+ hidden.add(id);
592
+ added++;
593
+ }
594
+ }
595
+ if (added > 0)
596
+ saveHiddenClaudeSessionIds(storage, hidden);
567
597
  }
568
- res.json({ ok: true, deleted: added });
598
+ res.json({ ok: true, deleted: deleted + toHide.length });
569
599
  }
570
600
  catch (error) {
571
601
  res.status(500).json({ error: getErrorMessage(error, "无法批量删除历史会话。") });
@@ -494,9 +494,14 @@
494
494
  '<button class="blank-chat-tool-btn" id="welcome-tool-claude" type="button">' +
495
495
  '<span class="tool-icon">🤖</span>新建终端会话' +
496
496
  '</button>' +
497
- '<button class="blank-chat-tool-btn" id="welcome-tool-folder" type="button" title="选择工作目录">' +
498
- '<span class="tool-icon">📎</span>目录' +
499
- '</button>' +
497
+ '</div>' +
498
+ '<div class="blank-chat-cwd-wrap">' +
499
+ '<div class="blank-chat-cwd" id="blank-chat-cwd" role="button" tabindex="0" title="点击切换工作目录">' +
500
+ '<span class="blank-chat-cwd-icon">📁</span>' +
501
+ '<span class="blank-chat-cwd-path" id="blank-chat-cwd-path">' + escapeHtml(state.workingDir || (state.config && state.config.defaultCwd ? state.config.defaultCwd : "/tmp")) + '</span>' +
502
+ '<span class="blank-chat-cwd-arrow" id="blank-chat-cwd-arrow">▼</span>' +
503
+ '</div>' +
504
+ '<div class="blank-chat-cwd-dropdown hidden" id="blank-chat-cwd-dropdown"></div>' +
500
505
  '</div>' +
501
506
  '</div>' +
502
507
  '</div>' +
@@ -1648,10 +1653,10 @@
1648
1653
  '<div class="field">' +
1649
1654
  '<label class="field-label" for="cwd">工作目录</label>' +
1650
1655
  '<div class="suggestions-wrap">' +
1651
- '<input id="cwd" type="text" class="field-input" autocomplete="off" placeholder="留空则使用默认目录" />' +
1656
+ '<input id="cwd" type="text" class="field-input" autocomplete="off" placeholder="' + escapeHtml(state.workingDir || (state.config && state.config.defaultCwd ? state.config.defaultCwd : "/tmp")) + '" />' +
1652
1657
  '<div id="cwd-suggestions" class="suggestions hidden"></div>' +
1653
1658
  '</div>' +
1654
- '<p class="field-hint">会话将在此目录启动,支持路径自动补全。</p>' +
1659
+ '<p class="field-hint">留空则使用上方目录,支持路径自动补全。</p>' +
1655
1660
  '<div id="recent-paths-bubbles" class="recent-paths-bubbles"></div>' +
1656
1661
  '</div>' +
1657
1662
  '<button id="run-button" class="btn btn-primary btn-block">启动会话</button>' +
@@ -1810,10 +1815,7 @@
1810
1815
  quickStartSession();
1811
1816
  });
1812
1817
  }
1813
- var welcomeFolderBtn = document.getElementById("welcome-tool-folder");
1814
- if (welcomeFolderBtn) {
1815
- welcomeFolderBtn.addEventListener("click", openFolderPickerWithInitialPath);
1816
- }
1818
+ initBlankChatCwd();
1817
1819
 
1818
1820
  var sessionsList = document.getElementById("sessions-list");
1819
1821
  if (sessionsList) {
@@ -2403,11 +2405,7 @@
2403
2405
  renderBreadcrumb(initialPath);
2404
2406
  }
2405
2407
 
2406
- // Welcome screen folder button
2407
- var welcomeFolderBtn = document.getElementById("welcome-tool-folder");
2408
- if (welcomeFolderBtn) {
2409
- welcomeFolderBtn.addEventListener("click", openFolderPickerWithInitialPath);
2410
- }
2408
+ // Welcome screen folder button (legacy, now handled by initBlankChatCwd)
2411
2409
 
2412
2410
  if (closeFolderPicker && folderPickerModal) {
2413
2411
  closeFolderPicker.addEventListener("click", function() {
@@ -2504,7 +2502,12 @@
2504
2502
  }
2505
2503
  if (_swipeState) return;
2506
2504
  if (item.dataset.sessionId) {
2507
- selectSession(item.dataset.sessionId);
2505
+ var clickedSession = state.sessions.find(function(s) { return s.id === item.dataset.sessionId; });
2506
+ if (clickedSession && clickedSession.status !== "running") {
2507
+ resumeSessionFromList(item.dataset.sessionId);
2508
+ } else {
2509
+ selectSession(item.dataset.sessionId);
2510
+ }
2508
2511
  closeSessionsDrawer();
2509
2512
  }
2510
2513
  }
@@ -2524,7 +2527,12 @@
2524
2527
  return;
2525
2528
  }
2526
2529
  if (item.dataset.sessionId) {
2527
- selectSession(item.dataset.sessionId);
2530
+ var keySession = state.sessions.find(function(s) { return s.id === item.dataset.sessionId; });
2531
+ if (keySession && keySession.status !== "running") {
2532
+ resumeSessionFromList(item.dataset.sessionId);
2533
+ } else {
2534
+ selectSession(item.dataset.sessionId);
2535
+ }
2528
2536
  closeSessionsDrawer();
2529
2537
  }
2530
2538
  }
@@ -3951,6 +3959,93 @@
3951
3959
  });
3952
3960
  }
3953
3961
 
3962
+ // Blank-chat CWD inline display + dropdown
3963
+ function initBlankChatCwd() {
3964
+ var cwdEl = document.getElementById("blank-chat-cwd");
3965
+ if (!cwdEl) return;
3966
+ cwdEl.addEventListener("click", toggleBlankChatCwdDropdown);
3967
+ cwdEl.addEventListener("keydown", function(e) {
3968
+ if (e.key === "Enter" || e.key === " ") {
3969
+ e.preventDefault();
3970
+ toggleBlankChatCwdDropdown();
3971
+ }
3972
+ });
3973
+ document.addEventListener("click", function(e) {
3974
+ var dropdown = document.getElementById("blank-chat-cwd-dropdown");
3975
+ if (!dropdown || dropdown.classList.contains("hidden")) return;
3976
+ if (!e.target.closest(".blank-chat-cwd-wrap")) {
3977
+ dropdown.classList.add("hidden");
3978
+ var arrow = document.getElementById("blank-chat-cwd-arrow");
3979
+ if (arrow) arrow.textContent = "▼";
3980
+ }
3981
+ });
3982
+ }
3983
+
3984
+ function toggleBlankChatCwdDropdown() {
3985
+ var dropdown = document.getElementById("blank-chat-cwd-dropdown");
3986
+ var arrow = document.getElementById("blank-chat-cwd-arrow");
3987
+ if (!dropdown) return;
3988
+ var isHidden = dropdown.classList.contains("hidden");
3989
+ if (isHidden) {
3990
+ loadBlankChatCwdDropdown(dropdown);
3991
+ dropdown.classList.remove("hidden");
3992
+ if (arrow) arrow.textContent = "▲";
3993
+ } else {
3994
+ dropdown.classList.add("hidden");
3995
+ if (arrow) arrow.textContent = "▼";
3996
+ }
3997
+ }
3998
+
3999
+ function loadBlankChatCwdDropdown(dropdown) {
4000
+ var defaultCwd = state.config && state.config.defaultCwd ? state.config.defaultCwd : "/tmp";
4001
+ dropdown.innerHTML = '<div class="blank-chat-cwd-loading">加载中...</div>';
4002
+ fetch("/api/recent-paths", { credentials: "same-origin" })
4003
+ .then(function(res) { return res.json(); })
4004
+ .then(function(items) {
4005
+ var html = "";
4006
+ // Default directory always first
4007
+ var currentDir = state.workingDir || defaultCwd;
4008
+ html += '<div class="blank-chat-cwd-item' + (currentDir === defaultCwd ? " active" : "") + '" data-path="' + escapeHtml(defaultCwd) + '">' +
4009
+ '<span class="blank-chat-cwd-item-label">默认</span>' +
4010
+ '<span class="blank-chat-cwd-item-path">' + escapeHtml(defaultCwd) + '</span>' +
4011
+ '</div>';
4012
+ // Recent paths (exclude default to avoid duplicate)
4013
+ if (items && items.length) {
4014
+ var seen = {};
4015
+ seen[defaultCwd] = true;
4016
+ items.forEach(function(item) {
4017
+ if (seen[item.path]) return;
4018
+ seen[item.path] = true;
4019
+ html += '<div class="blank-chat-cwd-item' + (currentDir === item.path ? " active" : "") + '" data-path="' + escapeHtml(item.path) + '">' +
4020
+ '<span class="blank-chat-cwd-item-path">' + escapeHtml(item.path) + '</span>' +
4021
+ '</div>';
4022
+ });
4023
+ }
4024
+ dropdown.innerHTML = html;
4025
+ dropdown.querySelectorAll(".blank-chat-cwd-item").forEach(function(el) {
4026
+ el.addEventListener("click", function(e) {
4027
+ e.stopPropagation();
4028
+ var path = el.dataset.path;
4029
+ state.workingDir = path;
4030
+ try { localStorage.setItem("wand-working-dir", path); } catch(e) {}
4031
+ var pathEl = document.getElementById("blank-chat-cwd-path");
4032
+ if (pathEl) pathEl.textContent = path;
4033
+ dropdown.classList.add("hidden");
4034
+ var arrow = document.getElementById("blank-chat-cwd-arrow");
4035
+ if (arrow) arrow.textContent = "▼";
4036
+ // Update folder picker input if exists
4037
+ var fpInput = document.getElementById("folder-picker-input");
4038
+ if (fpInput) fpInput.value = path;
4039
+ });
4040
+ });
4041
+ })
4042
+ .catch(function() {
4043
+ dropdown.innerHTML = '<div class="blank-chat-cwd-item" data-path="' + escapeHtml(defaultCwd) + '">' +
4044
+ '<span class="blank-chat-cwd-item-path">' + escapeHtml(defaultCwd) + '</span>' +
4045
+ '</div>';
4046
+ });
4047
+ }
4048
+
3954
4049
  function loadRecentPathBubbles() {
3955
4050
  var container = document.getElementById("recent-paths-bubbles");
3956
4051
  if (!container) return;
@@ -5670,6 +5670,103 @@
5670
5670
  .blank-chat-tool-btn .tool-icon {
5671
5671
  font-size: 1.125rem;
5672
5672
  }
5673
+ /* Blank-chat CWD inline selector */
5674
+ .blank-chat-cwd-wrap {
5675
+ position: relative;
5676
+ display: flex;
5677
+ flex-direction: column;
5678
+ align-items: center;
5679
+ margin-top: 8px;
5680
+ }
5681
+ .blank-chat-cwd {
5682
+ display: inline-flex;
5683
+ align-items: center;
5684
+ gap: 4px;
5685
+ cursor: pointer;
5686
+ font-size: 0.75rem;
5687
+ color: var(--text-muted);
5688
+ padding: 4px 8px;
5689
+ border-radius: var(--radius-sm);
5690
+ transition: color var(--transition-fast), background var(--transition-fast);
5691
+ user-select: none;
5692
+ }
5693
+ .blank-chat-cwd:hover {
5694
+ color: var(--accent);
5695
+ background: var(--accent-muted);
5696
+ }
5697
+ .blank-chat-cwd-icon {
5698
+ font-size: 0.8125rem;
5699
+ }
5700
+ .blank-chat-cwd-path {
5701
+ font-family: var(--font-mono);
5702
+ max-width: 280px;
5703
+ overflow: hidden;
5704
+ text-overflow: ellipsis;
5705
+ white-space: nowrap;
5706
+ }
5707
+ .blank-chat-cwd-arrow {
5708
+ font-size: 0.625rem;
5709
+ opacity: 0.6;
5710
+ transition: transform var(--transition-fast);
5711
+ }
5712
+ .blank-chat-cwd-dropdown {
5713
+ position: absolute;
5714
+ top: 100%;
5715
+ left: 50%;
5716
+ transform: translateX(-50%);
5717
+ min-width: 260px;
5718
+ max-width: 400px;
5719
+ max-height: 240px;
5720
+ overflow-y: auto;
5721
+ background: var(--bg-primary);
5722
+ border: 1px solid var(--border-default);
5723
+ border-radius: var(--radius-md);
5724
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
5725
+ z-index: 50;
5726
+ margin-top: 4px;
5727
+ padding: 4px;
5728
+ }
5729
+ .blank-chat-cwd-item {
5730
+ display: flex;
5731
+ align-items: center;
5732
+ gap: 6px;
5733
+ padding: 6px 10px;
5734
+ border-radius: var(--radius-sm);
5735
+ cursor: pointer;
5736
+ font-size: 0.75rem;
5737
+ color: var(--text-secondary);
5738
+ transition: background var(--transition-fast);
5739
+ }
5740
+ .blank-chat-cwd-item:hover {
5741
+ background: var(--accent-muted);
5742
+ color: var(--accent);
5743
+ }
5744
+ .blank-chat-cwd-item.active {
5745
+ background: var(--accent-muted);
5746
+ color: var(--accent);
5747
+ font-weight: 500;
5748
+ }
5749
+ .blank-chat-cwd-item-label {
5750
+ font-size: 0.625rem;
5751
+ background: var(--accent-muted);
5752
+ color: var(--accent);
5753
+ padding: 1px 5px;
5754
+ border-radius: 3px;
5755
+ font-weight: 500;
5756
+ white-space: nowrap;
5757
+ }
5758
+ .blank-chat-cwd-item-path {
5759
+ font-family: var(--font-mono);
5760
+ overflow: hidden;
5761
+ text-overflow: ellipsis;
5762
+ white-space: nowrap;
5763
+ }
5764
+ .blank-chat-cwd-loading {
5765
+ padding: 8px 10px;
5766
+ font-size: 0.75rem;
5767
+ color: var(--text-muted);
5768
+ text-align: center;
5769
+ }
5673
5770
  .blank-chat-hint {
5674
5771
  font-size: 0.8125rem;
5675
5772
  color: var(--text-muted);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@co0ontty/wand",
3
- "version": "1.1.2",
3
+ "version": "1.1.3",
4
4
  "description": "A web terminal for local CLI tools like Claude.",
5
5
  "type": "module",
6
6
  "bin": {