claude-remote-cli 2.10.2 → 2.12.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/dist/frontend/assets/index-CEJznk5F.js +47 -0
- package/dist/frontend/assets/{index-BVQ78t7Z.css → index-nIPDa7NP.css} +1 -1
- package/dist/frontend/index.html +2 -2
- package/dist/server/config.js +3 -0
- package/dist/server/git.js +30 -0
- package/dist/server/index.js +77 -18
- package/dist/server/sessions.js +51 -6
- package/dist/test/config.test.js +11 -0
- package/dist/test/git.test.js +67 -0
- package/dist/test/sessions.test.js +71 -0
- package/package.json +1 -1
- package/dist/frontend/assets/index-CyCuDCwK.js +0 -47
|
@@ -29,4 +29,4 @@
|
|
|
29
29
|
* The original design remains. The terminal itself
|
|
30
30
|
* has been extended to include xterm CSI codes, among
|
|
31
31
|
* other features.
|
|
32
|
-
*/.xterm{cursor:text;position:relative;user-select:none;-ms-user-select:none;-webkit-user-select:none}.xterm.focus,.xterm:focus{outline:none}.xterm .xterm-helpers{position:absolute;top:0;z-index:5}.xterm .xterm-helper-textarea{padding:0;border:0;margin:0;position:absolute;opacity:0;left:-9999em;top:0;width:0;height:0;z-index:-5;white-space:nowrap;overflow:hidden;resize:none}.xterm .composition-view{background:#000;color:#fff;display:none;position:absolute;white-space:nowrap;z-index:1}.xterm .composition-view.active{display:block}.xterm .xterm-viewport{background-color:#000;overflow-y:scroll;cursor:default;position:absolute;right:0;left:0;top:0;bottom:0}.xterm .xterm-screen{position:relative}.xterm .xterm-screen canvas{position:absolute;left:0;top:0}.xterm-char-measure-element{display:inline-block;visibility:hidden;position:absolute;top:0;left:-9999em;line-height:normal}.xterm.enable-mouse-events{cursor:default}.xterm.xterm-cursor-pointer,.xterm .xterm-cursor-pointer{cursor:pointer}.xterm.column-select.focus{cursor:crosshair}.xterm .xterm-accessibility:not(.debug),.xterm .xterm-message{position:absolute;left:0;top:0;bottom:0;right:0;z-index:10;color:transparent;pointer-events:none}.xterm .xterm-accessibility-tree:not(.debug) *::selection{color:transparent}.xterm .xterm-accessibility-tree{font-family:monospace;-webkit-user-select:text;user-select:text;white-space:pre}.xterm .xterm-accessibility-tree>div{transform-origin:left;width:fit-content}.xterm .live-region{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}.xterm-dim{opacity:1!important}.xterm-underline-1{text-decoration:underline}.xterm-underline-2{text-decoration:double underline}.xterm-underline-3{text-decoration:wavy underline}.xterm-underline-4{text-decoration:dotted underline}.xterm-underline-5{text-decoration:dashed underline}.xterm-overline{text-decoration:overline}.xterm-overline.xterm-underline-1{text-decoration:overline underline}.xterm-overline.xterm-underline-2{text-decoration:overline double underline}.xterm-overline.xterm-underline-3{text-decoration:overline wavy underline}.xterm-overline.xterm-underline-4{text-decoration:overline dotted underline}.xterm-overline.xterm-underline-5{text-decoration:overline dashed underline}.xterm-strikethrough{text-decoration:line-through}.xterm-screen .xterm-decoration-container .xterm-decoration{z-index:6;position:absolute}.xterm-screen .xterm-decoration-container .xterm-decoration.xterm-decoration-top-layer{z-index:7}.xterm-decoration-overview-ruler{z-index:8;position:absolute;top:0;right:0;pointer-events:none}.xterm-decoration-top{z-index:2;position:relative}.xterm .xterm-scrollable-element>.scrollbar{cursor:default}.xterm .xterm-scrollable-element>.scrollbar>.scra{cursor:pointer;font-size:11px!important}.xterm .xterm-scrollable-element>.visible{opacity:1;background:#0000;transition:opacity .1s linear;z-index:11}.xterm .xterm-scrollable-element>.invisible{opacity:0;pointer-events:none}.xterm .xterm-scrollable-element>.invisible.fade{transition:opacity .8s linear}.xterm .xterm-scrollable-element>.shadow{position:absolute;display:none}.xterm .xterm-scrollable-element>.shadow.top{display:block;top:0;left:3px;height:3px;width:100%;box-shadow:var(--vscode-scrollbar-shadow, #000) 0 6px 6px -6px inset}.xterm .xterm-scrollable-element>.shadow.left{display:block;top:3px;left:0;height:100%;width:3px;box-shadow:var(--vscode-scrollbar-shadow, #000) 6px 0 6px -6px inset}.xterm .xterm-scrollable-element>.shadow.top-left-corner{display:block;top:0;left:0;height:3px;width:3px}.xterm .xterm-scrollable-element>.shadow.top.left{box-shadow:var(--vscode-scrollbar-shadow, #000) 6px 0 6px -6px inset}.terminal-wrapper.svelte-5qgfij{display:flex;flex:1;min-height:0;position:relative;overflow:hidden}.terminal-container.svelte-5qgfij{flex:1;min-width:0;min-height:0;overflow:hidden;padding:4px}.terminal-wrapper.drag-over.svelte-5qgfij{outline:2px dashed var(--accent);outline-offset:-2px}.terminal-scrollbar.svelte-5qgfij{width:8px;background:transparent;position:relative;flex-shrink:0}.terminal-scrollbar-thumb.svelte-5qgfij{position:absolute;right:0;width:6px;background:var(--border);border-radius:3px;cursor:pointer}@media(hover:none){.terminal-wrapper.selection-mode.svelte-5qgfij .terminal-container:where(.svelte-5qgfij){outline:2px solid var(--accent);outline-offset:-2px}.terminal-container.svelte-5qgfij{touch-action:none}.terminal-scrollbar.svelte-5qgfij{width:12px}.terminal-scrollbar-thumb.svelte-5qgfij{width:8px;min-height:44px}.scroll-fabs.svelte-5qgfij{position:absolute;right:16px;top:50%;transform:translateY(-50%);display:flex;flex-direction:column;gap:12px;z-index:1;opacity:.6;pointer-events:auto}.scroll-fab.svelte-5qgfij{width:44px;height:44px;border-radius:50%;border:1px solid var(--border);background:var(--surface);color:var(--text);font-size:18px;display:flex;align-items:center;justify-content:center;cursor:pointer;touch-action:manipulation;-webkit-user-select:none;user-select:none}.scroll-fab.svelte-5qgfij:active{opacity:1;background:var(--border)}.scroll-fab-bottom.svelte-5qgfij{margin-top:4px}}.toolbar.svelte-k7we1m{background:var(--surface);border-top:1px solid var(--border);padding:4px;padding-bottom:calc(4px + env(safe-area-inset-bottom,0px));flex-shrink:0}.toolbar-grid.svelte-k7we1m{display:grid;grid-template-columns:repeat(6,1fr);gap:4px}.tb-btn.svelte-k7we1m{background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:.85rem;padding:8px 4px;cursor:pointer;touch-action:manipulation;min-height:40px;display:flex;align-items:center;justify-content:center;user-select:none;-webkit-user-select:none}.tb-btn.svelte-k7we1m:active{background:var(--border)}.tb-enter.svelte-k7we1m{background:var(--accent);border-color:var(--accent);color:#fff}.tb-enter.svelte-k7we1m:active{opacity:.8}.mobile-header.svelte-wp0i5g{display:none;align-items:center;gap:8px;padding:8px 12px;background:var(--surface);border-bottom:1px solid var(--border);min-height:44px;flex-shrink:0}@media(max-width:768px){.mobile-header.svelte-wp0i5g{display:flex}.mobile-header.hidden.svelte-wp0i5g{display:none}}.mobile-title.svelte-wp0i5g{font-size:.95rem;color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1}.icon-btn.svelte-wp0i5g{background:none;border:none;color:var(--text);font-size:1.2rem;cursor:pointer;padding:6px;min-width:36px;min-height:36px;display:flex;align-items:center;justify-content:center;border-radius:6px;touch-action:manipulation}.icon-btn.svelte-wp0i5g:hover{background:var(--border)}.mobile-input-form.svelte-us0qpd{position:fixed;top:0;left:0;width:1px;height:1px;padding:0;margin:0;overflow:hidden;clip-path:inset(50%);opacity:0;pointer-events:none}.mobile-input.svelte-us0qpd{width:100%;height:100%;background:transparent;border:none;outline:none;color:transparent;caret-color:transparent;font-size:16px}.mobile-input.svelte-us0qpd::-webkit-search-cancel-button,.mobile-input.svelte-us0qpd::-webkit-search-decoration{-webkit-appearance:none;-moz-appearance:none;appearance:none}.debug-toggle.svelte-us0qpd{position:fixed;bottom:60px;right:8px;z-index:10000;background:#333;color:#0f0;border:1px solid #0f0;border-radius:6px;font:12px monospace;padding:6px 10px;min-width:44px;min-height:44px;touch-action:manipulation}.debug-panel.svelte-us0qpd{position:fixed;top:0;left:0;right:0;bottom:0;overflow-y:scroll;-webkit-overflow-scrolling:touch;background:#000000eb;color:#0f0;font:11px/1.4 monospace;padding:6px 6px 6px 40px;z-index:9999;white-space:pre-wrap;word-break:break-all;overscroll-behavior:contain}.update-toast.svelte-1trivgv{position:fixed;bottom:0;left:0;right:0;z-index:150;display:flex;justify-content:center;padding:12px 12px calc(12px + env(safe-area-inset-bottom));pointer-events:none;animation:svelte-1trivgv-toast-slide-up .25s ease-out}@keyframes svelte-1trivgv-toast-slide-up{0%{transform:translateY(100%);opacity:0}to{transform:translateY(0);opacity:1}}.update-toast-content.svelte-1trivgv{display:flex;flex-direction:row;align-items:center;gap:12px;background:var(--surface);border:1px solid var(--border);border-radius:10px;padding:12px 16px;max-width:500px;box-shadow:0 4px 16px #0000004d;pointer-events:auto}.update-toast-text.svelte-1trivgv{flex:1;font-size:.85rem;color:var(--text)}.update-toast-actions.svelte-1trivgv{display:flex;gap:8px;flex-shrink:0}.update-toast-btn.svelte-1trivgv{padding:8px 14px;border-radius:6px;font-size:.8rem;border:none;background:var(--accent);color:#fff;cursor:pointer;white-space:nowrap}.update-toast-btn.svelte-1trivgv:disabled{opacity:.6;cursor:not-allowed}.update-toast-dismiss.svelte-1trivgv{background:none;border:none;color:var(--text-muted);font-size:1.2rem;padding:4px 6px;cursor:pointer}.update-toast-dismiss.svelte-1trivgv:hover{color:var(--text)}.image-toast.svelte-1y8sviv{position:fixed;bottom:60px;left:50%;transform:translate(-50%);z-index:1000;background:var(--surface);border:1px solid var(--border);border-radius:8px;padding:8px 14px;color:var(--text);font-size:13px;max-width:90vw;box-shadow:0 4px 12px #0006}.image-toast-content.svelte-1y8sviv{display:flex;align-items:center;gap:10px}.image-toast-text.svelte-1y8sviv{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:60vw}.image-toast-actions.svelte-1y8sviv{display:flex;gap:6px;align-items:center;flex-shrink:0}.image-toast-insert.svelte-1y8sviv{background:var(--accent);color:#fff;border:none;border-radius:4px;padding:4px 10px;font-size:12px;cursor:pointer}.image-toast-insert.svelte-1y8sviv:active{opacity:.8}.image-toast-dismiss.svelte-1y8sviv{background:none;border:none;color:var(--text-muted);cursor:pointer;font-size:16px;padding:2px 6px}.image-toast-dismiss.svelte-1y8sviv:hover{color:var(--text)}.dialog.svelte-thtd9b{background:var(--surface);color:var(--text);border:1px solid var(--border);border-radius:10px;padding:0;width:min(480px,95vw);max-height:90vh;overflow:hidden}.dialog.svelte-thtd9b::backdrop{background:#0009}.dialog-content.svelte-thtd9b{display:flex;flex-direction:column;max-height:90vh;overflow:hidden}.dialog-title.svelte-thtd9b{font-size:1.1rem;font-weight:600;padding:16px 20px 12px;margin:0;border-bottom:1px solid var(--border);flex-shrink:0}.dialog-tabs.svelte-thtd9b{display:flex;border-bottom:1px solid var(--border);flex-shrink:0}.dialog-tab.svelte-thtd9b{flex:1;padding:10px;background:none;border:none;color:var(--text-muted);font-size:.9rem;cursor:pointer;border-bottom:2px solid transparent;transition:color .15s,border-color .15s}.dialog-tab.active.svelte-thtd9b{color:var(--accent);border-bottom-color:var(--accent)}.dialog-tab.svelte-thtd9b:hover:not(.active){color:var(--text)}.dialog-body.svelte-thtd9b{padding:16px 20px;overflow-y:auto;flex:1;display:flex;flex-direction:column;gap:14px}.dialog-field.svelte-thtd9b{display:flex;flex-direction:column;gap:5px}.dialog-field--inline.svelte-thtd9b{flex-direction:row;align-items:center;gap:8px}.dialog-label.svelte-thtd9b{font-size:.85rem;color:var(--text-muted)}.dialog-label-inline.svelte-thtd9b{font-size:.9rem;cursor:pointer}.dialog-select.svelte-thtd9b,.dialog-input.svelte-thtd9b{background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:.9rem;padding:7px 10px;width:100%;box-sizing:border-box}.dialog-select.svelte-thtd9b:disabled{opacity:.5;cursor:not-allowed}.dialog-checkbox.svelte-thtd9b{width:16px;height:16px;accent-color:var(--accent);cursor:pointer;flex-shrink:0}.branch-input-wrap.svelte-thtd9b{position:relative}.branch-dropdown.svelte-thtd9b{position:absolute;top:calc(100% + 2px);left:0;right:0;background:var(--surface);border:1px solid var(--border);border-radius:6px;list-style:none;margin:0;padding:4px 0;z-index:100;max-height:180px;overflow-y:auto}.branch-dropdown.svelte-thtd9b li:where(.svelte-thtd9b){padding:7px 12px;font-size:.9rem;cursor:pointer}.branch-dropdown.svelte-thtd9b li:where(.svelte-thtd9b):hover{background:var(--border)}.branch-create-new.svelte-thtd9b{color:var(--accent);border-bottom:1px solid var(--border)}.dialog-footer.svelte-thtd9b{display:flex;justify-content:flex-end;gap:10px;padding:12px 20px 16px;border-top:1px solid var(--border);flex-shrink:0}.btn.svelte-thtd9b{padding:8px 18px;border-radius:6px;font-size:.9rem;cursor:pointer;border:1px solid transparent;font-weight:500;transition:opacity .15s}.btn.svelte-thtd9b:disabled{opacity:.4;cursor:not-allowed}.btn-primary.svelte-thtd9b{background:var(--accent);color:#fff}.btn-primary.svelte-thtd9b:hover:not(:disabled){opacity:.9}.btn-ghost.svelte-thtd9b{background:transparent;color:var(--text-muted);border-color:var(--border)}.btn-ghost.svelte-thtd9b:hover{background:var(--border);color:var(--text)}.dialog.svelte-fdtoa4{background:var(--surface);color:var(--text);border:1px solid var(--border);border-radius:10px;padding:0;width:min(460px,95vw);max-height:90vh;overflow:hidden}.dialog.svelte-fdtoa4::backdrop{background:#0009}.dialog-content.svelte-fdtoa4{display:flex;flex-direction:column;max-height:90vh;overflow:hidden}.dialog-header.svelte-fdtoa4{display:flex;align-items:center;justify-content:space-between;padding:16px 20px 12px;border-bottom:1px solid var(--border);flex-shrink:0}.dialog-title.svelte-fdtoa4{font-size:1.1rem;font-weight:600;margin:0}.close-btn.svelte-fdtoa4{background:none;border:none;color:var(--text-muted);font-size:1rem;cursor:pointer;padding:4px 6px;border-radius:4px}.close-btn.svelte-fdtoa4:hover{background:var(--border);color:var(--text)}.dialog-body.svelte-fdtoa4{padding:16px 20px;overflow-y:auto;flex:1;display:flex;flex-direction:column;gap:20px}.settings-section.svelte-fdtoa4{display:flex;flex-direction:column;gap:10px}.section-title.svelte-fdtoa4{font-size:.9rem;font-weight:600;color:var(--text);margin:0}.section-desc.svelte-fdtoa4{font-size:.82rem;color:var(--text-muted);margin:0}.empty-msg.svelte-fdtoa4{font-size:.85rem;color:var(--text-muted);font-style:italic;margin:0}.roots-list.svelte-fdtoa4{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:6px}.root-item.svelte-fdtoa4{display:flex;align-items:center;justify-content:space-between;gap:8px;background:var(--bg);border:1px solid var(--border);border-radius:6px;padding:7px 10px}.root-path.svelte-fdtoa4{font-size:.85rem;font-family:monospace;color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1}.remove-btn.svelte-fdtoa4{background:none;border:none;color:var(--text-muted);cursor:pointer;font-size:.9rem;padding:2px 5px;border-radius:4px;flex-shrink:0;line-height:1}.remove-btn.svelte-fdtoa4:hover{background:var(--border);color:var(--text)}.add-root-row.svelte-fdtoa4{display:flex;gap:8px}.add-root-input.svelte-fdtoa4{flex:1;background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:.9rem;padding:7px 10px}.error-msg.svelte-fdtoa4{font-size:.82rem;color:#e74c3c;margin:0}.agent-select.svelte-fdtoa4{background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:.9rem;padding:7px 10px;width:100%;box-sizing:border-box}.devtools-row.svelte-fdtoa4{display:flex;align-items:center;gap:8px}.dialog-checkbox.svelte-fdtoa4{width:16px;height:16px;accent-color:var(--accent);cursor:pointer;flex-shrink:0}.devtools-label.svelte-fdtoa4{font-size:.9rem;cursor:pointer}.dialog-footer.svelte-fdtoa4{display:flex;justify-content:flex-end;gap:10px;padding:12px 20px 16px;border-top:1px solid var(--border);flex-shrink:0}.btn.svelte-fdtoa4{padding:8px 18px;border-radius:6px;font-size:.9rem;cursor:pointer;border:1px solid transparent;font-weight:500}.btn-primary.svelte-fdtoa4{background:var(--accent);color:#fff;flex-shrink:0}.btn-primary.svelte-fdtoa4:hover{opacity:.9}.btn-ghost.svelte-fdtoa4{background:transparent;color:var(--text-muted);border-color:var(--border)}.btn-ghost.svelte-fdtoa4:hover{background:var(--border);color:var(--text)}.dialog.svelte-15sco2n{background:var(--surface);color:var(--text);border:1px solid var(--border);border-radius:10px;padding:0;width:min(400px,95vw);overflow:hidden}.dialog.svelte-15sco2n::backdrop{background:#0009}.dialog-content.svelte-15sco2n{display:flex;flex-direction:column}.dialog-title.svelte-15sco2n{font-size:1.1rem;font-weight:600;padding:16px 20px 12px;margin:0;border-bottom:1px solid var(--border)}.dialog-body.svelte-15sco2n{padding:16px 20px;display:flex;flex-direction:column;gap:8px}.confirm-msg.svelte-15sco2n{font-size:.95rem;margin:0;line-height:1.5}.wt-name.svelte-15sco2n{color:var(--text)}.wt-path.svelte-15sco2n{font-size:.82rem;color:var(--text-muted);font-family:monospace;margin:0;word-break:break-all}.warning-msg.svelte-15sco2n{font-size:.82rem;color:#e74c3c;margin:0}.error-msg.svelte-15sco2n{font-size:.85rem;color:#e74c3c;margin:0;padding:8px 10px;background:#e74c3c1a;border-radius:6px;border:1px solid rgba(231,76,60,.3)}.dialog-footer.svelte-15sco2n{display:flex;justify-content:flex-end;gap:10px;padding:12px 20px 16px;border-top:1px solid var(--border)}.btn.svelte-15sco2n{padding:8px 18px;border-radius:6px;font-size:.9rem;cursor:pointer;border:1px solid transparent;font-weight:500}.btn.svelte-15sco2n:disabled{opacity:.4;cursor:not-allowed}.btn-danger.svelte-15sco2n{background:#e74c3c;color:#fff}.btn-danger.svelte-15sco2n:hover:not(:disabled){opacity:.9}.btn-ghost.svelte-15sco2n{background:transparent;color:var(--text-muted);border-color:var(--border)}.btn-ghost.svelte-15sco2n:hover:not(:disabled){background:var(--border);color:var(--text)}.main-app.svelte-13zv0lp{display:flex;flex-direction:row;width:100%;height:100vh;height:100dvh;overflow:hidden}.sidebar-overlay.svelte-13zv0lp{display:none}.terminal-area.svelte-13zv0lp{flex:1;display:flex;flex-direction:column;min-width:0;overflow:hidden;position:relative}.no-session-msg.svelte-13zv0lp{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);color:var(--text-muted);font-size:.95rem;text-align:center;pointer-events:none}@media(max-width:600px){.main-app.svelte-13zv0lp{position:fixed;top:0;right:0;bottom:0;left:0;width:100%}.sidebar-overlay.svelte-13zv0lp{display:block;position:fixed;top:0;right:0;bottom:0;left:0;background:#00000080;z-index:99}.terminal-area.svelte-13zv0lp{width:100%}}:root{--bg: #1a1a1a;--surface: #2b2b2b;--accent: #d97757;--text: #ececec;--text-muted: #9b9b9b;--border: #3d3d3d;--sidebar-width: 240px;--toolbar-height: auto}[hidden]{display:none!important}*,*:before,*:after{box-sizing:border-box;margin:0;padding:0}dialog{margin:auto}html,body{height:100%;overflow:hidden;overscroll-behavior:none;background:var(--bg);color:var(--text);font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:15px}::-webkit-scrollbar{width:4px}::-webkit-scrollbar-track{background:transparent}::-webkit-scrollbar-thumb{background:var(--border);border-radius:4px}
|
|
32
|
+
*/.xterm{cursor:text;position:relative;user-select:none;-ms-user-select:none;-webkit-user-select:none}.xterm.focus,.xterm:focus{outline:none}.xterm .xterm-helpers{position:absolute;top:0;z-index:5}.xterm .xterm-helper-textarea{padding:0;border:0;margin:0;position:absolute;opacity:0;left:-9999em;top:0;width:0;height:0;z-index:-5;white-space:nowrap;overflow:hidden;resize:none}.xterm .composition-view{background:#000;color:#fff;display:none;position:absolute;white-space:nowrap;z-index:1}.xterm .composition-view.active{display:block}.xterm .xterm-viewport{background-color:#000;overflow-y:scroll;cursor:default;position:absolute;right:0;left:0;top:0;bottom:0}.xterm .xterm-screen{position:relative}.xterm .xterm-screen canvas{position:absolute;left:0;top:0}.xterm-char-measure-element{display:inline-block;visibility:hidden;position:absolute;top:0;left:-9999em;line-height:normal}.xterm.enable-mouse-events{cursor:default}.xterm.xterm-cursor-pointer,.xterm .xterm-cursor-pointer{cursor:pointer}.xterm.column-select.focus{cursor:crosshair}.xterm .xterm-accessibility:not(.debug),.xterm .xterm-message{position:absolute;left:0;top:0;bottom:0;right:0;z-index:10;color:transparent;pointer-events:none}.xterm .xterm-accessibility-tree:not(.debug) *::selection{color:transparent}.xterm .xterm-accessibility-tree{font-family:monospace;-webkit-user-select:text;user-select:text;white-space:pre}.xterm .xterm-accessibility-tree>div{transform-origin:left;width:fit-content}.xterm .live-region{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}.xterm-dim{opacity:1!important}.xterm-underline-1{text-decoration:underline}.xterm-underline-2{text-decoration:double underline}.xterm-underline-3{text-decoration:wavy underline}.xterm-underline-4{text-decoration:dotted underline}.xterm-underline-5{text-decoration:dashed underline}.xterm-overline{text-decoration:overline}.xterm-overline.xterm-underline-1{text-decoration:overline underline}.xterm-overline.xterm-underline-2{text-decoration:overline double underline}.xterm-overline.xterm-underline-3{text-decoration:overline wavy underline}.xterm-overline.xterm-underline-4{text-decoration:overline dotted underline}.xterm-overline.xterm-underline-5{text-decoration:overline dashed underline}.xterm-strikethrough{text-decoration:line-through}.xterm-screen .xterm-decoration-container .xterm-decoration{z-index:6;position:absolute}.xterm-screen .xterm-decoration-container .xterm-decoration.xterm-decoration-top-layer{z-index:7}.xterm-decoration-overview-ruler{z-index:8;position:absolute;top:0;right:0;pointer-events:none}.xterm-decoration-top{z-index:2;position:relative}.xterm .xterm-scrollable-element>.scrollbar{cursor:default}.xterm .xterm-scrollable-element>.scrollbar>.scra{cursor:pointer;font-size:11px!important}.xterm .xterm-scrollable-element>.visible{opacity:1;background:#0000;transition:opacity .1s linear;z-index:11}.xterm .xterm-scrollable-element>.invisible{opacity:0;pointer-events:none}.xterm .xterm-scrollable-element>.invisible.fade{transition:opacity .8s linear}.xterm .xterm-scrollable-element>.shadow{position:absolute;display:none}.xterm .xterm-scrollable-element>.shadow.top{display:block;top:0;left:3px;height:3px;width:100%;box-shadow:var(--vscode-scrollbar-shadow, #000) 0 6px 6px -6px inset}.xterm .xterm-scrollable-element>.shadow.left{display:block;top:3px;left:0;height:100%;width:3px;box-shadow:var(--vscode-scrollbar-shadow, #000) 6px 0 6px -6px inset}.xterm .xterm-scrollable-element>.shadow.top-left-corner{display:block;top:0;left:0;height:3px;width:3px}.xterm .xterm-scrollable-element>.shadow.top.left{box-shadow:var(--vscode-scrollbar-shadow, #000) 6px 0 6px -6px inset}.terminal-wrapper.svelte-5qgfij{display:flex;flex:1;min-height:0;position:relative;overflow:hidden}.terminal-container.svelte-5qgfij{flex:1;min-width:0;min-height:0;overflow:hidden;padding:4px}.terminal-wrapper.drag-over.svelte-5qgfij{outline:2px dashed var(--accent);outline-offset:-2px}.terminal-scrollbar.svelte-5qgfij{width:8px;background:transparent;position:relative;flex-shrink:0}.terminal-scrollbar-thumb.svelte-5qgfij{position:absolute;right:0;width:6px;background:var(--border);border-radius:3px;cursor:pointer}@media(hover:none){.terminal-wrapper.selection-mode.svelte-5qgfij .terminal-container:where(.svelte-5qgfij){outline:2px solid var(--accent);outline-offset:-2px}.terminal-container.svelte-5qgfij{touch-action:none}.terminal-scrollbar.svelte-5qgfij{width:12px}.terminal-scrollbar-thumb.svelte-5qgfij{width:8px;min-height:44px}.scroll-fabs.svelte-5qgfij{position:absolute;right:16px;top:50%;transform:translateY(-50%);display:flex;flex-direction:column;gap:12px;z-index:1;opacity:.6;pointer-events:auto}.scroll-fab.svelte-5qgfij{width:44px;height:44px;border-radius:50%;border:1px solid var(--border);background:var(--surface);color:var(--text);font-size:18px;display:flex;align-items:center;justify-content:center;cursor:pointer;touch-action:manipulation;-webkit-user-select:none;user-select:none}.scroll-fab.svelte-5qgfij:active{opacity:1;background:var(--border)}.scroll-fab-bottom.svelte-5qgfij{margin-top:4px}}.toolbar.svelte-k7we1m{background:var(--surface);border-top:1px solid var(--border);padding:4px;padding-bottom:calc(4px + env(safe-area-inset-bottom,0px));flex-shrink:0}.toolbar-grid.svelte-k7we1m{display:grid;grid-template-columns:repeat(6,1fr);gap:4px}.tb-btn.svelte-k7we1m{background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:.85rem;padding:8px 4px;cursor:pointer;touch-action:manipulation;min-height:40px;display:flex;align-items:center;justify-content:center;user-select:none;-webkit-user-select:none}.tb-btn.svelte-k7we1m:active{background:var(--border)}.tb-enter.svelte-k7we1m{background:var(--accent);border-color:var(--accent);color:#fff}.tb-enter.svelte-k7we1m:active{opacity:.8}.mobile-header.svelte-wp0i5g{display:none;align-items:center;gap:8px;padding:8px 12px;background:var(--surface);border-bottom:1px solid var(--border);min-height:44px;flex-shrink:0}@media(max-width:768px){.mobile-header.svelte-wp0i5g{display:flex}.mobile-header.hidden.svelte-wp0i5g{display:none}}.mobile-title.svelte-wp0i5g{font-size:.95rem;color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1}.icon-btn.svelte-wp0i5g{background:none;border:none;color:var(--text);font-size:1.2rem;cursor:pointer;padding:6px;min-width:36px;min-height:36px;display:flex;align-items:center;justify-content:center;border-radius:6px;touch-action:manipulation}.icon-btn.svelte-wp0i5g:hover{background:var(--border)}.mobile-input-form.svelte-us0qpd{position:fixed;top:0;left:0;width:1px;height:1px;padding:0;margin:0;overflow:hidden;clip-path:inset(50%);opacity:0;pointer-events:none}.mobile-input.svelte-us0qpd{width:100%;height:100%;background:transparent;border:none;outline:none;color:transparent;caret-color:transparent;font-size:16px}.mobile-input.svelte-us0qpd::-webkit-search-cancel-button,.mobile-input.svelte-us0qpd::-webkit-search-decoration{-webkit-appearance:none;-moz-appearance:none;appearance:none}.debug-toggle.svelte-us0qpd{position:fixed;bottom:60px;right:8px;z-index:10000;background:#333;color:#0f0;border:1px solid #0f0;border-radius:6px;font:12px monospace;padding:6px 10px;min-width:44px;min-height:44px;touch-action:manipulation}.debug-panel.svelte-us0qpd{position:fixed;top:0;left:0;right:0;bottom:0;overflow-y:scroll;-webkit-overflow-scrolling:touch;background:#000000eb;color:#0f0;font:11px/1.4 monospace;padding:6px 6px 6px 40px;z-index:9999;white-space:pre-wrap;word-break:break-all;overscroll-behavior:contain}.update-toast.svelte-1trivgv{position:fixed;bottom:0;left:0;right:0;z-index:150;display:flex;justify-content:center;padding:12px 12px calc(12px + env(safe-area-inset-bottom));pointer-events:none;animation:svelte-1trivgv-toast-slide-up .25s ease-out}@keyframes svelte-1trivgv-toast-slide-up{0%{transform:translateY(100%);opacity:0}to{transform:translateY(0);opacity:1}}.update-toast-content.svelte-1trivgv{display:flex;flex-direction:row;align-items:center;gap:12px;background:var(--surface);border:1px solid var(--border);border-radius:10px;padding:12px 16px;max-width:500px;box-shadow:0 4px 16px #0000004d;pointer-events:auto}.update-toast-text.svelte-1trivgv{flex:1;font-size:.85rem;color:var(--text)}.update-toast-actions.svelte-1trivgv{display:flex;gap:8px;flex-shrink:0}.update-toast-btn.svelte-1trivgv{padding:8px 14px;border-radius:6px;font-size:.8rem;border:none;background:var(--accent);color:#fff;cursor:pointer;white-space:nowrap}.update-toast-btn.svelte-1trivgv:disabled{opacity:.6;cursor:not-allowed}.update-toast-dismiss.svelte-1trivgv{background:none;border:none;color:var(--text-muted);font-size:1.2rem;padding:4px 6px;cursor:pointer}.update-toast-dismiss.svelte-1trivgv:hover{color:var(--text)}.image-toast.svelte-1y8sviv{position:fixed;bottom:60px;left:50%;transform:translate(-50%);z-index:1000;background:var(--surface);border:1px solid var(--border);border-radius:8px;padding:8px 14px;color:var(--text);font-size:13px;max-width:90vw;box-shadow:0 4px 12px #0006}.image-toast-content.svelte-1y8sviv{display:flex;align-items:center;gap:10px}.image-toast-text.svelte-1y8sviv{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:60vw}.image-toast-actions.svelte-1y8sviv{display:flex;gap:6px;align-items:center;flex-shrink:0}.image-toast-insert.svelte-1y8sviv{background:var(--accent);color:#fff;border:none;border-radius:4px;padding:4px 10px;font-size:12px;cursor:pointer}.image-toast-insert.svelte-1y8sviv:active{opacity:.8}.image-toast-dismiss.svelte-1y8sviv{background:none;border:none;color:var(--text-muted);cursor:pointer;font-size:16px;padding:2px 6px}.image-toast-dismiss.svelte-1y8sviv:hover{color:var(--text)}.dialog.svelte-thtd9b{background:var(--surface);color:var(--text);border:1px solid var(--border);border-radius:10px;padding:0;width:min(480px,95vw);max-height:90vh;overflow:hidden}.dialog.svelte-thtd9b::backdrop{background:#0009}.dialog-content.svelte-thtd9b{display:flex;flex-direction:column;max-height:90vh;overflow:hidden}.dialog-title.svelte-thtd9b{font-size:1.1rem;font-weight:600;padding:16px 20px 12px;margin:0;border-bottom:1px solid var(--border);flex-shrink:0}.dialog-tabs.svelte-thtd9b{display:flex;border-bottom:1px solid var(--border);flex-shrink:0}.dialog-tab.svelte-thtd9b{flex:1;padding:10px;background:none;border:none;color:var(--text-muted);font-size:.9rem;cursor:pointer;border-bottom:2px solid transparent;transition:color .15s,border-color .15s}.dialog-tab.active.svelte-thtd9b{color:var(--accent);border-bottom-color:var(--accent)}.dialog-tab.svelte-thtd9b:hover:not(.active){color:var(--text)}.dialog-body.svelte-thtd9b{padding:16px 20px;overflow-y:auto;flex:1;display:flex;flex-direction:column;gap:14px}.dialog-field.svelte-thtd9b{display:flex;flex-direction:column;gap:5px}.dialog-field--inline.svelte-thtd9b{flex-direction:row;align-items:center;gap:8px}.dialog-label.svelte-thtd9b{font-size:.85rem;color:var(--text-muted)}.dialog-label-row.svelte-thtd9b{display:flex;align-items:center;justify-content:space-between;gap:10px}.dialog-label-inline.svelte-thtd9b{font-size:.9rem;cursor:pointer}.dialog-select.svelte-thtd9b,.dialog-input.svelte-thtd9b{background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:.9rem;padding:7px 10px;width:100%;box-sizing:border-box}.dialog-select.svelte-thtd9b:disabled{opacity:.5;cursor:not-allowed}.dialog-checkbox.svelte-thtd9b{width:16px;height:16px;accent-color:var(--accent);cursor:pointer;flex-shrink:0}.branch-input-wrap.svelte-thtd9b{position:relative}.branch-refresh.svelte-thtd9b{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;padding:0;border:1px solid var(--border);border-radius:6px;background:var(--bg);color:var(--text-muted);cursor:pointer;transition:color .15s,border-color .15s,background .15s}.branch-refresh.svelte-thtd9b:hover:not(:disabled){color:var(--accent);border-color:var(--accent)}.branch-refresh.svelte-thtd9b:disabled{opacity:.5;cursor:not-allowed}.branch-refresh.svelte-thtd9b svg:where(.svelte-thtd9b){width:15px;height:15px}.branch-dropdown.svelte-thtd9b{position:absolute;top:calc(100% + 2px);left:0;right:0;background:var(--surface);border:1px solid var(--border);border-radius:6px;list-style:none;margin:0;padding:4px 0;z-index:100;max-height:180px;overflow-y:auto}.branch-dropdown.svelte-thtd9b li:where(.svelte-thtd9b){padding:7px 12px;font-size:.9rem;cursor:pointer}.branch-dropdown.svelte-thtd9b li:where(.svelte-thtd9b):hover{background:var(--border)}.branch-create-new.svelte-thtd9b{color:var(--accent);border-bottom:1px solid var(--border)}.dialog-footer.svelte-thtd9b{display:flex;justify-content:flex-end;gap:10px;padding:12px 20px 16px;border-top:1px solid var(--border);flex-shrink:0}.btn.svelte-thtd9b{padding:8px 18px;border-radius:6px;font-size:.9rem;cursor:pointer;border:1px solid transparent;font-weight:500;transition:opacity .15s}.btn.svelte-thtd9b:disabled{opacity:.4;cursor:not-allowed}.btn-primary.svelte-thtd9b{background:var(--accent);color:#fff}.btn-primary.svelte-thtd9b:hover:not(:disabled){opacity:.9}.btn-ghost.svelte-thtd9b{background:transparent;color:var(--text-muted);border-color:var(--border)}.btn-ghost.svelte-thtd9b:hover{background:var(--border);color:var(--text)}.spinning.svelte-thtd9b{animation:svelte-thtd9b-spin 1s linear infinite;transform-origin:center}@keyframes svelte-thtd9b-spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.dialog.svelte-fdtoa4{background:var(--surface);color:var(--text);border:1px solid var(--border);border-radius:10px;padding:0;width:min(460px,95vw);max-height:90vh;overflow:hidden}.dialog.svelte-fdtoa4::backdrop{background:#0009}.dialog-content.svelte-fdtoa4{display:flex;flex-direction:column;max-height:90vh;overflow:hidden}.dialog-header.svelte-fdtoa4{display:flex;align-items:center;justify-content:space-between;padding:16px 20px 12px;border-bottom:1px solid var(--border);flex-shrink:0}.dialog-title.svelte-fdtoa4{font-size:1.1rem;font-weight:600;margin:0}.close-btn.svelte-fdtoa4{background:none;border:none;color:var(--text-muted);font-size:1rem;cursor:pointer;padding:4px 6px;border-radius:4px}.close-btn.svelte-fdtoa4:hover{background:var(--border);color:var(--text)}.dialog-body.svelte-fdtoa4{padding:16px 20px;overflow-y:auto;flex:1;display:flex;flex-direction:column;gap:20px}.settings-section.svelte-fdtoa4{display:flex;flex-direction:column;gap:10px}.section-title.svelte-fdtoa4{font-size:.9rem;font-weight:600;color:var(--text);margin:0}.section-desc.svelte-fdtoa4{font-size:.82rem;color:var(--text-muted);margin:0}.empty-msg.svelte-fdtoa4{font-size:.85rem;color:var(--text-muted);font-style:italic;margin:0}.roots-list.svelte-fdtoa4{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:6px}.root-item.svelte-fdtoa4{display:flex;align-items:center;justify-content:space-between;gap:8px;background:var(--bg);border:1px solid var(--border);border-radius:6px;padding:7px 10px}.root-path.svelte-fdtoa4{font-size:.85rem;font-family:monospace;color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1}.remove-btn.svelte-fdtoa4{background:none;border:none;color:var(--text-muted);cursor:pointer;font-size:.9rem;padding:2px 5px;border-radius:4px;flex-shrink:0;line-height:1}.remove-btn.svelte-fdtoa4:hover{background:var(--border);color:var(--text)}.add-root-row.svelte-fdtoa4{display:flex;gap:8px}.add-root-input.svelte-fdtoa4{flex:1;background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:.9rem;padding:7px 10px}.error-msg.svelte-fdtoa4{font-size:.82rem;color:#e74c3c;margin:0}.agent-select.svelte-fdtoa4{background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:.9rem;padding:7px 10px;width:100%;box-sizing:border-box}.devtools-row.svelte-fdtoa4{display:flex;align-items:center;gap:8px}.dialog-checkbox.svelte-fdtoa4{width:16px;height:16px;accent-color:var(--accent);cursor:pointer;flex-shrink:0}.devtools-label.svelte-fdtoa4{font-size:.9rem;cursor:pointer}.dialog-footer.svelte-fdtoa4{display:flex;justify-content:flex-end;gap:10px;padding:12px 20px 16px;border-top:1px solid var(--border);flex-shrink:0}.btn.svelte-fdtoa4{padding:8px 18px;border-radius:6px;font-size:.9rem;cursor:pointer;border:1px solid transparent;font-weight:500}.btn-primary.svelte-fdtoa4{background:var(--accent);color:#fff;flex-shrink:0}.btn-primary.svelte-fdtoa4:hover{opacity:.9}.btn-ghost.svelte-fdtoa4{background:transparent;color:var(--text-muted);border-color:var(--border)}.btn-ghost.svelte-fdtoa4:hover{background:var(--border);color:var(--text)}.dialog.svelte-15sco2n{background:var(--surface);color:var(--text);border:1px solid var(--border);border-radius:10px;padding:0;width:min(400px,95vw);overflow:hidden}.dialog.svelte-15sco2n::backdrop{background:#0009}.dialog-content.svelte-15sco2n{display:flex;flex-direction:column}.dialog-title.svelte-15sco2n{font-size:1.1rem;font-weight:600;padding:16px 20px 12px;margin:0;border-bottom:1px solid var(--border)}.dialog-body.svelte-15sco2n{padding:16px 20px;display:flex;flex-direction:column;gap:8px}.confirm-msg.svelte-15sco2n{font-size:.95rem;margin:0;line-height:1.5}.wt-name.svelte-15sco2n{color:var(--text)}.wt-path.svelte-15sco2n{font-size:.82rem;color:var(--text-muted);font-family:monospace;margin:0;word-break:break-all}.warning-msg.svelte-15sco2n{font-size:.82rem;color:#e74c3c;margin:0}.error-msg.svelte-15sco2n{font-size:.85rem;color:#e74c3c;margin:0;padding:8px 10px;background:#e74c3c1a;border-radius:6px;border:1px solid rgba(231,76,60,.3)}.dialog-footer.svelte-15sco2n{display:flex;justify-content:flex-end;gap:10px;padding:12px 20px 16px;border-top:1px solid var(--border)}.btn.svelte-15sco2n{padding:8px 18px;border-radius:6px;font-size:.9rem;cursor:pointer;border:1px solid transparent;font-weight:500}.btn.svelte-15sco2n:disabled{opacity:.4;cursor:not-allowed}.btn-danger.svelte-15sco2n{background:#e74c3c;color:#fff}.btn-danger.svelte-15sco2n:hover:not(:disabled){opacity:.9}.btn-ghost.svelte-15sco2n{background:transparent;color:var(--text-muted);border-color:var(--border)}.btn-ghost.svelte-15sco2n:hover:not(:disabled){background:var(--border);color:var(--text)}.main-app.svelte-13zv0lp{display:flex;flex-direction:row;width:100%;height:100vh;height:100dvh;overflow:hidden}.sidebar-overlay.svelte-13zv0lp{display:none}.terminal-area.svelte-13zv0lp{flex:1;display:flex;flex-direction:column;min-width:0;overflow:hidden;position:relative}.no-session-msg.svelte-13zv0lp{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);color:var(--text-muted);font-size:.95rem;text-align:center;pointer-events:none}@media(max-width:600px){.main-app.svelte-13zv0lp{position:fixed;top:0;right:0;bottom:0;left:0;width:100%}.sidebar-overlay.svelte-13zv0lp{display:block;position:fixed;top:0;right:0;bottom:0;left:0;background:#00000080;z-index:99}.terminal-area.svelte-13zv0lp{width:100%}}:root{--bg: #1a1a1a;--surface: #2b2b2b;--accent: #d97757;--text: #ececec;--text-muted: #9b9b9b;--border: #3d3d3d;--sidebar-width: 240px;--toolbar-height: auto}[hidden]{display:none!important}*,*:before,*:after{box-sizing:border-box;margin:0;padding:0}dialog{margin:auto}html,body{height:100%;overflow:hidden;overscroll-behavior:none;background:var(--bg);color:var(--text);font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:15px}::-webkit-scrollbar{width:4px}::-webkit-scrollbar-track{background:transparent}::-webkit-scrollbar-thumb{background:var(--border);border-radius:4px}
|
package/dist/frontend/index.html
CHANGED
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
<meta name="apple-mobile-web-app-capable" content="yes" />
|
|
12
12
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
|
13
13
|
<meta name="theme-color" content="#1a1a1a" />
|
|
14
|
-
<script type="module" crossorigin src="/assets/index-
|
|
15
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
14
|
+
<script type="module" crossorigin src="/assets/index-CEJznk5F.js"></script>
|
|
15
|
+
<link rel="stylesheet" crossorigin href="/assets/index-nIPDa7NP.css">
|
|
16
16
|
</head>
|
|
17
17
|
<body>
|
|
18
18
|
<div id="app"></div>
|
package/dist/server/config.js
CHANGED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { promisify } from 'node:util';
|
|
3
|
+
const execFileAsync = promisify(execFile);
|
|
4
|
+
function normalizeBranchNames(stdout) {
|
|
5
|
+
const branches = stdout
|
|
6
|
+
.split('\n')
|
|
7
|
+
.map((branch) => branch.trim())
|
|
8
|
+
.filter((branch) => branch && !branch.includes('HEAD'))
|
|
9
|
+
.map((branch) => branch.replace(/^origin\//, ''));
|
|
10
|
+
return [...new Set(branches)].sort();
|
|
11
|
+
}
|
|
12
|
+
async function listBranches(repoPath, options = {}) {
|
|
13
|
+
const run = options.exec || execFileAsync;
|
|
14
|
+
if (options.refresh) {
|
|
15
|
+
try {
|
|
16
|
+
await run('git', ['fetch', '--all', '--prune'], { cwd: repoPath });
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
// Best effort — still return the locally-known refs below.
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
const { stdout } = await run('git', ['branch', '-a', '--format=%(refname:short)'], { cwd: repoPath });
|
|
24
|
+
return normalizeBranchNames(stdout);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export { listBranches, normalizeBranchNames };
|
package/dist/server/index.js
CHANGED
|
@@ -11,11 +11,12 @@ import cookieParser from 'cookie-parser';
|
|
|
11
11
|
import { loadConfig, saveConfig, DEFAULTS, readMeta, writeMeta, deleteMeta, ensureMetaDir } from './config.js';
|
|
12
12
|
import * as auth from './auth.js';
|
|
13
13
|
import * as sessions from './sessions.js';
|
|
14
|
-
import { AGENT_CONTINUE_ARGS } from './sessions.js';
|
|
14
|
+
import { AGENT_CONTINUE_ARGS, AGENT_YOLO_ARGS } from './sessions.js';
|
|
15
15
|
import { setupWebSocket } from './ws.js';
|
|
16
16
|
import { WorktreeWatcher, WORKTREE_DIRS, isValidWorktreePath, parseWorktreeListPorcelain, parseAllWorktrees } from './watcher.js';
|
|
17
17
|
import { isInstalled as serviceIsInstalled } from './service.js';
|
|
18
18
|
import { extensionForMime, setClipboardImage } from './clipboard.js';
|
|
19
|
+
import { listBranches } from './git.js';
|
|
19
20
|
const __filename = fileURLToPath(import.meta.url);
|
|
20
21
|
const __dirname = path.dirname(__filename);
|
|
21
22
|
const execFileAsync = promisify(execFile);
|
|
@@ -192,6 +193,30 @@ async function main() {
|
|
|
192
193
|
}
|
|
193
194
|
next();
|
|
194
195
|
};
|
|
196
|
+
function boolConfigEndpoints(name, defaultValue, onEnable) {
|
|
197
|
+
app.get(`/config/${name}`, requireAuth, (_req, res) => {
|
|
198
|
+
res.json({ [name]: config[name] ?? defaultValue });
|
|
199
|
+
});
|
|
200
|
+
app.patch(`/config/${name}`, requireAuth, async (req, res) => {
|
|
201
|
+
const value = req.body[name];
|
|
202
|
+
if (typeof value !== 'boolean') {
|
|
203
|
+
res.status(400).json({ error: `${name} must be a boolean` });
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
if (value && onEnable) {
|
|
207
|
+
try {
|
|
208
|
+
await onEnable();
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
res.status(400).json({ error: `Validation failed for ${name}` });
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
config[name] = value;
|
|
216
|
+
saveConfig(CONFIG_PATH, config);
|
|
217
|
+
res.json({ [name]: value });
|
|
218
|
+
});
|
|
219
|
+
}
|
|
195
220
|
const watcher = new WorktreeWatcher();
|
|
196
221
|
watcher.rebuild(config.rootDirs || []);
|
|
197
222
|
const server = http.createServer(app);
|
|
@@ -246,23 +271,12 @@ async function main() {
|
|
|
246
271
|
// GET /branches?repo=<path> — list local and remote branches for a repo
|
|
247
272
|
app.get('/branches', requireAuth, async (req, res) => {
|
|
248
273
|
const repoPath = typeof req.query.repo === 'string' ? req.query.repo : undefined;
|
|
274
|
+
const refresh = req.query.refresh === '1';
|
|
249
275
|
if (!repoPath) {
|
|
250
276
|
res.status(400).json({ error: 'repo query parameter is required' });
|
|
251
277
|
return;
|
|
252
278
|
}
|
|
253
|
-
|
|
254
|
-
const { stdout } = await execFileAsync('git', ['branch', '-a', '--format=%(refname:short)'], { cwd: repoPath });
|
|
255
|
-
const branches = stdout
|
|
256
|
-
.split('\n')
|
|
257
|
-
.map((b) => b.trim())
|
|
258
|
-
.filter((b) => b && !b.includes('HEAD'))
|
|
259
|
-
.map((b) => b.replace(/^origin\//, ''));
|
|
260
|
-
const unique = [...new Set(branches)];
|
|
261
|
-
res.json(unique.sort());
|
|
262
|
-
}
|
|
263
|
-
catch (_) {
|
|
264
|
-
res.json([]);
|
|
265
|
-
}
|
|
279
|
+
res.json(await listBranches(repoPath, { refresh }));
|
|
266
280
|
});
|
|
267
281
|
// GET /git-status?repo=<path>&branch=<name>
|
|
268
282
|
app.get('/git-status', requireAuth, async (req, res) => {
|
|
@@ -516,6 +530,11 @@ async function main() {
|
|
|
516
530
|
saveConfig(CONFIG_PATH, config);
|
|
517
531
|
res.json({ defaultAgent: config.defaultAgent });
|
|
518
532
|
});
|
|
533
|
+
boolConfigEndpoints('defaultContinue', true);
|
|
534
|
+
boolConfigEndpoints('defaultYolo', false);
|
|
535
|
+
boolConfigEndpoints('launchInTmux', false, async () => {
|
|
536
|
+
await execFileAsync('tmux', ['-V']);
|
|
537
|
+
});
|
|
519
538
|
// DELETE /worktrees — remove a worktree, prune, and delete its branch
|
|
520
539
|
app.delete('/worktrees', requireAuth, async (req, res) => {
|
|
521
540
|
const { worktreePath, repoPath } = req.body;
|
|
@@ -590,14 +609,18 @@ async function main() {
|
|
|
590
609
|
});
|
|
591
610
|
// POST /sessions
|
|
592
611
|
app.post('/sessions', requireAuth, async (req, res) => {
|
|
593
|
-
const { repoPath, repoName, worktreePath, branchName, claudeArgs, agent } = req.body;
|
|
612
|
+
const { repoPath, repoName, worktreePath, branchName, claudeArgs, yolo, agent, useTmux } = req.body;
|
|
594
613
|
if (!repoPath) {
|
|
595
614
|
res.status(400).json({ error: 'repoPath is required' });
|
|
596
615
|
return;
|
|
597
616
|
}
|
|
598
617
|
const resolvedAgent = agent || config.defaultAgent || 'claude';
|
|
599
618
|
const name = repoName || repoPath.split('/').filter(Boolean).pop() || 'session';
|
|
600
|
-
const baseArgs = [
|
|
619
|
+
const baseArgs = [
|
|
620
|
+
...(config.claudeArgs || []),
|
|
621
|
+
...(yolo ? AGENT_YOLO_ARGS[resolvedAgent] : []),
|
|
622
|
+
...(claudeArgs || []),
|
|
623
|
+
];
|
|
601
624
|
// Compute root by matching repoPath against configured rootDirs
|
|
602
625
|
const roots = config.rootDirs || [];
|
|
603
626
|
const root = roots.find(function (r) { return repoPath.startsWith(r); }) || '';
|
|
@@ -672,6 +695,7 @@ async function main() {
|
|
|
672
695
|
root,
|
|
673
696
|
displayName: name,
|
|
674
697
|
args: baseArgs,
|
|
698
|
+
useTmux: useTmux ?? config.launchInTmux,
|
|
675
699
|
});
|
|
676
700
|
res.status(201).json(repoSession);
|
|
677
701
|
return;
|
|
@@ -695,6 +719,7 @@ async function main() {
|
|
|
695
719
|
displayName: displayNameVal,
|
|
696
720
|
args,
|
|
697
721
|
configPath: CONFIG_PATH,
|
|
722
|
+
useTmux: useTmux ?? config.launchInTmux,
|
|
698
723
|
});
|
|
699
724
|
writeMeta(CONFIG_PATH, {
|
|
700
725
|
worktreePath: sessionRepoPath,
|
|
@@ -737,6 +762,7 @@ async function main() {
|
|
|
737
762
|
displayName,
|
|
738
763
|
args,
|
|
739
764
|
configPath: CONFIG_PATH,
|
|
765
|
+
useTmux: useTmux ?? config.launchInTmux,
|
|
740
766
|
});
|
|
741
767
|
if (!worktreePath) {
|
|
742
768
|
writeMeta(CONFIG_PATH, {
|
|
@@ -750,7 +776,7 @@ async function main() {
|
|
|
750
776
|
});
|
|
751
777
|
// POST /sessions/repo — start a session in the repo root (no worktree)
|
|
752
778
|
app.post('/sessions/repo', requireAuth, (req, res) => {
|
|
753
|
-
const { repoPath, repoName, continue: continueSession, claudeArgs, agent } = req.body;
|
|
779
|
+
const { repoPath, repoName, continue: continueSession, claudeArgs, yolo, agent, useTmux } = req.body;
|
|
754
780
|
if (!repoPath) {
|
|
755
781
|
res.status(400).json({ error: 'repoPath is required' });
|
|
756
782
|
return;
|
|
@@ -763,7 +789,11 @@ async function main() {
|
|
|
763
789
|
return;
|
|
764
790
|
}
|
|
765
791
|
const name = repoName || repoPath.split('/').filter(Boolean).pop() || 'session';
|
|
766
|
-
const baseArgs = [
|
|
792
|
+
const baseArgs = [
|
|
793
|
+
...(config.claudeArgs || []),
|
|
794
|
+
...(yolo ? AGENT_YOLO_ARGS[resolvedAgent] : []),
|
|
795
|
+
...(claudeArgs || []),
|
|
796
|
+
];
|
|
767
797
|
const args = continueSession ? [...AGENT_CONTINUE_ARGS[resolvedAgent], ...baseArgs] : [...baseArgs];
|
|
768
798
|
const roots = config.rootDirs || [];
|
|
769
799
|
const root = roots.find(function (r) { return repoPath.startsWith(r); }) || '';
|
|
@@ -776,6 +806,7 @@ async function main() {
|
|
|
776
806
|
root,
|
|
777
807
|
displayName: name,
|
|
778
808
|
args,
|
|
809
|
+
useTmux: useTmux ?? config.launchInTmux,
|
|
779
810
|
});
|
|
780
811
|
res.status(201).json(session);
|
|
781
812
|
});
|
|
@@ -891,6 +922,34 @@ async function main() {
|
|
|
891
922
|
res.status(500).json({ ok: false, error: message });
|
|
892
923
|
}
|
|
893
924
|
});
|
|
925
|
+
// Clean up orphaned tmux sessions from previous runs
|
|
926
|
+
try {
|
|
927
|
+
const { stdout } = await execFileAsync('tmux', ['list-sessions', '-F', '#{session_name}']);
|
|
928
|
+
const crcSessions = stdout.trim().split('\n').filter(name => name.startsWith('crc-'));
|
|
929
|
+
for (const name of crcSessions) {
|
|
930
|
+
execFileAsync('tmux', ['kill-session', '-t', name]).catch(() => { });
|
|
931
|
+
}
|
|
932
|
+
if (crcSessions.length > 0) {
|
|
933
|
+
console.log(`Cleaned up ${crcSessions.length} orphaned tmux session(s).`);
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
catch {
|
|
937
|
+
// tmux not installed or no sessions — ignore
|
|
938
|
+
}
|
|
939
|
+
function gracefulShutdown() {
|
|
940
|
+
server.close();
|
|
941
|
+
// Kill all active sessions (PTY + tmux)
|
|
942
|
+
for (const s of sessions.list()) {
|
|
943
|
+
try {
|
|
944
|
+
sessions.kill(s.id);
|
|
945
|
+
}
|
|
946
|
+
catch { /* already exiting */ }
|
|
947
|
+
}
|
|
948
|
+
// Brief delay to let async tmux kill-session calls fire
|
|
949
|
+
setTimeout(() => process.exit(0), 200);
|
|
950
|
+
}
|
|
951
|
+
process.on('SIGTERM', gracefulShutdown);
|
|
952
|
+
process.on('SIGINT', gracefulShutdown);
|
|
894
953
|
server.listen(config.port, config.host, () => {
|
|
895
954
|
console.log(`claude-remote-cli listening on ${config.host}:${config.port}`);
|
|
896
955
|
});
|
package/dist/server/sessions.js
CHANGED
|
@@ -3,6 +3,7 @@ import crypto from 'node:crypto';
|
|
|
3
3
|
import fs from 'node:fs';
|
|
4
4
|
import os from 'node:os';
|
|
5
5
|
import path from 'node:path';
|
|
6
|
+
import { execFile } from 'node:child_process';
|
|
6
7
|
import { readMeta, writeMeta } from './config.js';
|
|
7
8
|
const AGENT_COMMANDS = {
|
|
8
9
|
claude: 'claude',
|
|
@@ -12,6 +13,20 @@ const AGENT_CONTINUE_ARGS = {
|
|
|
12
13
|
claude: ['--continue'],
|
|
13
14
|
codex: ['resume', '--last'],
|
|
14
15
|
};
|
|
16
|
+
const AGENT_YOLO_ARGS = {
|
|
17
|
+
claude: ['--dangerously-skip-permissions'],
|
|
18
|
+
codex: ['--full-auto'],
|
|
19
|
+
};
|
|
20
|
+
function generateTmuxSessionName(displayName, id) {
|
|
21
|
+
const sanitized = displayName.replace(/[^a-zA-Z0-9-]/g, '-').replace(/-+/g, '-').slice(0, 30);
|
|
22
|
+
return `crc-${sanitized}-${id.slice(0, 8)}`;
|
|
23
|
+
}
|
|
24
|
+
function resolveTmuxSpawn(command, args, tmuxSessionName) {
|
|
25
|
+
return {
|
|
26
|
+
command: 'tmux',
|
|
27
|
+
args: ['new-session', '-s', tmuxSessionName, '--', command, ...args],
|
|
28
|
+
};
|
|
29
|
+
}
|
|
15
30
|
// In-memory registry: id -> Session
|
|
16
31
|
const sessions = new Map();
|
|
17
32
|
const IDLE_TIMEOUT_MS = 5000;
|
|
@@ -20,14 +35,23 @@ let idleChangeCallback = null;
|
|
|
20
35
|
function onIdleChange(cb) {
|
|
21
36
|
idleChangeCallback = cb;
|
|
22
37
|
}
|
|
23
|
-
function create({ type, agent = 'claude', repoName, repoPath, cwd, root, worktreeName, branchName, displayName, command, args = [], cols = 80, rows = 24, configPath }) {
|
|
38
|
+
function create({ type, agent = 'claude', repoName, repoPath, cwd, root, worktreeName, branchName, displayName, command, args = [], cols = 80, rows = 24, configPath, useTmux: paramUseTmux }) {
|
|
24
39
|
const id = crypto.randomBytes(8).toString('hex');
|
|
25
40
|
const createdAt = new Date().toISOString();
|
|
26
41
|
const resolvedCommand = command || AGENT_COMMANDS[agent];
|
|
27
42
|
// Strip CLAUDECODE env var to allow spawning claude inside a claude-managed server
|
|
28
43
|
const env = Object.assign({}, process.env);
|
|
29
44
|
delete env.CLAUDECODE;
|
|
30
|
-
const
|
|
45
|
+
const useTmux = !command && !!paramUseTmux;
|
|
46
|
+
let spawnCommand = resolvedCommand;
|
|
47
|
+
let spawnArgs = args;
|
|
48
|
+
const tmuxSessionName = useTmux ? generateTmuxSessionName(displayName || repoName || 'session', id) : '';
|
|
49
|
+
if (useTmux) {
|
|
50
|
+
const tmux = resolveTmuxSpawn(resolvedCommand, args, tmuxSessionName);
|
|
51
|
+
spawnCommand = tmux.command;
|
|
52
|
+
spawnArgs = tmux.args;
|
|
53
|
+
}
|
|
54
|
+
const ptyProcess = pty.spawn(spawnCommand, spawnArgs, {
|
|
31
55
|
name: 'xterm-256color',
|
|
32
56
|
cols,
|
|
33
57
|
rows,
|
|
@@ -53,6 +77,8 @@ function create({ type, agent = 'claude', repoName, repoPath, cwd, root, worktre
|
|
|
53
77
|
lastActivity: createdAt,
|
|
54
78
|
scrollback,
|
|
55
79
|
idle: false,
|
|
80
|
+
useTmux,
|
|
81
|
+
tmuxSessionName,
|
|
56
82
|
};
|
|
57
83
|
sessions.set(id, session);
|
|
58
84
|
// Load existing metadata to preserve a previously-set displayName
|
|
@@ -106,7 +132,14 @@ function create({ type, agent = 'claude', repoName, repoPath, cwd, root, worktre
|
|
|
106
132
|
const retryArgs = args.filter(a => !continueArgs.includes(a));
|
|
107
133
|
scrollback.length = 0;
|
|
108
134
|
scrollbackBytes = 0;
|
|
109
|
-
|
|
135
|
+
let retryCommand = resolvedCommand;
|
|
136
|
+
let retrySpawnArgs = retryArgs;
|
|
137
|
+
if (useTmux && tmuxSessionName) {
|
|
138
|
+
const tmux = resolveTmuxSpawn(resolvedCommand, retryArgs, tmuxSessionName);
|
|
139
|
+
retryCommand = tmux.command;
|
|
140
|
+
retrySpawnArgs = tmux.args;
|
|
141
|
+
}
|
|
142
|
+
const retryPty = pty.spawn(retryCommand, retrySpawnArgs, {
|
|
110
143
|
name: 'xterm-256color',
|
|
111
144
|
cols,
|
|
112
145
|
rows,
|
|
@@ -130,14 +163,14 @@ function create({ type, agent = 'claude', repoName, repoPath, cwd, root, worktre
|
|
|
130
163
|
});
|
|
131
164
|
}
|
|
132
165
|
attachHandlers(ptyProcess, continueArgs.some(a => args.includes(a)));
|
|
133
|
-
return { id, type: session.type, agent: session.agent, root: session.root, repoName: session.repoName, repoPath, worktreeName: session.worktreeName, branchName: session.branchName, displayName: session.displayName, pid: ptyProcess.pid, createdAt, lastActivity: createdAt, idle: false };
|
|
166
|
+
return { id, type: session.type, agent: session.agent, root: session.root, repoName: session.repoName, repoPath, worktreeName: session.worktreeName, branchName: session.branchName, displayName: session.displayName, pid: ptyProcess.pid, createdAt, lastActivity: createdAt, idle: false, useTmux, tmuxSessionName };
|
|
134
167
|
}
|
|
135
168
|
function get(id) {
|
|
136
169
|
return sessions.get(id);
|
|
137
170
|
}
|
|
138
171
|
function list() {
|
|
139
172
|
return Array.from(sessions.values())
|
|
140
|
-
.map(({ id, type, agent, root, repoName, repoPath, worktreeName, branchName, displayName, createdAt, lastActivity, idle }) => ({
|
|
173
|
+
.map(({ id, type, agent, root, repoName, repoPath, worktreeName, branchName, displayName, createdAt, lastActivity, idle, useTmux, tmuxSessionName }) => ({
|
|
141
174
|
id,
|
|
142
175
|
type,
|
|
143
176
|
agent,
|
|
@@ -150,6 +183,8 @@ function list() {
|
|
|
150
183
|
createdAt,
|
|
151
184
|
lastActivity,
|
|
152
185
|
idle,
|
|
186
|
+
useTmux,
|
|
187
|
+
tmuxSessionName,
|
|
153
188
|
}))
|
|
154
189
|
.sort((a, b) => b.lastActivity.localeCompare(a.lastActivity));
|
|
155
190
|
}
|
|
@@ -166,8 +201,18 @@ function kill(id) {
|
|
|
166
201
|
throw new Error(`Session not found: ${id}`);
|
|
167
202
|
}
|
|
168
203
|
session.pty.kill('SIGTERM');
|
|
204
|
+
if (session.tmuxSessionName) {
|
|
205
|
+
execFile('tmux', ['kill-session', '-t', session.tmuxSessionName], () => { });
|
|
206
|
+
}
|
|
169
207
|
sessions.delete(id);
|
|
170
208
|
}
|
|
209
|
+
function killAllTmuxSessions() {
|
|
210
|
+
for (const session of sessions.values()) {
|
|
211
|
+
if (session.tmuxSessionName) {
|
|
212
|
+
execFile('tmux', ['kill-session', '-t', session.tmuxSessionName], () => { });
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
171
216
|
function resize(id, cols, rows) {
|
|
172
217
|
const session = sessions.get(id);
|
|
173
218
|
if (!session) {
|
|
@@ -188,4 +233,4 @@ function findRepoSession(repoPath) {
|
|
|
188
233
|
function nextTerminalName() {
|
|
189
234
|
return `Terminal ${++terminalCounter}`;
|
|
190
235
|
}
|
|
191
|
-
export { create, get, list, kill, resize, updateDisplayName, write, onIdleChange, findRepoSession, nextTerminalName, AGENT_COMMANDS, AGENT_CONTINUE_ARGS };
|
|
236
|
+
export { create, get, list, kill, killAllTmuxSessions, resize, updateDisplayName, write, onIdleChange, findRepoSession, nextTerminalName, AGENT_COMMANDS, AGENT_CONTINUE_ARGS, AGENT_YOLO_ARGS, resolveTmuxSpawn, generateTmuxSessionName };
|
package/dist/test/config.test.js
CHANGED
|
@@ -61,6 +61,17 @@ test('DEFAULTS has expected keys and values', () => {
|
|
|
61
61
|
assert.equal(DEFAULTS.claudeCommand, 'claude');
|
|
62
62
|
assert.deepEqual(DEFAULTS.claudeArgs, []);
|
|
63
63
|
assert.equal(DEFAULTS.defaultAgent, 'claude');
|
|
64
|
+
assert.equal(DEFAULTS.defaultContinue, true);
|
|
65
|
+
assert.equal(DEFAULTS.defaultYolo, false);
|
|
66
|
+
assert.equal(DEFAULTS.launchInTmux, false);
|
|
67
|
+
});
|
|
68
|
+
test('loadConfig returns correct defaults for defaultContinue, defaultYolo, and launchInTmux', () => {
|
|
69
|
+
const configPath = path.join(tmpDir, 'config.json');
|
|
70
|
+
fs.writeFileSync(configPath, JSON.stringify({ port: 3456 }), 'utf8');
|
|
71
|
+
const config = loadConfig(configPath);
|
|
72
|
+
assert.equal(config.defaultContinue, true);
|
|
73
|
+
assert.equal(config.defaultYolo, false);
|
|
74
|
+
assert.equal(config.launchInTmux, false);
|
|
64
75
|
});
|
|
65
76
|
test('ensureMetaDir creates worktree-meta directory', () => {
|
|
66
77
|
const configPath = path.join(tmpDir, 'config.json');
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { describe, it } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { listBranches, normalizeBranchNames } from '../server/git.js';
|
|
4
|
+
describe('normalizeBranchNames', () => {
|
|
5
|
+
it('deduplicates refs, strips origin prefixes, and skips HEAD entries', () => {
|
|
6
|
+
const stdout = [
|
|
7
|
+
'main',
|
|
8
|
+
'origin/main',
|
|
9
|
+
'origin/feat/remote-only',
|
|
10
|
+
'feat/local',
|
|
11
|
+
'remotes/origin/HEAD -> origin/main',
|
|
12
|
+
'origin/feat/remote-only',
|
|
13
|
+
'',
|
|
14
|
+
].join('\n');
|
|
15
|
+
assert.deepEqual(normalizeBranchNames(stdout), [
|
|
16
|
+
'feat/local',
|
|
17
|
+
'feat/remote-only',
|
|
18
|
+
'main',
|
|
19
|
+
]);
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
describe('listBranches', () => {
|
|
23
|
+
it('refreshes remotes before listing when requested', async () => {
|
|
24
|
+
const calls = [];
|
|
25
|
+
const branches = await listBranches('/tmp/repo', {
|
|
26
|
+
refresh: true,
|
|
27
|
+
exec: async (file, args, options) => {
|
|
28
|
+
calls.push({ file, args, cwd: options.cwd });
|
|
29
|
+
if (args[0] === 'fetch') {
|
|
30
|
+
return { stdout: '', stderr: '' };
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
stdout: ['main', 'origin/main', 'origin/feature/remote'].join('\n'),
|
|
34
|
+
stderr: '',
|
|
35
|
+
};
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
assert.deepEqual(calls, [
|
|
39
|
+
{ file: 'git', args: ['fetch', '--all', '--prune'], cwd: '/tmp/repo' },
|
|
40
|
+
{ file: 'git', args: ['branch', '-a', '--format=%(refname:short)'], cwd: '/tmp/repo' },
|
|
41
|
+
]);
|
|
42
|
+
assert.deepEqual(branches, ['feature/remote', 'main']);
|
|
43
|
+
});
|
|
44
|
+
it('falls back to locally-known refs if fetch fails', async () => {
|
|
45
|
+
const branches = await listBranches('/tmp/repo', {
|
|
46
|
+
refresh: true,
|
|
47
|
+
exec: async (_file, args) => {
|
|
48
|
+
if (args[0] === 'fetch') {
|
|
49
|
+
throw new Error('network down');
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
stdout: ['main', 'origin/feature/stale'].join('\n'),
|
|
53
|
+
stderr: '',
|
|
54
|
+
};
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
assert.deepEqual(branches, ['feature/stale', 'main']);
|
|
58
|
+
});
|
|
59
|
+
it('returns an empty list when refs cannot be listed', async () => {
|
|
60
|
+
const branches = await listBranches('/tmp/repo', {
|
|
61
|
+
exec: async () => {
|
|
62
|
+
throw new Error('git failed');
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
assert.deepEqual(branches, []);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { describe, it, afterEach } from 'node:test';
|
|
2
2
|
import assert from 'node:assert';
|
|
3
3
|
import * as sessions from '../server/sessions.js';
|
|
4
|
+
import { resolveTmuxSpawn, generateTmuxSessionName } from '../server/sessions.js';
|
|
4
5
|
// Track created session IDs so we can clean up after each test
|
|
5
6
|
const createdIds = [];
|
|
6
7
|
afterEach(() => {
|
|
@@ -264,6 +265,38 @@ describe('sessions', () => {
|
|
|
264
265
|
createdIds.push(result.id);
|
|
265
266
|
assert.strictEqual(result.branchName, '');
|
|
266
267
|
});
|
|
268
|
+
it('resolveTmuxSpawn returns correct tmux command and args', () => {
|
|
269
|
+
const result = resolveTmuxSpawn('claude', ['--continue'], 'test-session');
|
|
270
|
+
assert.deepStrictEqual(result, {
|
|
271
|
+
command: 'tmux',
|
|
272
|
+
args: ['new-session', '-s', 'test-session', '--', 'claude', '--continue'],
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
it('generateTmuxSessionName has crc- prefix', () => {
|
|
276
|
+
const name = generateTmuxSessionName('my-session', 'abcdef1234567890');
|
|
277
|
+
assert.ok(name.startsWith('crc-'), `expected crc- prefix, got: ${name}`);
|
|
278
|
+
});
|
|
279
|
+
it('generateTmuxSessionName sanitizes special characters', () => {
|
|
280
|
+
const name = generateTmuxSessionName('feat/auth-flow', 'abcdef1234567890');
|
|
281
|
+
assert.ok(name.startsWith('crc-feat-auth-flow-'), `expected sanitized name, got: ${name}`);
|
|
282
|
+
});
|
|
283
|
+
it('generateTmuxSessionName limits display name to 30 chars', () => {
|
|
284
|
+
const longName = 'a-very-long-display-name-that-exceeds-thirty-characters';
|
|
285
|
+
const id = 'abcdef1234567890';
|
|
286
|
+
const name = generateTmuxSessionName(longName, id);
|
|
287
|
+
// Format is crc-<sanitized up to 30>-<8 char id>
|
|
288
|
+
// The sanitized portion should be at most 30 chars
|
|
289
|
+
const withoutPrefix = name.slice('crc-'.length);
|
|
290
|
+
const parts = withoutPrefix.split('-');
|
|
291
|
+
const idPart = parts[parts.length - 1];
|
|
292
|
+
const displayPart = withoutPrefix.slice(0, withoutPrefix.length - idPart.length - 1);
|
|
293
|
+
assert.ok(displayPart.length <= 30, `display portion should be <= 30 chars, got: ${displayPart.length}`);
|
|
294
|
+
});
|
|
295
|
+
it('generateTmuxSessionName uses 8 chars from the provided id', () => {
|
|
296
|
+
const id = 'abcdef1234567890';
|
|
297
|
+
const name = generateTmuxSessionName('my-session', id);
|
|
298
|
+
assert.ok(name.endsWith(id.slice(0, 8)), `expected name to end with ${id.slice(0, 8)}, got: ${name}`);
|
|
299
|
+
});
|
|
267
300
|
it('agent defaults to claude when not specified', () => {
|
|
268
301
|
const result = sessions.create({
|
|
269
302
|
repoName: 'test-repo',
|
|
@@ -299,4 +332,42 @@ describe('sessions', () => {
|
|
|
299
332
|
assert.ok(session);
|
|
300
333
|
assert.strictEqual(session.agent, 'codex');
|
|
301
334
|
});
|
|
335
|
+
it('useTmux defaults to false when not specified', () => {
|
|
336
|
+
const result = sessions.create({
|
|
337
|
+
repoName: 'test-repo',
|
|
338
|
+
repoPath: '/tmp',
|
|
339
|
+
command: '/bin/echo',
|
|
340
|
+
args: ['hello'],
|
|
341
|
+
});
|
|
342
|
+
createdIds.push(result.id);
|
|
343
|
+
assert.strictEqual(result.useTmux, false);
|
|
344
|
+
assert.strictEqual(result.tmuxSessionName, '');
|
|
345
|
+
});
|
|
346
|
+
it('useTmux is disabled when custom command is provided even if useTmux is true', () => {
|
|
347
|
+
const result = sessions.create({
|
|
348
|
+
repoName: 'test-repo',
|
|
349
|
+
repoPath: '/tmp',
|
|
350
|
+
command: '/bin/echo',
|
|
351
|
+
args: ['hello'],
|
|
352
|
+
useTmux: true,
|
|
353
|
+
});
|
|
354
|
+
createdIds.push(result.id);
|
|
355
|
+
// Custom command sessions should never use tmux
|
|
356
|
+
assert.strictEqual(result.useTmux, false);
|
|
357
|
+
assert.strictEqual(result.tmuxSessionName, '');
|
|
358
|
+
});
|
|
359
|
+
it('list includes useTmux and tmuxSessionName fields', () => {
|
|
360
|
+
const result = sessions.create({
|
|
361
|
+
repoName: 'test-repo',
|
|
362
|
+
repoPath: '/tmp',
|
|
363
|
+
command: '/bin/echo',
|
|
364
|
+
args: ['hello'],
|
|
365
|
+
});
|
|
366
|
+
createdIds.push(result.id);
|
|
367
|
+
const list = sessions.list();
|
|
368
|
+
const session = list.find(s => s.id === result.id);
|
|
369
|
+
assert.ok(session);
|
|
370
|
+
assert.strictEqual(session.useTmux, false);
|
|
371
|
+
assert.strictEqual(session.tmuxSessionName, '');
|
|
372
|
+
});
|
|
302
373
|
});
|