🚨 Latest Research:Tanstack npm Packages Compromised in Ongoing Mini Shai-Hulud Supply-Chain Attack.Learn More
Socket
Book a DemoSign in
Socket

opencode-multiplexer

Package Overview
Dependencies
Maintainers
1
Versions
11
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

opencode-multiplexer - npm Package Compare versions

Comparing version
0.6.1
to
0.7.0
+1
-1
package.json
{
"name": "opencode-multiplexer",
"version": "0.6.1",
"version": "0.7.0",
"type": "module",

@@ -5,0 +5,0 @@ "description": "Multiplexer for opencode AI coding agent sessions",

import { describe, test, expect } from "bun:test"
import { relativeTime, highlightMatches } from "../views/helpers.js"
import { relativeTime, highlightMatches, filterFilesForCwd, findDisplayLineMatches, getSearchScrollOffset } from "../views/helpers.js"
import { deriveRepoName } from "../poller.js"

@@ -147,1 +147,84 @@ import { getAllSessions, type DbSessionWithProject, NEEDS_INPUT_TOOLS } from "../db/reader.js"

})
describe("filterFilesForCwd", () => {
test("keeps relative files and absolute files within cwd", () => {
expect(filterFilesForCwd([
"src/app.ts",
"/repo/src/views/conversation.tsx",
"/other/file.ts",
], "/repo")).toEqual([
"src/app.ts",
"/repo/src/views/conversation.tsx",
])
})
test("does not treat sibling directories as inside cwd", () => {
expect(filterFilesForCwd([
"/repo-other/file.ts",
"/repo/file.ts",
], "/repo")).toEqual([
"/repo/file.ts",
])
})
})
describe("findDisplayLineMatches", () => {
const lines = [
{ kind: "role-header", role: "user", time: "10:00" },
{ kind: "text", text: "Hello world" },
{ kind: "tool", icon: "✓", color: "green", name: "bash", input: "python -V" },
{ kind: "question", header: "Confirm", question: "Search the visible line?", status: "running", options: [], custom: false },
{ kind: "thinking", text: "Working through the plan" },
{ kind: "spacer" },
] as const
test("matches visible text-bearing lines only", () => {
expect(findDisplayLineMatches(lines as any, "world")).toEqual([1])
expect(findDisplayLineMatches(lines as any, "python")).toEqual([2])
expect(findDisplayLineMatches(lines as any, "visible line")).toEqual([3])
expect(findDisplayLineMatches(lines as any, "working")).toEqual([4])
})
test("is case-insensitive and ignores non-searchable rows", () => {
expect(findDisplayLineMatches(lines as any, "HELLO")).toEqual([1])
expect(findDisplayLineMatches(lines as any, "10:00")).toEqual([])
})
test("uses only the visible tool detail when title and input both exist", () => {
const toolLine = [{
kind: "tool",
icon: "✓",
color: "green",
name: "bash",
title: "visible title",
input: "hidden input",
}] as const
expect(findDisplayLineMatches(toolLine as any, "visible title")).toEqual([0])
expect(findDisplayLineMatches(toolLine as any, "hidden input")).toEqual([])
})
test("matches text after stripping ansi styling", () => {
const styledLine = [{ kind: "text", text: "\x1b[1mHello\x1b[22m world" }] as const
expect(findDisplayLineMatches(styledLine as any, "hello")).toEqual([0])
})
test("returns no matches for blank queries", () => {
expect(findDisplayLineMatches(lines as any, "")).toEqual([])
expect(findDisplayLineMatches(lines as any, " ")).toEqual([])
})
})
describe("getSearchScrollOffset", () => {
test("keeps a matched line visible near the top of the viewport", () => {
expect(getSearchScrollOffset(100, 10, 30)).toBe(60)
})
test("clamps near the bottom when the match is already in the latest rows", () => {
expect(getSearchScrollOffset(100, 10, 95)).toBe(0)
})
test("clamps near the top when the first rows are matched", () => {
expect(getSearchScrollOffset(100, 10, 0)).toBe(90)
})
})
import { describe, test, expect, beforeEach, afterEach } from "bun:test"
import { execSync } from "child_process"
import { existsSync, mkdirSync, rmSync } from "fs"
import { existsSync, mkdirSync, rmSync, writeFileSync } from "fs"
import { join } from "path"

@@ -8,2 +8,3 @@ import { tmpdir } from "os"

import { getSessionModifiedFiles } from "../db/reader.js"
import { detectProjectVenv } from "../views/worktree.js"

