groove-dev 0.10.10 → 0.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/README.md +24 -16
- package/node_modules/@groove-dev/cli/bin/groove.js +32 -0
- package/node_modules/@groove-dev/cli/package.json +1 -1
- package/node_modules/@groove-dev/cli/src/commands/audit.js +60 -0
- package/node_modules/@groove-dev/cli/src/commands/connect.js +279 -0
- package/node_modules/@groove-dev/cli/src/commands/disconnect.js +91 -0
- package/node_modules/@groove-dev/cli/src/commands/federation.js +84 -0
- package/node_modules/@groove-dev/cli/src/commands/start.js +7 -2
- package/node_modules/@groove-dev/cli/src/commands/status.js +4 -1
- package/node_modules/@groove-dev/daemon/package.json +1 -1
- package/node_modules/@groove-dev/daemon/src/api.js +128 -2
- package/node_modules/@groove-dev/daemon/src/audit.js +65 -0
- package/node_modules/@groove-dev/daemon/src/federation.js +352 -0
- package/node_modules/@groove-dev/daemon/src/firstrun.js +27 -2
- package/node_modules/@groove-dev/daemon/src/index.js +64 -6
- package/node_modules/@groove-dev/daemon/src/indexer.js +324 -0
- package/node_modules/@groove-dev/daemon/src/introducer.js +55 -4
- package/node_modules/@groove-dev/daemon/src/journalist.js +140 -51
- package/node_modules/@groove-dev/daemon/src/process.js +3 -2
- package/node_modules/@groove-dev/gui/dist/assets/index-B49YqEXS.js +73 -0
- package/{packages/gui/dist/assets/index-CPzm9ZE9.css → node_modules/@groove-dev/gui/dist/assets/index-Gfb8Zxy9.css} +1 -1
- package/node_modules/@groove-dev/gui/dist/index.html +2 -2
- package/node_modules/@groove-dev/gui/package.json +1 -1
- package/node_modules/@groove-dev/gui/src/App.jsx +24 -1
- package/node_modules/@groove-dev/gui/src/components/AgentNode.jsx +6 -4
- package/node_modules/@groove-dev/gui/src/components/AgentStats.jsx +1 -0
- package/node_modules/@groove-dev/gui/src/components/SpawnPanel.jsx +71 -1
- package/node_modules/@groove-dev/gui/src/stores/groove.js +19 -2
- package/node_modules/@groove-dev/gui/src/theme.css +2 -2
- package/package.json +1 -1
- package/packages/cli/bin/groove.js +32 -0
- package/packages/cli/package.json +1 -1
- package/packages/cli/src/commands/audit.js +60 -0
- package/packages/cli/src/commands/connect.js +279 -0
- package/packages/cli/src/commands/disconnect.js +91 -0
- package/packages/cli/src/commands/federation.js +84 -0
- package/packages/cli/src/commands/start.js +7 -2
- package/packages/cli/src/commands/status.js +4 -1
- package/packages/daemon/package.json +1 -1
- package/packages/daemon/src/api.js +128 -2
- package/packages/daemon/src/audit.js +65 -0
- package/packages/daemon/src/federation.js +352 -0
- package/packages/daemon/src/firstrun.js +27 -2
- package/packages/daemon/src/index.js +64 -6
- package/packages/daemon/src/indexer.js +324 -0
- package/packages/daemon/src/introducer.js +55 -4
- package/packages/daemon/src/journalist.js +140 -51
- package/packages/daemon/src/process.js +3 -2
- package/packages/gui/dist/assets/index-B49YqEXS.js +73 -0
- package/{node_modules/@groove-dev/gui/dist/assets/index-CPzm9ZE9.css → packages/gui/dist/assets/index-Gfb8Zxy9.css} +1 -1
- package/packages/gui/dist/index.html +2 -2
- package/packages/gui/package.json +1 -1
- package/packages/gui/src/App.jsx +24 -1
- package/packages/gui/src/components/AgentNode.jsx +6 -4
- package/packages/gui/src/components/AgentStats.jsx +1 -0
- package/packages/gui/src/components/SpawnPanel.jsx +71 -1
- package/packages/gui/src/stores/groove.js +19 -2
- package/packages/gui/src/theme.css +2 -2
- package/groove-logo.png +0 -0
- package/node_modules/@groove-dev/gui/dist/assets/index-BPVh7Oqk.js +0 -73
- package/packages/gui/dist/assets/index-BPVh7Oqk.js +0 -73
|
@@ -1 +1 @@
|
|
|
1
|
-
@import"https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700;800&display=swap";:root{--bg-base: #24282f;--bg-chrome: #282c34;--bg-surface: #2c313a;--bg-hover: #333842;--bg-active: #3a3f4b;--text-primary: #abb2bf;--text-bright: #e6e6e6;--text-dim: #5c6370;--text-muted: #3e4451;--border: #4b5263;--accent: #33afbc;--green: #4ae168;--amber: #e5c07b;--red: #e06c75;--purple: #c678dd;--blue: #61afef;--font: "JetBrains Mono", "SF Mono", "Fira Code", "Cascadia Code", Consolas, monospace}*{margin:0;padding:0;box-sizing:border-box}body{font-family:var(--font);background:var(--bg-base);color:var(--text-primary);font-size:12px;-webkit-font-smoothing:antialiased}#root{width:100vw;height:100vh}::-webkit-scrollbar{width:6px}::-webkit-scrollbar-track{background:transparent}::-webkit-scrollbar-thumb{background:var(--bg-hover);border-radius:2px}::selection{background:#33afbc40}@keyframes pulse{0%,to{opacity:1}50%{opacity:.5}}@keyframes neuralFlow{0%{transform:translate(-50%)}to{transform:translate(0)}}.react-flow__edge-path{stroke-width:1!important}.react-flow__edge.animated path{stroke-dasharray:6 4!important;stroke-width:1!important}.react-flow{direction:ltr;--xy-edge-stroke-default: #b1b1b7;--xy-edge-stroke-width-default: 1;--xy-edge-stroke-selected-default: #555;--xy-connectionline-stroke-default: #b1b1b7;--xy-connectionline-stroke-width-default: 1;--xy-attribution-background-color-default: rgba(255, 255, 255, .5);--xy-minimap-background-color-default: #fff;--xy-minimap-mask-background-color-default: rgba(240, 240, 240, .6);--xy-minimap-mask-stroke-color-default: transparent;--xy-minimap-mask-stroke-width-default: 1;--xy-minimap-node-background-color-default: #e2e2e2;--xy-minimap-node-stroke-color-default: transparent;--xy-minimap-node-stroke-width-default: 2;--xy-background-color-default: transparent;--xy-background-pattern-dots-color-default: #91919a;--xy-background-pattern-lines-color-default: #eee;--xy-background-pattern-cross-color-default: #e2e2e2;background-color:var(--xy-background-color, var(--xy-background-color-default));--xy-node-color-default: inherit;--xy-node-border-default: 1px solid #1a192b;--xy-node-background-color-default: #fff;--xy-node-group-background-color-default: rgba(240, 240, 240, .25);--xy-node-boxshadow-hover-default: 0 1px 4px 1px rgba(0, 0, 0, .08);--xy-node-boxshadow-selected-default: 0 0 0 .5px #1a192b;--xy-node-border-radius-default: 3px;--xy-handle-background-color-default: #1a192b;--xy-handle-border-color-default: #fff;--xy-selection-background-color-default: rgba(0, 89, 220, .08);--xy-selection-border-default: 1px dotted rgba(0, 89, 220, .8);--xy-controls-button-background-color-default: #fefefe;--xy-controls-button-background-color-hover-default: #f4f4f4;--xy-controls-button-color-default: inherit;--xy-controls-button-color-hover-default: inherit;--xy-controls-button-border-color-default: #eee;--xy-controls-box-shadow-default: 0 0 2px 1px rgba(0, 0, 0, .08);--xy-edge-label-background-color-default: #ffffff;--xy-edge-label-color-default: inherit;--xy-resize-background-color-default: #3367d9}.react-flow.dark{--xy-edge-stroke-default: #3e3e3e;--xy-edge-stroke-width-default: 1;--xy-edge-stroke-selected-default: #727272;--xy-connectionline-stroke-default: #b1b1b7;--xy-connectionline-stroke-width-default: 1;--xy-attribution-background-color-default: rgba(150, 150, 150, .25);--xy-minimap-background-color-default: #141414;--xy-minimap-mask-background-color-default: rgba(60, 60, 60, .6);--xy-minimap-mask-stroke-color-default: transparent;--xy-minimap-mask-stroke-width-default: 1;--xy-minimap-node-background-color-default: #2b2b2b;--xy-minimap-node-stroke-color-default: transparent;--xy-minimap-node-stroke-width-default: 2;--xy-background-color-default: #141414;--xy-background-pattern-dots-color-default: #777;--xy-background-pattern-lines-color-default: #777;--xy-background-pattern-cross-color-default: #777;--xy-node-color-default: #f8f8f8;--xy-node-border-default: 1px solid #3c3c3c;--xy-node-background-color-default: #1e1e1e;--xy-node-group-background-color-default: rgba(240, 240, 240, .25);--xy-node-boxshadow-hover-default: 0 1px 4px 1px rgba(255, 255, 255, .08);--xy-node-boxshadow-selected-default: 0 0 0 .5px #999;--xy-handle-background-color-default: #bebebe;--xy-handle-border-color-default: #1e1e1e;--xy-selection-background-color-default: rgba(200, 200, 220, .08);--xy-selection-border-default: 1px dotted rgba(200, 200, 220, .8);--xy-controls-button-background-color-default: #2b2b2b;--xy-controls-button-background-color-hover-default: #3e3e3e;--xy-controls-button-color-default: #f8f8f8;--xy-controls-button-color-hover-default: #fff;--xy-controls-button-border-color-default: #5b5b5b;--xy-controls-box-shadow-default: 0 0 2px 1px rgba(0, 0, 0, .08);--xy-edge-label-background-color-default: #141414;--xy-edge-label-color-default: #f8f8f8}.react-flow__background{background-color:var(--xy-background-color-props, var(--xy-background-color, var(--xy-background-color-default)));pointer-events:none;z-index:-1}.react-flow__container{position:absolute;width:100%;height:100%;top:0;left:0}.react-flow__pane{z-index:1}.react-flow__pane.draggable{cursor:grab}.react-flow__pane.dragging{cursor:grabbing}.react-flow__pane.selection{cursor:pointer}.react-flow__viewport{transform-origin:0 0;z-index:2;pointer-events:none}.react-flow__renderer{z-index:4}.react-flow__selection{z-index:6}.react-flow__nodesselection-rect:focus,.react-flow__nodesselection-rect:focus-visible{outline:none}.react-flow__edge-path{stroke:var(--xy-edge-stroke, var(--xy-edge-stroke-default));stroke-width:var(--xy-edge-stroke-width, var(--xy-edge-stroke-width-default));fill:none}.react-flow__connection-path{stroke:var(--xy-connectionline-stroke, var(--xy-connectionline-stroke-default));stroke-width:var(--xy-connectionline-stroke-width, var(--xy-connectionline-stroke-width-default));fill:none}.react-flow .react-flow__edges{position:absolute}.react-flow .react-flow__edges svg{overflow:visible;position:absolute;pointer-events:none}.react-flow__edge{pointer-events:visibleStroke}.react-flow__edge.selectable{cursor:pointer}.react-flow__edge.animated path{stroke-dasharray:5;animation:dashdraw .5s linear infinite}.react-flow__edge.animated path.react-flow__edge-interaction{stroke-dasharray:none;animation:none}.react-flow__edge.inactive{pointer-events:none}.react-flow__edge.selected,.react-flow__edge:focus,.react-flow__edge:focus-visible{outline:none}.react-flow__edge.selected .react-flow__edge-path,.react-flow__edge.selectable:focus .react-flow__edge-path,.react-flow__edge.selectable:focus-visible .react-flow__edge-path{stroke:var(--xy-edge-stroke-selected, var(--xy-edge-stroke-selected-default))}.react-flow__edge-textwrapper{pointer-events:all}.react-flow__edge .react-flow__edge-text{pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}.react-flow__arrowhead polyline{stroke:var(--xy-edge-stroke, var(--xy-edge-stroke-default))}.react-flow__arrowhead polyline.arrowclosed{fill:var(--xy-edge-stroke, var(--xy-edge-stroke-default))}.react-flow__connection{pointer-events:none}.react-flow__connection .animated{stroke-dasharray:5;animation:dashdraw .5s linear infinite}svg.react-flow__connectionline{z-index:1001;overflow:visible;position:absolute}.react-flow__nodes{pointer-events:none;transform-origin:0 0}.react-flow__node{position:absolute;-webkit-user-select:none;-moz-user-select:none;user-select:none;pointer-events:all;transform-origin:0 0;box-sizing:border-box;cursor:default}.react-flow__node.selectable{cursor:pointer}.react-flow__node.draggable{cursor:grab;pointer-events:all}.react-flow__node.draggable.dragging{cursor:grabbing}.react-flow__nodesselection{z-index:3;transform-origin:left top;pointer-events:none}.react-flow__nodesselection-rect{position:absolute;pointer-events:all;cursor:grab}.react-flow__handle{position:absolute;pointer-events:none;min-width:5px;min-height:5px;width:6px;height:6px;background-color:var(--xy-handle-background-color, var(--xy-handle-background-color-default));border:1px solid var(--xy-handle-border-color, var(--xy-handle-border-color-default));border-radius:100%}.react-flow__handle.connectingfrom{pointer-events:all}.react-flow__handle.connectionindicator{pointer-events:all;cursor:crosshair}.react-flow__handle-bottom{top:auto;left:50%;bottom:0;transform:translate(-50%,50%)}.react-flow__handle-top{top:0;left:50%;transform:translate(-50%,-50%)}.react-flow__handle-left{top:50%;left:0;transform:translate(-50%,-50%)}.react-flow__handle-right{top:50%;right:0;transform:translate(50%,-50%)}.react-flow__edgeupdater{cursor:move;pointer-events:all}.react-flow__pane.selection .react-flow__panel{pointer-events:none}.react-flow__panel{position:absolute;z-index:5;margin:15px}.react-flow__panel.top{top:0}.react-flow__panel.bottom{bottom:0}.react-flow__panel.top.center,.react-flow__panel.bottom.center{left:50%;transform:translate(-15px) translate(-50%)}.react-flow__panel.left{left:0}.react-flow__panel.right{right:0}.react-flow__panel.left.center,.react-flow__panel.right.center{top:50%;transform:translateY(-15px) translateY(-50%)}.react-flow__attribution{font-size:10px;background:var(--xy-attribution-background-color, var(--xy-attribution-background-color-default));padding:2px 3px;margin:0}.react-flow__attribution a{text-decoration:none;color:#999}@keyframes dashdraw{0%{stroke-dashoffset:10}}.react-flow__edgelabel-renderer{position:absolute;width:100%;height:100%;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;left:0;top:0}.react-flow__viewport-portal{position:absolute;width:100%;height:100%;left:0;top:0;-webkit-user-select:none;-moz-user-select:none;user-select:none}.react-flow__minimap{background:var( --xy-minimap-background-color-props, var(--xy-minimap-background-color, var(--xy-minimap-background-color-default)) )}.react-flow__minimap-svg{display:block}.react-flow__minimap-mask{fill:var( --xy-minimap-mask-background-color-props, var(--xy-minimap-mask-background-color, var(--xy-minimap-mask-background-color-default)) );stroke:var( --xy-minimap-mask-stroke-color-props, var(--xy-minimap-mask-stroke-color, var(--xy-minimap-mask-stroke-color-default)) );stroke-width:var( --xy-minimap-mask-stroke-width-props, var(--xy-minimap-mask-stroke-width, var(--xy-minimap-mask-stroke-width-default)) )}.react-flow__minimap-node{fill:var( --xy-minimap-node-background-color-props, var(--xy-minimap-node-background-color, var(--xy-minimap-node-background-color-default)) );stroke:var( --xy-minimap-node-stroke-color-props, var(--xy-minimap-node-stroke-color, var(--xy-minimap-node-stroke-color-default)) );stroke-width:var( --xy-minimap-node-stroke-width-props, var(--xy-minimap-node-stroke-width, var(--xy-minimap-node-stroke-width-default)) )}.react-flow__background-pattern.dots{fill:var( --xy-background-pattern-color-props, var(--xy-background-pattern-color, var(--xy-background-pattern-dots-color-default)) )}.react-flow__background-pattern.lines{stroke:var( --xy-background-pattern-color-props, var(--xy-background-pattern-color, var(--xy-background-pattern-lines-color-default)) )}.react-flow__background-pattern.cross{stroke:var( --xy-background-pattern-color-props, var(--xy-background-pattern-color, var(--xy-background-pattern-cross-color-default)) )}.react-flow__controls{display:flex;flex-direction:column;box-shadow:var(--xy-controls-box-shadow, var(--xy-controls-box-shadow-default))}.react-flow__controls.horizontal{flex-direction:row}.react-flow__controls-button{display:flex;justify-content:center;align-items:center;height:26px;width:26px;padding:4px;border:none;background:var(--xy-controls-button-background-color, var(--xy-controls-button-background-color-default));border-bottom:1px solid var( --xy-controls-button-border-color-props, var(--xy-controls-button-border-color, var(--xy-controls-button-border-color-default)) );color:var( --xy-controls-button-color-props, var(--xy-controls-button-color, var(--xy-controls-button-color-default)) );cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none}.react-flow__controls-button svg{width:100%;max-width:12px;max-height:12px;fill:currentColor}.react-flow__edge.updating .react-flow__edge-path{stroke:#777}.react-flow__edge-text{font-size:10px}.react-flow__node.selectable:focus,.react-flow__node.selectable:focus-visible{outline:none}.react-flow__node-input,.react-flow__node-default,.react-flow__node-output,.react-flow__node-group{padding:10px;border-radius:var(--xy-node-border-radius, var(--xy-node-border-radius-default));width:150px;font-size:12px;color:var(--xy-node-color, var(--xy-node-color-default));text-align:center;border:var(--xy-node-border, var(--xy-node-border-default));background-color:var(--xy-node-background-color, var(--xy-node-background-color-default))}.react-flow__node-input.selectable:hover,.react-flow__node-default.selectable:hover,.react-flow__node-output.selectable:hover,.react-flow__node-group.selectable:hover{box-shadow:var(--xy-node-boxshadow-hover, var(--xy-node-boxshadow-hover-default))}.react-flow__node-input.selectable.selected,.react-flow__node-input.selectable:focus,.react-flow__node-input.selectable:focus-visible,.react-flow__node-default.selectable.selected,.react-flow__node-default.selectable:focus,.react-flow__node-default.selectable:focus-visible,.react-flow__node-output.selectable.selected,.react-flow__node-output.selectable:focus,.react-flow__node-output.selectable:focus-visible,.react-flow__node-group.selectable.selected,.react-flow__node-group.selectable:focus,.react-flow__node-group.selectable:focus-visible{box-shadow:var(--xy-node-boxshadow-selected, var(--xy-node-boxshadow-selected-default))}.react-flow__node-group{background-color:var(--xy-node-group-background-color, var(--xy-node-group-background-color-default))}.react-flow__nodesselection-rect,.react-flow__selection{background:var(--xy-selection-background-color, var(--xy-selection-background-color-default));border:var(--xy-selection-border, var(--xy-selection-border-default))}.react-flow__nodesselection-rect:focus,.react-flow__nodesselection-rect:focus-visible,.react-flow__selection:focus,.react-flow__selection:focus-visible{outline:none}.react-flow__controls-button:hover{background:var( --xy-controls-button-background-color-hover-props, var(--xy-controls-button-background-color-hover, var(--xy-controls-button-background-color-hover-default)) );color:var( --xy-controls-button-color-hover-props, var(--xy-controls-button-color-hover, var(--xy-controls-button-color-hover-default)) )}.react-flow__controls-button:disabled{pointer-events:none}.react-flow__controls-button:disabled svg{fill-opacity:.4}.react-flow__controls-button:last-child{border-bottom:none}.react-flow__controls.horizontal .react-flow__controls-button{border-bottom:none;border-right:1px solid var( --xy-controls-button-border-color-props, var(--xy-controls-button-border-color, var(--xy-controls-button-border-color-default)) )}.react-flow__controls.horizontal .react-flow__controls-button:last-child{border-right:none}.react-flow__resize-control{position:absolute}.react-flow__resize-control.left,.react-flow__resize-control.right{cursor:ew-resize}.react-flow__resize-control.top,.react-flow__resize-control.bottom{cursor:ns-resize}.react-flow__resize-control.top.left,.react-flow__resize-control.bottom.right{cursor:nwse-resize}.react-flow__resize-control.bottom.left,.react-flow__resize-control.top.right{cursor:nesw-resize}.react-flow__resize-control.handle{width:5px;height:5px;border:1px solid #fff;border-radius:1px;background-color:var(--xy-resize-background-color, var(--xy-resize-background-color-default));translate:-50% -50%}.react-flow__resize-control.handle.left{left:0;top:50%}.react-flow__resize-control.handle.right{left:100%;top:50%}.react-flow__resize-control.handle.top{left:50%;top:0}.react-flow__resize-control.handle.bottom{left:50%;top:100%}.react-flow__resize-control.handle.top.left,.react-flow__resize-control.handle.bottom.left{left:0}.react-flow__resize-control.handle.top.right,.react-flow__resize-control.handle.bottom.right{left:100%}.react-flow__resize-control.line{border-color:var(--xy-resize-background-color, var(--xy-resize-background-color-default));border-width:0;border-style:solid}.react-flow__resize-control.line.left,.react-flow__resize-control.line.right{width:1px;transform:translate(-50%);top:0;height:100%}.react-flow__resize-control.line.left{left:0;border-left-width:1px}.react-flow__resize-control.line.right{left:100%;border-right-width:1px}.react-flow__resize-control.line.top,.react-flow__resize-control.line.bottom{height:1px;transform:translateY(-50%);left:0;width:100%}.react-flow__resize-control.line.top{top:0;border-top-width:1px}.react-flow__resize-control.line.bottom{border-bottom-width:1px;top:100%}.react-flow__edge-textbg{fill:var(--xy-edge-label-background-color, var(--xy-edge-label-background-color-default))}.react-flow__edge-text{fill:var(--xy-edge-label-color, var(--xy-edge-label-color-default))}
|
|
1
|
+
@import"https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700;800&display=swap";:root{--bg-base: #24282f;--bg-chrome: #282c34;--bg-surface: #2c313a;--bg-hover: #333842;--bg-active: #3a3f4b;--text-primary: #abb2bf;--text-bright: #e6e6e6;--text-dim: #7a8394;--text-muted: #5c6370;--border: #4b5263;--accent: #33afbc;--green: #4ae168;--amber: #e5c07b;--red: #e06c75;--purple: #c678dd;--blue: #61afef;--font: "JetBrains Mono", "SF Mono", "Fira Code", "Cascadia Code", Consolas, monospace}*{margin:0;padding:0;box-sizing:border-box}body{font-family:var(--font);background:var(--bg-base);color:var(--text-primary);font-size:12px;-webkit-font-smoothing:antialiased}#root{width:100vw;height:100vh}::-webkit-scrollbar{width:6px}::-webkit-scrollbar-track{background:transparent}::-webkit-scrollbar-thumb{background:var(--bg-hover);border-radius:2px}::selection{background:#33afbc40}@keyframes pulse{0%,to{opacity:1}50%{opacity:.5}}@keyframes neuralFlow{0%{transform:translate(-50%)}to{transform:translate(0)}}.react-flow__edge-path{stroke-width:1!important}.react-flow__edge.animated path{stroke-dasharray:6 4!important;stroke-width:1!important}.react-flow{direction:ltr;--xy-edge-stroke-default: #b1b1b7;--xy-edge-stroke-width-default: 1;--xy-edge-stroke-selected-default: #555;--xy-connectionline-stroke-default: #b1b1b7;--xy-connectionline-stroke-width-default: 1;--xy-attribution-background-color-default: rgba(255, 255, 255, .5);--xy-minimap-background-color-default: #fff;--xy-minimap-mask-background-color-default: rgba(240, 240, 240, .6);--xy-minimap-mask-stroke-color-default: transparent;--xy-minimap-mask-stroke-width-default: 1;--xy-minimap-node-background-color-default: #e2e2e2;--xy-minimap-node-stroke-color-default: transparent;--xy-minimap-node-stroke-width-default: 2;--xy-background-color-default: transparent;--xy-background-pattern-dots-color-default: #91919a;--xy-background-pattern-lines-color-default: #eee;--xy-background-pattern-cross-color-default: #e2e2e2;background-color:var(--xy-background-color, var(--xy-background-color-default));--xy-node-color-default: inherit;--xy-node-border-default: 1px solid #1a192b;--xy-node-background-color-default: #fff;--xy-node-group-background-color-default: rgba(240, 240, 240, .25);--xy-node-boxshadow-hover-default: 0 1px 4px 1px rgba(0, 0, 0, .08);--xy-node-boxshadow-selected-default: 0 0 0 .5px #1a192b;--xy-node-border-radius-default: 3px;--xy-handle-background-color-default: #1a192b;--xy-handle-border-color-default: #fff;--xy-selection-background-color-default: rgba(0, 89, 220, .08);--xy-selection-border-default: 1px dotted rgba(0, 89, 220, .8);--xy-controls-button-background-color-default: #fefefe;--xy-controls-button-background-color-hover-default: #f4f4f4;--xy-controls-button-color-default: inherit;--xy-controls-button-color-hover-default: inherit;--xy-controls-button-border-color-default: #eee;--xy-controls-box-shadow-default: 0 0 2px 1px rgba(0, 0, 0, .08);--xy-edge-label-background-color-default: #ffffff;--xy-edge-label-color-default: inherit;--xy-resize-background-color-default: #3367d9}.react-flow.dark{--xy-edge-stroke-default: #3e3e3e;--xy-edge-stroke-width-default: 1;--xy-edge-stroke-selected-default: #727272;--xy-connectionline-stroke-default: #b1b1b7;--xy-connectionline-stroke-width-default: 1;--xy-attribution-background-color-default: rgba(150, 150, 150, .25);--xy-minimap-background-color-default: #141414;--xy-minimap-mask-background-color-default: rgba(60, 60, 60, .6);--xy-minimap-mask-stroke-color-default: transparent;--xy-minimap-mask-stroke-width-default: 1;--xy-minimap-node-background-color-default: #2b2b2b;--xy-minimap-node-stroke-color-default: transparent;--xy-minimap-node-stroke-width-default: 2;--xy-background-color-default: #141414;--xy-background-pattern-dots-color-default: #777;--xy-background-pattern-lines-color-default: #777;--xy-background-pattern-cross-color-default: #777;--xy-node-color-default: #f8f8f8;--xy-node-border-default: 1px solid #3c3c3c;--xy-node-background-color-default: #1e1e1e;--xy-node-group-background-color-default: rgba(240, 240, 240, .25);--xy-node-boxshadow-hover-default: 0 1px 4px 1px rgba(255, 255, 255, .08);--xy-node-boxshadow-selected-default: 0 0 0 .5px #999;--xy-handle-background-color-default: #bebebe;--xy-handle-border-color-default: #1e1e1e;--xy-selection-background-color-default: rgba(200, 200, 220, .08);--xy-selection-border-default: 1px dotted rgba(200, 200, 220, .8);--xy-controls-button-background-color-default: #2b2b2b;--xy-controls-button-background-color-hover-default: #3e3e3e;--xy-controls-button-color-default: #f8f8f8;--xy-controls-button-color-hover-default: #fff;--xy-controls-button-border-color-default: #5b5b5b;--xy-controls-box-shadow-default: 0 0 2px 1px rgba(0, 0, 0, .08);--xy-edge-label-background-color-default: #141414;--xy-edge-label-color-default: #f8f8f8}.react-flow__background{background-color:var(--xy-background-color-props, var(--xy-background-color, var(--xy-background-color-default)));pointer-events:none;z-index:-1}.react-flow__container{position:absolute;width:100%;height:100%;top:0;left:0}.react-flow__pane{z-index:1}.react-flow__pane.draggable{cursor:grab}.react-flow__pane.dragging{cursor:grabbing}.react-flow__pane.selection{cursor:pointer}.react-flow__viewport{transform-origin:0 0;z-index:2;pointer-events:none}.react-flow__renderer{z-index:4}.react-flow__selection{z-index:6}.react-flow__nodesselection-rect:focus,.react-flow__nodesselection-rect:focus-visible{outline:none}.react-flow__edge-path{stroke:var(--xy-edge-stroke, var(--xy-edge-stroke-default));stroke-width:var(--xy-edge-stroke-width, var(--xy-edge-stroke-width-default));fill:none}.react-flow__connection-path{stroke:var(--xy-connectionline-stroke, var(--xy-connectionline-stroke-default));stroke-width:var(--xy-connectionline-stroke-width, var(--xy-connectionline-stroke-width-default));fill:none}.react-flow .react-flow__edges{position:absolute}.react-flow .react-flow__edges svg{overflow:visible;position:absolute;pointer-events:none}.react-flow__edge{pointer-events:visibleStroke}.react-flow__edge.selectable{cursor:pointer}.react-flow__edge.animated path{stroke-dasharray:5;animation:dashdraw .5s linear infinite}.react-flow__edge.animated path.react-flow__edge-interaction{stroke-dasharray:none;animation:none}.react-flow__edge.inactive{pointer-events:none}.react-flow__edge.selected,.react-flow__edge:focus,.react-flow__edge:focus-visible{outline:none}.react-flow__edge.selected .react-flow__edge-path,.react-flow__edge.selectable:focus .react-flow__edge-path,.react-flow__edge.selectable:focus-visible .react-flow__edge-path{stroke:var(--xy-edge-stroke-selected, var(--xy-edge-stroke-selected-default))}.react-flow__edge-textwrapper{pointer-events:all}.react-flow__edge .react-flow__edge-text{pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}.react-flow__arrowhead polyline{stroke:var(--xy-edge-stroke, var(--xy-edge-stroke-default))}.react-flow__arrowhead polyline.arrowclosed{fill:var(--xy-edge-stroke, var(--xy-edge-stroke-default))}.react-flow__connection{pointer-events:none}.react-flow__connection .animated{stroke-dasharray:5;animation:dashdraw .5s linear infinite}svg.react-flow__connectionline{z-index:1001;overflow:visible;position:absolute}.react-flow__nodes{pointer-events:none;transform-origin:0 0}.react-flow__node{position:absolute;-webkit-user-select:none;-moz-user-select:none;user-select:none;pointer-events:all;transform-origin:0 0;box-sizing:border-box;cursor:default}.react-flow__node.selectable{cursor:pointer}.react-flow__node.draggable{cursor:grab;pointer-events:all}.react-flow__node.draggable.dragging{cursor:grabbing}.react-flow__nodesselection{z-index:3;transform-origin:left top;pointer-events:none}.react-flow__nodesselection-rect{position:absolute;pointer-events:all;cursor:grab}.react-flow__handle{position:absolute;pointer-events:none;min-width:5px;min-height:5px;width:6px;height:6px;background-color:var(--xy-handle-background-color, var(--xy-handle-background-color-default));border:1px solid var(--xy-handle-border-color, var(--xy-handle-border-color-default));border-radius:100%}.react-flow__handle.connectingfrom{pointer-events:all}.react-flow__handle.connectionindicator{pointer-events:all;cursor:crosshair}.react-flow__handle-bottom{top:auto;left:50%;bottom:0;transform:translate(-50%,50%)}.react-flow__handle-top{top:0;left:50%;transform:translate(-50%,-50%)}.react-flow__handle-left{top:50%;left:0;transform:translate(-50%,-50%)}.react-flow__handle-right{top:50%;right:0;transform:translate(50%,-50%)}.react-flow__edgeupdater{cursor:move;pointer-events:all}.react-flow__pane.selection .react-flow__panel{pointer-events:none}.react-flow__panel{position:absolute;z-index:5;margin:15px}.react-flow__panel.top{top:0}.react-flow__panel.bottom{bottom:0}.react-flow__panel.top.center,.react-flow__panel.bottom.center{left:50%;transform:translate(-15px) translate(-50%)}.react-flow__panel.left{left:0}.react-flow__panel.right{right:0}.react-flow__panel.left.center,.react-flow__panel.right.center{top:50%;transform:translateY(-15px) translateY(-50%)}.react-flow__attribution{font-size:10px;background:var(--xy-attribution-background-color, var(--xy-attribution-background-color-default));padding:2px 3px;margin:0}.react-flow__attribution a{text-decoration:none;color:#999}@keyframes dashdraw{0%{stroke-dashoffset:10}}.react-flow__edgelabel-renderer{position:absolute;width:100%;height:100%;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;left:0;top:0}.react-flow__viewport-portal{position:absolute;width:100%;height:100%;left:0;top:0;-webkit-user-select:none;-moz-user-select:none;user-select:none}.react-flow__minimap{background:var( --xy-minimap-background-color-props, var(--xy-minimap-background-color, var(--xy-minimap-background-color-default)) )}.react-flow__minimap-svg{display:block}.react-flow__minimap-mask{fill:var( --xy-minimap-mask-background-color-props, var(--xy-minimap-mask-background-color, var(--xy-minimap-mask-background-color-default)) );stroke:var( --xy-minimap-mask-stroke-color-props, var(--xy-minimap-mask-stroke-color, var(--xy-minimap-mask-stroke-color-default)) );stroke-width:var( --xy-minimap-mask-stroke-width-props, var(--xy-minimap-mask-stroke-width, var(--xy-minimap-mask-stroke-width-default)) )}.react-flow__minimap-node{fill:var( --xy-minimap-node-background-color-props, var(--xy-minimap-node-background-color, var(--xy-minimap-node-background-color-default)) );stroke:var( --xy-minimap-node-stroke-color-props, var(--xy-minimap-node-stroke-color, var(--xy-minimap-node-stroke-color-default)) );stroke-width:var( --xy-minimap-node-stroke-width-props, var(--xy-minimap-node-stroke-width, var(--xy-minimap-node-stroke-width-default)) )}.react-flow__background-pattern.dots{fill:var( --xy-background-pattern-color-props, var(--xy-background-pattern-color, var(--xy-background-pattern-dots-color-default)) )}.react-flow__background-pattern.lines{stroke:var( --xy-background-pattern-color-props, var(--xy-background-pattern-color, var(--xy-background-pattern-lines-color-default)) )}.react-flow__background-pattern.cross{stroke:var( --xy-background-pattern-color-props, var(--xy-background-pattern-color, var(--xy-background-pattern-cross-color-default)) )}.react-flow__controls{display:flex;flex-direction:column;box-shadow:var(--xy-controls-box-shadow, var(--xy-controls-box-shadow-default))}.react-flow__controls.horizontal{flex-direction:row}.react-flow__controls-button{display:flex;justify-content:center;align-items:center;height:26px;width:26px;padding:4px;border:none;background:var(--xy-controls-button-background-color, var(--xy-controls-button-background-color-default));border-bottom:1px solid var( --xy-controls-button-border-color-props, var(--xy-controls-button-border-color, var(--xy-controls-button-border-color-default)) );color:var( --xy-controls-button-color-props, var(--xy-controls-button-color, var(--xy-controls-button-color-default)) );cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none}.react-flow__controls-button svg{width:100%;max-width:12px;max-height:12px;fill:currentColor}.react-flow__edge.updating .react-flow__edge-path{stroke:#777}.react-flow__edge-text{font-size:10px}.react-flow__node.selectable:focus,.react-flow__node.selectable:focus-visible{outline:none}.react-flow__node-input,.react-flow__node-default,.react-flow__node-output,.react-flow__node-group{padding:10px;border-radius:var(--xy-node-border-radius, var(--xy-node-border-radius-default));width:150px;font-size:12px;color:var(--xy-node-color, var(--xy-node-color-default));text-align:center;border:var(--xy-node-border, var(--xy-node-border-default));background-color:var(--xy-node-background-color, var(--xy-node-background-color-default))}.react-flow__node-input.selectable:hover,.react-flow__node-default.selectable:hover,.react-flow__node-output.selectable:hover,.react-flow__node-group.selectable:hover{box-shadow:var(--xy-node-boxshadow-hover, var(--xy-node-boxshadow-hover-default))}.react-flow__node-input.selectable.selected,.react-flow__node-input.selectable:focus,.react-flow__node-input.selectable:focus-visible,.react-flow__node-default.selectable.selected,.react-flow__node-default.selectable:focus,.react-flow__node-default.selectable:focus-visible,.react-flow__node-output.selectable.selected,.react-flow__node-output.selectable:focus,.react-flow__node-output.selectable:focus-visible,.react-flow__node-group.selectable.selected,.react-flow__node-group.selectable:focus,.react-flow__node-group.selectable:focus-visible{box-shadow:var(--xy-node-boxshadow-selected, var(--xy-node-boxshadow-selected-default))}.react-flow__node-group{background-color:var(--xy-node-group-background-color, var(--xy-node-group-background-color-default))}.react-flow__nodesselection-rect,.react-flow__selection{background:var(--xy-selection-background-color, var(--xy-selection-background-color-default));border:var(--xy-selection-border, var(--xy-selection-border-default))}.react-flow__nodesselection-rect:focus,.react-flow__nodesselection-rect:focus-visible,.react-flow__selection:focus,.react-flow__selection:focus-visible{outline:none}.react-flow__controls-button:hover{background:var( --xy-controls-button-background-color-hover-props, var(--xy-controls-button-background-color-hover, var(--xy-controls-button-background-color-hover-default)) );color:var( --xy-controls-button-color-hover-props, var(--xy-controls-button-color-hover, var(--xy-controls-button-color-hover-default)) )}.react-flow__controls-button:disabled{pointer-events:none}.react-flow__controls-button:disabled svg{fill-opacity:.4}.react-flow__controls-button:last-child{border-bottom:none}.react-flow__controls.horizontal .react-flow__controls-button{border-bottom:none;border-right:1px solid var( --xy-controls-button-border-color-props, var(--xy-controls-button-border-color, var(--xy-controls-button-border-color-default)) )}.react-flow__controls.horizontal .react-flow__controls-button:last-child{border-right:none}.react-flow__resize-control{position:absolute}.react-flow__resize-control.left,.react-flow__resize-control.right{cursor:ew-resize}.react-flow__resize-control.top,.react-flow__resize-control.bottom{cursor:ns-resize}.react-flow__resize-control.top.left,.react-flow__resize-control.bottom.right{cursor:nwse-resize}.react-flow__resize-control.bottom.left,.react-flow__resize-control.top.right{cursor:nesw-resize}.react-flow__resize-control.handle{width:5px;height:5px;border:1px solid #fff;border-radius:1px;background-color:var(--xy-resize-background-color, var(--xy-resize-background-color-default));translate:-50% -50%}.react-flow__resize-control.handle.left{left:0;top:50%}.react-flow__resize-control.handle.right{left:100%;top:50%}.react-flow__resize-control.handle.top{left:50%;top:0}.react-flow__resize-control.handle.bottom{left:50%;top:100%}.react-flow__resize-control.handle.top.left,.react-flow__resize-control.handle.bottom.left{left:0}.react-flow__resize-control.handle.top.right,.react-flow__resize-control.handle.bottom.right{left:100%}.react-flow__resize-control.line{border-color:var(--xy-resize-background-color, var(--xy-resize-background-color-default));border-width:0;border-style:solid}.react-flow__resize-control.line.left,.react-flow__resize-control.line.right{width:1px;transform:translate(-50%);top:0;height:100%}.react-flow__resize-control.line.left{left:0;border-left-width:1px}.react-flow__resize-control.line.right{left:100%;border-right-width:1px}.react-flow__resize-control.line.top,.react-flow__resize-control.line.bottom{height:1px;transform:translateY(-50%);left:0;width:100%}.react-flow__resize-control.line.top{top:0;border-top-width:1px}.react-flow__resize-control.line.bottom{border-bottom-width:1px;top:100%}.react-flow__edge-textbg{fill:var(--xy-edge-label-background-color, var(--xy-edge-label-background-color-default))}.react-flow__edge-text{fill:var(--xy-edge-label-color, var(--xy-edge-label-color-default))}
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>GROOVE</title>
|
|
7
|
-
<script type="module" crossorigin src="/assets/index-
|
|
8
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
7
|
+
<script type="module" crossorigin src="/assets/index-B49YqEXS.js"></script>
|
|
8
|
+
<link rel="stylesheet" crossorigin href="/assets/index-Gfb8Zxy9.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
|
11
11
|
<div id="root"></div>
|
|
@@ -25,6 +25,8 @@ export default function App() {
|
|
|
25
25
|
const activeTab = useGrooveStore((s) => s.activeTab);
|
|
26
26
|
const detailPanel = useGrooveStore((s) => s.detailPanel);
|
|
27
27
|
const statusMessage = useGrooveStore((s) => s.statusMessage);
|
|
28
|
+
const daemonHost = useGrooveStore((s) => s.daemonHost);
|
|
29
|
+
const tunneled = useGrooveStore((s) => s.tunneled);
|
|
28
30
|
const connect = useGrooveStore((s) => s.connect);
|
|
29
31
|
const setActiveTab = useGrooveStore((s) => s.setActiveTab);
|
|
30
32
|
const openDetail = useGrooveStore((s) => s.openDetail);
|
|
@@ -41,6 +43,9 @@ export default function App() {
|
|
|
41
43
|
<header style={styles.header}>
|
|
42
44
|
<div style={styles.headerLeft}>
|
|
43
45
|
<img src="/groove-logo-short.png" alt="GROOVE" style={{ height: 18, marginTop: 3, opacity: 0.85 }} />
|
|
46
|
+
{daemonHost && (
|
|
47
|
+
<span style={styles.hostBadge}>{daemonHost}</span>
|
|
48
|
+
)}
|
|
44
49
|
</div>
|
|
45
50
|
|
|
46
51
|
|
|
@@ -112,8 +117,19 @@ export default function App() {
|
|
|
112
117
|
fontFamily: 'var(--font)',
|
|
113
118
|
animation: 'pulse 3s infinite',
|
|
114
119
|
}}>
|
|
115
|
-
{connected ? 'connected' : 'offline'}
|
|
120
|
+
{connected ? (tunneled ? 'tunneled' : daemonHost ? 'remote' : 'connected') : 'offline'}
|
|
116
121
|
</span>
|
|
122
|
+
{connected && tunneled && (
|
|
123
|
+
<span
|
|
124
|
+
title="Connected via SSH tunnel. Run 'groove disconnect' in your terminal to close."
|
|
125
|
+
style={{
|
|
126
|
+
fontSize: 9, color: 'var(--text-dim)', fontFamily: 'var(--font)',
|
|
127
|
+
cursor: 'help', marginLeft: 2,
|
|
128
|
+
}}
|
|
129
|
+
>
|
|
130
|
+
(via ssh)
|
|
131
|
+
</span>
|
|
132
|
+
)}
|
|
117
133
|
</div>
|
|
118
134
|
|
|
119
135
|
{/* Main row */}
|
|
@@ -162,6 +178,13 @@ const styles = {
|
|
|
162
178
|
headerLeft: {
|
|
163
179
|
display: 'flex', alignItems: 'center', gap: 8,
|
|
164
180
|
},
|
|
181
|
+
hostBadge: {
|
|
182
|
+
fontSize: 9, fontWeight: 600, letterSpacing: 0.5,
|
|
183
|
+
color: 'var(--text-dim)', background: 'var(--bg-active)',
|
|
184
|
+
padding: '2px 6px', borderRadius: 3,
|
|
185
|
+
border: '1px solid var(--border)',
|
|
186
|
+
fontFamily: 'var(--font)',
|
|
187
|
+
},
|
|
165
188
|
logo: {
|
|
166
189
|
fontSize: 13, fontWeight: 600, letterSpacing: 1.5,
|
|
167
190
|
color: 'var(--text-bright)',
|
|
@@ -35,10 +35,12 @@ export default function AgentNode({ data }) {
|
|
|
35
35
|
? data.tokensUsed > 999 ? `${(data.tokensUsed / 1000).toFixed(1)}k` : `${data.tokensUsed}`
|
|
36
36
|
: '0';
|
|
37
37
|
|
|
38
|
-
// Get scope summary for activity text
|
|
39
|
-
const activity = data.
|
|
40
|
-
? data.
|
|
41
|
-
: data.
|
|
38
|
+
// Get scope summary for activity text — prefer workingDir if set
|
|
39
|
+
const activity = data.workingDir
|
|
40
|
+
? data.workingDir.replace(/^\.\//, '')
|
|
41
|
+
: data.scope?.length > 0
|
|
42
|
+
? data.scope[0].replace(/\/\*\*$/, '').replace(/^src\//, '')
|
|
43
|
+
: data.role;
|
|
42
44
|
|
|
43
45
|
return (
|
|
44
46
|
<div style={{
|
|
@@ -61,6 +61,7 @@ export default function AgentStats({ agent }) {
|
|
|
61
61
|
<InfoRow label="Role" value={agent.role} />
|
|
62
62
|
<InfoRow label="Provider" value={agent.provider} />
|
|
63
63
|
<InfoRow label="Model" value={agent.model || 'default'} />
|
|
64
|
+
{agent.workingDir && <InfoRow label="Directory" value={agent.workingDir} />}
|
|
64
65
|
<InfoRow label="Scope" value={(agent.scope || []).join(', ') || 'unrestricted'} />
|
|
65
66
|
<InfoRow label="Spawned" value={agent.spawnedAt ? new Date(agent.spawnedAt).toLocaleTimeString() : '-'} />
|
|
66
67
|
<InfoRow label="Last Active" value={agent.lastActivity ? new Date(agent.lastActivity).toLocaleTimeString() : '-'} />
|
|
@@ -34,12 +34,15 @@ export default function SpawnPanel() {
|
|
|
34
34
|
const [submitting, setSubmitting] = useState(false);
|
|
35
35
|
const [error, setError] = useState('');
|
|
36
36
|
const [showAdvanced, setShowAdvanced] = useState(false);
|
|
37
|
+
const [workingDir, setWorkingDir] = useState('');
|
|
38
|
+
const [workspaces, setWorkspaces] = useState([]);
|
|
37
39
|
const [connectingProvider, setConnectingProvider] = useState(null);
|
|
38
40
|
const [apiKeyInput, setApiKeyInput] = useState('');
|
|
39
41
|
const [keySaving, setKeySaving] = useState(false);
|
|
40
42
|
|
|
41
43
|
useEffect(() => {
|
|
42
44
|
fetchProviders();
|
|
45
|
+
fetchWorkspaces();
|
|
43
46
|
}, []);
|
|
44
47
|
|
|
45
48
|
async function fetchProviders() {
|
|
@@ -49,6 +52,14 @@ export default function SpawnPanel() {
|
|
|
49
52
|
} catch { /* ignore */ }
|
|
50
53
|
}
|
|
51
54
|
|
|
55
|
+
async function fetchWorkspaces() {
|
|
56
|
+
try {
|
|
57
|
+
const res = await fetch('/api/indexer/workspaces');
|
|
58
|
+
const data = await res.json();
|
|
59
|
+
setWorkspaces(data.workspaces || []);
|
|
60
|
+
} catch { /* ignore */ }
|
|
61
|
+
}
|
|
62
|
+
|
|
52
63
|
const selectedPreset = ROLE_PRESETS.find((p) => p.id === role);
|
|
53
64
|
const effectiveScope = role === 'custom'
|
|
54
65
|
? scope
|
|
@@ -113,6 +124,7 @@ export default function SpawnPanel() {
|
|
|
113
124
|
model: model || 'auto',
|
|
114
125
|
provider,
|
|
115
126
|
permission,
|
|
127
|
+
...(workingDir.trim() ? { workingDir: workingDir.trim() } : {}),
|
|
116
128
|
});
|
|
117
129
|
closeDetail();
|
|
118
130
|
} catch (err) {
|
|
@@ -198,6 +210,42 @@ export default function SpawnPanel() {
|
|
|
198
210
|
rows={3}
|
|
199
211
|
/>
|
|
200
212
|
|
|
213
|
+
{/* Workspace picker — visible by default when workspaces detected */}
|
|
214
|
+
{workspaces.length > 0 && (
|
|
215
|
+
<>
|
|
216
|
+
<div style={styles.label}>DIRECTORY</div>
|
|
217
|
+
<div style={styles.wsRow}>
|
|
218
|
+
<button
|
|
219
|
+
type="button"
|
|
220
|
+
onClick={() => setWorkingDir('')}
|
|
221
|
+
style={{
|
|
222
|
+
...styles.wsBtn,
|
|
223
|
+
...(!workingDir ? { borderColor: 'var(--accent)', color: 'var(--text-bright)' } : {}),
|
|
224
|
+
}}
|
|
225
|
+
>
|
|
226
|
+
project root
|
|
227
|
+
</button>
|
|
228
|
+
{workspaces.map((ws) => (
|
|
229
|
+
<button
|
|
230
|
+
key={ws.path}
|
|
231
|
+
type="button"
|
|
232
|
+
onClick={() => setWorkingDir(ws.path)}
|
|
233
|
+
style={{
|
|
234
|
+
...styles.wsBtn,
|
|
235
|
+
...(workingDir === ws.path ? { borderColor: 'var(--accent)', color: 'var(--text-bright)' } : {}),
|
|
236
|
+
}}
|
|
237
|
+
title={`${ws.name} (${ws.files} files)`}
|
|
238
|
+
>
|
|
239
|
+
{ws.path}
|
|
240
|
+
</button>
|
|
241
|
+
))}
|
|
242
|
+
</div>
|
|
243
|
+
<div style={styles.hint}>
|
|
244
|
+
Agent spawns inside this directory and only sees this subtree
|
|
245
|
+
</div>
|
|
246
|
+
</>
|
|
247
|
+
)}
|
|
248
|
+
|
|
201
249
|
{/* Permissions */}
|
|
202
250
|
<div style={styles.label}>PERMISSIONS</div>
|
|
203
251
|
<div style={styles.permGrid}>
|
|
@@ -231,6 +279,18 @@ export default function SpawnPanel() {
|
|
|
231
279
|
|
|
232
280
|
{showAdvanced && (
|
|
233
281
|
<>
|
|
282
|
+
{/* Working directory — manual input for custom paths */}
|
|
283
|
+
<div style={styles.label}>WORKING DIRECTORY</div>
|
|
284
|
+
<input
|
|
285
|
+
style={styles.input}
|
|
286
|
+
placeholder="e.g. packages/frontend (default: project root)"
|
|
287
|
+
value={workingDir}
|
|
288
|
+
onChange={(e) => setWorkingDir(e.target.value)}
|
|
289
|
+
/>
|
|
290
|
+
<div style={styles.hint}>
|
|
291
|
+
Relative path — or use the directory buttons above
|
|
292
|
+
</div>
|
|
293
|
+
|
|
234
294
|
{/* Provider selector with connection flow */}
|
|
235
295
|
<div style={styles.label}>PROVIDER</div>
|
|
236
296
|
{providerList.map((p) => {
|
|
@@ -499,7 +559,17 @@ const styles = {
|
|
|
499
559
|
fontFamily: 'var(--font)', resize: 'vertical',
|
|
500
560
|
},
|
|
501
561
|
hint: {
|
|
502
|
-
fontSize: 10, color: 'var(--text-
|
|
562
|
+
fontSize: 10, color: 'var(--text-dim)', marginTop: 3,
|
|
563
|
+
},
|
|
564
|
+
wsRow: {
|
|
565
|
+
display: 'flex', flexWrap: 'wrap', gap: 4, marginTop: 6,
|
|
566
|
+
},
|
|
567
|
+
wsBtn: {
|
|
568
|
+
background: 'var(--bg-surface)', border: '1px solid var(--border)',
|
|
569
|
+
borderRadius: 2, padding: '3px 8px',
|
|
570
|
+
color: 'var(--text-dim)', fontSize: 10, cursor: 'pointer',
|
|
571
|
+
fontFamily: 'var(--font)',
|
|
572
|
+
transition: 'color 0.1s, border-color 0.1s',
|
|
503
573
|
},
|
|
504
574
|
error: {
|
|
505
575
|
color: 'var(--red)', fontSize: 11, marginTop: 8,
|
|
@@ -11,6 +11,8 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
11
11
|
agents: [],
|
|
12
12
|
connected: false,
|
|
13
13
|
ws: null,
|
|
14
|
+
daemonHost: null, // bound host IP (null = localhost)
|
|
15
|
+
tunneled: false, // true when accessed via SSH tunnel (port mismatch)
|
|
14
16
|
|
|
15
17
|
// UI state — unified panel model
|
|
16
18
|
activeTab: 'agents', // 'agents' | 'stats' | 'teams' | 'approvals'
|
|
@@ -28,7 +30,22 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
28
30
|
|
|
29
31
|
const ws = new WebSocket(WS_URL);
|
|
30
32
|
|
|
31
|
-
ws.onopen = () =>
|
|
33
|
+
ws.onopen = () => {
|
|
34
|
+
set({ connected: true, ws });
|
|
35
|
+
// Fetch daemon info for instance badge + tunnel detection
|
|
36
|
+
fetch(`${API_BASE}/api/status`).then((r) => r.json()).then((s) => {
|
|
37
|
+
const updates = {};
|
|
38
|
+
if (s.host && s.host !== '127.0.0.1') {
|
|
39
|
+
updates.daemonHost = s.host;
|
|
40
|
+
}
|
|
41
|
+
// Detect tunnel: browser port differs from daemon's actual port
|
|
42
|
+
const browserPort = window.location.port || '80';
|
|
43
|
+
if (String(s.port) !== browserPort) {
|
|
44
|
+
updates.tunneled = true;
|
|
45
|
+
}
|
|
46
|
+
if (Object.keys(updates).length > 0) set(updates);
|
|
47
|
+
}).catch(() => {});
|
|
48
|
+
};
|
|
32
49
|
|
|
33
50
|
ws.onmessage = (event) => {
|
|
34
51
|
const msg = JSON.parse(event.data);
|
|
@@ -113,7 +130,7 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
113
130
|
};
|
|
114
131
|
|
|
115
132
|
ws.onclose = () => {
|
|
116
|
-
set({ connected: false, ws: null });
|
|
133
|
+
set({ connected: false, ws: null, daemonHost: null, tunneled: false });
|
|
117
134
|
setTimeout(() => get().connect(), 2000);
|
|
118
135
|
};
|
|
119
136
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "groove-dev",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "Open-source agent orchestration layer for AI coding tools. GUI dashboard, multi-agent coordination, zero cold-start (Journalist), infinite sessions (adaptive context rotation), AI Project Manager, Quick Launch. Works with Claude Code, Codex, Gemini CLI, Ollama.",
|
|
5
5
|
"license": "FSL-1.1-Apache-2.0",
|
|
6
6
|
"author": "Groove Dev <hello@groovedev.ai> (https://groovedev.ai)",
|
|
@@ -16,6 +16,10 @@ import { teamSave, teamLoad, teamList, teamDelete, teamExport, teamImport } from
|
|
|
16
16
|
import { approvals, approve, reject } from '../src/commands/approve.js';
|
|
17
17
|
import { providers, setKey } from '../src/commands/providers.js';
|
|
18
18
|
import { configShow, configSet } from '../src/commands/config.js';
|
|
19
|
+
import { connect } from '../src/commands/connect.js';
|
|
20
|
+
import { disconnect } from '../src/commands/disconnect.js';
|
|
21
|
+
import { audit } from '../src/commands/audit.js';
|
|
22
|
+
import { federationPair, federationUnpair, federationList, federationStatus } from '../src/commands/federation.js';
|
|
19
23
|
|
|
20
24
|
program
|
|
21
25
|
.name('groove')
|
|
@@ -26,6 +30,7 @@ program
|
|
|
26
30
|
.command('start')
|
|
27
31
|
.description('Start the GROOVE daemon')
|
|
28
32
|
.option('-p, --port <port>', 'Port to run on', '31415')
|
|
33
|
+
.option('-H, --host <host>', 'Host/IP to bind to (use "tailscale" for auto-detect)', '127.0.0.1')
|
|
29
34
|
.action(start);
|
|
30
35
|
|
|
31
36
|
program
|
|
@@ -90,6 +95,33 @@ program
|
|
|
90
95
|
program.command('providers').description('List available AI providers').action(providers);
|
|
91
96
|
program.command('set-key <provider> <key>').description('Set API key for a provider').action(setKey);
|
|
92
97
|
|
|
98
|
+
// Remote
|
|
99
|
+
program
|
|
100
|
+
.command('connect <target>')
|
|
101
|
+
.description('Connect to a remote GROOVE daemon via SSH tunnel')
|
|
102
|
+
.option('-i, --identity <keyfile>', 'SSH private key file')
|
|
103
|
+
.option('--no-browser', 'Don\'t open browser automatically')
|
|
104
|
+
.action(connect);
|
|
105
|
+
|
|
106
|
+
program
|
|
107
|
+
.command('disconnect')
|
|
108
|
+
.description('Disconnect from remote GROOVE daemon')
|
|
109
|
+
.action(disconnect);
|
|
110
|
+
|
|
111
|
+
// Audit
|
|
112
|
+
program
|
|
113
|
+
.command('audit')
|
|
114
|
+
.description('View audit log of state-changing operations')
|
|
115
|
+
.option('-n, --limit <count>', 'Number of entries to show', '25')
|
|
116
|
+
.action(audit);
|
|
117
|
+
|
|
118
|
+
// Federation
|
|
119
|
+
const federation = program.command('federation').description('Manage daemon-to-daemon federation');
|
|
120
|
+
federation.command('pair <target>').description('Pair with a remote GROOVE daemon (ip or ip:port)').action(federationPair);
|
|
121
|
+
federation.command('unpair <id>').description('Remove a paired peer').action(federationUnpair);
|
|
122
|
+
federation.command('list').description('List paired peers').action(federationList);
|
|
123
|
+
federation.command('status').description('Show federation status').action(federationStatus);
|
|
124
|
+
|
|
93
125
|
// Config
|
|
94
126
|
const config = program.command('config').description('View and modify configuration');
|
|
95
127
|
config.command('show').description('Show current configuration').action(configShow);
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// GROOVE CLI — audit command
|
|
2
|
+
// FSL-1.1-Apache-2.0 — see LICENSE
|
|
3
|
+
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import { apiCall } from '../client.js';
|
|
6
|
+
|
|
7
|
+
const ACTION_COLORS = {
|
|
8
|
+
'agent.spawn': 'green',
|
|
9
|
+
'agent.kill': 'red',
|
|
10
|
+
'agent.kill_all': 'red',
|
|
11
|
+
'agent.rotate': 'yellow',
|
|
12
|
+
'agent.instruct': 'cyan',
|
|
13
|
+
'team.save': 'blue',
|
|
14
|
+
'team.load': 'blue',
|
|
15
|
+
'team.delete': 'red',
|
|
16
|
+
'team.import': 'blue',
|
|
17
|
+
'team.launch': 'green',
|
|
18
|
+
'config.set': 'yellow',
|
|
19
|
+
'credential.set': 'yellow',
|
|
20
|
+
'credential.delete': 'red',
|
|
21
|
+
'approval.approve': 'green',
|
|
22
|
+
'approval.reject': 'red',
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
function formatEntry(entry) {
|
|
26
|
+
const time = entry.t ? new Date(entry.t).toLocaleTimeString() : '??:??:??';
|
|
27
|
+
const color = ACTION_COLORS[entry.action] || 'white';
|
|
28
|
+
const action = chalk[color](entry.action.padEnd(20));
|
|
29
|
+
|
|
30
|
+
// Build detail string from remaining fields
|
|
31
|
+
const { t, action: _, ...detail } = entry;
|
|
32
|
+
const detailStr = Object.entries(detail)
|
|
33
|
+
.map(([k, v]) => `${k}=${typeof v === 'string' ? v : JSON.stringify(v)}`)
|
|
34
|
+
.join(' ');
|
|
35
|
+
|
|
36
|
+
return ` ${chalk.dim(time)} ${action} ${chalk.dim(detailStr)}`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function audit(options) {
|
|
40
|
+
try {
|
|
41
|
+
const limit = parseInt(options.limit, 10) || 25;
|
|
42
|
+
const entries = await apiCall('GET', `/api/audit?limit=${limit}`);
|
|
43
|
+
|
|
44
|
+
console.log('');
|
|
45
|
+
if (entries.length === 0) {
|
|
46
|
+
console.log(chalk.dim(' No audit entries yet.'));
|
|
47
|
+
} else {
|
|
48
|
+
console.log(chalk.bold(` Audit Log`) + chalk.dim(` (${entries.length} entries, newest first)`));
|
|
49
|
+
console.log('');
|
|
50
|
+
for (const entry of entries) {
|
|
51
|
+
console.log(formatEntry(entry));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
console.log('');
|
|
55
|
+
} catch {
|
|
56
|
+
console.log('');
|
|
57
|
+
console.log(chalk.yellow(' Daemon not running.'));
|
|
58
|
+
console.log('');
|
|
59
|
+
}
|
|
60
|
+
}
|