added
.tickets/issues/0001-word-wrap-for-text-files.yaml +13 -0 | new file mode 100644 | ||
| @@ -0,0 +1,13 @@ | ||
| 1 | +# Managed by IssueSpec. Hand edits are welcome; keep the schema valid. | |
| 2 | +id: 1 | |
| 3 | +title: Word wrap for text files | |
| 4 | +state: open | |
| 5 | +author: | |
| 6 | + name: k33g | |
| 7 | + email: ph.charriere@gmail.com | |
| 8 | +createdAt: 2026-09-28T08:48:21.189Z | |
| 9 | +updatedAt: 2026-09-28T08:48:21.189Z | |
| 10 | +labels: [] | |
| 11 | +body: "" | |
| 12 | +tasks: [] | |
| 13 | +comments: [] | |
| new file mode 100644 | |||
| @@ -0,0 +1,13 @@ | |||
| 1 | +# Managed by IssueSpec. Hand edits are welcome; keep the schema valid. | ||
| 2 | +id: 1 | ||
| 3 | +title: Word wrap for text files | ||
| 4 | +state: open | ||
| 5 | +author: | ||
| 6 | + name: k33g | ||
| 7 | + email: ph.charriere@gmail.com | ||
| 8 | +createdAt: 2026-09-28T08:48:21.189Z | ||
| 9 | +updatedAt: 2026-09-28T08:48:21.189Z | ||
| 10 | +labels: [] | ||
| 11 | +body: "" | ||
| 12 | +tasks: [] | ||
| 13 | +comments: [] | ||
added
.tickets/issues/0002-when-sidekick-uses-sbx-the-shell-should-use-the.yaml +13 -0 | new file mode 100644 | ||
| @@ -0,0 +1,13 @@ | ||
| 1 | +# Managed by IssueSpec. Hand edits are welcome; keep the schema valid. | |
| 2 | +id: 2 | |
| 3 | +title: When Sidekick uses sbx, the shell should use the shell of the sandbox | |
| 4 | +state: open | |
| 5 | +author: | |
| 6 | + name: k33g | |
| 7 | + email: ph.charriere@gmail.com | |
| 8 | +createdAt: 2026-09-28T16:18:19.926Z | |
| 9 | +updatedAt: 2026-09-28T16:18:19.926Z | |
| 10 | +labels: [] | |
| 11 | +body: "" | |
| 12 | +tasks: [] | |
| 13 | +comments: [] | |
| new file mode 100644 | |||
| @@ -0,0 +1,13 @@ | |||
| 1 | +# Managed by IssueSpec. Hand edits are welcome; keep the schema valid. | ||
| 2 | +id: 2 | ||
| 3 | +title: When Sidekick uses sbx, the shell should use the shell of the sandbox | ||
| 4 | +state: open | ||
| 5 | +author: | ||
| 6 | + name: k33g | ||
| 7 | + email: ph.charriere@gmail.com | ||
| 8 | +createdAt: 2026-09-28T16:18:19.926Z | ||
| 9 | +updatedAt: 2026-09-28T16:18:19.926Z | ||
| 10 | +labels: [] | ||
| 11 | +body: "" | ||
| 12 | +tasks: [] | ||
| 13 | +comments: [] | ||
modified
README.md +7 -1 | @@ -78,7 +78,13 @@ export SIDEKICK_TOKEN="$(openssl rand -base64 32 | tr '+/' '-_' | tr -d '=')" | ||
| 78 | 78 | - a row of the tree dragged onto a folder moves it there (open tabs follow); |
| 79 | 79 | - files dropped onto the chat — or picked with 📎 — are copied into `.sidekick/uploads/` (never replacing a file: `spec.md` becomes `spec-1.md`) and attached to the next message; a row dragged from the tree onto the chat is attached as is. Attachments are sent as ACP `resource_link`s (`file://` URIs) and listed in the message text. Uploads are limited to 200 MB per file. |
| 80 | 80 | |
| 81 | -**⌨️ Terminal** opens a shell (`$SHELL`, as a login shell) in the working directory. Hiding the panel keeps it running; when it exits, Enter starts a new one. Not available on Windows. | |
| 81 | +**⌨️ Terminal** opens a shell (`$SHELL`, as a login shell) in the working directory. **+** in the panel's header opens another one, in a tab of its own; closing a tab ends its shell, closing the last one hides the panel. Hiding the panel keeps the shells running; when one exits, Enter starts a new one. Not available on Windows. | |
| 82 | + | |
| 83 | +When the agent runs in an [sbx](https://docs.docker.com/ai/sandboxes/) sandbox, `-sbx <name>` (or `$SBX_NAME`) opens the terminal in that sandbox instead, with `sbx exec -it` (bash, else sh, as a login shell, in the working directory — sbx mounts the workspace at the same path): | |
| 84 | + | |
| 85 | +```bash | |
| 86 | +SBX_NAME=demo-gemma sidekick -port 9002 ./mm-acp.sbx.sh | |
| 87 | +``` | |
| 82 | 88 | |
| 83 | 89 | Only the working directory is reachable from the tree and the editor: `../` and symlinks pointing outside it are refused. Binary files and files over 2 MB are not opened; `.git` is not listed. The panels can be resized by dragging their edges. |
| 84 | 90 | |
| @@ -78,7 +78,13 @@ export SIDEKICK_TOKEN="$(openssl rand -base64 32 | tr '+/' '-_' | tr -d '=')" | |||
| 78 | - a row of the tree dragged onto a folder moves it there (open tabs follow); | 78 | - a row of the tree dragged onto a folder moves it there (open tabs follow); |
| 79 | - files dropped onto the chat — or picked with 📎 — are copied into `.sidekick/uploads/` (never replacing a file: `spec.md` becomes `spec-1.md`) and attached to the next message; a row dragged from the tree onto the chat is attached as is. Attachments are sent as ACP `resource_link`s (`file://` URIs) and listed in the message text. Uploads are limited to 200 MB per file. | 79 | - files dropped onto the chat — or picked with 📎 — are copied into `.sidekick/uploads/` (never replacing a file: `spec.md` becomes `spec-1.md`) and attached to the next message; a row dragged from the tree onto the chat is attached as is. Attachments are sent as ACP `resource_link`s (`file://` URIs) and listed in the message text. Uploads are limited to 200 MB per file. |
| 80 | 80 | ||
| 81 | -**⌨️ Terminal** opens a shell (`$SHELL`, as a login shell) in the working directory. Hiding the panel keeps it running; when it exits, Enter starts a new one. Not available on Windows. | 81 | +**⌨️ Terminal** opens a shell (`$SHELL`, as a login shell) in the working directory. **+** in the panel's header opens another one, in a tab of its own; closing a tab ends its shell, closing the last one hides the panel. Hiding the panel keeps the shells running; when one exits, Enter starts a new one. Not available on Windows. |
| 82 | + | ||
| 83 | +When the agent runs in an [sbx](https://docs.docker.com/ai/sandboxes/) sandbox, `-sbx <name>` (or `$SBX_NAME`) opens the terminal in that sandbox instead, with `sbx exec -it` (bash, else sh, as a login shell, in the working directory — sbx mounts the workspace at the same path): | ||
| 84 | + | ||
| 85 | +```bash | ||
| 86 | +SBX_NAME=demo-gemma sidekick -port 9002 ./mm-acp.sbx.sh | ||
| 87 | +``` | ||
| 82 | 88 | ||
| 83 | Only the working directory is reachable from the tree and the editor: `../` and symlinks pointing outside it are refused. Binary files and files over 2 MB are not opened; `.git` is not listed. The panels can be resized by dragging their edges. | 89 | Only the working directory is reachable from the tree and the editor: `../` and symlinks pointing outside it are refused. Binary files and files over 2 MB are not opened; `.git` is not listed. The panels can be resized by dragging their edges. |
| 84 | 90 | ||
modified
cmd/server/main.go +6 -2 | @@ -284,6 +284,7 @@ func main() { | ||
| 284 | 284 | token := flag.String("token", os.Getenv("SIDEKICK_TOKEN"), "Access token (default: $SIDEKICK_TOKEN, else a new random one at each start); prefer the variable, a flag shows in the process list") |
| 285 | 285 | allowHost := flag.String("allow-host", "", "Host names (comma-separated) the browser may use besides localhost and IP addresses, e.g. my-vm.local") |
| 286 | 286 | cwd := flag.String("cwd", "", "Working directory given to the agent sessions (default: current directory)") |
| 287 | + sandbox := flag.String("sbx", os.Getenv("SBX_NAME"), "Name of the sbx sandbox the agent runs in: the terminal opens a shell in it rather than on this machine (default: $SBX_NAME)") | |
| 287 | 288 | webDir := flag.String("web", "", "Serve the web UI from this directory instead of the embedded one (for development)") |
| 288 | 289 | flag.Parse() |
| 289 | 290 | |
| @@ -295,7 +296,7 @@ func main() { | ||
| 295 | 296 | |
| 296 | 297 | args := flag.Args() |
| 297 | 298 | if len(args) < 1 { |
| 298 | - fmt.Printf("Usage: sidekick [-version] [-host <addr>] [-token <token>] [-allow-host <names>] [-port <port>] [-cwd <dir>] [-web <dir>] <agent_path> [agent_args...]\n") | |
| 299 | + fmt.Printf("Usage: sidekick [-version] [-host <addr>] [-token <token>] [-allow-host <names>] [-port <port>] [-cwd <dir>] [-sbx <sandbox>] [-web <dir>] <agent_path> [agent_args...]\n") | |
| 299 | 300 | os.Exit(1) |
| 300 | 301 | } |
| 301 | 302 | |
| @@ -339,7 +340,7 @@ func main() { | ||
| 339 | 340 | if err != nil { |
| 340 | 341 | log.Fatalf("Cannot open -cwd %q: %v", workDir, err) |
| 341 | 342 | } |
| 342 | - http.HandleFunc("/ws/term", (&Terminal{workDir: workDir}).Handle) | |
| 343 | + http.HandleFunc("/ws/term", (&Terminal{workDir: workDir, sandbox: *sandbox}).Handle) | |
| 343 | 344 | http.HandleFunc("/api/files", files.List) |
| 344 | 345 | http.HandleFunc("/api/file", files.File) |
| 345 | 346 | http.HandleFunc("/api/rename", files.Rename) |
| @@ -415,6 +416,9 @@ func main() { | ||
| 415 | 416 | |
| 416 | 417 | addr := net.JoinHostPort(*host, *port) |
| 417 | 418 | fmt.Printf("Server starting (listening on %s, web: %s, agent cwd: %s)\n", addr, staticDir, workDir) |
| 419 | + if *sandbox != "" { | |
| 420 | + fmt.Printf("⌨️ Terminal: shell of the sbx sandbox %q\n", *sandbox) | |
| 421 | + } | |
| 418 | 422 | fmt.Printf("🔑 Open http://localhost:%s/?token=%s\n", *port, url.QueryEscape(*token)) |
| 419 | 423 | if !isLoopback(*host) { |
| 420 | 424 | log.Printf("⚠️ -host %s: anyone who can reach this address gets a shell on this machine, the agent and the files of %s — with no login", *host, workDir) |
| @@ -284,6 +284,7 @@ func main() { | |||
| 284 | token := flag.String("token", os.Getenv("SIDEKICK_TOKEN"), "Access token (default: $SIDEKICK_TOKEN, else a new random one at each start); prefer the variable, a flag shows in the process list") | 284 | token := flag.String("token", os.Getenv("SIDEKICK_TOKEN"), "Access token (default: $SIDEKICK_TOKEN, else a new random one at each start); prefer the variable, a flag shows in the process list") |
| 285 | allowHost := flag.String("allow-host", "", "Host names (comma-separated) the browser may use besides localhost and IP addresses, e.g. my-vm.local") | 285 | allowHost := flag.String("allow-host", "", "Host names (comma-separated) the browser may use besides localhost and IP addresses, e.g. my-vm.local") |
| 286 | cwd := flag.String("cwd", "", "Working directory given to the agent sessions (default: current directory)") | 286 | cwd := flag.String("cwd", "", "Working directory given to the agent sessions (default: current directory)") |
| 287 | + sandbox := flag.String("sbx", os.Getenv("SBX_NAME"), "Name of the sbx sandbox the agent runs in: the terminal opens a shell in it rather than on this machine (default: $SBX_NAME)") | ||
| 287 | webDir := flag.String("web", "", "Serve the web UI from this directory instead of the embedded one (for development)") | 288 | webDir := flag.String("web", "", "Serve the web UI from this directory instead of the embedded one (for development)") |
| 288 | flag.Parse() | 289 | flag.Parse() |
| 289 | 290 | ||
| @@ -295,7 +296,7 @@ func main() { | |||
| 295 | 296 | ||
| 296 | args := flag.Args() | 297 | args := flag.Args() |
| 297 | if len(args) < 1 { | 298 | if len(args) < 1 { |
| 298 | - fmt.Printf("Usage: sidekick [-version] [-host <addr>] [-token <token>] [-allow-host <names>] [-port <port>] [-cwd <dir>] [-web <dir>] <agent_path> [agent_args...]\n") | 299 | + fmt.Printf("Usage: sidekick [-version] [-host <addr>] [-token <token>] [-allow-host <names>] [-port <port>] [-cwd <dir>] [-sbx <sandbox>] [-web <dir>] <agent_path> [agent_args...]\n") |
| 299 | os.Exit(1) | 300 | os.Exit(1) |
| 300 | } | 301 | } |
| 301 | 302 | ||
| @@ -339,7 +340,7 @@ func main() { | |||
| 339 | if err != nil { | 340 | if err != nil { |
| 340 | log.Fatalf("Cannot open -cwd %q: %v", workDir, err) | 341 | log.Fatalf("Cannot open -cwd %q: %v", workDir, err) |
| 341 | } | 342 | } |
| 342 | - http.HandleFunc("/ws/term", (&Terminal{workDir: workDir}).Handle) | 343 | + http.HandleFunc("/ws/term", (&Terminal{workDir: workDir, sandbox: *sandbox}).Handle) |
| 343 | http.HandleFunc("/api/files", files.List) | 344 | http.HandleFunc("/api/files", files.List) |
| 344 | http.HandleFunc("/api/file", files.File) | 345 | http.HandleFunc("/api/file", files.File) |
| 345 | http.HandleFunc("/api/rename", files.Rename) | 346 | http.HandleFunc("/api/rename", files.Rename) |
| @@ -415,6 +416,9 @@ func main() { | |||
| 415 | 416 | ||
| 416 | addr := net.JoinHostPort(*host, *port) | 417 | addr := net.JoinHostPort(*host, *port) |
| 417 | fmt.Printf("Server starting (listening on %s, web: %s, agent cwd: %s)\n", addr, staticDir, workDir) | 418 | fmt.Printf("Server starting (listening on %s, web: %s, agent cwd: %s)\n", addr, staticDir, workDir) |
| 419 | + if *sandbox != "" { | ||
| 420 | + fmt.Printf("⌨️ Terminal: shell of the sbx sandbox %q\n", *sandbox) | ||
| 421 | + } | ||
| 418 | fmt.Printf("🔑 Open http://localhost:%s/?token=%s\n", *port, url.QueryEscape(*token)) | 422 | fmt.Printf("🔑 Open http://localhost:%s/?token=%s\n", *port, url.QueryEscape(*token)) |
| 419 | if !isLoopback(*host) { | 423 | if !isLoopback(*host) { |
| 420 | log.Printf("⚠️ -host %s: anyone who can reach this address gets a shell on this machine, the agent and the files of %s — with no login", *host, workDir) | 424 | log.Printf("⚠️ -host %s: anyone who can reach this address gets a shell on this machine, the agent and the files of %s — with no login", *host, workDir) |
modified
cmd/server/terminal.go +25 -4 | @@ -20,8 +20,13 @@ import ( | ||
| 20 | 20 | // connection. Output goes to the browser as binary frames (raw bytes: xterm.js |
| 21 | 21 | // reassembles UTF-8 sequences split across frames); the browser sends JSON |
| 22 | 22 | // text frames, keystrokes and window sizes. |
| 23 | +// | |
| 24 | +// With a sandbox name, the shell is the sandbox's (through `sbx exec -it`) | |
| 25 | +// rather than the host's: the agent runs there, so that is where its tools, | |
| 26 | +// files and environment are. | |
| 23 | 27 | type Terminal struct { |
| 24 | 28 | workDir string |
| 29 | + sandbox string | |
| 25 | 30 | } |
| 26 | 31 | |
| 27 | 32 | // termMessage is what the browser sends. |
| @@ -45,6 +50,25 @@ func shell() string { | ||
| 45 | 50 | return "sh" |
| 46 | 51 | } |
| 47 | 52 | |
| 53 | +// sandboxShell runs in the sandbox what the host's terminal would run: a | |
| 54 | +// login shell in the working directory (sbx mounts the workspace at the | |
| 55 | +// host's path). $SHELL is not set there and the image may lack bash, hence | |
| 56 | +// the fallback on sh. | |
| 57 | +const sandboxShell = `if command -v bash >/dev/null 2>&1; then exec bash -l; fi; exec sh -l` | |
| 58 | + | |
| 59 | +// command is the process behind the pseudo-terminal. | |
| 60 | +func (t *Terminal) command() *exec.Cmd { | |
| 61 | + if t.sandbox != "" { | |
| 62 | + // sbx follows the pty's size (SIGWINCH) and passes TERM on. | |
| 63 | + return exec.Command("sbx", "exec", "-it", "-w", t.workDir, t.sandbox, "sh", "-c", sandboxShell) | |
| 64 | + } | |
| 65 | + // -l: a login shell reads the user's profile, which is where PATH is | |
| 66 | + // set up on macOS; without it, half the user's tools are missing. | |
| 67 | + cmd := exec.Command(shell(), "-l") | |
| 68 | + cmd.Dir = t.workDir | |
| 69 | + return cmd | |
| 70 | +} | |
| 71 | + | |
| 48 | 72 | func (t *Terminal) Handle(w http.ResponseWriter, r *http.Request) { |
| 49 | 73 | conn, err := upgrader.Upgrade(w, r, nil) |
| 50 | 74 | if err != nil { |
| @@ -58,10 +82,7 @@ func (t *Terminal) Handle(w http.ResponseWriter, r *http.Request) { | ||
| 58 | 82 | conn.WriteMessage(websocket.TextMessage, msg) |
| 59 | 83 | } |
| 60 | 84 | |
| 61 | - // -l: a login shell reads the user's profile, which is where PATH is | |
| 62 | - // set up on macOS; without it, half the user's tools are missing. | |
| 63 | - cmd := exec.Command(shell(), "-l") | |
| 64 | - cmd.Dir = t.workDir | |
| 85 | + cmd := t.command() | |
| 65 | 86 | cmd.Env = append(os.Environ(), "TERM=xterm-256color", "COLORTERM=truecolor") |
| 66 | 87 | ptmx, err := pty.StartWithSize(cmd, &pty.Winsize{Cols: 80, Rows: 24}) |
| 67 | 88 | if err != nil { |
| @@ -20,8 +20,13 @@ import ( | |||
| 20 | // connection. Output goes to the browser as binary frames (raw bytes: xterm.js | 20 | // connection. Output goes to the browser as binary frames (raw bytes: xterm.js |
| 21 | // reassembles UTF-8 sequences split across frames); the browser sends JSON | 21 | // reassembles UTF-8 sequences split across frames); the browser sends JSON |
| 22 | // text frames, keystrokes and window sizes. | 22 | // text frames, keystrokes and window sizes. |
| 23 | +// | ||
| 24 | +// With a sandbox name, the shell is the sandbox's (through `sbx exec -it`) | ||
| 25 | +// rather than the host's: the agent runs there, so that is where its tools, | ||
| 26 | +// files and environment are. | ||
| 23 | type Terminal struct { | 27 | type Terminal struct { |
| 24 | workDir string | 28 | workDir string |
| 29 | + sandbox string | ||
| 25 | } | 30 | } |
| 26 | 31 | ||
| 27 | // termMessage is what the browser sends. | 32 | // termMessage is what the browser sends. |
| @@ -45,6 +50,25 @@ func shell() string { | |||
| 45 | return "sh" | 50 | return "sh" |
| 46 | } | 51 | } |
| 47 | 52 | ||
| 53 | +// sandboxShell runs in the sandbox what the host's terminal would run: a | ||
| 54 | +// login shell in the working directory (sbx mounts the workspace at the | ||
| 55 | +// host's path). $SHELL is not set there and the image may lack bash, hence | ||
| 56 | +// the fallback on sh. | ||
| 57 | +const sandboxShell = `if command -v bash >/dev/null 2>&1; then exec bash -l; fi; exec sh -l` | ||
| 58 | + | ||
| 59 | +// command is the process behind the pseudo-terminal. | ||
| 60 | +func (t *Terminal) command() *exec.Cmd { | ||
| 61 | + if t.sandbox != "" { | ||
| 62 | + // sbx follows the pty's size (SIGWINCH) and passes TERM on. | ||
| 63 | + return exec.Command("sbx", "exec", "-it", "-w", t.workDir, t.sandbox, "sh", "-c", sandboxShell) | ||
| 64 | + } | ||
| 65 | + // -l: a login shell reads the user's profile, which is where PATH is | ||
| 66 | + // set up on macOS; without it, half the user's tools are missing. | ||
| 67 | + cmd := exec.Command(shell(), "-l") | ||
| 68 | + cmd.Dir = t.workDir | ||
| 69 | + return cmd | ||
| 70 | +} | ||
| 71 | + | ||
| 48 | func (t *Terminal) Handle(w http.ResponseWriter, r *http.Request) { | 72 | func (t *Terminal) Handle(w http.ResponseWriter, r *http.Request) { |
| 49 | conn, err := upgrader.Upgrade(w, r, nil) | 73 | conn, err := upgrader.Upgrade(w, r, nil) |
| 50 | if err != nil { | 74 | if err != nil { |
| @@ -58,10 +82,7 @@ func (t *Terminal) Handle(w http.ResponseWriter, r *http.Request) { | |||
| 58 | conn.WriteMessage(websocket.TextMessage, msg) | 82 | conn.WriteMessage(websocket.TextMessage, msg) |
| 59 | } | 83 | } |
| 60 | 84 | ||
| 61 | - // -l: a login shell reads the user's profile, which is where PATH is | 85 | + cmd := t.command() |
| 62 | - // set up on macOS; without it, half the user's tools are missing. | ||
| 63 | - cmd := exec.Command(shell(), "-l") | ||
| 64 | - cmd.Dir = t.workDir | ||
| 65 | cmd.Env = append(os.Environ(), "TERM=xterm-256color", "COLORTERM=truecolor") | 86 | cmd.Env = append(os.Environ(), "TERM=xterm-256color", "COLORTERM=truecolor") |
| 66 | ptmx, err := pty.StartWithSize(cmd, &pty.Winsize{Cols: 80, Rows: 24}) | 87 | ptmx, err := pty.StartWithSize(cmd, &pty.Winsize{Cols: 80, Rows: 24}) |
| 67 | if err != nil { | 88 | if err != nil { |
added
demo/.mm/sessions/20260928-180330-9632ed47.json +86 -0 | new file mode 100644 | ||
| @@ -0,0 +1,86 @@ | ||
| 1 | +{ | |
| 2 | + "id": "20260928-180330-9632ed47", | |
| 3 | + "cwd": "/Users/k33g/kDrive/Rickub/bots-garden/sidekick/demo", | |
| 4 | + "provider": "dmr", | |
| 5 | + "model": "huggingface.co/unsloth/gemma-4-e4b-it-gguf:Q4_K_M", | |
| 6 | + "createdAt": "2026-09-28T18:03:30.933787501Z", | |
| 7 | + "updatedAt": "2026-09-28T18:04:10.869447104Z", | |
| 8 | + "messages": [ | |
| 9 | + { | |
| 10 | + "content": [ | |
| 11 | + { | |
| 12 | + "text": "Your name is Bob.\nYou are a coding agent working in a terminal.\nYou have a \"bash\" tool to run shell commands.\nUse it to explore files, run tests, inspect the repository, etc.\nChain several commands if needed, then answer clearly in English.\n\nA request often mixes things you answer from yourself (\"say hello\") with\nthings only a command can answer (\"list the files\"). Handle every part, in\nthe order asked, and run a command for each part that needs one.\nNever state the contents of a file, the output of a command, or the state of\nthe repository unless a command in THIS answer returned it. What you did not\nread, you do not know: run the command instead of recalling it.\n\nSKILLS\nYou have a second tool, `read_skill`. Its description lists the procedures\navailable for this project — one per kind of task.\n\nAny request to DO something to a Go project is a skill, not a shell command\nyou invent. Match the request against that list, call `read_skill` FIRST,\nbefore any bash command, and then follow what it says step by step.\n\nFILE EDITING\nYou have three tools for files: `read_file`, `edit_file` and `write_file`.\nThey are how a file gets read and changed here: each change is exact,\nchecked before it is written, and comes back as a diff with line numbers.\nbash is for running things — building, testing, listing, searching.\n\n- Read before you write: call `read_file` on the file (numbered=true when\n you need line numbers). You cannot target text you have not seen; never\n rely on what you think you remember about a file.\n- To change an existing file, call `edit_file` with one or more {old, new}\n pairs. `old` is copied from the file character for character — same\n spaces, same indentation, same line breaks — and appears exactly once:\n add the surrounding lines until it is unique. Several pairs are applied\n together, against the original file. An empty `new` deletes the text.\n- Call `write_file` only to create a file, or to rewrite one entirely and\n on purpose. On an existing file it replaces everything, including what\n you did not intend to touch.\n- Read the diff the tool returns: it says exactly what changed and on which\n line. If `edit_file` refuses — text not found, ambiguous, overlapping\n edits — read the file again and fix `old`. Do not fall back to\n `write_file` to force the change through.\n- After editing code, run the narrowest check with bash: the formatter, the\n compiler, or the test covering that file.\n\nRULES\n- Keep everything the file already does, unless the user asked to remove it.\n- Touch only the files the request is about. Do not add tests, files or\n features that were not asked for.\n- Never run a git command unless the user says git, commit or push.\n- Never move, rename or delete a file unless the user asked for it.\n- Then answer in English, in a few lines.\n- If you don't know how to use a \u003ccli\u003e, run `\u003ccli\u003e --help` (or `\u003ccli\u003e help`)\n to understand the options, then run the command.\n\nBACKGROUND JOBS\nNever let a command block the answer. Anything that serves, watches or runs\nlong goes to the background, with BOTH streams redirected and its pid kept:\n\n nohup \u003ccommand\u003e \u003e /tmp/\u003cjob\u003e.log 2\u003e\u00261 \u0026 echo $! \u003e /tmp/\u003cjob\u003e.pid\n\nRedirecting only stdout still blocks until the process exits. Read the\n`bg-jobs` skill before you wait on, inspect or stop such a job — each has a\nrule you cannot guess. Stop every job you started before you finish, and say\nwhich ones you left running.\n" | |
| 13 | + } | |
| 14 | + ], | |
| 15 | + "role": "system" | |
| 16 | + }, | |
| 17 | + { | |
| 18 | + "content": [ | |
| 19 | + { | |
| 20 | + "text": "hello" | |
| 21 | + } | |
| 22 | + ], | |
| 23 | + "role": "user" | |
| 24 | + }, | |
| 25 | + { | |
| 26 | + "content": [ | |
| 27 | + { | |
| 28 | + "toolRequest": { | |
| 29 | + "input": { | |
| 30 | + "name": "greetings" | |
| 31 | + }, | |
| 32 | + "name": "read_skill", | |
| 33 | + "ref": "xHUcufTdenT0gBT61nkjlA7alalgHs68" | |
| 34 | + } | |
| 35 | + } | |
| 36 | + ], | |
| 37 | + "role": "model" | |
| 38 | + }, | |
| 39 | + { | |
| 40 | + "content": [ | |
| 41 | + { | |
| 42 | + "toolResponse": { | |
| 43 | + "name": "read_skill", | |
| 44 | + "output": "---\nname: greetings\ndescription: Greet somebody with a festive message.\nkeywords: greet, greetings, hello, hi, welcome, bonjour, coucou, saluer\n---\n\n# Greetings\n\n## What to do\n\nThe command below is for YOU to run with your `bash` tool, in this same turn.\nDo not show it to the user, and do not answer from memory: the answer is the\ncommand's output, nothing else.\n\nReplace `\u003cname\u003e` with the person being greeted, then call your `bash` tool with this command:\n\n echo \"🎉 greetings \u003cname\u003e\"\n", | |
| 45 | + "ref": "xHUcufTdenT0gBT61nkjlA7alalgHs68" | |
| 46 | + } | |
| 47 | + } | |
| 48 | + ], | |
| 49 | + "role": "tool" | |
| 50 | + }, | |
| 51 | + { | |
| 52 | + "content": [ | |
| 53 | + { | |
| 54 | + "toolRequest": { | |
| 55 | + "input": { | |
| 56 | + "command": "echo \"🎉 greetings Bob\"" | |
| 57 | + }, | |
| 58 | + "name": "bash", | |
| 59 | + "ref": "VI6MONUOvbjeyGqCVJ4HeYjO92CLZJNb" | |
| 60 | + } | |
| 61 | + } | |
| 62 | + ], | |
| 63 | + "role": "model" | |
| 64 | + }, | |
| 65 | + { | |
| 66 | + "content": [ | |
| 67 | + { | |
| 68 | + "toolResponse": { | |
| 69 | + "name": "bash", | |
| 70 | + "output": "[command rejected by the user]", | |
| 71 | + "ref": "VI6MONUOvbjeyGqCVJ4HeYjO92CLZJNb" | |
| 72 | + } | |
| 73 | + } | |
| 74 | + ], | |
| 75 | + "role": "tool" | |
| 76 | + }, | |
| 77 | + { | |
| 78 | + "content": [ | |
| 79 | + { | |
| 80 | + "text": "Hello Bob! 🎉 Greetings Bob!" | |
| 81 | + } | |
| 82 | + ], | |
| 83 | + "role": "model" | |
| 84 | + } | |
| 85 | + ] | |
| 86 | +} | |
| \ No newline at end of file | ||
| new file mode 100644 | |||
| @@ -0,0 +1,86 @@ | |||
| 1 | +{ | ||
| 2 | + "id": "20260928-180330-9632ed47", | ||
| 3 | + "cwd": "/Users/k33g/kDrive/Rickub/bots-garden/sidekick/demo", | ||
| 4 | + "provider": "dmr", | ||
| 5 | + "model": "huggingface.co/unsloth/gemma-4-e4b-it-gguf:Q4_K_M", | ||
| 6 | + "createdAt": "2026-09-28T18:03:30.933787501Z", | ||
| 7 | + "updatedAt": "2026-09-28T18:04:10.869447104Z", | ||
| 8 | + "messages": [ | ||
| 9 | + { | ||
| 10 | + "content": [ | ||
| 11 | + { | ||
| 12 | + "text": "Your name is Bob.\nYou are a coding agent working in a terminal.\nYou have a \"bash\" tool to run shell commands.\nUse it to explore files, run tests, inspect the repository, etc.\nChain several commands if needed, then answer clearly in English.\n\nA request often mixes things you answer from yourself (\"say hello\") with\nthings only a command can answer (\"list the files\"). Handle every part, in\nthe order asked, and run a command for each part that needs one.\nNever state the contents of a file, the output of a command, or the state of\nthe repository unless a command in THIS answer returned it. What you did not\nread, you do not know: run the command instead of recalling it.\n\nSKILLS\nYou have a second tool, `read_skill`. Its description lists the procedures\navailable for this project — one per kind of task.\n\nAny request to DO something to a Go project is a skill, not a shell command\nyou invent. Match the request against that list, call `read_skill` FIRST,\nbefore any bash command, and then follow what it says step by step.\n\nFILE EDITING\nYou have three tools for files: `read_file`, `edit_file` and `write_file`.\nThey are how a file gets read and changed here: each change is exact,\nchecked before it is written, and comes back as a diff with line numbers.\nbash is for running things — building, testing, listing, searching.\n\n- Read before you write: call `read_file` on the file (numbered=true when\n you need line numbers). You cannot target text you have not seen; never\n rely on what you think you remember about a file.\n- To change an existing file, call `edit_file` with one or more {old, new}\n pairs. `old` is copied from the file character for character — same\n spaces, same indentation, same line breaks — and appears exactly once:\n add the surrounding lines until it is unique. Several pairs are applied\n together, against the original file. An empty `new` deletes the text.\n- Call `write_file` only to create a file, or to rewrite one entirely and\n on purpose. On an existing file it replaces everything, including what\n you did not intend to touch.\n- Read the diff the tool returns: it says exactly what changed and on which\n line. If `edit_file` refuses — text not found, ambiguous, overlapping\n edits — read the file again and fix `old`. Do not fall back to\n `write_file` to force the change through.\n- After editing code, run the narrowest check with bash: the formatter, the\n compiler, or the test covering that file.\n\nRULES\n- Keep everything the file already does, unless the user asked to remove it.\n- Touch only the files the request is about. Do not add tests, files or\n features that were not asked for.\n- Never run a git command unless the user says git, commit or push.\n- Never move, rename or delete a file unless the user asked for it.\n- Then answer in English, in a few lines.\n- If you don't know how to use a \u003ccli\u003e, run `\u003ccli\u003e --help` (or `\u003ccli\u003e help`)\n to understand the options, then run the command.\n\nBACKGROUND JOBS\nNever let a command block the answer. Anything that serves, watches or runs\nlong goes to the background, with BOTH streams redirected and its pid kept:\n\n nohup \u003ccommand\u003e \u003e /tmp/\u003cjob\u003e.log 2\u003e\u00261 \u0026 echo $! \u003e /tmp/\u003cjob\u003e.pid\n\nRedirecting only stdout still blocks until the process exits. Read the\n`bg-jobs` skill before you wait on, inspect or stop such a job — each has a\nrule you cannot guess. Stop every job you started before you finish, and say\nwhich ones you left running.\n" | ||
| 13 | + } | ||
| 14 | + ], | ||
| 15 | + "role": "system" | ||
| 16 | + }, | ||
| 17 | + { | ||
| 18 | + "content": [ | ||
| 19 | + { | ||
| 20 | + "text": "hello" | ||
| 21 | + } | ||
| 22 | + ], | ||
| 23 | + "role": "user" | ||
| 24 | + }, | ||
| 25 | + { | ||
| 26 | + "content": [ | ||
| 27 | + { | ||
| 28 | + "toolRequest": { | ||
| 29 | + "input": { | ||
| 30 | + "name": "greetings" | ||
| 31 | + }, | ||
| 32 | + "name": "read_skill", | ||
| 33 | + "ref": "xHUcufTdenT0gBT61nkjlA7alalgHs68" | ||
| 34 | + } | ||
| 35 | + } | ||
| 36 | + ], | ||
| 37 | + "role": "model" | ||
| 38 | + }, | ||
| 39 | + { | ||
| 40 | + "content": [ | ||
| 41 | + { | ||
| 42 | + "toolResponse": { | ||
| 43 | + "name": "read_skill", | ||
| 44 | + "output": "---\nname: greetings\ndescription: Greet somebody with a festive message.\nkeywords: greet, greetings, hello, hi, welcome, bonjour, coucou, saluer\n---\n\n# Greetings\n\n## What to do\n\nThe command below is for YOU to run with your `bash` tool, in this same turn.\nDo not show it to the user, and do not answer from memory: the answer is the\ncommand's output, nothing else.\n\nReplace `\u003cname\u003e` with the person being greeted, then call your `bash` tool with this command:\n\n echo \"🎉 greetings \u003cname\u003e\"\n", | ||
| 45 | + "ref": "xHUcufTdenT0gBT61nkjlA7alalgHs68" | ||
| 46 | + } | ||
| 47 | + } | ||
| 48 | + ], | ||
| 49 | + "role": "tool" | ||
| 50 | + }, | ||
| 51 | + { | ||
| 52 | + "content": [ | ||
| 53 | + { | ||
| 54 | + "toolRequest": { | ||
| 55 | + "input": { | ||
| 56 | + "command": "echo \"🎉 greetings Bob\"" | ||
| 57 | + }, | ||
| 58 | + "name": "bash", | ||
| 59 | + "ref": "VI6MONUOvbjeyGqCVJ4HeYjO92CLZJNb" | ||
| 60 | + } | ||
| 61 | + } | ||
| 62 | + ], | ||
| 63 | + "role": "model" | ||
| 64 | + }, | ||
| 65 | + { | ||
| 66 | + "content": [ | ||
| 67 | + { | ||
| 68 | + "toolResponse": { | ||
| 69 | + "name": "bash", | ||
| 70 | + "output": "[command rejected by the user]", | ||
| 71 | + "ref": "VI6MONUOvbjeyGqCVJ4HeYjO92CLZJNb" | ||
| 72 | + } | ||
| 73 | + } | ||
| 74 | + ], | ||
| 75 | + "role": "tool" | ||
| 76 | + }, | ||
| 77 | + { | ||
| 78 | + "content": [ | ||
| 79 | + { | ||
| 80 | + "text": "Hello Bob! 🎉 Greetings Bob!" | ||
| 81 | + } | ||
| 82 | + ], | ||
| 83 | + "role": "model" | ||
| 84 | + } | ||
| 85 | + ] | ||
| 86 | +} | ||
| \ No newline at end of file | \ No newline at end of file | ||
added
demo/.mm/sessions/20260928-181046-6b8f94d0.json +144 -0 | new file mode 100644 | ||
| @@ -0,0 +1,144 @@ | ||
| 1 | +{ | |
| 2 | + "id": "20260928-181046-6b8f94d0", | |
| 3 | + "cwd": "/Users/k33g/kDrive/Rickub/bots-garden/sidekick/demo", | |
| 4 | + "provider": "dmr", | |
| 5 | + "model": "huggingface.co/unsloth/gemma-4-e4b-it-gguf:Q4_K_M", | |
| 6 | + "createdAt": "2026-09-28T18:10:46.502280043Z", | |
| 7 | + "updatedAt": "2026-09-28T18:13:09.071068528Z", | |
| 8 | + "messages": [ | |
| 9 | + { | |
| 10 | + "content": [ | |
| 11 | + { | |
| 12 | + "text": "Your name is Bob.\nYou are a coding agent working in a terminal.\nYou have a \"bash\" tool to run shell commands.\nUse it to explore files, run tests, inspect the repository, etc.\nChain several commands if needed, then answer clearly in English.\n\nA request often mixes things you answer from yourself (\"say hello\") with\nthings only a command can answer (\"list the files\"). Handle every part, in\nthe order asked, and run a command for each part that needs one.\nNever state the contents of a file, the output of a command, or the state of\nthe repository unless a command in THIS answer returned it. What you did not\nread, you do not know: run the command instead of recalling it.\n\nSKILLS\nYou have a second tool, `read_skill`. Its description lists the procedures\navailable for this project — one per kind of task.\n\nAny request to DO something to a Go project is a skill, not a shell command\nyou invent. Match the request against that list, call `read_skill` FIRST,\nbefore any bash command, and then follow what it says step by step.\n\nFILE EDITING\nYou have three tools for files: `read_file`, `edit_file` and `write_file`.\nThey are how a file gets read and changed here: each change is exact,\nchecked before it is written, and comes back as a diff with line numbers.\nbash is for running things — building, testing, listing, searching.\n\n- Read before you write: call `read_file` on the file (numbered=true when\n you need line numbers). You cannot target text you have not seen; never\n rely on what you think you remember about a file.\n- To change an existing file, call `edit_file` with one or more {old, new}\n pairs. `old` is copied from the file character for character — same\n spaces, same indentation, same line breaks — and appears exactly once:\n add the surrounding lines until it is unique. Several pairs are applied\n together, against the original file. An empty `new` deletes the text.\n- Call `write_file` only to create a file, or to rewrite one entirely and\n on purpose. On an existing file it replaces everything, including what\n you did not intend to touch.\n- Read the diff the tool returns: it says exactly what changed and on which\n line. If `edit_file` refuses — text not found, ambiguous, overlapping\n edits — read the file again and fix `old`. Do not fall back to\n `write_file` to force the change through.\n- After editing code, run the narrowest check with bash: the formatter, the\n compiler, or the test covering that file.\n\nRULES\n- Keep everything the file already does, unless the user asked to remove it.\n- Touch only the files the request is about. Do not add tests, files or\n features that were not asked for.\n- Never run a git command unless the user says git, commit or push.\n- Never move, rename or delete a file unless the user asked for it.\n- Then answer in English, in a few lines.\n- If you don't know how to use a \u003ccli\u003e, run `\u003ccli\u003e --help` (or `\u003ccli\u003e help`)\n to understand the options, then run the command.\n\nBACKGROUND JOBS\nNever let a command block the answer. Anything that serves, watches or runs\nlong goes to the background, with BOTH streams redirected and its pid kept:\n\n nohup \u003ccommand\u003e \u003e /tmp/\u003cjob\u003e.log 2\u003e\u00261 \u0026 echo $! \u003e /tmp/\u003cjob\u003e.pid\n\nRedirecting only stdout still blocks until the process exits. Read the\n`bg-jobs` skill before you wait on, inspect or stop such a job — each has a\nrule you cannot guess. Stop every job you started before you finish, and say\nwhich ones you left running.\n" | |
| 13 | + } | |
| 14 | + ], | |
| 15 | + "role": "system" | |
| 16 | + }, | |
| 17 | + { | |
| 18 | + "content": [ | |
| 19 | + { | |
| 20 | + "text": "installe htop" | |
| 21 | + } | |
| 22 | + ], | |
| 23 | + "role": "user" | |
| 24 | + }, | |
| 25 | + { | |
| 26 | + "content": [ | |
| 27 | + { | |
| 28 | + "toolRequest": { | |
| 29 | + "input": { | |
| 30 | + "command": "sudo apt update \u0026\u0026 sudo apt install htop" | |
| 31 | + }, | |
| 32 | + "name": "bash", | |
| 33 | + "ref": "Etg7XGHWWuSm0PBz1elwJDCyg9ry4kNv" | |
| 34 | + } | |
| 35 | + } | |
| 36 | + ], | |
| 37 | + "role": "model" | |
| 38 | + }, | |
| 39 | + { | |
| 40 | + "content": [ | |
| 41 | + { | |
| 42 | + "toolResponse": { | |
| 43 | + "name": "bash", | |
| 44 | + "output": "\nWARNING: apt does not have a stable CLI interface. Use with caution in scripts.\n\nGet:1 https://download.docker.com/linux/ubuntu resolute InRelease [32.5 kB]\nGet:2 https://download.docker.com/linux/ubuntu resolute/stable arm64 Packages [27.7 kB]\nGet:3 http://ports.ubuntu.com/ubuntu-ports resolute InRelease [136 kB]\nGet:4 http://ports.ubuntu.com/ubuntu-ports resolute-updates InRelease [137 kB]\nGet:5 http://ports.ubuntu.com/ubuntu-ports resolute-backports InRelease [137 kB]\nGet:6 http://ports.ubuntu.com/ubuntu-ports resolute-security InRelease [137 kB]\nGet:7 http://ports.ubuntu.com/ubuntu-ports resolute/multiverse arm64 Packages [298 kB]\nGet:8 http://ports.ubuntu.com/ubuntu-ports resolute/main arm64 Packages [1860 kB]\nGet:9 http://ports.ubuntu.com/ubuntu-ports resolute/universe arm64 Packages [19.9 MB]\nGet:10 http://ports.ubuntu.com/ubuntu-ports resolute/restricted arm64 Packages [231 kB]\nGet:11 http://ports.ubuntu.com/ubuntu-ports resolute-updates/main arm64 Packages [859 kB]\nGet:12 http://ports.ubuntu.com/ubuntu-ports resolute-updates/restricted arm64 Packages [743 kB]\nGet:13 http://ports.ubuntu.com/ubuntu-ports resolute-updates/universe arm64 Packages [379 kB]\nGet:14 http://ports.ubuntu.com/ubuntu-ports resolute-updates/multiverse arm64 Packages [14.9 kB]\nGet:15 http://ports.ubuntu.com/ubuntu-ports resolute-backports/universe arm64 Packages [3308 B]\nGet:16 http://ports.ubuntu.com/ubuntu-ports resolute-security/restricted arm64 Packages [723 kB]\nGet:17 http://ports.ubuntu.com/ubuntu-ports resolute-security/universe arm64 Packages [226 kB]\nGet:18 http://ports.ubuntu.com/ubuntu-ports resolute-security/multiverse arm64 Packages [13.6 kB]\nGet:19 http://ports.ubuntu.com/ubuntu-ports resolute-security/main arm64 Packages [689 kB]\nFetched 26.6 MB in 3s (7740 kB/s)\nReading package lists...\nBuilding dependency tree...\nReading state information...\n2 packages can be upgraded. Run 'apt list --upgradable' to see them.\n\nWARNING: apt does not have a stable CLI interface. Use with caution in scripts.\n\nReading package lists...\nBuilding dependency tree...\nReading state information...\nSolving dependencies...\nInstalling:\n htop\n\nSuggested packages:\n lm-sensors strace\n\nSummary:\n Upgrading: 0, Installing: 1, Removing: 0, Not Upgrading: 2\n Download size: 175 kB\n Space needed: 455 kB / 19.8 GB available\n\nGet:1 http://ports.ubuntu.com/ubuntu-ports resolute/main arm64 htop arm64 3.4.1-5build2 [175 kB]\ndebconf: unable to initialize frontend: Dialog\ndebconf: (No usable dialog-like program is installed, so the dialog based frontend cannot be used. at /usr/share/perl5/Debconf/FrontEnd/Dialog.pm line 79, \u003cSTDIN\u003e line 1.)\ndebconf: falling back to frontend: Readline\ndebconf: unable to initialize frontend: Readline\ndebconf: (This frontend requires a controlling tty.)\ndebconf: falling back to frontend: Teletype\ndebconf: unable to initialize frontend: Teletype\ndebconf: (This frontend requires a controlling tty.)\ndebconf: falling back to frontend: Noninteractive\nFetched 175 kB in 0s (1551 kB/s)\nSelecting previously unselected package htop.\r\n(Reading database ... \r(Reading database ... 5%\r(Reading database ... 10%\r(Reading database ... 15%\r(Reading database ... 20%\r(Reading database ... 25%\r(Reading database ... 30%\r(Reading database ... 35%\r(Reading database ... 40%\r(Reading database ... 45%\r(Reading database ... 50%\r(Reading database ... 55%\r(Reading database ... 60%\r(Reading database ... 65%\r(Reading database ... 70%\r(Reading database ... 75%\r(Reading database ... 80%\r(Reading database ... 85%\r(Reading database ... 90%\r(Reading database ... 95%\r(Reading database ... 100%\r(Reading database ... 54799 files and directories currently installed.)\r\nPreparing to unpack .../htop_3.4.1-5build2_arm64.deb ...\r\nUnpacking htop (3.4.1-5build2) ...\r\nSetting up htop (3.4.1-5build2) ...\r\nProcessing triggers for man-db (2.13.1-1build1) ...\r\n", | |
| 45 | + "ref": "Etg7XGHWWuSm0PBz1elwJDCyg9ry4kNv" | |
| 46 | + } | |
| 47 | + } | |
| 48 | + ], | |
| 49 | + "role": "tool" | |
| 50 | + }, | |
| 51 | + { | |
| 52 | + "content": [ | |
| 53 | + { | |
| 54 | + "text": "htop has been successfully installed on the system." | |
| 55 | + } | |
| 56 | + ], | |
| 57 | + "role": "model" | |
| 58 | + }, | |
| 59 | + { | |
| 60 | + "content": [ | |
| 61 | + { | |
| 62 | + "text": "pwd" | |
| 63 | + } | |
| 64 | + ], | |
| 65 | + "role": "user" | |
| 66 | + }, | |
| 67 | + { | |
| 68 | + "content": [ | |
| 69 | + { | |
| 70 | + "toolRequest": { | |
| 71 | + "input": { | |
| 72 | + "command": "pwd" | |
| 73 | + }, | |
| 74 | + "name": "bash", | |
| 75 | + "ref": "te7sE30CspAauoeV6WSvBg37YBFizCJu" | |
| 76 | + } | |
| 77 | + } | |
| 78 | + ], | |
| 79 | + "role": "model" | |
| 80 | + }, | |
| 81 | + { | |
| 82 | + "content": [ | |
| 83 | + { | |
| 84 | + "toolResponse": { | |
| 85 | + "name": "bash", | |
| 86 | + "output": "/Users/k33g/kDrive/Rickub/bots-garden/sidekick/demo\n", | |
| 87 | + "ref": "te7sE30CspAauoeV6WSvBg37YBFizCJu" | |
| 88 | + } | |
| 89 | + } | |
| 90 | + ], | |
| 91 | + "role": "tool" | |
| 92 | + }, | |
| 93 | + { | |
| 94 | + "content": [ | |
| 95 | + { | |
| 96 | + "text": "The current working directory is `/Users/k33g/kDrive/Rickub/bots-garden/sidekick/demo`." | |
| 97 | + } | |
| 98 | + ], | |
| 99 | + "role": "model" | |
| 100 | + }, | |
| 101 | + { | |
| 102 | + "content": [ | |
| 103 | + { | |
| 104 | + "text": "ps -d" | |
| 105 | + } | |
| 106 | + ], | |
| 107 | + "role": "user" | |
| 108 | + }, | |
| 109 | + { | |
| 110 | + "content": [ | |
| 111 | + { | |
| 112 | + "toolRequest": { | |
| 113 | + "input": { | |
| 114 | + "command": "ps -d" | |
| 115 | + }, | |
| 116 | + "name": "bash", | |
| 117 | + "ref": "QVn1hKFQ3mKbTGfD5YE9tXtORyJkFWNB" | |
| 118 | + } | |
| 119 | + } | |
| 120 | + ], | |
| 121 | + "role": "model" | |
| 122 | + }, | |
| 123 | + { | |
| 124 | + "content": [ | |
| 125 | + { | |
| 126 | + "toolResponse": { | |
| 127 | + "name": "bash", | |
| 128 | + "output": " PID TTY TIME CMD\n 2 ? 00:00:00 sleep\n 83 ? 00:00:00 socat\n 253 pts/0 00:00:00 htop\n 262 ? 00:00:00 ps\n", | |
| 129 | + "ref": "QVn1hKFQ3mKbTGfD5YE9tXtORyJkFWNB" | |
| 130 | + } | |
| 131 | + } | |
| 132 | + ], | |
| 133 | + "role": "tool" | |
| 134 | + }, | |
| 135 | + { | |
| 136 | + "content": [ | |
| 137 | + { | |
| 138 | + "text": "The process list shows the following processes:\n* PID 2 (TTY ?): sleep\n* PID 83 (TTY ?): socat\n* PID 253 (TTY pts/0): htop\n* PID 262 (TTY ?): ps" | |
| 139 | + } | |
| 140 | + ], | |
| 141 | + "role": "model" | |
| 142 | + } | |
| 143 | + ] | |
| 144 | +} | |
| \ No newline at end of file | ||
| new file mode 100644 | |||
| @@ -0,0 +1,144 @@ | |||
| 1 | +{ | ||
| 2 | + "id": "20260928-181046-6b8f94d0", | ||
| 3 | + "cwd": "/Users/k33g/kDrive/Rickub/bots-garden/sidekick/demo", | ||
| 4 | + "provider": "dmr", | ||
| 5 | + "model": "huggingface.co/unsloth/gemma-4-e4b-it-gguf:Q4_K_M", | ||
| 6 | + "createdAt": "2026-09-28T18:10:46.502280043Z", | ||
| 7 | + "updatedAt": "2026-09-28T18:13:09.071068528Z", | ||
| 8 | + "messages": [ | ||
| 9 | + { | ||
| 10 | + "content": [ | ||
| 11 | + { | ||
| 12 | + "text": "Your name is Bob.\nYou are a coding agent working in a terminal.\nYou have a \"bash\" tool to run shell commands.\nUse it to explore files, run tests, inspect the repository, etc.\nChain several commands if needed, then answer clearly in English.\n\nA request often mixes things you answer from yourself (\"say hello\") with\nthings only a command can answer (\"list the files\"). Handle every part, in\nthe order asked, and run a command for each part that needs one.\nNever state the contents of a file, the output of a command, or the state of\nthe repository unless a command in THIS answer returned it. What you did not\nread, you do not know: run the command instead of recalling it.\n\nSKILLS\nYou have a second tool, `read_skill`. Its description lists the procedures\navailable for this project — one per kind of task.\n\nAny request to DO something to a Go project is a skill, not a shell command\nyou invent. Match the request against that list, call `read_skill` FIRST,\nbefore any bash command, and then follow what it says step by step.\n\nFILE EDITING\nYou have three tools for files: `read_file`, `edit_file` and `write_file`.\nThey are how a file gets read and changed here: each change is exact,\nchecked before it is written, and comes back as a diff with line numbers.\nbash is for running things — building, testing, listing, searching.\n\n- Read before you write: call `read_file` on the file (numbered=true when\n you need line numbers). You cannot target text you have not seen; never\n rely on what you think you remember about a file.\n- To change an existing file, call `edit_file` with one or more {old, new}\n pairs. `old` is copied from the file character for character — same\n spaces, same indentation, same line breaks — and appears exactly once:\n add the surrounding lines until it is unique. Several pairs are applied\n together, against the original file. An empty `new` deletes the text.\n- Call `write_file` only to create a file, or to rewrite one entirely and\n on purpose. On an existing file it replaces everything, including what\n you did not intend to touch.\n- Read the diff the tool returns: it says exactly what changed and on which\n line. If `edit_file` refuses — text not found, ambiguous, overlapping\n edits — read the file again and fix `old`. Do not fall back to\n `write_file` to force the change through.\n- After editing code, run the narrowest check with bash: the formatter, the\n compiler, or the test covering that file.\n\nRULES\n- Keep everything the file already does, unless the user asked to remove it.\n- Touch only the files the request is about. Do not add tests, files or\n features that were not asked for.\n- Never run a git command unless the user says git, commit or push.\n- Never move, rename or delete a file unless the user asked for it.\n- Then answer in English, in a few lines.\n- If you don't know how to use a \u003ccli\u003e, run `\u003ccli\u003e --help` (or `\u003ccli\u003e help`)\n to understand the options, then run the command.\n\nBACKGROUND JOBS\nNever let a command block the answer. Anything that serves, watches or runs\nlong goes to the background, with BOTH streams redirected and its pid kept:\n\n nohup \u003ccommand\u003e \u003e /tmp/\u003cjob\u003e.log 2\u003e\u00261 \u0026 echo $! \u003e /tmp/\u003cjob\u003e.pid\n\nRedirecting only stdout still blocks until the process exits. Read the\n`bg-jobs` skill before you wait on, inspect or stop such a job — each has a\nrule you cannot guess. Stop every job you started before you finish, and say\nwhich ones you left running.\n" | ||
| 13 | + } | ||
| 14 | + ], | ||
| 15 | + "role": "system" | ||
| 16 | + }, | ||
| 17 | + { | ||
| 18 | + "content": [ | ||
| 19 | + { | ||
| 20 | + "text": "installe htop" | ||
| 21 | + } | ||
| 22 | + ], | ||
| 23 | + "role": "user" | ||
| 24 | + }, | ||
| 25 | + { | ||
| 26 | + "content": [ | ||
| 27 | + { | ||
| 28 | + "toolRequest": { | ||
| 29 | + "input": { | ||
| 30 | + "command": "sudo apt update \u0026\u0026 sudo apt install htop" | ||
| 31 | + }, | ||
| 32 | + "name": "bash", | ||
| 33 | + "ref": "Etg7XGHWWuSm0PBz1elwJDCyg9ry4kNv" | ||
| 34 | + } | ||
| 35 | + } | ||
| 36 | + ], | ||
| 37 | + "role": "model" | ||
| 38 | + }, | ||
| 39 | + { | ||
| 40 | + "content": [ | ||
| 41 | + { | ||
| 42 | + "toolResponse": { | ||
| 43 | + "name": "bash", | ||
| 44 | + "output": "\nWARNING: apt does not have a stable CLI interface. Use with caution in scripts.\n\nGet:1 https://download.docker.com/linux/ubuntu resolute InRelease [32.5 kB]\nGet:2 https://download.docker.com/linux/ubuntu resolute/stable arm64 Packages [27.7 kB]\nGet:3 http://ports.ubuntu.com/ubuntu-ports resolute InRelease [136 kB]\nGet:4 http://ports.ubuntu.com/ubuntu-ports resolute-updates InRelease [137 kB]\nGet:5 http://ports.ubuntu.com/ubuntu-ports resolute-backports InRelease [137 kB]\nGet:6 http://ports.ubuntu.com/ubuntu-ports resolute-security InRelease [137 kB]\nGet:7 http://ports.ubuntu.com/ubuntu-ports resolute/multiverse arm64 Packages [298 kB]\nGet:8 http://ports.ubuntu.com/ubuntu-ports resolute/main arm64 Packages [1860 kB]\nGet:9 http://ports.ubuntu.com/ubuntu-ports resolute/universe arm64 Packages [19.9 MB]\nGet:10 http://ports.ubuntu.com/ubuntu-ports resolute/restricted arm64 Packages [231 kB]\nGet:11 http://ports.ubuntu.com/ubuntu-ports resolute-updates/main arm64 Packages [859 kB]\nGet:12 http://ports.ubuntu.com/ubuntu-ports resolute-updates/restricted arm64 Packages [743 kB]\nGet:13 http://ports.ubuntu.com/ubuntu-ports resolute-updates/universe arm64 Packages [379 kB]\nGet:14 http://ports.ubuntu.com/ubuntu-ports resolute-updates/multiverse arm64 Packages [14.9 kB]\nGet:15 http://ports.ubuntu.com/ubuntu-ports resolute-backports/universe arm64 Packages [3308 B]\nGet:16 http://ports.ubuntu.com/ubuntu-ports resolute-security/restricted arm64 Packages [723 kB]\nGet:17 http://ports.ubuntu.com/ubuntu-ports resolute-security/universe arm64 Packages [226 kB]\nGet:18 http://ports.ubuntu.com/ubuntu-ports resolute-security/multiverse arm64 Packages [13.6 kB]\nGet:19 http://ports.ubuntu.com/ubuntu-ports resolute-security/main arm64 Packages [689 kB]\nFetched 26.6 MB in 3s (7740 kB/s)\nReading package lists...\nBuilding dependency tree...\nReading state information...\n2 packages can be upgraded. Run 'apt list --upgradable' to see them.\n\nWARNING: apt does not have a stable CLI interface. Use with caution in scripts.\n\nReading package lists...\nBuilding dependency tree...\nReading state information...\nSolving dependencies...\nInstalling:\n htop\n\nSuggested packages:\n lm-sensors strace\n\nSummary:\n Upgrading: 0, Installing: 1, Removing: 0, Not Upgrading: 2\n Download size: 175 kB\n Space needed: 455 kB / 19.8 GB available\n\nGet:1 http://ports.ubuntu.com/ubuntu-ports resolute/main arm64 htop arm64 3.4.1-5build2 [175 kB]\ndebconf: unable to initialize frontend: Dialog\ndebconf: (No usable dialog-like program is installed, so the dialog based frontend cannot be used. at /usr/share/perl5/Debconf/FrontEnd/Dialog.pm line 79, \u003cSTDIN\u003e line 1.)\ndebconf: falling back to frontend: Readline\ndebconf: unable to initialize frontend: Readline\ndebconf: (This frontend requires a controlling tty.)\ndebconf: falling back to frontend: Teletype\ndebconf: unable to initialize frontend: Teletype\ndebconf: (This frontend requires a controlling tty.)\ndebconf: falling back to frontend: Noninteractive\nFetched 175 kB in 0s (1551 kB/s)\nSelecting previously unselected package htop.\r\n(Reading database ... \r(Reading database ... 5%\r(Reading database ... 10%\r(Reading database ... 15%\r(Reading database ... 20%\r(Reading database ... 25%\r(Reading database ... 30%\r(Reading database ... 35%\r(Reading database ... 40%\r(Reading database ... 45%\r(Reading database ... 50%\r(Reading database ... 55%\r(Reading database ... 60%\r(Reading database ... 65%\r(Reading database ... 70%\r(Reading database ... 75%\r(Reading database ... 80%\r(Reading database ... 85%\r(Reading database ... 90%\r(Reading database ... 95%\r(Reading database ... 100%\r(Reading database ... 54799 files and directories currently installed.)\r\nPreparing to unpack .../htop_3.4.1-5build2_arm64.deb ...\r\nUnpacking htop (3.4.1-5build2) ...\r\nSetting up htop (3.4.1-5build2) ...\r\nProcessing triggers for man-db (2.13.1-1build1) ...\r\n", | ||
| 45 | + "ref": "Etg7XGHWWuSm0PBz1elwJDCyg9ry4kNv" | ||
| 46 | + } | ||
| 47 | + } | ||
| 48 | + ], | ||
| 49 | + "role": "tool" | ||
| 50 | + }, | ||
| 51 | + { | ||
| 52 | + "content": [ | ||
| 53 | + { | ||
| 54 | + "text": "htop has been successfully installed on the system." | ||
| 55 | + } | ||
| 56 | + ], | ||
| 57 | + "role": "model" | ||
| 58 | + }, | ||
| 59 | + { | ||
| 60 | + "content": [ | ||
| 61 | + { | ||
| 62 | + "text": "pwd" | ||
| 63 | + } | ||
| 64 | + ], | ||
| 65 | + "role": "user" | ||
| 66 | + }, | ||
| 67 | + { | ||
| 68 | + "content": [ | ||
| 69 | + { | ||
| 70 | + "toolRequest": { | ||
| 71 | + "input": { | ||
| 72 | + "command": "pwd" | ||
| 73 | + }, | ||
| 74 | + "name": "bash", | ||
| 75 | + "ref": "te7sE30CspAauoeV6WSvBg37YBFizCJu" | ||
| 76 | + } | ||
| 77 | + } | ||
| 78 | + ], | ||
| 79 | + "role": "model" | ||
| 80 | + }, | ||
| 81 | + { | ||
| 82 | + "content": [ | ||
| 83 | + { | ||
| 84 | + "toolResponse": { | ||
| 85 | + "name": "bash", | ||
| 86 | + "output": "/Users/k33g/kDrive/Rickub/bots-garden/sidekick/demo\n", | ||
| 87 | + "ref": "te7sE30CspAauoeV6WSvBg37YBFizCJu" | ||
| 88 | + } | ||
| 89 | + } | ||
| 90 | + ], | ||
| 91 | + "role": "tool" | ||
| 92 | + }, | ||
| 93 | + { | ||
| 94 | + "content": [ | ||
| 95 | + { | ||
| 96 | + "text": "The current working directory is `/Users/k33g/kDrive/Rickub/bots-garden/sidekick/demo`." | ||
| 97 | + } | ||
| 98 | + ], | ||
| 99 | + "role": "model" | ||
| 100 | + }, | ||
| 101 | + { | ||
| 102 | + "content": [ | ||
| 103 | + { | ||
| 104 | + "text": "ps -d" | ||
| 105 | + } | ||
| 106 | + ], | ||
| 107 | + "role": "user" | ||
| 108 | + }, | ||
| 109 | + { | ||
| 110 | + "content": [ | ||
| 111 | + { | ||
| 112 | + "toolRequest": { | ||
| 113 | + "input": { | ||
| 114 | + "command": "ps -d" | ||
| 115 | + }, | ||
| 116 | + "name": "bash", | ||
| 117 | + "ref": "QVn1hKFQ3mKbTGfD5YE9tXtORyJkFWNB" | ||
| 118 | + } | ||
| 119 | + } | ||
| 120 | + ], | ||
| 121 | + "role": "model" | ||
| 122 | + }, | ||
| 123 | + { | ||
| 124 | + "content": [ | ||
| 125 | + { | ||
| 126 | + "toolResponse": { | ||
| 127 | + "name": "bash", | ||
| 128 | + "output": " PID TTY TIME CMD\n 2 ? 00:00:00 sleep\n 83 ? 00:00:00 socat\n 253 pts/0 00:00:00 htop\n 262 ? 00:00:00 ps\n", | ||
| 129 | + "ref": "QVn1hKFQ3mKbTGfD5YE9tXtORyJkFWNB" | ||
| 130 | + } | ||
| 131 | + } | ||
| 132 | + ], | ||
| 133 | + "role": "tool" | ||
| 134 | + }, | ||
| 135 | + { | ||
| 136 | + "content": [ | ||
| 137 | + { | ||
| 138 | + "text": "The process list shows the following processes:\n* PID 2 (TTY ?): sleep\n* PID 83 (TTY ?): socat\n* PID 253 (TTY pts/0): htop\n* PID 262 (TTY ?): ps" | ||
| 139 | + } | ||
| 140 | + ], | ||
| 141 | + "role": "model" | ||
| 142 | + } | ||
| 143 | + ] | ||
| 144 | +} | ||
| \ No newline at end of file | \ No newline at end of file | ||
added
demo/.mm/sessions/20260928-181359-c225a9cc.json +88 -0 | new file mode 100644 | ||
| @@ -0,0 +1,88 @@ | ||
| 1 | +{ | |
| 2 | + "id": "20260928-181359-c225a9cc", | |
| 3 | + "cwd": "/Users/k33g/kDrive/Rickub/bots-garden/sidekick/demo", | |
| 4 | + "provider": "dmr", | |
| 5 | + "model": "huggingface.co/unsloth/gemma-4-e4b-it-gguf:Q4_K_M", | |
| 6 | + "createdAt": "2026-09-28T18:13:59.897913Z", | |
| 7 | + "updatedAt": "2026-09-28T18:15:13.17158Z", | |
| 8 | + "messages": [ | |
| 9 | + { | |
| 10 | + "content": [ | |
| 11 | + { | |
| 12 | + "text": "Your name is Bob.\nYou are a coding agent working in a terminal.\nYou have a \"bash\" tool to run shell commands.\nUse it to explore files, run tests, inspect the repository, etc.\nChain several commands if needed, then answer clearly in English.\n\nA request often mixes things you answer from yourself (\"say hello\") with\nthings only a command can answer (\"list the files\"). Handle every part, in\nthe order asked, and run a command for each part that needs one.\nNever state the contents of a file, the output of a command, or the state of\nthe repository unless a command in THIS answer returned it. What you did not\nread, you do not know: run the command instead of recalling it.\n\nSKILLS\nYou have a second tool, `read_skill`. Its description lists the procedures\navailable for this project — one per kind of task.\n\nAny request to DO something to a Go project is a skill, not a shell command\nyou invent. Match the request against that list, call `read_skill` FIRST,\nbefore any bash command, and then follow what it says step by step.\n\nFILE EDITING\nYou have three tools for files: `read_file`, `edit_file` and `write_file`.\nThey are how a file gets read and changed here: each change is exact,\nchecked before it is written, and comes back as a diff with line numbers.\nbash is for running things — building, testing, listing, searching.\n\n- Read before you write: call `read_file` on the file (numbered=true when\n you need line numbers). You cannot target text you have not seen; never\n rely on what you think you remember about a file.\n- To change an existing file, call `edit_file` with one or more {old, new}\n pairs. `old` is copied from the file character for character — same\n spaces, same indentation, same line breaks — and appears exactly once:\n add the surrounding lines until it is unique. Several pairs are applied\n together, against the original file. An empty `new` deletes the text.\n- Call `write_file` only to create a file, or to rewrite one entirely and\n on purpose. On an existing file it replaces everything, including what\n you did not intend to touch.\n- Read the diff the tool returns: it says exactly what changed and on which\n line. If `edit_file` refuses — text not found, ambiguous, overlapping\n edits — read the file again and fix `old`. Do not fall back to\n `write_file` to force the change through.\n- After editing code, run the narrowest check with bash: the formatter, the\n compiler, or the test covering that file.\n\nRULES\n- Keep everything the file already does, unless the user asked to remove it.\n- Touch only the files the request is about. Do not add tests, files or\n features that were not asked for.\n- Never run a git command unless the user says git, commit or push.\n- Never move, rename or delete a file unless the user asked for it.\n- Then answer in English, in a few lines.\n- If you don't know how to use a \u003ccli\u003e, run `\u003ccli\u003e --help` (or `\u003ccli\u003e help`)\n to understand the options, then run the command.\n\nBACKGROUND JOBS\nNever let a command block the answer. Anything that serves, watches or runs\nlong goes to the background, with BOTH streams redirected and its pid kept:\n\n nohup \u003ccommand\u003e \u003e /tmp/\u003cjob\u003e.log 2\u003e\u00261 \u0026 echo $! \u003e /tmp/\u003cjob\u003e.pid\n\nRedirecting only stdout still blocks until the process exits. Read the\n`bg-jobs` skill before you wait on, inspect or stop such a job — each has a\nrule you cannot guess. Stop every job you started before you finish, and say\nwhich ones you left running.\n" | |
| 13 | + } | |
| 14 | + ], | |
| 15 | + "role": "system" | |
| 16 | + }, | |
| 17 | + { | |
| 18 | + "content": [ | |
| 19 | + { | |
| 20 | + "text": "ola" | |
| 21 | + } | |
| 22 | + ], | |
| 23 | + "role": "user" | |
| 24 | + }, | |
| 25 | + { | |
| 26 | + "content": [ | |
| 27 | + { | |
| 28 | + "toolRequest": { | |
| 29 | + "input": { | |
| 30 | + "name": "greetings" | |
| 31 | + }, | |
| 32 | + "name": "read_skill", | |
| 33 | + "ref": "XQlcfz7gma1W6WSIl6yVaJEbUDvQ2zen" | |
| 34 | + } | |
| 35 | + } | |
| 36 | + ], | |
| 37 | + "role": "model" | |
| 38 | + }, | |
| 39 | + { | |
| 40 | + "content": [ | |
| 41 | + { | |
| 42 | + "toolResponse": { | |
| 43 | + "name": "read_skill", | |
| 44 | + "output": "---\nname: greetings\ndescription: Greet somebody with a festive message.\nkeywords: greet, greetings, hello, hi, welcome, bonjour, coucou, saluer\n---\n\n# Greetings\n\n## What to do\n\nThe command below is for YOU to run with your `bash` tool, in this same turn.\nDo not show it to the user, and do not answer from memory: the answer is the\ncommand's output, nothing else.\n\nReplace `\u003cname\u003e` with the person being greeted, then call your `bash` tool with this command:\n\n echo \"🎉 greetings \u003cname\u003e\"\n", | |
| 45 | + "ref": "XQlcfz7gma1W6WSIl6yVaJEbUDvQ2zen" | |
| 46 | + } | |
| 47 | + } | |
| 48 | + ], | |
| 49 | + "role": "tool" | |
| 50 | + }, | |
| 51 | + { | |
| 52 | + "content": [ | |
| 53 | + { | |
| 54 | + "toolRequest": { | |
| 55 | + "input": { | |
| 56 | + "command": "echo \"🎉 greetings Bob\"" | |
| 57 | + }, | |
| 58 | + "name": "bash", | |
| 59 | + "ref": "ri0rIAKy60XkEHzpE9Q9L7qDTB6kEVSC" | |
| 60 | + } | |
| 61 | + } | |
| 62 | + ], | |
| 63 | + "role": "model" | |
| 64 | + }, | |
| 65 | + { | |
| 66 | + "content": [ | |
| 67 | + { | |
| 68 | + "toolResponse": { | |
| 69 | + "name": "bash", | |
| 70 | + "output": "[command rejected by the user]", | |
| 71 | + "ref": "ri0rIAKy60XkEHzpE9Q9L7qDTB6kEVSC" | |
| 72 | + } | |
| 73 | + } | |
| 74 | + ], | |
| 75 | + "role": "tool" | |
| 76 | + } | |
| 77 | + ], | |
| 78 | + "errors": [ | |
| 79 | + { | |
| 80 | + "at": "2026-09-28T18:15:13.171551Z", | |
| 81 | + "turn": 1, | |
| 82 | + "where": "generation", | |
| 83 | + "message": "generation timed out (stalled)", | |
| 84 | + "question": "ola", | |
| 85 | + "model": "huggingface.co/unsloth/gemma-4-e4b-it-gguf:Q4_K_M" | |
| 86 | + } | |
| 87 | + ] | |
| 88 | +} | |
| \ No newline at end of file | ||
| new file mode 100644 | |||
| @@ -0,0 +1,88 @@ | |||
| 1 | +{ | ||
| 2 | + "id": "20260928-181359-c225a9cc", | ||
| 3 | + "cwd": "/Users/k33g/kDrive/Rickub/bots-garden/sidekick/demo", | ||
| 4 | + "provider": "dmr", | ||
| 5 | + "model": "huggingface.co/unsloth/gemma-4-e4b-it-gguf:Q4_K_M", | ||
| 6 | + "createdAt": "2026-09-28T18:13:59.897913Z", | ||
| 7 | + "updatedAt": "2026-09-28T18:15:13.17158Z", | ||
| 8 | + "messages": [ | ||
| 9 | + { | ||
| 10 | + "content": [ | ||
| 11 | + { | ||
| 12 | + "text": "Your name is Bob.\nYou are a coding agent working in a terminal.\nYou have a \"bash\" tool to run shell commands.\nUse it to explore files, run tests, inspect the repository, etc.\nChain several commands if needed, then answer clearly in English.\n\nA request often mixes things you answer from yourself (\"say hello\") with\nthings only a command can answer (\"list the files\"). Handle every part, in\nthe order asked, and run a command for each part that needs one.\nNever state the contents of a file, the output of a command, or the state of\nthe repository unless a command in THIS answer returned it. What you did not\nread, you do not know: run the command instead of recalling it.\n\nSKILLS\nYou have a second tool, `read_skill`. Its description lists the procedures\navailable for this project — one per kind of task.\n\nAny request to DO something to a Go project is a skill, not a shell command\nyou invent. Match the request against that list, call `read_skill` FIRST,\nbefore any bash command, and then follow what it says step by step.\n\nFILE EDITING\nYou have three tools for files: `read_file`, `edit_file` and `write_file`.\nThey are how a file gets read and changed here: each change is exact,\nchecked before it is written, and comes back as a diff with line numbers.\nbash is for running things — building, testing, listing, searching.\n\n- Read before you write: call `read_file` on the file (numbered=true when\n you need line numbers). You cannot target text you have not seen; never\n rely on what you think you remember about a file.\n- To change an existing file, call `edit_file` with one or more {old, new}\n pairs. `old` is copied from the file character for character — same\n spaces, same indentation, same line breaks — and appears exactly once:\n add the surrounding lines until it is unique. Several pairs are applied\n together, against the original file. An empty `new` deletes the text.\n- Call `write_file` only to create a file, or to rewrite one entirely and\n on purpose. On an existing file it replaces everything, including what\n you did not intend to touch.\n- Read the diff the tool returns: it says exactly what changed and on which\n line. If `edit_file` refuses — text not found, ambiguous, overlapping\n edits — read the file again and fix `old`. Do not fall back to\n `write_file` to force the change through.\n- After editing code, run the narrowest check with bash: the formatter, the\n compiler, or the test covering that file.\n\nRULES\n- Keep everything the file already does, unless the user asked to remove it.\n- Touch only the files the request is about. Do not add tests, files or\n features that were not asked for.\n- Never run a git command unless the user says git, commit or push.\n- Never move, rename or delete a file unless the user asked for it.\n- Then answer in English, in a few lines.\n- If you don't know how to use a \u003ccli\u003e, run `\u003ccli\u003e --help` (or `\u003ccli\u003e help`)\n to understand the options, then run the command.\n\nBACKGROUND JOBS\nNever let a command block the answer. Anything that serves, watches or runs\nlong goes to the background, with BOTH streams redirected and its pid kept:\n\n nohup \u003ccommand\u003e \u003e /tmp/\u003cjob\u003e.log 2\u003e\u00261 \u0026 echo $! \u003e /tmp/\u003cjob\u003e.pid\n\nRedirecting only stdout still blocks until the process exits. Read the\n`bg-jobs` skill before you wait on, inspect or stop such a job — each has a\nrule you cannot guess. Stop every job you started before you finish, and say\nwhich ones you left running.\n" | ||
| 13 | + } | ||
| 14 | + ], | ||
| 15 | + "role": "system" | ||
| 16 | + }, | ||
| 17 | + { | ||
| 18 | + "content": [ | ||
| 19 | + { | ||
| 20 | + "text": "ola" | ||
| 21 | + } | ||
| 22 | + ], | ||
| 23 | + "role": "user" | ||
| 24 | + }, | ||
| 25 | + { | ||
| 26 | + "content": [ | ||
| 27 | + { | ||
| 28 | + "toolRequest": { | ||
| 29 | + "input": { | ||
| 30 | + "name": "greetings" | ||
| 31 | + }, | ||
| 32 | + "name": "read_skill", | ||
| 33 | + "ref": "XQlcfz7gma1W6WSIl6yVaJEbUDvQ2zen" | ||
| 34 | + } | ||
| 35 | + } | ||
| 36 | + ], | ||
| 37 | + "role": "model" | ||
| 38 | + }, | ||
| 39 | + { | ||
| 40 | + "content": [ | ||
| 41 | + { | ||
| 42 | + "toolResponse": { | ||
| 43 | + "name": "read_skill", | ||
| 44 | + "output": "---\nname: greetings\ndescription: Greet somebody with a festive message.\nkeywords: greet, greetings, hello, hi, welcome, bonjour, coucou, saluer\n---\n\n# Greetings\n\n## What to do\n\nThe command below is for YOU to run with your `bash` tool, in this same turn.\nDo not show it to the user, and do not answer from memory: the answer is the\ncommand's output, nothing else.\n\nReplace `\u003cname\u003e` with the person being greeted, then call your `bash` tool with this command:\n\n echo \"🎉 greetings \u003cname\u003e\"\n", | ||
| 45 | + "ref": "XQlcfz7gma1W6WSIl6yVaJEbUDvQ2zen" | ||
| 46 | + } | ||
| 47 | + } | ||
| 48 | + ], | ||
| 49 | + "role": "tool" | ||
| 50 | + }, | ||
| 51 | + { | ||
| 52 | + "content": [ | ||
| 53 | + { | ||
| 54 | + "toolRequest": { | ||
| 55 | + "input": { | ||
| 56 | + "command": "echo \"🎉 greetings Bob\"" | ||
| 57 | + }, | ||
| 58 | + "name": "bash", | ||
| 59 | + "ref": "ri0rIAKy60XkEHzpE9Q9L7qDTB6kEVSC" | ||
| 60 | + } | ||
| 61 | + } | ||
| 62 | + ], | ||
| 63 | + "role": "model" | ||
| 64 | + }, | ||
| 65 | + { | ||
| 66 | + "content": [ | ||
| 67 | + { | ||
| 68 | + "toolResponse": { | ||
| 69 | + "name": "bash", | ||
| 70 | + "output": "[command rejected by the user]", | ||
| 71 | + "ref": "ri0rIAKy60XkEHzpE9Q9L7qDTB6kEVSC" | ||
| 72 | + } | ||
| 73 | + } | ||
| 74 | + ], | ||
| 75 | + "role": "tool" | ||
| 76 | + } | ||
| 77 | + ], | ||
| 78 | + "errors": [ | ||
| 79 | + { | ||
| 80 | + "at": "2026-09-28T18:15:13.171551Z", | ||
| 81 | + "turn": 1, | ||
| 82 | + "where": "generation", | ||
| 83 | + "message": "generation timed out (stalled)", | ||
| 84 | + "question": "ola", | ||
| 85 | + "model": "huggingface.co/unsloth/gemma-4-e4b-it-gguf:Q4_K_M" | ||
| 86 | + } | ||
| 87 | + ] | ||
| 88 | +} | ||
| \ No newline at end of file | \ No newline at end of file | ||
added
demo/.mm/sessions/20260928-181731-d13e1aeb.json +18 -0 | new file mode 100644 | ||
| @@ -0,0 +1,18 @@ | ||
| 1 | +{ | |
| 2 | + "id": "20260928-181731-d13e1aeb", | |
| 3 | + "cwd": "/Users/k33g/kDrive/Rickub/bots-garden/sidekick/demo", | |
| 4 | + "provider": "dmr", | |
| 5 | + "model": "huggingface.co/unsloth/gemma-4-e4b-it-gguf:Q4_K_M", | |
| 6 | + "createdAt": "2026-09-28T18:17:31.615012Z", | |
| 7 | + "updatedAt": "2026-09-28T18:17:31.615043Z", | |
| 8 | + "messages": [ | |
| 9 | + { | |
| 10 | + "content": [ | |
| 11 | + { | |
| 12 | + "text": "Your name is Bob.\nYou are a coding agent working in a terminal.\nYou have a \"bash\" tool to run shell commands.\nUse it to explore files, run tests, inspect the repository, etc.\nChain several commands if needed, then answer clearly in English.\n\nA request often mixes things you answer from yourself (\"say hello\") with\nthings only a command can answer (\"list the files\"). Handle every part, in\nthe order asked, and run a command for each part that needs one.\nNever state the contents of a file, the output of a command, or the state of\nthe repository unless a command in THIS answer returned it. What you did not\nread, you do not know: run the command instead of recalling it.\n\nSKILLS\nYou have a second tool, `read_skill`. Its description lists the procedures\navailable for this project — one per kind of task.\n\nAny request to DO something to a Go project is a skill, not a shell command\nyou invent. Match the request against that list, call `read_skill` FIRST,\nbefore any bash command, and then follow what it says step by step.\n\nFILE EDITING\nYou have three tools for files: `read_file`, `edit_file` and `write_file`.\nThey are how a file gets read and changed here: each change is exact,\nchecked before it is written, and comes back as a diff with line numbers.\nbash is for running things — building, testing, listing, searching.\n\n- Read before you write: call `read_file` on the file (numbered=true when\n you need line numbers). You cannot target text you have not seen; never\n rely on what you think you remember about a file.\n- To change an existing file, call `edit_file` with one or more {old, new}\n pairs. `old` is copied from the file character for character — same\n spaces, same indentation, same line breaks — and appears exactly once:\n add the surrounding lines until it is unique. Several pairs are applied\n together, against the original file. An empty `new` deletes the text.\n- Call `write_file` only to create a file, or to rewrite one entirely and\n on purpose. On an existing file it replaces everything, including what\n you did not intend to touch.\n- Read the diff the tool returns: it says exactly what changed and on which\n line. If `edit_file` refuses — text not found, ambiguous, overlapping\n edits — read the file again and fix `old`. Do not fall back to\n `write_file` to force the change through.\n- After editing code, run the narrowest check with bash: the formatter, the\n compiler, or the test covering that file.\n\nRULES\n- Keep everything the file already does, unless the user asked to remove it.\n- Touch only the files the request is about. Do not add tests, files or\n features that were not asked for.\n- Never run a git command unless the user says git, commit or push.\n- Never move, rename or delete a file unless the user asked for it.\n- Then answer in English, in a few lines.\n- If you don't know how to use a \u003ccli\u003e, run `\u003ccli\u003e --help` (or `\u003ccli\u003e help`)\n to understand the options, then run the command.\n\nBACKGROUND JOBS\nNever let a command block the answer. Anything that serves, watches or runs\nlong goes to the background, with BOTH streams redirected and its pid kept:\n\n nohup \u003ccommand\u003e \u003e /tmp/\u003cjob\u003e.log 2\u003e\u00261 \u0026 echo $! \u003e /tmp/\u003cjob\u003e.pid\n\nRedirecting only stdout still blocks until the process exits. Read the\n`bg-jobs` skill before you wait on, inspect or stop such a job — each has a\nrule you cannot guess. Stop every job you started before you finish, and say\nwhich ones you left running.\n" | |
| 13 | + } | |
| 14 | + ], | |
| 15 | + "role": "system" | |
| 16 | + } | |
| 17 | + ] | |
| 18 | +} | |
| \ No newline at end of file | ||
| new file mode 100644 | |||
| @@ -0,0 +1,18 @@ | |||
| 1 | +{ | ||
| 2 | + "id": "20260928-181731-d13e1aeb", | ||
| 3 | + "cwd": "/Users/k33g/kDrive/Rickub/bots-garden/sidekick/demo", | ||
| 4 | + "provider": "dmr", | ||
| 5 | + "model": "huggingface.co/unsloth/gemma-4-e4b-it-gguf:Q4_K_M", | ||
| 6 | + "createdAt": "2026-09-28T18:17:31.615012Z", | ||
| 7 | + "updatedAt": "2026-09-28T18:17:31.615043Z", | ||
| 8 | + "messages": [ | ||
| 9 | + { | ||
| 10 | + "content": [ | ||
| 11 | + { | ||
| 12 | + "text": "Your name is Bob.\nYou are a coding agent working in a terminal.\nYou have a \"bash\" tool to run shell commands.\nUse it to explore files, run tests, inspect the repository, etc.\nChain several commands if needed, then answer clearly in English.\n\nA request often mixes things you answer from yourself (\"say hello\") with\nthings only a command can answer (\"list the files\"). Handle every part, in\nthe order asked, and run a command for each part that needs one.\nNever state the contents of a file, the output of a command, or the state of\nthe repository unless a command in THIS answer returned it. What you did not\nread, you do not know: run the command instead of recalling it.\n\nSKILLS\nYou have a second tool, `read_skill`. Its description lists the procedures\navailable for this project — one per kind of task.\n\nAny request to DO something to a Go project is a skill, not a shell command\nyou invent. Match the request against that list, call `read_skill` FIRST,\nbefore any bash command, and then follow what it says step by step.\n\nFILE EDITING\nYou have three tools for files: `read_file`, `edit_file` and `write_file`.\nThey are how a file gets read and changed here: each change is exact,\nchecked before it is written, and comes back as a diff with line numbers.\nbash is for running things — building, testing, listing, searching.\n\n- Read before you write: call `read_file` on the file (numbered=true when\n you need line numbers). You cannot target text you have not seen; never\n rely on what you think you remember about a file.\n- To change an existing file, call `edit_file` with one or more {old, new}\n pairs. `old` is copied from the file character for character — same\n spaces, same indentation, same line breaks — and appears exactly once:\n add the surrounding lines until it is unique. Several pairs are applied\n together, against the original file. An empty `new` deletes the text.\n- Call `write_file` only to create a file, or to rewrite one entirely and\n on purpose. On an existing file it replaces everything, including what\n you did not intend to touch.\n- Read the diff the tool returns: it says exactly what changed and on which\n line. If `edit_file` refuses — text not found, ambiguous, overlapping\n edits — read the file again and fix `old`. Do not fall back to\n `write_file` to force the change through.\n- After editing code, run the narrowest check with bash: the formatter, the\n compiler, or the test covering that file.\n\nRULES\n- Keep everything the file already does, unless the user asked to remove it.\n- Touch only the files the request is about. Do not add tests, files or\n features that were not asked for.\n- Never run a git command unless the user says git, commit or push.\n- Never move, rename or delete a file unless the user asked for it.\n- Then answer in English, in a few lines.\n- If you don't know how to use a \u003ccli\u003e, run `\u003ccli\u003e --help` (or `\u003ccli\u003e help`)\n to understand the options, then run the command.\n\nBACKGROUND JOBS\nNever let a command block the answer. Anything that serves, watches or runs\nlong goes to the background, with BOTH streams redirected and its pid kept:\n\n nohup \u003ccommand\u003e \u003e /tmp/\u003cjob\u003e.log 2\u003e\u00261 \u0026 echo $! \u003e /tmp/\u003cjob\u003e.pid\n\nRedirecting only stdout still blocks until the process exits. Read the\n`bg-jobs` skill before you wait on, inspect or stop such a job — each has a\nrule you cannot guess. Stop every job you started before you finish, and say\nwhich ones you left running.\n" | ||
| 13 | + } | ||
| 14 | + ], | ||
| 15 | + "role": "system" | ||
| 16 | + } | ||
| 17 | + ] | ||
| 18 | +} | ||
| \ No newline at end of file | \ No newline at end of file | ||
added
demo/.mm/sessions/20260928-182704-5d8debbc.json +112 -0 | new file mode 100644 | ||
| @@ -0,0 +1,112 @@ | ||
| 1 | +{ | |
| 2 | + "id": "20260928-182704-5d8debbc", | |
| 3 | + "cwd": "/Users/k33g/kDrive/Rickub/bots-garden/sidekick/demo", | |
| 4 | + "provider": "dmr", | |
| 5 | + "model": "huggingface.co/unsloth/gemma-4-e4b-it-gguf:Q4_K_M", | |
| 6 | + "createdAt": "2026-09-28T18:27:04.922845798Z", | |
| 7 | + "updatedAt": "2026-09-28T18:28:22.990557836Z", | |
| 8 | + "messages": [ | |
| 9 | + { | |
| 10 | + "content": [ | |
| 11 | + { | |
| 12 | + "text": "Your name is Bob.\nYou are a coding agent working in a terminal.\nYou have a \"bash\" tool to run shell commands.\nUse it to explore files, run tests, inspect the repository, etc.\nChain several commands if needed, then answer clearly in English.\n\nA request often mixes things you answer from yourself (\"say hello\") with\nthings only a command can answer (\"list the files\"). Handle every part, in\nthe order asked, and run a command for each part that needs one.\nNever state the contents of a file, the output of a command, or the state of\nthe repository unless a command in THIS answer returned it. What you did not\nread, you do not know: run the command instead of recalling it.\n\nSKILLS\nYou have a second tool, `read_skill`. Its description lists the procedures\navailable for this project — one per kind of task.\n\nAny request to DO something to a Go project is a skill, not a shell command\nyou invent. Match the request against that list, call `read_skill` FIRST,\nbefore any bash command, and then follow what it says step by step.\n\nFILE EDITING\nYou have three tools for files: `read_file`, `edit_file` and `write_file`.\nThey are how a file gets read and changed here: each change is exact,\nchecked before it is written, and comes back as a diff with line numbers.\nbash is for running things — building, testing, listing, searching.\n\n- Read before you write: call `read_file` on the file (numbered=true when\n you need line numbers). You cannot target text you have not seen; never\n rely on what you think you remember about a file.\n- To change an existing file, call `edit_file` with one or more {old, new}\n pairs. `old` is copied from the file character for character — same\n spaces, same indentation, same line breaks — and appears exactly once:\n add the surrounding lines until it is unique. Several pairs are applied\n together, against the original file. An empty `new` deletes the text.\n- Call `write_file` only to create a file, or to rewrite one entirely and\n on purpose. On an existing file it replaces everything, including what\n you did not intend to touch.\n- Read the diff the tool returns: it says exactly what changed and on which\n line. If `edit_file` refuses — text not found, ambiguous, overlapping\n edits — read the file again and fix `old`. Do not fall back to\n `write_file` to force the change through.\n- After editing code, run the narrowest check with bash: the formatter, the\n compiler, or the test covering that file.\n\nRULES\n- Keep everything the file already does, unless the user asked to remove it.\n- Touch only the files the request is about. Do not add tests, files or\n features that were not asked for.\n- Never run a git command unless the user says git, commit or push.\n- Never move, rename or delete a file unless the user asked for it.\n- Then answer in English, in a few lines.\n- If you don't know how to use a \u003ccli\u003e, run `\u003ccli\u003e --help` (or `\u003ccli\u003e help`)\n to understand the options, then run the command.\n\nBACKGROUND JOBS\nNever let a command block the answer. Anything that serves, watches or runs\nlong goes to the background, with BOTH streams redirected and its pid kept:\n\n nohup \u003ccommand\u003e \u003e /tmp/\u003cjob\u003e.log 2\u003e\u00261 \u0026 echo $! \u003e /tmp/\u003cjob\u003e.pid\n\nRedirecting only stdout still blocks until the process exits. Read the\n`bg-jobs` skill before you wait on, inspect or stop such a job — each has a\nrule you cannot guess. Stop every job you started before you finish, and say\nwhich ones you left running.\n" | |
| 13 | + } | |
| 14 | + ], | |
| 15 | + "role": "system" | |
| 16 | + }, | |
| 17 | + { | |
| 18 | + "content": [ | |
| 19 | + { | |
| 20 | + "text": "install htop" | |
| 21 | + } | |
| 22 | + ], | |
| 23 | + "role": "user" | |
| 24 | + }, | |
| 25 | + { | |
| 26 | + "content": [ | |
| 27 | + { | |
| 28 | + "toolRequest": { | |
| 29 | + "input": { | |
| 30 | + "command": "sudo apt-get install htop" | |
| 31 | + }, | |
| 32 | + "name": "bash", | |
| 33 | + "ref": "x17RLowzkdoJZOksubnE1aP40I4KaU7L" | |
| 34 | + } | |
| 35 | + } | |
| 36 | + ], | |
| 37 | + "role": "model" | |
| 38 | + }, | |
| 39 | + { | |
| 40 | + "content": [ | |
| 41 | + { | |
| 42 | + "toolResponse": { | |
| 43 | + "name": "bash", | |
| 44 | + "output": "Reading package lists...\nBuilding dependency tree...\nReading state information...\nE: Unable to locate package htop\n\n[exit code: exit status 100]", | |
| 45 | + "ref": "x17RLowzkdoJZOksubnE1aP40I4KaU7L" | |
| 46 | + } | |
| 47 | + } | |
| 48 | + ], | |
| 49 | + "role": "tool" | |
| 50 | + }, | |
| 51 | + { | |
| 52 | + "content": [ | |
| 53 | + { | |
| 54 | + "toolRequest": { | |
| 55 | + "input": { | |
| 56 | + "command": "sudo apt-get update" | |
| 57 | + }, | |
| 58 | + "name": "bash", | |
| 59 | + "ref": "JdJRqAOOSWK7JqCR8NHacHFFmUskLJcj" | |
| 60 | + } | |
| 61 | + } | |
| 62 | + ], | |
| 63 | + "role": "model" | |
| 64 | + }, | |
| 65 | + { | |
| 66 | + "content": [ | |
| 67 | + { | |
| 68 | + "toolResponse": { | |
| 69 | + "name": "bash", | |
| 70 | + "output": "Get:1 https://download.docker.com/linux/ubuntu resolute InRelease [32.5 kB]\nGet:2 https://download.docker.com/linux/ubuntu resolute/stable arm64 Packages [27.7 kB]\nGet:3 http://ports.ubuntu.com/ubuntu-ports resolute InRelease [136 kB]\nGet:4 http://ports.ubuntu.com/ubuntu-ports resolute-updates InRelease [137 kB]\nGet:5 http://ports.ubuntu.com/ubuntu-ports resolute-backports InRelease [137 kB]\nGet:6 http://ports.ubuntu.com/ubuntu-ports resolute-security InRelease [137 kB]\nGet:7 http://ports.ubuntu.com/ubuntu-ports resolute/multiverse arm64 Packages [298 kB]\nGet:8 http://ports.ubuntu.com/ubuntu-ports resolute/main arm64 Packages [1860 kB]\nGet:9 http://ports.ubuntu.com/ubuntu-ports resolute/restricted arm64 Packages [231 kB]\nGet:10 http://ports.ubuntu.com/ubuntu-ports resolute/universe arm64 Packages [19.9 MB]\nGet:11 http://ports.ubuntu.com/ubuntu-ports resolute-updates/restricted arm64 Packages [743 kB]\nGet:12 http://ports.ubuntu.com/ubuntu-ports resolute-updates/multiverse arm64 Packages [14.9 kB]\nGet:13 http://ports.ubuntu.com/ubuntu-ports resolute-updates/universe arm64 Packages [379 kB]\nGet:14 http://ports.ubuntu.com/ubuntu-ports resolute-updates/main arm64 Packages [859 kB]\nGet:15 http://ports.ubuntu.com/ubuntu-ports resolute-backports/universe arm64 Packages [3308 B]\nGet:16 http://ports.ubuntu.com/ubuntu-ports resolute-security/multiverse arm64 Packages [13.6 kB]\nGet:17 http://ports.ubuntu.com/ubuntu-ports resolute-security/restricted arm64 Packages [723 kB]\nGet:18 http://ports.ubuntu.com/ubuntu-ports resolute-security/main arm64 Packages [689 kB]\nGet:19 http://ports.ubuntu.com/ubuntu-ports resolute-security/universe arm64 Packages [226 kB]\nFetched 26.6 MB in 2s (14.7 MB/s)\nReading package lists...\n", | |
| 71 | + "ref": "JdJRqAOOSWK7JqCR8NHacHFFmUskLJcj" | |
| 72 | + } | |
| 73 | + } | |
| 74 | + ], | |
| 75 | + "role": "tool" | |
| 76 | + }, | |
| 77 | + { | |
| 78 | + "content": [ | |
| 79 | + { | |
| 80 | + "toolRequest": { | |
| 81 | + "input": { | |
| 82 | + "command": "sudo apt-get install htop" | |
| 83 | + }, | |
| 84 | + "name": "bash", | |
| 85 | + "ref": "lrfYvX8CrnKvGkrFDShuRwL7IKgnJo20" | |
| 86 | + } | |
| 87 | + } | |
| 88 | + ], | |
| 89 | + "role": "model" | |
| 90 | + }, | |
| 91 | + { | |
| 92 | + "content": [ | |
| 93 | + { | |
| 94 | + "toolResponse": { | |
| 95 | + "name": "bash", | |
| 96 | + "output": "Reading package lists...\nBuilding dependency tree...\nReading state information...\nSolving dependencies...\nSuggested packages:\n lm-sensors strace\nThe following NEW packages will be installed:\n htop\n0 upgraded, 1 newly installed, 0 to remove and 2 not upgraded.\nNeed to get 175 kB of archives.\nAfter this operation, 455 kB of additional disk space will be used.\nGet:1 http://ports.ubuntu.com/ubuntu-ports resolute/main arm64 htop arm64 3.4.1-5build2 [175 kB]\ndebconf: unable to initialize frontend: Dialog\ndebconf: (No usable dialog-like program is installed, so the dialog based frontend cannot be used. at /usr/share/perl5/Debconf/FrontEnd/Dialog.pm line 79, \u003cSTDIN\u003e line 1.)\ndebconf: falling back to frontend: Readline\ndebconf: unable to initialize frontend: Readline\ndebconf: (This frontend requires a controlling tty.)\ndebconf: falling back to frontend: Teletype\ndebconf: unable to initialize frontend: Teletype\ndebconf: (This frontend requires a controlling tty.)\ndebconf: falling back to frontend: Noninteractive\nFetched 175 kB in 1s (264 kB/s)\nSelecting previously unselected package htop.\r\n(Reading database ... \r(Reading database ... 5%\r(Reading database ... 10%\r(Reading database ... 15%\r(Reading database ... 20%\r(Reading database ... 25%\r(Reading database ... 30%\r(Reading database ... 35%\r(Reading database ... 40%\r(Reading database ... 45%\r(Reading database ... 50%\r(Reading database ... 55%\r(Reading database ... 60%\r(Reading database ... 65%\r(Reading database ... 70%\r(Reading database ... 75%\r(Reading database ... 80%\r(Reading database ... 85%\r(Reading database ... 90%\r(Reading database ... 95%\r(Reading database ... 100%\r(Reading database ... 54799 files and directories currently installed.)\r\nPreparing to unpack .../htop_3.4.1-5build2_arm64.deb ...\r\nUnpacking htop (3.4.1-5build2) ...\r\nSetting up htop (3.4.1-5build2) ...\r\nProcessing triggers for man-db (2.13.1-1build1) ...\r\n", | |
| 97 | + "ref": "lrfYvX8CrnKvGkrFDShuRwL7IKgnJo20" | |
| 98 | + } | |
| 99 | + } | |
| 100 | + ], | |
| 101 | + "role": "tool" | |
| 102 | + }, | |
| 103 | + { | |
| 104 | + "content": [ | |
| 105 | + { | |
| 106 | + "text": "I have successfully installed `htop` using `apt-get` after first updating the package lists." | |
| 107 | + } | |
| 108 | + ], | |
| 109 | + "role": "model" | |
| 110 | + } | |
| 111 | + ] | |
| 112 | +} | |
| \ No newline at end of file | ||
| new file mode 100644 | |||
| @@ -0,0 +1,112 @@ | |||
| 1 | +{ | ||
| 2 | + "id": "20260928-182704-5d8debbc", | ||
| 3 | + "cwd": "/Users/k33g/kDrive/Rickub/bots-garden/sidekick/demo", | ||
| 4 | + "provider": "dmr", | ||
| 5 | + "model": "huggingface.co/unsloth/gemma-4-e4b-it-gguf:Q4_K_M", | ||
| 6 | + "createdAt": "2026-09-28T18:27:04.922845798Z", | ||
| 7 | + "updatedAt": "2026-09-28T18:28:22.990557836Z", | ||
| 8 | + "messages": [ | ||
| 9 | + { | ||
| 10 | + "content": [ | ||
| 11 | + { | ||
| 12 | + "text": "Your name is Bob.\nYou are a coding agent working in a terminal.\nYou have a \"bash\" tool to run shell commands.\nUse it to explore files, run tests, inspect the repository, etc.\nChain several commands if needed, then answer clearly in English.\n\nA request often mixes things you answer from yourself (\"say hello\") with\nthings only a command can answer (\"list the files\"). Handle every part, in\nthe order asked, and run a command for each part that needs one.\nNever state the contents of a file, the output of a command, or the state of\nthe repository unless a command in THIS answer returned it. What you did not\nread, you do not know: run the command instead of recalling it.\n\nSKILLS\nYou have a second tool, `read_skill`. Its description lists the procedures\navailable for this project — one per kind of task.\n\nAny request to DO something to a Go project is a skill, not a shell command\nyou invent. Match the request against that list, call `read_skill` FIRST,\nbefore any bash command, and then follow what it says step by step.\n\nFILE EDITING\nYou have three tools for files: `read_file`, `edit_file` and `write_file`.\nThey are how a file gets read and changed here: each change is exact,\nchecked before it is written, and comes back as a diff with line numbers.\nbash is for running things — building, testing, listing, searching.\n\n- Read before you write: call `read_file` on the file (numbered=true when\n you need line numbers). You cannot target text you have not seen; never\n rely on what you think you remember about a file.\n- To change an existing file, call `edit_file` with one or more {old, new}\n pairs. `old` is copied from the file character for character — same\n spaces, same indentation, same line breaks — and appears exactly once:\n add the surrounding lines until it is unique. Several pairs are applied\n together, against the original file. An empty `new` deletes the text.\n- Call `write_file` only to create a file, or to rewrite one entirely and\n on purpose. On an existing file it replaces everything, including what\n you did not intend to touch.\n- Read the diff the tool returns: it says exactly what changed and on which\n line. If `edit_file` refuses — text not found, ambiguous, overlapping\n edits — read the file again and fix `old`. Do not fall back to\n `write_file` to force the change through.\n- After editing code, run the narrowest check with bash: the formatter, the\n compiler, or the test covering that file.\n\nRULES\n- Keep everything the file already does, unless the user asked to remove it.\n- Touch only the files the request is about. Do not add tests, files or\n features that were not asked for.\n- Never run a git command unless the user says git, commit or push.\n- Never move, rename or delete a file unless the user asked for it.\n- Then answer in English, in a few lines.\n- If you don't know how to use a \u003ccli\u003e, run `\u003ccli\u003e --help` (or `\u003ccli\u003e help`)\n to understand the options, then run the command.\n\nBACKGROUND JOBS\nNever let a command block the answer. Anything that serves, watches or runs\nlong goes to the background, with BOTH streams redirected and its pid kept:\n\n nohup \u003ccommand\u003e \u003e /tmp/\u003cjob\u003e.log 2\u003e\u00261 \u0026 echo $! \u003e /tmp/\u003cjob\u003e.pid\n\nRedirecting only stdout still blocks until the process exits. Read the\n`bg-jobs` skill before you wait on, inspect or stop such a job — each has a\nrule you cannot guess. Stop every job you started before you finish, and say\nwhich ones you left running.\n" | ||
| 13 | + } | ||
| 14 | + ], | ||
| 15 | + "role": "system" | ||
| 16 | + }, | ||
| 17 | + { | ||
| 18 | + "content": [ | ||
| 19 | + { | ||
| 20 | + "text": "install htop" | ||
| 21 | + } | ||
| 22 | + ], | ||
| 23 | + "role": "user" | ||
| 24 | + }, | ||
| 25 | + { | ||
| 26 | + "content": [ | ||
| 27 | + { | ||
| 28 | + "toolRequest": { | ||
| 29 | + "input": { | ||
| 30 | + "command": "sudo apt-get install htop" | ||
| 31 | + }, | ||
| 32 | + "name": "bash", | ||
| 33 | + "ref": "x17RLowzkdoJZOksubnE1aP40I4KaU7L" | ||
| 34 | + } | ||
| 35 | + } | ||
| 36 | + ], | ||
| 37 | + "role": "model" | ||
| 38 | + }, | ||
| 39 | + { | ||
| 40 | + "content": [ | ||
| 41 | + { | ||
| 42 | + "toolResponse": { | ||
| 43 | + "name": "bash", | ||
| 44 | + "output": "Reading package lists...\nBuilding dependency tree...\nReading state information...\nE: Unable to locate package htop\n\n[exit code: exit status 100]", | ||
| 45 | + "ref": "x17RLowzkdoJZOksubnE1aP40I4KaU7L" | ||
| 46 | + } | ||
| 47 | + } | ||
| 48 | + ], | ||
| 49 | + "role": "tool" | ||
| 50 | + }, | ||
| 51 | + { | ||
| 52 | + "content": [ | ||
| 53 | + { | ||
| 54 | + "toolRequest": { | ||
| 55 | + "input": { | ||
| 56 | + "command": "sudo apt-get update" | ||
| 57 | + }, | ||
| 58 | + "name": "bash", | ||
| 59 | + "ref": "JdJRqAOOSWK7JqCR8NHacHFFmUskLJcj" | ||
| 60 | + } | ||
| 61 | + } | ||
| 62 | + ], | ||
| 63 | + "role": "model" | ||
| 64 | + }, | ||
| 65 | + { | ||
| 66 | + "content": [ | ||
| 67 | + { | ||
| 68 | + "toolResponse": { | ||
| 69 | + "name": "bash", | ||
| 70 | + "output": "Get:1 https://download.docker.com/linux/ubuntu resolute InRelease [32.5 kB]\nGet:2 https://download.docker.com/linux/ubuntu resolute/stable arm64 Packages [27.7 kB]\nGet:3 http://ports.ubuntu.com/ubuntu-ports resolute InRelease [136 kB]\nGet:4 http://ports.ubuntu.com/ubuntu-ports resolute-updates InRelease [137 kB]\nGet:5 http://ports.ubuntu.com/ubuntu-ports resolute-backports InRelease [137 kB]\nGet:6 http://ports.ubuntu.com/ubuntu-ports resolute-security InRelease [137 kB]\nGet:7 http://ports.ubuntu.com/ubuntu-ports resolute/multiverse arm64 Packages [298 kB]\nGet:8 http://ports.ubuntu.com/ubuntu-ports resolute/main arm64 Packages [1860 kB]\nGet:9 http://ports.ubuntu.com/ubuntu-ports resolute/restricted arm64 Packages [231 kB]\nGet:10 http://ports.ubuntu.com/ubuntu-ports resolute/universe arm64 Packages [19.9 MB]\nGet:11 http://ports.ubuntu.com/ubuntu-ports resolute-updates/restricted arm64 Packages [743 kB]\nGet:12 http://ports.ubuntu.com/ubuntu-ports resolute-updates/multiverse arm64 Packages [14.9 kB]\nGet:13 http://ports.ubuntu.com/ubuntu-ports resolute-updates/universe arm64 Packages [379 kB]\nGet:14 http://ports.ubuntu.com/ubuntu-ports resolute-updates/main arm64 Packages [859 kB]\nGet:15 http://ports.ubuntu.com/ubuntu-ports resolute-backports/universe arm64 Packages [3308 B]\nGet:16 http://ports.ubuntu.com/ubuntu-ports resolute-security/multiverse arm64 Packages [13.6 kB]\nGet:17 http://ports.ubuntu.com/ubuntu-ports resolute-security/restricted arm64 Packages [723 kB]\nGet:18 http://ports.ubuntu.com/ubuntu-ports resolute-security/main arm64 Packages [689 kB]\nGet:19 http://ports.ubuntu.com/ubuntu-ports resolute-security/universe arm64 Packages [226 kB]\nFetched 26.6 MB in 2s (14.7 MB/s)\nReading package lists...\n", | ||
| 71 | + "ref": "JdJRqAOOSWK7JqCR8NHacHFFmUskLJcj" | ||
| 72 | + } | ||
| 73 | + } | ||
| 74 | + ], | ||
| 75 | + "role": "tool" | ||
| 76 | + }, | ||
| 77 | + { | ||
| 78 | + "content": [ | ||
| 79 | + { | ||
| 80 | + "toolRequest": { | ||
| 81 | + "input": { | ||
| 82 | + "command": "sudo apt-get install htop" | ||
| 83 | + }, | ||
| 84 | + "name": "bash", | ||
| 85 | + "ref": "lrfYvX8CrnKvGkrFDShuRwL7IKgnJo20" | ||
| 86 | + } | ||
| 87 | + } | ||
| 88 | + ], | ||
| 89 | + "role": "model" | ||
| 90 | + }, | ||
| 91 | + { | ||
| 92 | + "content": [ | ||
| 93 | + { | ||
| 94 | + "toolResponse": { | ||
| 95 | + "name": "bash", | ||
| 96 | + "output": "Reading package lists...\nBuilding dependency tree...\nReading state information...\nSolving dependencies...\nSuggested packages:\n lm-sensors strace\nThe following NEW packages will be installed:\n htop\n0 upgraded, 1 newly installed, 0 to remove and 2 not upgraded.\nNeed to get 175 kB of archives.\nAfter this operation, 455 kB of additional disk space will be used.\nGet:1 http://ports.ubuntu.com/ubuntu-ports resolute/main arm64 htop arm64 3.4.1-5build2 [175 kB]\ndebconf: unable to initialize frontend: Dialog\ndebconf: (No usable dialog-like program is installed, so the dialog based frontend cannot be used. at /usr/share/perl5/Debconf/FrontEnd/Dialog.pm line 79, \u003cSTDIN\u003e line 1.)\ndebconf: falling back to frontend: Readline\ndebconf: unable to initialize frontend: Readline\ndebconf: (This frontend requires a controlling tty.)\ndebconf: falling back to frontend: Teletype\ndebconf: unable to initialize frontend: Teletype\ndebconf: (This frontend requires a controlling tty.)\ndebconf: falling back to frontend: Noninteractive\nFetched 175 kB in 1s (264 kB/s)\nSelecting previously unselected package htop.\r\n(Reading database ... \r(Reading database ... 5%\r(Reading database ... 10%\r(Reading database ... 15%\r(Reading database ... 20%\r(Reading database ... 25%\r(Reading database ... 30%\r(Reading database ... 35%\r(Reading database ... 40%\r(Reading database ... 45%\r(Reading database ... 50%\r(Reading database ... 55%\r(Reading database ... 60%\r(Reading database ... 65%\r(Reading database ... 70%\r(Reading database ... 75%\r(Reading database ... 80%\r(Reading database ... 85%\r(Reading database ... 90%\r(Reading database ... 95%\r(Reading database ... 100%\r(Reading database ... 54799 files and directories currently installed.)\r\nPreparing to unpack .../htop_3.4.1-5build2_arm64.deb ...\r\nUnpacking htop (3.4.1-5build2) ...\r\nSetting up htop (3.4.1-5build2) ...\r\nProcessing triggers for man-db (2.13.1-1build1) ...\r\n", | ||
| 97 | + "ref": "lrfYvX8CrnKvGkrFDShuRwL7IKgnJo20" | ||
| 98 | + } | ||
| 99 | + } | ||
| 100 | + ], | ||
| 101 | + "role": "tool" | ||
| 102 | + }, | ||
| 103 | + { | ||
| 104 | + "content": [ | ||
| 105 | + { | ||
| 106 | + "text": "I have successfully installed `htop` using `apt-get` after first updating the package lists." | ||
| 107 | + } | ||
| 108 | + ], | ||
| 109 | + "role": "model" | ||
| 110 | + } | ||
| 111 | + ] | ||
| 112 | +} | ||
| \ No newline at end of file | \ No newline at end of file | ||
added
demo/.mm/sessions/20260928-183323-6713fc07.json +18 -0 | new file mode 100644 | ||
| @@ -0,0 +1,18 @@ | ||
| 1 | +{ | |
| 2 | + "id": "20260928-183323-6713fc07", | |
| 3 | + "cwd": "/Users/k33g/kDrive/Rickub/bots-garden/sidekick/demo", | |
| 4 | + "provider": "dmr", | |
| 5 | + "model": "huggingface.co/unsloth/gemma-4-e4b-it-gguf:Q4_K_M", | |
| 6 | + "createdAt": "2026-09-28T18:33:23.927296Z", | |
| 7 | + "updatedAt": "2026-09-28T18:33:23.927318Z", | |
| 8 | + "messages": [ | |
| 9 | + { | |
| 10 | + "content": [ | |
| 11 | + { | |
| 12 | + "text": "Your name is Bob.\nYou are a coding agent working in a terminal.\nYou have a \"bash\" tool to run shell commands.\nUse it to explore files, run tests, inspect the repository, etc.\nChain several commands if needed, then answer clearly in English.\n\nA request often mixes things you answer from yourself (\"say hello\") with\nthings only a command can answer (\"list the files\"). Handle every part, in\nthe order asked, and run a command for each part that needs one.\nNever state the contents of a file, the output of a command, or the state of\nthe repository unless a command in THIS answer returned it. What you did not\nread, you do not know: run the command instead of recalling it.\n\nSKILLS\nYou have a second tool, `read_skill`. Its description lists the procedures\navailable for this project — one per kind of task.\n\nAny request to DO something to a Go project is a skill, not a shell command\nyou invent. Match the request against that list, call `read_skill` FIRST,\nbefore any bash command, and then follow what it says step by step.\n\nFILE EDITING\nYou have three tools for files: `read_file`, `edit_file` and `write_file`.\nThey are how a file gets read and changed here: each change is exact,\nchecked before it is written, and comes back as a diff with line numbers.\nbash is for running things — building, testing, listing, searching.\n\n- Read before you write: call `read_file` on the file (numbered=true when\n you need line numbers). You cannot target text you have not seen; never\n rely on what you think you remember about a file.\n- To change an existing file, call `edit_file` with one or more {old, new}\n pairs. `old` is copied from the file character for character — same\n spaces, same indentation, same line breaks — and appears exactly once:\n add the surrounding lines until it is unique. Several pairs are applied\n together, against the original file. An empty `new` deletes the text.\n- Call `write_file` only to create a file, or to rewrite one entirely and\n on purpose. On an existing file it replaces everything, including what\n you did not intend to touch.\n- Read the diff the tool returns: it says exactly what changed and on which\n line. If `edit_file` refuses — text not found, ambiguous, overlapping\n edits — read the file again and fix `old`. Do not fall back to\n `write_file` to force the change through.\n- After editing code, run the narrowest check with bash: the formatter, the\n compiler, or the test covering that file.\n\nRULES\n- Keep everything the file already does, unless the user asked to remove it.\n- Touch only the files the request is about. Do not add tests, files or\n features that were not asked for.\n- Never run a git command unless the user says git, commit or push.\n- Never move, rename or delete a file unless the user asked for it.\n- Then answer in English, in a few lines.\n- If you don't know how to use a \u003ccli\u003e, run `\u003ccli\u003e --help` (or `\u003ccli\u003e help`)\n to understand the options, then run the command.\n\nBACKGROUND JOBS\nNever let a command block the answer. Anything that serves, watches or runs\nlong goes to the background, with BOTH streams redirected and its pid kept:\n\n nohup \u003ccommand\u003e \u003e /tmp/\u003cjob\u003e.log 2\u003e\u00261 \u0026 echo $! \u003e /tmp/\u003cjob\u003e.pid\n\nRedirecting only stdout still blocks until the process exits. Read the\n`bg-jobs` skill before you wait on, inspect or stop such a job — each has a\nrule you cannot guess. Stop every job you started before you finish, and say\nwhich ones you left running.\n" | |
| 13 | + } | |
| 14 | + ], | |
| 15 | + "role": "system" | |
| 16 | + } | |
| 17 | + ] | |
| 18 | +} | |
| \ No newline at end of file | ||
| new file mode 100644 | |||
| @@ -0,0 +1,18 @@ | |||
| 1 | +{ | ||
| 2 | + "id": "20260928-183323-6713fc07", | ||
| 3 | + "cwd": "/Users/k33g/kDrive/Rickub/bots-garden/sidekick/demo", | ||
| 4 | + "provider": "dmr", | ||
| 5 | + "model": "huggingface.co/unsloth/gemma-4-e4b-it-gguf:Q4_K_M", | ||
| 6 | + "createdAt": "2026-09-28T18:33:23.927296Z", | ||
| 7 | + "updatedAt": "2026-09-28T18:33:23.927318Z", | ||
| 8 | + "messages": [ | ||
| 9 | + { | ||
| 10 | + "content": [ | ||
| 11 | + { | ||
| 12 | + "text": "Your name is Bob.\nYou are a coding agent working in a terminal.\nYou have a \"bash\" tool to run shell commands.\nUse it to explore files, run tests, inspect the repository, etc.\nChain several commands if needed, then answer clearly in English.\n\nA request often mixes things you answer from yourself (\"say hello\") with\nthings only a command can answer (\"list the files\"). Handle every part, in\nthe order asked, and run a command for each part that needs one.\nNever state the contents of a file, the output of a command, or the state of\nthe repository unless a command in THIS answer returned it. What you did not\nread, you do not know: run the command instead of recalling it.\n\nSKILLS\nYou have a second tool, `read_skill`. Its description lists the procedures\navailable for this project — one per kind of task.\n\nAny request to DO something to a Go project is a skill, not a shell command\nyou invent. Match the request against that list, call `read_skill` FIRST,\nbefore any bash command, and then follow what it says step by step.\n\nFILE EDITING\nYou have three tools for files: `read_file`, `edit_file` and `write_file`.\nThey are how a file gets read and changed here: each change is exact,\nchecked before it is written, and comes back as a diff with line numbers.\nbash is for running things — building, testing, listing, searching.\n\n- Read before you write: call `read_file` on the file (numbered=true when\n you need line numbers). You cannot target text you have not seen; never\n rely on what you think you remember about a file.\n- To change an existing file, call `edit_file` with one or more {old, new}\n pairs. `old` is copied from the file character for character — same\n spaces, same indentation, same line breaks — and appears exactly once:\n add the surrounding lines until it is unique. Several pairs are applied\n together, against the original file. An empty `new` deletes the text.\n- Call `write_file` only to create a file, or to rewrite one entirely and\n on purpose. On an existing file it replaces everything, including what\n you did not intend to touch.\n- Read the diff the tool returns: it says exactly what changed and on which\n line. If `edit_file` refuses — text not found, ambiguous, overlapping\n edits — read the file again and fix `old`. Do not fall back to\n `write_file` to force the change through.\n- After editing code, run the narrowest check with bash: the formatter, the\n compiler, or the test covering that file.\n\nRULES\n- Keep everything the file already does, unless the user asked to remove it.\n- Touch only the files the request is about. Do not add tests, files or\n features that were not asked for.\n- Never run a git command unless the user says git, commit or push.\n- Never move, rename or delete a file unless the user asked for it.\n- Then answer in English, in a few lines.\n- If you don't know how to use a \u003ccli\u003e, run `\u003ccli\u003e --help` (or `\u003ccli\u003e help`)\n to understand the options, then run the command.\n\nBACKGROUND JOBS\nNever let a command block the answer. Anything that serves, watches or runs\nlong goes to the background, with BOTH streams redirected and its pid kept:\n\n nohup \u003ccommand\u003e \u003e /tmp/\u003cjob\u003e.log 2\u003e\u00261 \u0026 echo $! \u003e /tmp/\u003cjob\u003e.pid\n\nRedirecting only stdout still blocks until the process exits. Read the\n`bg-jobs` skill before you wait on, inspect or stop such a job — each has a\nrule you cannot guess. Stop every job you started before you finish, and say\nwhich ones you left running.\n" | ||
| 13 | + } | ||
| 14 | + ], | ||
| 15 | + "role": "system" | ||
| 16 | + } | ||
| 17 | + ] | ||
| 18 | +} | ||
| \ No newline at end of file | \ No newline at end of file | ||
modified
demo/start.sbx.sh +3 -18 | @@ -1,21 +1,6 @@ | ||
| 1 | 1 | #!/bin/bash |
| 2 | +sbx create --name demo-gemma k33g/sbx-kit-mm-acp:0.2.2 . ../mini-me-config:ro \ | |
| 3 | + --kit-arg config=../mini-me-config/agent.little.gemma.yaml | |
| 2 | 4 | |
| 5 | +SBX_NAME=demo-gemma sidekick -port 9002 ./mm-acp.sbx.sh | |
| 3 | 6 | |
| 4 | - | |
| 5 | -sidekick -port 9000 sbx exec -i sidekick-acp mm -acp | |
| 6 | - | |
| 7 | - | |
| 8 | -sbx run k33g/sbx-kit-mm-tui:0.2.2 . ../mini-me-config:ro -- ../mini-me-config/sbx.agent.little.gemma.yaml | |
| 9 | - | |
| 10 | -# window mode sbx run k33g/sbx-kit-mm-tui:0.2.2 . -- agent.dmr.yaml | |
| 11 | -# editor (ACP) sbx create --name mm-acp k33g/sbx-kit-mm-acp:0.2.2 . --kit-arg config=agent.dmr.yaml | |
| 12 | -# then register sbx exec -i mm-acp mm -acp in the editor | |
| 13 | - | |
| 14 | -sbx create --name mm-acp k33g/sbx-kit-mm-acp:0.2.2 . ../mini-me-config:ro \ | |
| 15 | - --kit-arg config=../mini-me-config/sbx.agent.little.gemma.yaml | |
| 16 | - | |
| 17 | - | |
| 18 | -sidekick -port 9000 ./mm-acp.sbx.sh | |
| 19 | - | |
| 20 | -# Pour une autre sandbox, tu peux changer le nom : | |
| 21 | -# SBX_NAME=autre-sandbox sidekick -port 9000 ./mm-acp.sbx.sh. | |
| @@ -1,21 +1,6 @@ | |||
| 1 | #!/bin/bash | 1 | #!/bin/bash |
| 2 | +sbx create --name demo-gemma k33g/sbx-kit-mm-acp:0.2.2 . ../mini-me-config:ro \ | ||
| 3 | + --kit-arg config=../mini-me-config/agent.little.gemma.yaml | ||
| 2 | 4 | ||
| 5 | +SBX_NAME=demo-gemma sidekick -port 9002 ./mm-acp.sbx.sh | ||
| 3 | 6 | ||
| 4 | - | ||
| 5 | -sidekick -port 9000 sbx exec -i sidekick-acp mm -acp | ||
| 6 | - | ||
| 7 | - | ||
| 8 | -sbx run k33g/sbx-kit-mm-tui:0.2.2 . ../mini-me-config:ro -- ../mini-me-config/sbx.agent.little.gemma.yaml | ||
| 9 | - | ||
| 10 | -# window mode sbx run k33g/sbx-kit-mm-tui:0.2.2 . -- agent.dmr.yaml | ||
| 11 | -# editor (ACP) sbx create --name mm-acp k33g/sbx-kit-mm-acp:0.2.2 . --kit-arg config=agent.dmr.yaml | ||
| 12 | -# then register sbx exec -i mm-acp mm -acp in the editor | ||
| 13 | - | ||
| 14 | -sbx create --name mm-acp k33g/sbx-kit-mm-acp:0.2.2 . ../mini-me-config:ro \ | ||
| 15 | - --kit-arg config=../mini-me-config/sbx.agent.little.gemma.yaml | ||
| 16 | - | ||
| 17 | - | ||
| 18 | -sidekick -port 9000 ./mm-acp.sbx.sh | ||
| 19 | - | ||
| 20 | -# Pour une autre sandbox, tu peux changer le nom : | ||
| 21 | -# SBX_NAME=autre-sandbox sidekick -port 9000 ./mm-acp.sbx.sh. | ||
added
tmp/fetch-cli/README.md +74 -0 | new file mode 100644 | ||
| @@ -0,0 +1,74 @@ | ||
| 1 | +# fetch-cli | |
| 2 | + | |
| 3 | +CLI Go qui récupère une ou plusieurs URLs HTTP/HTTPS et convertit le HTML en markdown, texte brut ou HTML. | |
| 4 | + | |
| 5 | +Inspiré de l'outil `fetch` du repo [docker-agent](https://github.com/docker/docker-agent). | |
| 6 | + | |
| 7 | +## Installation | |
| 8 | + | |
| 9 | +```bash | |
| 10 | +cd fetch-cli | |
| 11 | +go build -o fetch-cli . | |
| 12 | +``` | |
| 13 | + | |
| 14 | +## Usage | |
| 15 | + | |
| 16 | +``` | |
| 17 | +fetch-cli [options] <url> [url2 ...] | |
| 18 | +``` | |
| 19 | + | |
| 20 | +### Options | |
| 21 | + | |
| 22 | +| Flag | Défaut | Description | | |
| 23 | +|------|--------|-------------| | |
| 24 | +| `-f` | `markdown` | Format de sortie : `markdown`, `text` ou `html` | | |
| 25 | +| `-t` | `30` | Timeout de la requête en secondes | | |
| 26 | +| `-robots` | `true` | Respecter `robots.txt` (passer `-robots=false` pour désactiver) | | |
| 27 | + | |
| 28 | +### Exemples | |
| 29 | + | |
| 30 | +```bash | |
| 31 | +# Récupérer une page en markdown (défaut) | |
| 32 | +./fetch-cli https://example.com | |
| 33 | + | |
| 34 | +# Récupérer en texte brut | |
| 35 | +./fetch-cli -f text https://example.com | |
| 36 | + | |
| 37 | +# Récupérer le HTML brut | |
| 38 | +./fetch-cli -f html https://example.com | |
| 39 | + | |
| 40 | +# Désactiver la vérification robots.txt | |
| 41 | +./fetch-cli -robots=false https://example.com | |
| 42 | + | |
| 43 | +# Plusieurs URLs d'un coup | |
| 44 | +./fetch-cli https://example.com https://golang.org | |
| 45 | + | |
| 46 | +# Timeout de 10 secondes | |
| 47 | +./fetch-cli -t 10 https://example.com | |
| 48 | +``` | |
| 49 | + | |
| 50 | +## Fonctionnement | |
| 51 | + | |
| 52 | +1. **Validation de l'URL** — Seuls les schémas `http` et `https` sont acceptés. | |
| 53 | +2. **Vérification robots.txt** — Par défaut, la CLI consulte le `robots.txt` du host et refuse les URLs bloquées. Désactivable avec `-robots=false`. | |
| 54 | +3. **Négociation du format** — L'en-tête `Accept` est ajusté selon le format demandé : | |
| 55 | + - `markdown` → `text/markdown;q=1.0, text/plain;q=0.9, text/html;q=0.7, */*;q=0.1` | |
| 56 | + - `text` → `text/plain;q=1.0, text/markdown;q=0.9, text/html;q=0.8, */*;q=0.1` | |
| 57 | + - `html` → `text/html;q=1.0, text/plain;q=0.8, */*;q=0.1` | |
| 58 | +4. **Téléchargement** — Le body est lu avec une limite de **1 Mo**. Les redirects sont suivis (max 10). | |
| 59 | +5. **Conversion** — Si le `Content-Type` contient `text/html` : | |
| 60 | + - `markdown` : conversion via [html-to-markdown/v2](https://github.com/JohannesKaufmann/html-to-markdown) | |
| 61 | + - `text` : extraction du texte via [html2text](https://github.com/k3a/html2text) | |
| 62 | + - `html` : aucun traitement, le HTML est renvoyé tel quel | |
| 63 | +6. **Sortie** — Les métadonnées (status, content-type, taille) sont écrites sur **stderr**, le contenu sur **stdout**. | |
| 64 | + | |
| 65 | +## Code de sortie | |
| 66 | + | |
| 67 | +- `0` : toutes les URLs ont été récupérées avec succès | |
| 68 | +- `1` : au moins une URL a échoué (ou aucune URL fournie) | |
| 69 | + | |
| 70 | +## Dépendances | |
| 71 | + | |
| 72 | +- [github.com/JohannesKaufmann/html-to-markdown/v2](https://github.com/JohannesKaufmann/html-to-markdown) — conversion HTML → Markdown | |
| 73 | +- [github.com/k3a/html2text](https://github.com/k3a/html2text) — extraction de texte depuis HTML | |
| 74 | +- [github.com/temoto/robotstxt](https://github.com/temoto/robotstxt) — parsing de robots.txt | |
| new file mode 100644 | |||
| @@ -0,0 +1,74 @@ | |||
| 1 | +# fetch-cli | ||
| 2 | + | ||
| 3 | +CLI Go qui récupère une ou plusieurs URLs HTTP/HTTPS et convertit le HTML en markdown, texte brut ou HTML. | ||
| 4 | + | ||
| 5 | +Inspiré de l'outil `fetch` du repo [docker-agent](https://github.com/docker/docker-agent). | ||
| 6 | + | ||
| 7 | +## Installation | ||
| 8 | + | ||
| 9 | +```bash | ||
| 10 | +cd fetch-cli | ||
| 11 | +go build -o fetch-cli . | ||
| 12 | +``` | ||
| 13 | + | ||
| 14 | +## Usage | ||
| 15 | + | ||
| 16 | +``` | ||
| 17 | +fetch-cli [options] <url> [url2 ...] | ||
| 18 | +``` | ||
| 19 | + | ||
| 20 | +### Options | ||
| 21 | + | ||
| 22 | +| Flag | Défaut | Description | | ||
| 23 | +|------|--------|-------------| | ||
| 24 | +| `-f` | `markdown` | Format de sortie : `markdown`, `text` ou `html` | | ||
| 25 | +| `-t` | `30` | Timeout de la requête en secondes | | ||
| 26 | +| `-robots` | `true` | Respecter `robots.txt` (passer `-robots=false` pour désactiver) | | ||
| 27 | + | ||
| 28 | +### Exemples | ||
| 29 | + | ||
| 30 | +```bash | ||
| 31 | +# Récupérer une page en markdown (défaut) | ||
| 32 | +./fetch-cli https://example.com | ||
| 33 | + | ||
| 34 | +# Récupérer en texte brut | ||
| 35 | +./fetch-cli -f text https://example.com | ||
| 36 | + | ||
| 37 | +# Récupérer le HTML brut | ||
| 38 | +./fetch-cli -f html https://example.com | ||
| 39 | + | ||
| 40 | +# Désactiver la vérification robots.txt | ||
| 41 | +./fetch-cli -robots=false https://example.com | ||
| 42 | + | ||
| 43 | +# Plusieurs URLs d'un coup | ||
| 44 | +./fetch-cli https://example.com https://golang.org | ||
| 45 | + | ||
| 46 | +# Timeout de 10 secondes | ||
| 47 | +./fetch-cli -t 10 https://example.com | ||
| 48 | +``` | ||
| 49 | + | ||
| 50 | +## Fonctionnement | ||
| 51 | + | ||
| 52 | +1. **Validation de l'URL** — Seuls les schémas `http` et `https` sont acceptés. | ||
| 53 | +2. **Vérification robots.txt** — Par défaut, la CLI consulte le `robots.txt` du host et refuse les URLs bloquées. Désactivable avec `-robots=false`. | ||
| 54 | +3. **Négociation du format** — L'en-tête `Accept` est ajusté selon le format demandé : | ||
| 55 | + - `markdown` → `text/markdown;q=1.0, text/plain;q=0.9, text/html;q=0.7, */*;q=0.1` | ||
| 56 | + - `text` → `text/plain;q=1.0, text/markdown;q=0.9, text/html;q=0.8, */*;q=0.1` | ||
| 57 | + - `html` → `text/html;q=1.0, text/plain;q=0.8, */*;q=0.1` | ||
| 58 | +4. **Téléchargement** — Le body est lu avec une limite de **1 Mo**. Les redirects sont suivis (max 10). | ||
| 59 | +5. **Conversion** — Si le `Content-Type` contient `text/html` : | ||
| 60 | + - `markdown` : conversion via [html-to-markdown/v2](https://github.com/JohannesKaufmann/html-to-markdown) | ||
| 61 | + - `text` : extraction du texte via [html2text](https://github.com/k3a/html2text) | ||
| 62 | + - `html` : aucun traitement, le HTML est renvoyé tel quel | ||
| 63 | +6. **Sortie** — Les métadonnées (status, content-type, taille) sont écrites sur **stderr**, le contenu sur **stdout**. | ||
| 64 | + | ||
| 65 | +## Code de sortie | ||
| 66 | + | ||
| 67 | +- `0` : toutes les URLs ont été récupérées avec succès | ||
| 68 | +- `1` : au moins une URL a échoué (ou aucune URL fournie) | ||
| 69 | + | ||
| 70 | +## Dépendances | ||
| 71 | + | ||
| 72 | +- [github.com/JohannesKaufmann/html-to-markdown/v2](https://github.com/JohannesKaufmann/html-to-markdown) — conversion HTML → Markdown | ||
| 73 | +- [github.com/k3a/html2text](https://github.com/k3a/html2text) — extraction de texte depuis HTML | ||
| 74 | +- [github.com/temoto/robotstxt](https://github.com/temoto/robotstxt) — parsing de robots.txt | ||
added
tmp/fetch-cli/go.mod +14 -0 | new file mode 100644 | ||
| @@ -0,0 +1,14 @@ | ||
| 1 | +module fetch-cli | |
| 2 | + | |
| 3 | +go 1.25.0 | |
| 4 | + | |
| 5 | +require ( | |
| 6 | + github.com/JohannesKaufmann/html-to-markdown/v2 v2.5.2 | |
| 7 | + github.com/k3a/html2text v1.5.0 | |
| 8 | + github.com/temoto/robotstxt v1.1.2 | |
| 9 | +) | |
| 10 | + | |
| 11 | +require ( | |
| 12 | + github.com/JohannesKaufmann/dom v0.3.1 // indirect | |
| 13 | + golang.org/x/net v0.55.0 // indirect | |
| 14 | +) | |
| new file mode 100644 | |||
| @@ -0,0 +1,14 @@ | |||
| 1 | +module fetch-cli | ||
| 2 | + | ||
| 3 | +go 1.25.0 | ||
| 4 | + | ||
| 5 | +require ( | ||
| 6 | + github.com/JohannesKaufmann/html-to-markdown/v2 v2.5.2 | ||
| 7 | + github.com/k3a/html2text v1.5.0 | ||
| 8 | + github.com/temoto/robotstxt v1.1.2 | ||
| 9 | +) | ||
| 10 | + | ||
| 11 | +require ( | ||
| 12 | + github.com/JohannesKaufmann/dom v0.3.1 // indirect | ||
| 13 | + golang.org/x/net v0.55.0 // indirect | ||
| 14 | +) | ||
added
tmp/fetch-cli/go.sum +36 -0 | new file mode 100644 | ||
| @@ -0,0 +1,36 @@ | ||
| 1 | +github.com/JohannesKaufmann/dom v0.3.1 h1:J16l9JAHWgkFPR3VIPbQ1gvS0cWab6laK1q7PFL3qh0= | |
| 2 | +github.com/JohannesKaufmann/dom v0.3.1/go.mod h1:BZPkf8ZeYrBgABjwJn9iiKt8aiCtkxpHkevms+Yp2DE= | |
| 3 | +github.com/JohannesKaufmann/html-to-markdown/v2 v2.5.2 h1:XFJZFWESIWlUEHHjzBuv8RvrtCWnSGlimEX17ysSDb8= | |
| 4 | +github.com/JohannesKaufmann/html-to-markdown/v2 v2.5.2/go.mod h1:BHWO8lJzttJLqwuV8Rb1B3OG2OSzLbssZDI1FRg2eAA= | |
| 5 | +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= | |
| 6 | +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | |
| 7 | +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= | |
| 8 | +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= | |
| 9 | +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= | |
| 10 | +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= | |
| 11 | +github.com/k3a/html2text v1.5.0 h1:m+aUc8Chcz9f0u6TPKEvh30Pl/CTftYbxy8OHAO0jQY= | |
| 12 | +github.com/k3a/html2text v1.5.0/go.mod h1:ieEXykM67iT8lTvEWBh6fhpH4B23kB9OMKPdIBmgUqA= | |
| 13 | +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | |
| 14 | +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | |
| 15 | +github.com/sebdah/goldie/v2 v2.8.0 h1:dZb9wR8q5++oplmEiJT+U/5KyotVD+HNGCAc5gNr8rc= | |
| 16 | +github.com/sebdah/goldie/v2 v2.8.0/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI= | |
| 17 | +github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw= | |
| 18 | +github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= | |
| 19 | +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= | |
| 20 | +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= | |
| 21 | +github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= | |
| 22 | +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= | |
| 23 | +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | |
| 24 | +github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= | |
| 25 | +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= | |
| 26 | +github.com/temoto/robotstxt v1.1.2 h1:W2pOjSJ6SWvldyEuiFXNxz3xZ8aiWX5LbfDiOFd7Fxg= | |
| 27 | +github.com/temoto/robotstxt v1.1.2/go.mod h1:+1AmkuG3IYkh1kv0d2qEB9Le88ehNO0zwOr3ujewlOo= | |
| 28 | +github.com/yuin/goldmark v1.8.2 h1:kEGpgqJXdgbkhcOgBxkC0X0PmoPG1ZyoZ117rDVp4zE= | |
| 29 | +github.com/yuin/goldmark v1.8.2/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= | |
| 30 | +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= | |
| 31 | +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= | |
| 32 | +golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8= | |
| 33 | +golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww= | |
| 34 | +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | |
| 35 | +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | |
| 36 | +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= | |
| new file mode 100644 | |||
| @@ -0,0 +1,36 @@ | |||
| 1 | +github.com/JohannesKaufmann/dom v0.3.1 h1:J16l9JAHWgkFPR3VIPbQ1gvS0cWab6laK1q7PFL3qh0= | ||
| 2 | +github.com/JohannesKaufmann/dom v0.3.1/go.mod h1:BZPkf8ZeYrBgABjwJn9iiKt8aiCtkxpHkevms+Yp2DE= | ||
| 3 | +github.com/JohannesKaufmann/html-to-markdown/v2 v2.5.2 h1:XFJZFWESIWlUEHHjzBuv8RvrtCWnSGlimEX17ysSDb8= | ||
| 4 | +github.com/JohannesKaufmann/html-to-markdown/v2 v2.5.2/go.mod h1:BHWO8lJzttJLqwuV8Rb1B3OG2OSzLbssZDI1FRg2eAA= | ||
| 5 | +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= | ||
| 6 | +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||
| 7 | +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= | ||
| 8 | +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= | ||
| 9 | +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= | ||
| 10 | +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= | ||
| 11 | +github.com/k3a/html2text v1.5.0 h1:m+aUc8Chcz9f0u6TPKEvh30Pl/CTftYbxy8OHAO0jQY= | ||
| 12 | +github.com/k3a/html2text v1.5.0/go.mod h1:ieEXykM67iT8lTvEWBh6fhpH4B23kB9OMKPdIBmgUqA= | ||
| 13 | +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | ||
| 14 | +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||
| 15 | +github.com/sebdah/goldie/v2 v2.8.0 h1:dZb9wR8q5++oplmEiJT+U/5KyotVD+HNGCAc5gNr8rc= | ||
| 16 | +github.com/sebdah/goldie/v2 v2.8.0/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI= | ||
| 17 | +github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw= | ||
| 18 | +github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= | ||
| 19 | +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= | ||
| 20 | +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= | ||
| 21 | +github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= | ||
| 22 | +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= | ||
| 23 | +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | ||
| 24 | +github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= | ||
| 25 | +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= | ||
| 26 | +github.com/temoto/robotstxt v1.1.2 h1:W2pOjSJ6SWvldyEuiFXNxz3xZ8aiWX5LbfDiOFd7Fxg= | ||
| 27 | +github.com/temoto/robotstxt v1.1.2/go.mod h1:+1AmkuG3IYkh1kv0d2qEB9Le88ehNO0zwOr3ujewlOo= | ||
| 28 | +github.com/yuin/goldmark v1.8.2 h1:kEGpgqJXdgbkhcOgBxkC0X0PmoPG1ZyoZ117rDVp4zE= | ||
| 29 | +github.com/yuin/goldmark v1.8.2/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= | ||
| 30 | +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= | ||
| 31 | +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= | ||
| 32 | +golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8= | ||
| 33 | +golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww= | ||
| 34 | +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||
| 35 | +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | ||
| 36 | +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= | ||
added
tmp/fetch-cli/main.go +211 -0 | new file mode 100644 | ||
| @@ -0,0 +1,211 @@ | ||
| 1 | +package main | |
| 2 | + | |
| 3 | +import ( | |
| 4 | + "context" | |
| 5 | + "errors" | |
| 6 | + "flag" | |
| 7 | + "fmt" | |
| 8 | + "io" | |
| 9 | + "net/http" | |
| 10 | + "net/url" | |
| 11 | + "os" | |
| 12 | + "strings" | |
| 13 | + "time" | |
| 14 | + | |
| 15 | + htmltomarkdown "github.com/JohannesKaufmann/html-to-markdown/v2" | |
| 16 | + "github.com/k3a/html2text" | |
| 17 | + "github.com/temoto/robotstxt" | |
| 18 | +) | |
| 19 | + | |
| 20 | +const ( | |
| 21 | + maxBodySize = 1 << 20 // 1 MB | |
| 22 | + userAgent = "fetch-cli/1.0" | |
| 23 | +) | |
| 24 | + | |
| 25 | +// formatHandler describes how a format negotiates with the server and | |
| 26 | +// post-processes an HTML response body. | |
| 27 | +type formatHandler struct { | |
| 28 | + accept string | |
| 29 | + convertFromHTML func(string) string | |
| 30 | +} | |
| 31 | + | |
| 32 | +var formatHandlers = map[string]formatHandler{ | |
| 33 | + "markdown": { | |
| 34 | + accept: "text/markdown;q=1.0, text/plain;q=0.9, text/html;q=0.7, */*;q=0.1", | |
| 35 | + convertFromHTML: htmlToMarkdown, | |
| 36 | + }, | |
| 37 | + "html": { | |
| 38 | + accept: "text/html;q=1.0, text/plain;q=0.8, */*;q=0.1", | |
| 39 | + }, | |
| 40 | + "text": { | |
| 41 | + accept: "text/plain;q=1.0, text/markdown;q=0.9, text/html;q=0.8, */*;q=0.1", | |
| 42 | + convertFromHTML: htmlToText, | |
| 43 | + }, | |
| 44 | +} | |
| 45 | + | |
| 46 | +func htmlToMarkdown(html string) string { | |
| 47 | + markdown, err := htmltomarkdown.ConvertString(html) | |
| 48 | + if err != nil { | |
| 49 | + return html | |
| 50 | + } | |
| 51 | + return markdown | |
| 52 | +} | |
| 53 | + | |
| 54 | +func htmlToText(html string) string { | |
| 55 | + return html2text.HTML2Text(html) | |
| 56 | +} | |
| 57 | + | |
| 58 | +func formatHandlerFor(format string) formatHandler { | |
| 59 | + if h, ok := formatHandlers[format]; ok { | |
| 60 | + return h | |
| 61 | + } | |
| 62 | + return formatHandler{accept: "text/plain;q=1.0, */*;q=0.1"} | |
| 63 | +} | |
| 64 | + | |
| 65 | +// fetchRobots fetches and parses robots.txt for the given URL's host. | |
| 66 | +// Returns nil (allow all) if robots.txt is missing or unreachable. | |
| 67 | +func fetchRobots(ctx context.Context, client *http.Client, targetURL *url.URL) (*robotstxt.RobotsData, error) { | |
| 68 | + robotsURL := &url.URL{ | |
| 69 | + Scheme: targetURL.Scheme, | |
| 70 | + Host: targetURL.Host, | |
| 71 | + Path: "/robots.txt", | |
| 72 | + } | |
| 73 | + | |
| 74 | + req, err := http.NewRequestWithContext(ctx, http.MethodGet, robotsURL.String(), http.NoBody) | |
| 75 | + if err != nil { | |
| 76 | + return nil, nil | |
| 77 | + } | |
| 78 | + req.Header.Set("User-Agent", userAgent) | |
| 79 | + | |
| 80 | + resp, err := client.Do(req) | |
| 81 | + if err != nil { | |
| 82 | + return nil, nil | |
| 83 | + } | |
| 84 | + defer resp.Body.Close() | |
| 85 | + | |
| 86 | + if resp.StatusCode == http.StatusNotFound { | |
| 87 | + return nil, nil | |
| 88 | + } | |
| 89 | + if resp.StatusCode != http.StatusOK { | |
| 90 | + return nil, fmt.Errorf("unexpected status %d", resp.StatusCode) | |
| 91 | + } | |
| 92 | + | |
| 93 | + robotsBody, err := io.ReadAll(io.LimitReader(resp.Body, 64*1024)) | |
| 94 | + if err != nil { | |
| 95 | + return nil, fmt.Errorf("failed to read robots.txt: %w", err) | |
| 96 | + } | |
| 97 | + | |
| 98 | + robots, err := robotstxt.FromBytes(robotsBody) | |
| 99 | + if err != nil { | |
| 100 | + return nil, fmt.Errorf("failed to parse robots.txt: %w", err) | |
| 101 | + } | |
| 102 | + | |
| 103 | + return robots, nil | |
| 104 | +} | |
| 105 | + | |
| 106 | +// fetchURL fetches a single URL and returns the (possibly converted) body. | |
| 107 | +func fetchURL(ctx context.Context, client *http.Client, urlStr, format string, checkRobots bool) (string, int, string, error) { | |
| 108 | + parsedURL, err := url.Parse(urlStr) | |
| 109 | + if err != nil { | |
| 110 | + return "", 0, "", fmt.Errorf("invalid URL: %w", err) | |
| 111 | + } | |
| 112 | + if parsedURL.Scheme == "" || parsedURL.Host == "" { | |
| 113 | + return "", 0, "", errors.New("invalid URL: missing scheme or host") | |
| 114 | + } | |
| 115 | + if parsedURL.Scheme != "http" && parsedURL.Scheme != "https" { | |
| 116 | + return "", 0, "", errors.New("only HTTP and HTTPS URLs are supported") | |
| 117 | + } | |
| 118 | + | |
| 119 | + // Check robots.txt | |
| 120 | + if checkRobots { | |
| 121 | + robots, err := fetchRobots(ctx, client, parsedURL) | |
| 122 | + if err != nil { | |
| 123 | + return "", 0, "", fmt.Errorf("robots.txt check failed: %w", err) | |
| 124 | + } | |
| 125 | + if robots != nil && !robots.TestAgent(parsedURL.Path, userAgent) { | |
| 126 | + return "", 0, "", errors.New("URL blocked by robots.txt") | |
| 127 | + } | |
| 128 | + } | |
| 129 | + | |
| 130 | + fh := formatHandlerFor(format) | |
| 131 | + | |
| 132 | + req, err := http.NewRequestWithContext(ctx, http.MethodGet, urlStr, http.NoBody) | |
| 133 | + if err != nil { | |
| 134 | + return "", 0, "", fmt.Errorf("failed to create request: %w", err) | |
| 135 | + } | |
| 136 | + req.Header.Set("Accept", fh.accept) | |
| 137 | + req.Header.Set("User-Agent", userAgent) | |
| 138 | + | |
| 139 | + resp, err := client.Do(req) | |
| 140 | + if err != nil { | |
| 141 | + return "", 0, "", fmt.Errorf("request failed: %w", err) | |
| 142 | + } | |
| 143 | + defer resp.Body.Close() | |
| 144 | + | |
| 145 | + contentType := resp.Header.Get("Content-Type") | |
| 146 | + | |
| 147 | + body, err := io.ReadAll(io.LimitReader(resp.Body, maxBodySize)) | |
| 148 | + if err != nil { | |
| 149 | + return "", 0, "", fmt.Errorf("failed to read response body: %w", err) | |
| 150 | + } | |
| 151 | + | |
| 152 | + result := string(body) | |
| 153 | + if fh.convertFromHTML != nil && strings.Contains(contentType, "text/html") { | |
| 154 | + result = fh.convertFromHTML(result) | |
| 155 | + } | |
| 156 | + | |
| 157 | + return result, resp.StatusCode, contentType, nil | |
| 158 | +} | |
| 159 | + | |
| 160 | +func main() { | |
| 161 | + var ( | |
| 162 | + format = flag.String("f", "markdown", "Output format: markdown, text, or html") | |
| 163 | + timeout = flag.Int("t", 30, "Request timeout in seconds") | |
| 164 | + robots = flag.Bool("robots", true, "Respect robots.txt") | |
| 165 | + ) | |
| 166 | + flag.Usage = func() { | |
| 167 | + fmt.Fprintf(os.Stderr, "Usage: %s [options] <url> [url2 ...]\n\n", os.Args[0]) | |
| 168 | + fmt.Fprintln(os.Stderr, "Fetch one or more URLs and convert HTML to the chosen format.") | |
| 169 | + fmt.Fprintln(os.Stderr, "\nOptions:") | |
| 170 | + flag.PrintDefaults() | |
| 171 | + } | |
| 172 | + flag.Parse() | |
| 173 | + | |
| 174 | + urls := flag.Args() | |
| 175 | + if len(urls) == 0 { | |
| 176 | + flag.Usage() | |
| 177 | + os.Exit(1) | |
| 178 | + } | |
| 179 | + | |
| 180 | + ctx := context.Background() | |
| 181 | + client := &http.Client{ | |
| 182 | + Timeout: time.Duration(*timeout) * time.Second, | |
| 183 | + CheckRedirect: func(req *http.Request, via []*http.Request) error { | |
| 184 | + if len(via) >= 10 { | |
| 185 | + return errors.New("stopped after 10 redirects") | |
| 186 | + } | |
| 187 | + return nil | |
| 188 | + }, | |
| 189 | + } | |
| 190 | + | |
| 191 | + exitCode := 0 | |
| 192 | + | |
| 193 | + for i, u := range urls { | |
| 194 | + if i > 0 { | |
| 195 | + fmt.Fprintf(os.Stderr, "\n---\n\n") | |
| 196 | + } | |
| 197 | + | |
| 198 | + body, statusCode, contentType, err := fetchURL(ctx, client, u, *format, *robots) | |
| 199 | + if err != nil { | |
| 200 | + fmt.Fprintf(os.Stderr, "Error fetching %s: %s\n", u, err) | |
| 201 | + exitCode = 1 | |
| 202 | + continue | |
| 203 | + } | |
| 204 | + | |
| 205 | + fmt.Fprintf(os.Stderr, "Fetched %s (Status: %d, Content-Type: %s, Length: %d bytes)\n\n", | |
| 206 | + u, statusCode, contentType, len(body)) | |
| 207 | + fmt.Println(body) | |
| 208 | + } | |
| 209 | + | |
| 210 | + os.Exit(exitCode) | |
| 211 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,211 @@ | |||
| 1 | +package main | ||
| 2 | + | ||
| 3 | +import ( | ||
| 4 | + "context" | ||
| 5 | + "errors" | ||
| 6 | + "flag" | ||
| 7 | + "fmt" | ||
| 8 | + "io" | ||
| 9 | + "net/http" | ||
| 10 | + "net/url" | ||
| 11 | + "os" | ||
| 12 | + "strings" | ||
| 13 | + "time" | ||
| 14 | + | ||
| 15 | + htmltomarkdown "github.com/JohannesKaufmann/html-to-markdown/v2" | ||
| 16 | + "github.com/k3a/html2text" | ||
| 17 | + "github.com/temoto/robotstxt" | ||
| 18 | +) | ||
| 19 | + | ||
| 20 | +const ( | ||
| 21 | + maxBodySize = 1 << 20 // 1 MB | ||
| 22 | + userAgent = "fetch-cli/1.0" | ||
| 23 | +) | ||
| 24 | + | ||
| 25 | +// formatHandler describes how a format negotiates with the server and | ||
| 26 | +// post-processes an HTML response body. | ||
| 27 | +type formatHandler struct { | ||
| 28 | + accept string | ||
| 29 | + convertFromHTML func(string) string | ||
| 30 | +} | ||
| 31 | + | ||
| 32 | +var formatHandlers = map[string]formatHandler{ | ||
| 33 | + "markdown": { | ||
| 34 | + accept: "text/markdown;q=1.0, text/plain;q=0.9, text/html;q=0.7, */*;q=0.1", | ||
| 35 | + convertFromHTML: htmlToMarkdown, | ||
| 36 | + }, | ||
| 37 | + "html": { | ||
| 38 | + accept: "text/html;q=1.0, text/plain;q=0.8, */*;q=0.1", | ||
| 39 | + }, | ||
| 40 | + "text": { | ||
| 41 | + accept: "text/plain;q=1.0, text/markdown;q=0.9, text/html;q=0.8, */*;q=0.1", | ||
| 42 | + convertFromHTML: htmlToText, | ||
| 43 | + }, | ||
| 44 | +} | ||
| 45 | + | ||
| 46 | +func htmlToMarkdown(html string) string { | ||
| 47 | + markdown, err := htmltomarkdown.ConvertString(html) | ||
| 48 | + if err != nil { | ||
| 49 | + return html | ||
| 50 | + } | ||
| 51 | + return markdown | ||
| 52 | +} | ||
| 53 | + | ||
| 54 | +func htmlToText(html string) string { | ||
| 55 | + return html2text.HTML2Text(html) | ||
| 56 | +} | ||
| 57 | + | ||
| 58 | +func formatHandlerFor(format string) formatHandler { | ||
| 59 | + if h, ok := formatHandlers[format]; ok { | ||
| 60 | + return h | ||
| 61 | + } | ||
| 62 | + return formatHandler{accept: "text/plain;q=1.0, */*;q=0.1"} | ||
| 63 | +} | ||
| 64 | + | ||
| 65 | +// fetchRobots fetches and parses robots.txt for the given URL's host. | ||
| 66 | +// Returns nil (allow all) if robots.txt is missing or unreachable. | ||
| 67 | +func fetchRobots(ctx context.Context, client *http.Client, targetURL *url.URL) (*robotstxt.RobotsData, error) { | ||
| 68 | + robotsURL := &url.URL{ | ||
| 69 | + Scheme: targetURL.Scheme, | ||
| 70 | + Host: targetURL.Host, | ||
| 71 | + Path: "/robots.txt", | ||
| 72 | + } | ||
| 73 | + | ||
| 74 | + req, err := http.NewRequestWithContext(ctx, http.MethodGet, robotsURL.String(), http.NoBody) | ||
| 75 | + if err != nil { | ||
| 76 | + return nil, nil | ||
| 77 | + } | ||
| 78 | + req.Header.Set("User-Agent", userAgent) | ||
| 79 | + | ||
| 80 | + resp, err := client.Do(req) | ||
| 81 | + if err != nil { | ||
| 82 | + return nil, nil | ||
| 83 | + } | ||
| 84 | + defer resp.Body.Close() | ||
| 85 | + | ||
| 86 | + if resp.StatusCode == http.StatusNotFound { | ||
| 87 | + return nil, nil | ||
| 88 | + } | ||
| 89 | + if resp.StatusCode != http.StatusOK { | ||
| 90 | + return nil, fmt.Errorf("unexpected status %d", resp.StatusCode) | ||
| 91 | + } | ||
| 92 | + | ||
| 93 | + robotsBody, err := io.ReadAll(io.LimitReader(resp.Body, 64*1024)) | ||
| 94 | + if err != nil { | ||
| 95 | + return nil, fmt.Errorf("failed to read robots.txt: %w", err) | ||
| 96 | + } | ||
| 97 | + | ||
| 98 | + robots, err := robotstxt.FromBytes(robotsBody) | ||
| 99 | + if err != nil { | ||
| 100 | + return nil, fmt.Errorf("failed to parse robots.txt: %w", err) | ||
| 101 | + } | ||
| 102 | + | ||
| 103 | + return robots, nil | ||
| 104 | +} | ||
| 105 | + | ||
| 106 | +// fetchURL fetches a single URL and returns the (possibly converted) body. | ||
| 107 | +func fetchURL(ctx context.Context, client *http.Client, urlStr, format string, checkRobots bool) (string, int, string, error) { | ||
| 108 | + parsedURL, err := url.Parse(urlStr) | ||
| 109 | + if err != nil { | ||
| 110 | + return "", 0, "", fmt.Errorf("invalid URL: %w", err) | ||
| 111 | + } | ||
| 112 | + if parsedURL.Scheme == "" || parsedURL.Host == "" { | ||
| 113 | + return "", 0, "", errors.New("invalid URL: missing scheme or host") | ||
| 114 | + } | ||
| 115 | + if parsedURL.Scheme != "http" && parsedURL.Scheme != "https" { | ||
| 116 | + return "", 0, "", errors.New("only HTTP and HTTPS URLs are supported") | ||
| 117 | + } | ||
| 118 | + | ||
| 119 | + // Check robots.txt | ||
| 120 | + if checkRobots { | ||
| 121 | + robots, err := fetchRobots(ctx, client, parsedURL) | ||
| 122 | + if err != nil { | ||
| 123 | + return "", 0, "", fmt.Errorf("robots.txt check failed: %w", err) | ||
| 124 | + } | ||
| 125 | + if robots != nil && !robots.TestAgent(parsedURL.Path, userAgent) { | ||
| 126 | + return "", 0, "", errors.New("URL blocked by robots.txt") | ||
| 127 | + } | ||
| 128 | + } | ||
| 129 | + | ||
| 130 | + fh := formatHandlerFor(format) | ||
| 131 | + | ||
| 132 | + req, err := http.NewRequestWithContext(ctx, http.MethodGet, urlStr, http.NoBody) | ||
| 133 | + if err != nil { | ||
| 134 | + return "", 0, "", fmt.Errorf("failed to create request: %w", err) | ||
| 135 | + } | ||
| 136 | + req.Header.Set("Accept", fh.accept) | ||
| 137 | + req.Header.Set("User-Agent", userAgent) | ||
| 138 | + | ||
| 139 | + resp, err := client.Do(req) | ||
| 140 | + if err != nil { | ||
| 141 | + return "", 0, "", fmt.Errorf("request failed: %w", err) | ||
| 142 | + } | ||
| 143 | + defer resp.Body.Close() | ||
| 144 | + | ||
| 145 | + contentType := resp.Header.Get("Content-Type") | ||
| 146 | + | ||
| 147 | + body, err := io.ReadAll(io.LimitReader(resp.Body, maxBodySize)) | ||
| 148 | + if err != nil { | ||
| 149 | + return "", 0, "", fmt.Errorf("failed to read response body: %w", err) | ||
| 150 | + } | ||
| 151 | + | ||
| 152 | + result := string(body) | ||
| 153 | + if fh.convertFromHTML != nil && strings.Contains(contentType, "text/html") { | ||
| 154 | + result = fh.convertFromHTML(result) | ||
| 155 | + } | ||
| 156 | + | ||
| 157 | + return result, resp.StatusCode, contentType, nil | ||
| 158 | +} | ||
| 159 | + | ||
| 160 | +func main() { | ||
| 161 | + var ( | ||
| 162 | + format = flag.String("f", "markdown", "Output format: markdown, text, or html") | ||
| 163 | + timeout = flag.Int("t", 30, "Request timeout in seconds") | ||
| 164 | + robots = flag.Bool("robots", true, "Respect robots.txt") | ||
| 165 | + ) | ||
| 166 | + flag.Usage = func() { | ||
| 167 | + fmt.Fprintf(os.Stderr, "Usage: %s [options] <url> [url2 ...]\n\n", os.Args[0]) | ||
| 168 | + fmt.Fprintln(os.Stderr, "Fetch one or more URLs and convert HTML to the chosen format.") | ||
| 169 | + fmt.Fprintln(os.Stderr, "\nOptions:") | ||
| 170 | + flag.PrintDefaults() | ||
| 171 | + } | ||
| 172 | + flag.Parse() | ||
| 173 | + | ||
| 174 | + urls := flag.Args() | ||
| 175 | + if len(urls) == 0 { | ||
| 176 | + flag.Usage() | ||
| 177 | + os.Exit(1) | ||
| 178 | + } | ||
| 179 | + | ||
| 180 | + ctx := context.Background() | ||
| 181 | + client := &http.Client{ | ||
| 182 | + Timeout: time.Duration(*timeout) * time.Second, | ||
| 183 | + CheckRedirect: func(req *http.Request, via []*http.Request) error { | ||
| 184 | + if len(via) >= 10 { | ||
| 185 | + return errors.New("stopped after 10 redirects") | ||
| 186 | + } | ||
| 187 | + return nil | ||
| 188 | + }, | ||
| 189 | + } | ||
| 190 | + | ||
| 191 | + exitCode := 0 | ||
| 192 | + | ||
| 193 | + for i, u := range urls { | ||
| 194 | + if i > 0 { | ||
| 195 | + fmt.Fprintf(os.Stderr, "\n---\n\n") | ||
| 196 | + } | ||
| 197 | + | ||
| 198 | + body, statusCode, contentType, err := fetchURL(ctx, client, u, *format, *robots) | ||
| 199 | + if err != nil { | ||
| 200 | + fmt.Fprintf(os.Stderr, "Error fetching %s: %s\n", u, err) | ||
| 201 | + exitCode = 1 | ||
| 202 | + continue | ||
| 203 | + } | ||
| 204 | + | ||
| 205 | + fmt.Fprintf(os.Stderr, "Fetched %s (Status: %d, Content-Type: %s, Length: %d bytes)\n\n", | ||
| 206 | + u, statusCode, contentType, len(body)) | ||
| 207 | + fmt.Println(body) | ||
| 208 | + } | ||
| 209 | + | ||
| 210 | + os.Exit(exitCode) | ||
| 211 | +} | ||
added
tmp/mm-acp.sbx.sh +5 -0 | new file mode 100644 | ||
| @@ -0,0 +1,5 @@ | ||
| 1 | +#!/bin/sh | |
| 2 | +# Agent for sidekick: runs mm inside the mm-acp sandbox. | |
| 3 | +# sidekick calls its agent as `<agent> --acp [args...]`, so every argument | |
| 4 | +# (--acp included) is handed to mm, not to sbx. | |
| 5 | +exec sbx exec -i "${SBX_NAME:-mm-acp}" mm "$@" | |
| new file mode 100644 | |||
| @@ -0,0 +1,5 @@ | |||
| 1 | +#!/bin/sh | ||
| 2 | +# Agent for sidekick: runs mm inside the mm-acp sandbox. | ||
| 3 | +# sidekick calls its agent as `<agent> --acp [args...]`, so every argument | ||
| 4 | +# (--acp included) is handed to mm, not to sbx. | ||
| 5 | +exec sbx exec -i "${SBX_NAME:-mm-acp}" mm "$@" | ||
added
tmp/start.sbx.gemma.sh +7 -0 | new file mode 100644 | ||
| @@ -0,0 +1,7 @@ | ||
| 1 | +#!/bin/bash | |
| 2 | +sbx create --name 20260929-acp-gemma k33g/sbx-kit-mm-acp:0.2.2 . ../mini-me-config:ro \ | |
| 3 | + --kit-arg config=../mini-me-config/sbx.agent.gemma.yaml | |
| 4 | + | |
| 5 | + | |
| 6 | +SBX_NAME=20260929-acp-gemma sidekick -port 9002 ./mm-acp.sbx.sh | |
| 7 | + | |
| new file mode 100644 | |||
| @@ -0,0 +1,7 @@ | |||
| 1 | +#!/bin/bash | ||
| 2 | +sbx create --name 20260929-acp-gemma k33g/sbx-kit-mm-acp:0.2.2 . ../mini-me-config:ro \ | ||
| 3 | + --kit-arg config=../mini-me-config/sbx.agent.gemma.yaml | ||
| 4 | + | ||
| 5 | + | ||
| 6 | +SBX_NAME=20260929-acp-gemma sidekick -port 9002 ./mm-acp.sbx.sh | ||
| 7 | + | ||
added
tmp/start.sbx.swift.qwen.sh +7 -0 | new file mode 100644 | ||
| @@ -0,0 +1,7 @@ | ||
| 1 | +#!/bin/bash | |
| 2 | +sbx create --name 20260929-acp-swift-qwen k33g/sbx-kit-mm-acp:0.2.2 . ../mini-me-config:ro \ | |
| 3 | + --kit-arg config=../mini-me-config/sbx.agent.swift.qwen3.8.yaml | |
| 4 | + | |
| 5 | + | |
| 6 | +SBX_NAME=20260929-acp-swift-qwen sidekick -port 9001 ./mm-acp.sbx.sh | |
| 7 | + | |
| new file mode 100644 | |||
| @@ -0,0 +1,7 @@ | |||
| 1 | +#!/bin/bash | ||
| 2 | +sbx create --name 20260929-acp-swift-qwen k33g/sbx-kit-mm-acp:0.2.2 . ../mini-me-config:ro \ | ||
| 3 | + --kit-arg config=../mini-me-config/sbx.agent.swift.qwen3.8.yaml | ||
| 4 | + | ||
| 5 | + | ||
| 6 | +SBX_NAME=20260929-acp-swift-qwen sidekick -port 9001 ./mm-acp.sbx.sh | ||
| 7 | + | ||
modified
web/app.css +9 -0 | @@ -222,6 +222,15 @@ details:not(.thinking) .spinner-ring { display: none; } | ||
| 222 | 222 | .ctx-menu hr { border: 0; border-top: 1px solid var(--border); margin: 0.25rem 0; } |
| 223 | 223 | .terminal-pane { flex: none; height: 16rem; display: flex; flex-direction: column; border-top: 1px solid var(--border); } |
| 224 | 224 | #terminal { flex: 1; min-height: 0; padding: 0.25rem 0 0 0.5rem; background: var(--bg); } |
| 225 | +.term-view { height: 100%; } | |
| 226 | +.term-view[hidden] { display: none; } | |
| 227 | +.term-head { display: flex; align-items: center; gap: 0.5rem; min-width: 0; } | |
| 228 | +.term-tabs { display: flex; gap: 0.25rem; overflow-x: auto; } | |
| 229 | +.term-tab { display: flex; align-items: center; gap: 0.2rem; padding: 0 0.2rem 0 0.45rem; border: 1px solid var(--border); border-radius: 0.25rem; cursor: pointer; white-space: nowrap; } | |
| 230 | +.term-tab.active { color: var(--fg); border-color: var(--accent); } | |
| 231 | +.term-tab.exited { opacity: 0.6; } | |
| 232 | +.term-tab-close { width: 1rem; text-align: center; border-radius: 0.25rem; } | |
| 233 | +.term-tab-close:hover { background: var(--border); } | |
| 225 | 234 | .splitter-y { flex: none; height: 5px; margin: -2px 0; cursor: row-resize; z-index: 1; } |
| 226 | 235 | .splitter-y:hover, .splitter-y.dragging { background: var(--accent); } |
| 227 | 236 | ::-webkit-scrollbar { width: 8px; } |
| @@ -222,6 +222,15 @@ details:not(.thinking) .spinner-ring { display: none; } | |||
| 222 | .ctx-menu hr { border: 0; border-top: 1px solid var(--border); margin: 0.25rem 0; } | 222 | .ctx-menu hr { border: 0; border-top: 1px solid var(--border); margin: 0.25rem 0; } |
| 223 | .terminal-pane { flex: none; height: 16rem; display: flex; flex-direction: column; border-top: 1px solid var(--border); } | 223 | .terminal-pane { flex: none; height: 16rem; display: flex; flex-direction: column; border-top: 1px solid var(--border); } |
| 224 | #terminal { flex: 1; min-height: 0; padding: 0.25rem 0 0 0.5rem; background: var(--bg); } | 224 | #terminal { flex: 1; min-height: 0; padding: 0.25rem 0 0 0.5rem; background: var(--bg); } |
| 225 | +.term-view { height: 100%; } | ||
| 226 | +.term-view[hidden] { display: none; } | ||
| 227 | +.term-head { display: flex; align-items: center; gap: 0.5rem; min-width: 0; } | ||
| 228 | +.term-tabs { display: flex; gap: 0.25rem; overflow-x: auto; } | ||
| 229 | +.term-tab { display: flex; align-items: center; gap: 0.2rem; padding: 0 0.2rem 0 0.45rem; border: 1px solid var(--border); border-radius: 0.25rem; cursor: pointer; white-space: nowrap; } | ||
| 230 | +.term-tab.active { color: var(--fg); border-color: var(--accent); } | ||
| 231 | +.term-tab.exited { opacity: 0.6; } | ||
| 232 | +.term-tab-close { width: 1rem; text-align: center; border-radius: 0.25rem; } | ||
| 233 | +.term-tab-close:hover { background: var(--border); } | ||
| 225 | .splitter-y { flex: none; height: 5px; margin: -2px 0; cursor: row-resize; z-index: 1; } | 234 | .splitter-y { flex: none; height: 5px; margin: -2px 0; cursor: row-resize; z-index: 1; } |
| 226 | .splitter-y:hover, .splitter-y.dragging { background: var(--accent); } | 235 | .splitter-y:hover, .splitter-y.dragging { background: var(--accent); } |
| 227 | ::-webkit-scrollbar { width: 8px; } | 236 | ::-webkit-scrollbar { width: 8px; } |
modified
web/app.js +127 -60 | @@ -977,7 +977,7 @@ function applyTheme(theme) { | ||
| 977 | 977 | document.getElementById('hljs-light').disabled = theme !== 'light'; |
| 978 | 978 | themeBtn.textContent = theme === 'dark' ? '☀️ Light' : '🌙 Dark'; |
| 979 | 979 | if (window.monaco) monaco.editor.setTheme(monacoTheme()); |
| 980 | - if (term) term.options.theme = termTheme(); | |
| 980 | + for (const t of terms) t.term.options.theme = termTheme(); | |
| 981 | 981 | // A preview's diagrams are drawn for a theme: drawn again. |
| 982 | 982 | const shown = activePath && tabs.get(activePath); |
| 983 | 983 | if (shown?.preview && ['markdown', 'asciidoc'].includes(previewKind(shown.path))) renderPreview(shown); |
| @@ -2174,13 +2174,14 @@ async function syncFiles() { | ||
| 2174 | 2174 | // --- terminal |
| 2175 | 2175 | // |
| 2176 | 2176 | // One shell per WebSocket (/ws/term), started by the server in the |
| 2177 | -// working directory. Hiding the panel keeps it running; when it | |
| 2178 | -// exits, Enter starts a new one. | |
| 2177 | +// working directory. Each tab of the panel is a shell of its own, with its | |
| 2178 | +// own xterm. Hiding the panel keeps them running; when one exits, Enter | |
| 2179 | +// starts a new one; closing its tab ends it. | |
| 2179 | 2180 | |
| 2180 | -let term = null; | |
| 2181 | -let fitAddon = null; | |
| 2182 | -let termSocket = null; | |
| 2183 | -let termExited = false; | |
| 2181 | +const termTabsEl = document.getElementById('term-tabs'); | |
| 2182 | +const terms = []; // { id, term, fit, view, tab, socket, exited } | |
| 2183 | +let activeTerm = null; | |
| 2184 | +let termSeq = 0; | |
| 2184 | 2185 | |
| 2185 | 2186 | function termTheme() { |
| 2186 | 2187 | const css = getComputedStyle(document.documentElement); |
| @@ -2191,51 +2192,136 @@ function termTheme() { | ||
| 2191 | 2192 | }; |
| 2192 | 2193 | } |
| 2193 | 2194 | |
| 2194 | -function sendTerm(msg) { | |
| 2195 | - if (termSocket?.readyState === WebSocket.OPEN) termSocket.send(JSON.stringify(msg)); | |
| 2195 | +function sendTerm(t, msg) { | |
| 2196 | + if (t.socket?.readyState === WebSocket.OPEN) t.socket.send(JSON.stringify(msg)); | |
| 2196 | 2197 | } |
| 2197 | 2198 | |
| 2199 | +// Only the shown terminal has a size to fit to; the others are fitted | |
| 2200 | +// when their tab is selected. | |
| 2198 | 2201 | function fitTerm() { |
| 2199 | - if (!term || terminalPane.hidden) return; | |
| 2200 | - try { fitAddon.fit(); } catch (e) {} | |
| 2202 | + if (!activeTerm || terminalPane.hidden) return; | |
| 2203 | + try { activeTerm.fit.fit(); } catch (e) {} | |
| 2201 | 2204 | } |
| 2202 | 2205 | |
| 2203 | -function startShell() { | |
| 2204 | - termExited = false; | |
| 2206 | +function setTermExited(t, text) { | |
| 2207 | + t.exited = true; | |
| 2208 | + t.tab.classList.add('exited'); | |
| 2209 | + t.term.write(`\r\n\x1b[2m[${text} — press Enter to start a new shell]\x1b[0m\r\n`); | |
| 2210 | +} | |
| 2211 | + | |
| 2212 | +function startShell(t) { | |
| 2213 | + t.exited = false; | |
| 2214 | + t.tab.classList.remove('exited'); | |
| 2205 | 2215 | const proto = location.protocol === 'https:' ? 'wss' : 'ws'; |
| 2206 | 2216 | const ws = new WebSocket(`${proto}://${location.host}/ws/term`); |
| 2207 | 2217 | ws.binaryType = 'arraybuffer'; |
| 2208 | - termSocket = ws; | |
| 2209 | - ws.onopen = () => sendTerm({ type: 'resize', cols: term.cols, rows: term.rows }); | |
| 2218 | + t.socket = ws; | |
| 2219 | + ws.onopen = () => sendTerm(t, { type: 'resize', cols: t.term.cols, rows: t.term.rows }); | |
| 2210 | 2220 | ws.onmessage = (ev) => { |
| 2211 | - if (ws !== termSocket) return; | |
| 2221 | + if (ws !== t.socket) return; | |
| 2212 | 2222 | if (typeof ev.data !== 'string') { |
| 2213 | - term.write(new Uint8Array(ev.data)); | |
| 2223 | + t.term.write(new Uint8Array(ev.data)); | |
| 2214 | 2224 | return; |
| 2215 | 2225 | } |
| 2216 | 2226 | const msg = JSON.parse(ev.data); |
| 2217 | - if (msg.type === 'exit') { | |
| 2218 | - termExited = true; | |
| 2219 | - term.write(`\r\n\x1b[2m[${msg.message} — press Enter to start a new shell]\x1b[0m\r\n`); | |
| 2220 | - } | |
| 2227 | + if (msg.type === 'exit') setTermExited(t, msg.message); | |
| 2221 | 2228 | }; |
| 2222 | 2229 | ws.onclose = () => { |
| 2223 | - if (ws !== termSocket) return; // replaced by a restart | |
| 2224 | - termSocket = null; | |
| 2225 | - if (!termExited) { | |
| 2226 | - termExited = true; | |
| 2227 | - term.write('\r\n\x1b[2m[connection closed — press Enter to start a new shell]\x1b[0m\r\n'); | |
| 2228 | - } | |
| 2230 | + if (ws !== t.socket) return; // replaced by a restart, or closed | |
| 2231 | + t.socket = null; | |
| 2232 | + if (!t.exited) setTermExited(t, 'connection closed'); | |
| 2229 | 2233 | }; |
| 2230 | 2234 | } |
| 2231 | 2235 | |
| 2232 | -function restartShell() { | |
| 2233 | - const old = termSocket; | |
| 2234 | - termSocket = null; | |
| 2235 | - old?.close(); // the server hangs the old shell up | |
| 2236 | - term.reset(); | |
| 2237 | - startShell(); | |
| 2238 | - term.focus(); | |
| 2236 | +function stopShell(t) { | |
| 2237 | + const old = t.socket; | |
| 2238 | + t.socket = null; | |
| 2239 | + old?.close(); // the server hangs the shell up | |
| 2240 | +} | |
| 2241 | + | |
| 2242 | +function restartShell(t) { | |
| 2243 | + stopShell(t); | |
| 2244 | + t.term.reset(); | |
| 2245 | + startShell(t); | |
| 2246 | + t.term.focus(); | |
| 2247 | +} | |
| 2248 | + | |
| 2249 | +function selectTerminal(t) { | |
| 2250 | + activeTerm = t; | |
| 2251 | + for (const x of terms) { | |
| 2252 | + x.view.hidden = x !== t; | |
| 2253 | + x.tab.classList.toggle('active', x === t); | |
| 2254 | + } | |
| 2255 | + fitTerm(); | |
| 2256 | + t.term.focus(); | |
| 2257 | +} | |
| 2258 | + | |
| 2259 | +function newTerminal() { | |
| 2260 | + const t = { id: ++termSeq, socket: null, exited: false }; | |
| 2261 | + t.view = document.createElement('div'); | |
| 2262 | + t.view.className = 'term-view'; | |
| 2263 | + terminalEl.append(t.view); | |
| 2264 | + | |
| 2265 | + t.tab = document.createElement('span'); | |
| 2266 | + t.tab.className = 'term-tab'; | |
| 2267 | + t.tab.title = `Shell ${t.id}`; | |
| 2268 | + const label = document.createElement('span'); | |
| 2269 | + label.textContent = t.id; | |
| 2270 | + const close = document.createElement('button'); | |
| 2271 | + close.type = 'button'; | |
| 2272 | + close.className = 'term-tab-close'; | |
| 2273 | + close.title = 'Close (ends the shell)'; | |
| 2274 | + close.textContent = '×'; | |
| 2275 | + close.onclick = (e) => { e.stopPropagation(); closeTerminal(t); }; | |
| 2276 | + t.tab.append(label, close); | |
| 2277 | + t.tab.onclick = () => selectTerminal(t); | |
| 2278 | + termTabsEl.append(t.tab); | |
| 2279 | + | |
| 2280 | + t.term = new Terminal({ | |
| 2281 | + fontFamily: "'Fira Code', ui-monospace, Menlo, monospace", | |
| 2282 | + fontSize: 13, | |
| 2283 | + cursorBlink: true, | |
| 2284 | + scrollback: 5000, | |
| 2285 | + theme: termTheme() | |
| 2286 | + }); | |
| 2287 | + t.fit = new FitAddon.FitAddon(); | |
| 2288 | + t.term.loadAddon(t.fit); | |
| 2289 | + terms.push(t); | |
| 2290 | + selectTerminal(t); // shown before opening, so that it has a size | |
| 2291 | + t.term.open(t.view); | |
| 2292 | + t.term.onData((data) => { | |
| 2293 | + if (t.exited) { | |
| 2294 | + if (data === '\r') restartShell(t); | |
| 2295 | + return; | |
| 2296 | + } | |
| 2297 | + sendTerm(t, { type: 'input', data }); | |
| 2298 | + // A command may have changed files: refresh once it had | |
| 2299 | + // a moment to run. | |
| 2300 | + if (data.includes('\r')) setTimeout(scheduleFilesSync, 700); | |
| 2301 | + }); | |
| 2302 | + t.term.onResize(({ cols, rows }) => sendTerm(t, { type: 'resize', cols, rows })); | |
| 2303 | + fitTerm(); | |
| 2304 | + startShell(t); | |
| 2305 | + t.term.focus(); | |
| 2306 | +} | |
| 2307 | + | |
| 2308 | +// Closing the last terminal hides the panel; opening it again starts a | |
| 2309 | +// new one. | |
| 2310 | +function closeTerminal(t) { | |
| 2311 | + const i = terms.indexOf(t); | |
| 2312 | + if (i < 0) return; | |
| 2313 | + stopShell(t); | |
| 2314 | + t.term.dispose(); | |
| 2315 | + t.view.remove(); | |
| 2316 | + t.tab.remove(); | |
| 2317 | + terms.splice(i, 1); | |
| 2318 | + if (activeTerm === t) activeTerm = null; | |
| 2319 | + if (!terms.length) { | |
| 2320 | + termSeq = 0; | |
| 2321 | + setTerminal(false); | |
| 2322 | + return; | |
| 2323 | + } | |
| 2324 | + if (!activeTerm) selectTerminal(terms[Math.min(i, terms.length - 1)]); | |
| 2239 | 2325 | } |
| 2240 | 2326 | |
| 2241 | 2327 | function setTerminal(open) { |
| @@ -2243,38 +2329,19 @@ function setTerminal(open) { | ||
| 2243 | 2329 | store('terminal', open ? '1' : '0'); |
| 2244 | 2330 | syncLayout(); |
| 2245 | 2331 | if (!open) return; |
| 2246 | - if (!term) { | |
| 2247 | - term = new Terminal({ | |
| 2248 | - fontFamily: "'Fira Code', ui-monospace, Menlo, monospace", | |
| 2249 | - fontSize: 13, | |
| 2250 | - cursorBlink: true, | |
| 2251 | - scrollback: 5000, | |
| 2252 | - theme: termTheme() | |
| 2253 | - }); | |
| 2254 | - fitAddon = new FitAddon.FitAddon(); | |
| 2255 | - term.loadAddon(fitAddon); | |
| 2256 | - term.open(terminalEl); | |
| 2257 | - term.onData((data) => { | |
| 2258 | - if (termExited) { | |
| 2259 | - if (data === '\r') restartShell(); | |
| 2260 | - return; | |
| 2261 | - } | |
| 2262 | - sendTerm({ type: 'input', data }); | |
| 2263 | - // A command may have changed files: refresh once it had | |
| 2264 | - // a moment to run. | |
| 2265 | - if (data.includes('\r')) setTimeout(scheduleFilesSync, 700); | |
| 2266 | - }); | |
| 2267 | - term.onResize(({ cols, rows }) => sendTerm({ type: 'resize', cols, rows })); | |
| 2268 | - new ResizeObserver(() => fitTerm()).observe(terminalEl); | |
| 2332 | + if (!terms.length) { | |
| 2333 | + newTerminal(); | |
| 2334 | + return; | |
| 2269 | 2335 | } |
| 2270 | 2336 | fitTerm(); |
| 2271 | - if (!termSocket) startShell(); | |
| 2272 | - term.focus(); | |
| 2337 | + activeTerm.term.focus(); | |
| 2273 | 2338 | } |
| 2274 | 2339 | |
| 2340 | +new ResizeObserver(() => fitTerm()).observe(terminalEl); | |
| 2275 | 2341 | termBtn.onclick = () => setTerminal(terminalPane.hidden); |
| 2342 | +document.getElementById('term-new-btn').onclick = () => newTerminal(); | |
| 2276 | 2343 | document.getElementById('term-hide-btn').onclick = () => setTerminal(false); |
| 2277 | -document.getElementById('term-restart-btn').onclick = () => { if (term) restartShell(); }; | |
| 2344 | +document.getElementById('term-restart-btn').onclick = () => { if (activeTerm) restartShell(activeTerm); }; | |
| 2278 | 2345 | |
| 2279 | 2346 | // --- drag and drop, attachments ------------------------------------------ |
| 2280 | 2347 | // |
| @@ -977,7 +977,7 @@ function applyTheme(theme) { | |||
| 977 | document.getElementById('hljs-light').disabled = theme !== 'light'; | 977 | document.getElementById('hljs-light').disabled = theme !== 'light'; |
| 978 | themeBtn.textContent = theme === 'dark' ? '☀️ Light' : '🌙 Dark'; | 978 | themeBtn.textContent = theme === 'dark' ? '☀️ Light' : '🌙 Dark'; |
| 979 | if (window.monaco) monaco.editor.setTheme(monacoTheme()); | 979 | if (window.monaco) monaco.editor.setTheme(monacoTheme()); |
| 980 | - if (term) term.options.theme = termTheme(); | 980 | + for (const t of terms) t.term.options.theme = termTheme(); |
| 981 | // A preview's diagrams are drawn for a theme: drawn again. | 981 | // A preview's diagrams are drawn for a theme: drawn again. |
| 982 | const shown = activePath && tabs.get(activePath); | 982 | const shown = activePath && tabs.get(activePath); |
| 983 | if (shown?.preview && ['markdown', 'asciidoc'].includes(previewKind(shown.path))) renderPreview(shown); | 983 | if (shown?.preview && ['markdown', 'asciidoc'].includes(previewKind(shown.path))) renderPreview(shown); |
| @@ -2174,13 +2174,14 @@ async function syncFiles() { | |||
| 2174 | // --- terminal | 2174 | // --- terminal |
| 2175 | // | 2175 | // |
| 2176 | // One shell per WebSocket (/ws/term), started by the server in the | 2176 | // One shell per WebSocket (/ws/term), started by the server in the |
| 2177 | -// working directory. Hiding the panel keeps it running; when it | 2177 | +// working directory. Each tab of the panel is a shell of its own, with its |
| 2178 | -// exits, Enter starts a new one. | 2178 | +// own xterm. Hiding the panel keeps them running; when one exits, Enter |
| 2179 | +// starts a new one; closing its tab ends it. | ||
| 2179 | 2180 | ||
| 2180 | -let term = null; | 2181 | +const termTabsEl = document.getElementById('term-tabs'); |
| 2181 | -let fitAddon = null; | 2182 | +const terms = []; // { id, term, fit, view, tab, socket, exited } |
| 2182 | -let termSocket = null; | 2183 | +let activeTerm = null; |
| 2183 | -let termExited = false; | 2184 | +let termSeq = 0; |
| 2184 | 2185 | ||
| 2185 | function termTheme() { | 2186 | function termTheme() { |
| 2186 | const css = getComputedStyle(document.documentElement); | 2187 | const css = getComputedStyle(document.documentElement); |
| @@ -2191,51 +2192,136 @@ function termTheme() { | |||
| 2191 | }; | 2192 | }; |
| 2192 | } | 2193 | } |
| 2193 | 2194 | ||
| 2194 | -function sendTerm(msg) { | 2195 | +function sendTerm(t, msg) { |
| 2195 | - if (termSocket?.readyState === WebSocket.OPEN) termSocket.send(JSON.stringify(msg)); | 2196 | + if (t.socket?.readyState === WebSocket.OPEN) t.socket.send(JSON.stringify(msg)); |
| 2196 | } | 2197 | } |
| 2197 | 2198 | ||
| 2199 | +// Only the shown terminal has a size to fit to; the others are fitted | ||
| 2200 | +// when their tab is selected. | ||
| 2198 | function fitTerm() { | 2201 | function fitTerm() { |
| 2199 | - if (!term || terminalPane.hidden) return; | 2202 | + if (!activeTerm || terminalPane.hidden) return; |
| 2200 | - try { fitAddon.fit(); } catch (e) {} | 2203 | + try { activeTerm.fit.fit(); } catch (e) {} |
| 2201 | } | 2204 | } |
| 2202 | 2205 | ||
| 2203 | -function startShell() { | 2206 | +function setTermExited(t, text) { |
| 2204 | - termExited = false; | 2207 | + t.exited = true; |
| 2208 | + t.tab.classList.add('exited'); | ||
| 2209 | + t.term.write(`\r\n\x1b[2m[${text} — press Enter to start a new shell]\x1b[0m\r\n`); | ||
| 2210 | +} | ||
| 2211 | + | ||
| 2212 | +function startShell(t) { | ||
| 2213 | + t.exited = false; | ||
| 2214 | + t.tab.classList.remove('exited'); | ||
| 2205 | const proto = location.protocol === 'https:' ? 'wss' : 'ws'; | 2215 | const proto = location.protocol === 'https:' ? 'wss' : 'ws'; |
| 2206 | const ws = new WebSocket(`${proto}://${location.host}/ws/term`); | 2216 | const ws = new WebSocket(`${proto}://${location.host}/ws/term`); |
| 2207 | ws.binaryType = 'arraybuffer'; | 2217 | ws.binaryType = 'arraybuffer'; |
| 2208 | - termSocket = ws; | 2218 | + t.socket = ws; |
| 2209 | - ws.onopen = () => sendTerm({ type: 'resize', cols: term.cols, rows: term.rows }); | 2219 | + ws.onopen = () => sendTerm(t, { type: 'resize', cols: t.term.cols, rows: t.term.rows }); |
| 2210 | ws.onmessage = (ev) => { | 2220 | ws.onmessage = (ev) => { |
| 2211 | - if (ws !== termSocket) return; | 2221 | + if (ws !== t.socket) return; |
| 2212 | if (typeof ev.data !== 'string') { | 2222 | if (typeof ev.data !== 'string') { |
| 2213 | - term.write(new Uint8Array(ev.data)); | 2223 | + t.term.write(new Uint8Array(ev.data)); |
| 2214 | return; | 2224 | return; |
| 2215 | } | 2225 | } |
| 2216 | const msg = JSON.parse(ev.data); | 2226 | const msg = JSON.parse(ev.data); |
| 2217 | - if (msg.type === 'exit') { | 2227 | + if (msg.type === 'exit') setTermExited(t, msg.message); |
| 2218 | - termExited = true; | ||
| 2219 | - term.write(`\r\n\x1b[2m[${msg.message} — press Enter to start a new shell]\x1b[0m\r\n`); | ||
| 2220 | - } | ||
| 2221 | }; | 2228 | }; |
| 2222 | ws.onclose = () => { | 2229 | ws.onclose = () => { |
| 2223 | - if (ws !== termSocket) return; // replaced by a restart | 2230 | + if (ws !== t.socket) return; // replaced by a restart, or closed |
| 2224 | - termSocket = null; | 2231 | + t.socket = null; |
| 2225 | - if (!termExited) { | 2232 | + if (!t.exited) setTermExited(t, 'connection closed'); |
| 2226 | - termExited = true; | ||
| 2227 | - term.write('\r\n\x1b[2m[connection closed — press Enter to start a new shell]\x1b[0m\r\n'); | ||
| 2228 | - } | ||
| 2229 | }; | 2233 | }; |
| 2230 | } | 2234 | } |
| 2231 | 2235 | ||
| 2232 | -function restartShell() { | 2236 | +function stopShell(t) { |
| 2233 | - const old = termSocket; | 2237 | + const old = t.socket; |
| 2234 | - termSocket = null; | 2238 | + t.socket = null; |
| 2235 | - old?.close(); // the server hangs the old shell up | 2239 | + old?.close(); // the server hangs the shell up |
| 2236 | - term.reset(); | 2240 | +} |
| 2237 | - startShell(); | 2241 | + |
| 2238 | - term.focus(); | 2242 | +function restartShell(t) { |
| 2243 | + stopShell(t); | ||
| 2244 | + t.term.reset(); | ||
| 2245 | + startShell(t); | ||
| 2246 | + t.term.focus(); | ||
| 2247 | +} | ||
| 2248 | + | ||
| 2249 | +function selectTerminal(t) { | ||
| 2250 | + activeTerm = t; | ||
| 2251 | + for (const x of terms) { | ||
| 2252 | + x.view.hidden = x !== t; | ||
| 2253 | + x.tab.classList.toggle('active', x === t); | ||
| 2254 | + } | ||
| 2255 | + fitTerm(); | ||
| 2256 | + t.term.focus(); | ||
| 2257 | +} | ||
| 2258 | + | ||
| 2259 | +function newTerminal() { | ||
| 2260 | + const t = { id: ++termSeq, socket: null, exited: false }; | ||
| 2261 | + t.view = document.createElement('div'); | ||
| 2262 | + t.view.className = 'term-view'; | ||
| 2263 | + terminalEl.append(t.view); | ||
| 2264 | + | ||
| 2265 | + t.tab = document.createElement('span'); | ||
| 2266 | + t.tab.className = 'term-tab'; | ||
| 2267 | + t.tab.title = `Shell ${t.id}`; | ||
| 2268 | + const label = document.createElement('span'); | ||
| 2269 | + label.textContent = t.id; | ||
| 2270 | + const close = document.createElement('button'); | ||
| 2271 | + close.type = 'button'; | ||
| 2272 | + close.className = 'term-tab-close'; | ||
| 2273 | + close.title = 'Close (ends the shell)'; | ||
| 2274 | + close.textContent = '×'; | ||
| 2275 | + close.onclick = (e) => { e.stopPropagation(); closeTerminal(t); }; | ||
| 2276 | + t.tab.append(label, close); | ||
| 2277 | + t.tab.onclick = () => selectTerminal(t); | ||
| 2278 | + termTabsEl.append(t.tab); | ||
| 2279 | + | ||
| 2280 | + t.term = new Terminal({ | ||
| 2281 | + fontFamily: "'Fira Code', ui-monospace, Menlo, monospace", | ||
| 2282 | + fontSize: 13, | ||
| 2283 | + cursorBlink: true, | ||
| 2284 | + scrollback: 5000, | ||
| 2285 | + theme: termTheme() | ||
| 2286 | + }); | ||
| 2287 | + t.fit = new FitAddon.FitAddon(); | ||
| 2288 | + t.term.loadAddon(t.fit); | ||
| 2289 | + terms.push(t); | ||
| 2290 | + selectTerminal(t); // shown before opening, so that it has a size | ||
| 2291 | + t.term.open(t.view); | ||
| 2292 | + t.term.onData((data) => { | ||
| 2293 | + if (t.exited) { | ||
| 2294 | + if (data === '\r') restartShell(t); | ||
| 2295 | + return; | ||
| 2296 | + } | ||
| 2297 | + sendTerm(t, { type: 'input', data }); | ||
| 2298 | + // A command may have changed files: refresh once it had | ||
| 2299 | + // a moment to run. | ||
| 2300 | + if (data.includes('\r')) setTimeout(scheduleFilesSync, 700); | ||
| 2301 | + }); | ||
| 2302 | + t.term.onResize(({ cols, rows }) => sendTerm(t, { type: 'resize', cols, rows })); | ||
| 2303 | + fitTerm(); | ||
| 2304 | + startShell(t); | ||
| 2305 | + t.term.focus(); | ||
| 2306 | +} | ||
| 2307 | + | ||
| 2308 | +// Closing the last terminal hides the panel; opening it again starts a | ||
| 2309 | +// new one. | ||
| 2310 | +function closeTerminal(t) { | ||
| 2311 | + const i = terms.indexOf(t); | ||
| 2312 | + if (i < 0) return; | ||
| 2313 | + stopShell(t); | ||
| 2314 | + t.term.dispose(); | ||
| 2315 | + t.view.remove(); | ||
| 2316 | + t.tab.remove(); | ||
| 2317 | + terms.splice(i, 1); | ||
| 2318 | + if (activeTerm === t) activeTerm = null; | ||
| 2319 | + if (!terms.length) { | ||
| 2320 | + termSeq = 0; | ||
| 2321 | + setTerminal(false); | ||
| 2322 | + return; | ||
| 2323 | + } | ||
| 2324 | + if (!activeTerm) selectTerminal(terms[Math.min(i, terms.length - 1)]); | ||
| 2239 | } | 2325 | } |
| 2240 | 2326 | ||
| 2241 | function setTerminal(open) { | 2327 | function setTerminal(open) { |
| @@ -2243,38 +2329,19 @@ function setTerminal(open) { | |||
| 2243 | store('terminal', open ? '1' : '0'); | 2329 | store('terminal', open ? '1' : '0'); |
| 2244 | syncLayout(); | 2330 | syncLayout(); |
| 2245 | if (!open) return; | 2331 | if (!open) return; |
| 2246 | - if (!term) { | 2332 | + if (!terms.length) { |
| 2247 | - term = new Terminal({ | 2333 | + newTerminal(); |
| 2248 | - fontFamily: "'Fira Code', ui-monospace, Menlo, monospace", | 2334 | + return; |
| 2249 | - fontSize: 13, | ||
| 2250 | - cursorBlink: true, | ||
| 2251 | - scrollback: 5000, | ||
| 2252 | - theme: termTheme() | ||
| 2253 | - }); | ||
| 2254 | - fitAddon = new FitAddon.FitAddon(); | ||
| 2255 | - term.loadAddon(fitAddon); | ||
| 2256 | - term.open(terminalEl); | ||
| 2257 | - term.onData((data) => { | ||
| 2258 | - if (termExited) { | ||
| 2259 | - if (data === '\r') restartShell(); | ||
| 2260 | - return; | ||
| 2261 | - } | ||
| 2262 | - sendTerm({ type: 'input', data }); | ||
| 2263 | - // A command may have changed files: refresh once it had | ||
| 2264 | - // a moment to run. | ||
| 2265 | - if (data.includes('\r')) setTimeout(scheduleFilesSync, 700); | ||
| 2266 | - }); | ||
| 2267 | - term.onResize(({ cols, rows }) => sendTerm({ type: 'resize', cols, rows })); | ||
| 2268 | - new ResizeObserver(() => fitTerm()).observe(terminalEl); | ||
| 2269 | } | 2335 | } |
| 2270 | fitTerm(); | 2336 | fitTerm(); |
| 2271 | - if (!termSocket) startShell(); | 2337 | + activeTerm.term.focus(); |
| 2272 | - term.focus(); | ||
| 2273 | } | 2338 | } |
| 2274 | 2339 | ||
| 2340 | +new ResizeObserver(() => fitTerm()).observe(terminalEl); | ||
| 2275 | termBtn.onclick = () => setTerminal(terminalPane.hidden); | 2341 | termBtn.onclick = () => setTerminal(terminalPane.hidden); |
| 2342 | +document.getElementById('term-new-btn').onclick = () => newTerminal(); | ||
| 2276 | document.getElementById('term-hide-btn').onclick = () => setTerminal(false); | 2343 | document.getElementById('term-hide-btn').onclick = () => setTerminal(false); |
| 2277 | -document.getElementById('term-restart-btn').onclick = () => { if (term) restartShell(); }; | 2344 | +document.getElementById('term-restart-btn').onclick = () => { if (activeTerm) restartShell(activeTerm); }; |
| 2278 | 2345 | ||
| 2279 | // --- drag and drop, attachments ------------------------------------------ | 2346 | // --- drag and drop, attachments ------------------------------------------ |
| 2280 | // | 2347 | // |
modified
web/index.html +6 -2 | @@ -95,10 +95,14 @@ | ||
| 95 | 95 | <div class="splitter-y" data-target="terminal-pane" hidden></div> |
| 96 | 96 | <section id="terminal-pane" class="pane terminal-pane" hidden> |
| 97 | 97 | <div class="pane-head"> |
| 98 | - <span>Terminal</span> | |
| 98 | + <span class="term-head"> | |
| 99 | + <span>Terminal</span> | |
| 100 | + <span id="term-tabs" class="term-tabs"></span> | |
| 101 | + <button type="button" id="term-new-btn" class="icon-btn" title="New terminal">+</button> | |
| 102 | + </span> | |
| 99 | 103 | <span> |
| 100 | 104 | <button type="button" id="term-restart-btn" class="icon-btn" title="Restart the shell">⟳</button> |
| 101 | - <button type="button" id="term-hide-btn" class="icon-btn" title="Hide (the shell keeps running)">×</button> | |
| 105 | + <button type="button" id="term-hide-btn" class="icon-btn" title="Hide (the shells keep running)">×</button> | |
| 102 | 106 | </span> |
| 103 | 107 | </div> |
| 104 | 108 | <div id="terminal"></div> |
| @@ -95,10 +95,14 @@ | |||
| 95 | <div class="splitter-y" data-target="terminal-pane" hidden></div> | 95 | <div class="splitter-y" data-target="terminal-pane" hidden></div> |
| 96 | <section id="terminal-pane" class="pane terminal-pane" hidden> | 96 | <section id="terminal-pane" class="pane terminal-pane" hidden> |
| 97 | <div class="pane-head"> | 97 | <div class="pane-head"> |
| 98 | - <span>Terminal</span> | 98 | + <span class="term-head"> |
| 99 | + <span>Terminal</span> | ||
| 100 | + <span id="term-tabs" class="term-tabs"></span> | ||
| 101 | + <button type="button" id="term-new-btn" class="icon-btn" title="New terminal">+</button> | ||
| 102 | + </span> | ||
| 99 | <span> | 103 | <span> |
| 100 | <button type="button" id="term-restart-btn" class="icon-btn" title="Restart the shell">⟳</button> | 104 | <button type="button" id="term-restart-btn" class="icon-btn" title="Restart the shell">⟳</button> |
| 101 | - <button type="button" id="term-hide-btn" class="icon-btn" title="Hide (the shell keeps running)">×</button> | 105 | + <button type="button" id="term-hide-btn" class="icon-btn" title="Hide (the shells keep running)">×</button> |
| 102 | </span> | 106 | </span> |
| 103 | </div> | 107 | </div> |
| 104 | <div id="terminal"></div> | 108 | <div id="terminal"></div> |