@@ -95,1 +96,40 @@ // Create a temporary git repo for testing

})
describe("detectProjectVenv", () => {
test("prefers project root .venv over worktree cwd", () => {
const repoDir = join(TEST_DIR, "repo-with-venv")
const worktreeDir = join(repoDir, ".worktrees", "feature-branch")
const rootPython = join(repoDir, ".venv", "bin", "python")
const worktreePython = join(worktreeDir, ".venv", "bin", "python")
mkdirSync(join(repoDir, ".venv", "bin"), { recursive: true })
mkdirSync(join(worktreeDir, ".venv", "bin"), { recursive: true })
writeFileSync(rootPython, "")
writeFileSync(worktreePython, "")
const venv = detectProjectVenv(repoDir)
expect(venv?.root).toBe(join(repoDir, ".venv"))
expect(venv?.binDir).toBe(join(repoDir, ".venv", "bin"))
})
test("falls back to project root venv when .venv is absent", () => {
const repoDir = join(TEST_DIR, "repo-with-venv-fallback")
const pythonPath = join(repoDir, "venv", "bin", "python")
mkdirSync(join(repoDir, "venv", "bin"), { recursive: true })
writeFileSync(pythonPath, "")
const venv = detectProjectVenv(repoDir)
expect(venv?.root).toBe(join(repoDir, "venv"))
expect(venv?.binDir).toBe(join(repoDir, "venv", "bin"))
})
test("returns null when no supported project-root virtualenv exists", () => {
const repoDir = join(TEST_DIR, "repo-without-venv")
mkdirSync(repoDir, { recursive: true })
expect(detectProjectVenv(repoDir)).toBeNull()
})
})
import stripAnsi from "strip-ansi"
import { relative as pathRelative } from "path"
import type { SessionStatus } from "../store.js"
import type { DisplayLine } from "./display-lines.js"

@@ -87,1 +89,41 @@ /**

}
export function filterFilesForCwd(files: string[], cwd: string): string[] {
return files.filter((file) => {
if (!file.startsWith("/")) return true
const relativePath = pathRelative(cwd, file)
return relativePath === "" || (!relativePath.startsWith("..") && relativePath !== "..")
})
}
function getDisplayLineSearchText(line: DisplayLine): string {
switch (line.kind) {
case "thinking":
case "text":
return line.text
case "tool":
return [line.name, line.title || line.input].filter(Boolean).join(" ")
case "question":
return [line.header, line.question].filter(Boolean).join(" ")
default:
return ""
}
}
export function findDisplayLineMatches(lines: DisplayLine[], query: string): number[] {
const trimmed = query.trim()
if (!trimmed) return []
const lowerQuery = trimmed.toLowerCase()
return lines.flatMap((line, index) =>
stripAnsi(getDisplayLineSearchText(line)).toLowerCase().includes(lowerQuery) ? [index] : []
)
}
export function getSearchScrollOffset(totalLines: number, msgAreaHeight: number, lineIndex: number): number {
const safeHeight = Math.max(1, msgAreaHeight)
const maxStart = Math.max(0, totalLines - safeHeight)
const targetStart = Math.max(0, Math.min(lineIndex, maxStart))
return Math.max(0, totalLines - safeHeight - targetStart)
}

@@ -6,2 +6,3 @@ import React from "react"

import { existsSync } from "fs"
import { join } from "path"
import { createOpencodeClient } from "@opencode-ai/sdk"

@@ -72,2 +73,13 @@ import { useStore } from "../store.js"

export function detectProjectVenv(projectRoot: string): { root: string; binDir: string } | null {
for (const dirName of [".venv", "venv"]) {
const root = join(projectRoot, dirName)
const binDir = join(root, "bin")
if (existsSync(join(binDir, "python"))) {
return { root, binDir }
}
}
return null
}
export function Worktree() {

@@ -117,3 +129,3 @@ const navigate = useStore((s) => s.navigate)

const doSpawn = React.useCallback(async (cwd: string) => {
const doSpawn = React.useCallback(async (cwd: string, projectRoot: string) => {
setStep("spawning")

@@ -124,2 +136,10 @@ setErrorMsg("")

const port = await findNextPort()
const projectVenv = detectProjectVenv(projectRoot)
const env = projectVenv
? {
...process.env,
PATH: `${projectVenv.binDir}:${process.env.PATH ?? ""}`,
VIRTUAL_ENV: projectVenv.root,
}
: undefined
const proc = spawnProcess("opencode", ["serve", "--port", String(port)], {

@@ -129,2 +149,3 @@ cwd,

stdio: "ignore",
env,
})

@@ -165,3 +186,3 @@ proc.unref()

if (!name.trim()) {
void doSpawn(repoDir)
void doSpawn(repoDir, repoDir)
return

@@ -171,3 +192,3 @@ }

const worktreeDir = createWorktree(repoDir, name.trim())
void doSpawn(worktreeDir)
void doSpawn(worktreeDir, repoDir)
} catch (e) {

@@ -174,0 +195,0 @@ setErrorMsg(`Failed to create worktree: ${String(e)}`)

Sorry, the diff of this file is too big to display