Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,8 @@
.worktrees/
hero-and-task-detail-flow.md
bin/
test.pdf
docs/*.mp4
# Local sample docs / PDFs used during real-backend smoke testing.
# Catches the long-standing test.pdf as well.
*.pdf
*.docx
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,27 @@
# Changelog

## 0.6.1 — 2026-05-14

### Changed

- Integration tests now build the `vibeknow` binary **once per `go
test ./tests/integration/...` run** (via `sync.Once` + `TestMain`)
instead of rebuilding inside each of the 15+ test functions that
called `build(t)`. Removes redundant compile work on cold-cache CI.
- `runVideoCmd` helper signature changed from `(stdout, combined,
exitCode)` to `(stdout, stderr, exitCode)`. Callers that want a
combined view compute `stdout + stderr` explicitly. All hand-rolled
`exec.Command(bin, ...)` blocks in the integration tests
(`create_credits`, `create_engine`, `create_mode`, `kb_prune`)
migrated to use this helper, removing ~80 lines of duplicated
env-setup / ExitError-unwrap boilerplate.

### Fixed (housekeeping)

- `.gitignore` now covers `*.pdf` and `*.docx` (with `!test.pdf`
preserved) so local smoke-test files don't appear as untracked
candidates for accidental commit.

## 0.6.0 — 2026-05-14

### New
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "vibeknow-cli",
"version": "0.6.0",
"version": "0.6.1",
"description": "VibeKnow CLI — turn docs / URLs into videos",
"license": "MIT",
"bin": {
Expand Down
2 changes: 1 addition & 1 deletion skills/vibeknow-core/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: vibeknow-core
version: 0.6.0
version: 0.6.1
description: "vibeknow CLI setup, authentication, profile management, and diagnostics. Use when: first-time setup, auth errors, switching environments, diagnosing connection issues."
metadata:
requires:
Expand Down
2 changes: 1 addition & 1 deletion skills/vibeknow-create/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: vibeknow-create
version: 0.6.0
version: 0.6.1
description: "Generate videos from documents/URLs/files, track video task progress, download results, list voice templates. Use when: user wants to create a video, check task status, download video, or browse voices."
metadata:
requires:
Expand Down
2 changes: 1 addition & 1 deletion skills/vibeknow-doc/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: vibeknow-doc
version: 0.6.0
version: 0.6.1
description: "Upload documents to vectoria and check processing status. Use when: user wants to upload a document, check if a document is ready, or get a doc_id for use with vibeknow create."
metadata:
requires:
Expand Down
69 changes: 52 additions & 17 deletions tests/integration/cli_smoke_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,65 @@ import (
"path/filepath"
"runtime"
"strings"
"sync"
"testing"
)

// Shared binary across all integration tests in this package. Built once
// per `go test` run via sync.Once, reused by every build(t) caller.
// Before this caching, each test function paid a fresh ~30s go-build
// cost; with 15+ build(t) callers across the package, that was ~450s
// of redundant compile per CI run.
var (
sharedBin string
sharedBinErr error
sharedBinDir string
sharedBinOnce sync.Once
)

func build(t *testing.T) string {
t.Helper()
dir := t.TempDir()
name := "vibeknow"
if runtime.GOOS == "windows" {
// Windows requires the .exe suffix for exec.Command to locate and
// run the binary. Without it, `go build -o` still writes the file
// but subsequent exec.Command(bin, ...) fails with "executable
// file not found in %PATH%".
name += ".exe"
sharedBinOnce.Do(func() {
dir, err := os.MkdirTemp("", "vibeknow-integ-bin-")
if err != nil {
sharedBinErr = err
return
}
sharedBinDir = dir
name := "vibeknow"
if runtime.GOOS == "windows" {
// Windows requires the .exe suffix for exec.Command to locate
// and run the binary. Without it, `go build -o` still writes
// the file but exec.Command(bin, ...) fails with "executable
// file not found in %PATH%".
name += ".exe"
}
bin := filepath.Join(dir, name)
root, _ := filepath.Abs("../..")
cmd := exec.Command("go", "build", "-o", bin, ".")
cmd.Dir = root
cmd.Stderr = os.Stderr
cmd.Stdout = os.Stdout
if err := cmd.Run(); err != nil {
sharedBinErr = err
return
}
sharedBin = bin
})
if sharedBinErr != nil {
t.Fatalf("shared build: %v", sharedBinErr)
}
bin := filepath.Join(dir, name)
root, _ := filepath.Abs("../..")
cmd := exec.Command("go", "build", "-o", bin, ".")
cmd.Dir = root
cmd.Stderr = os.Stderr
cmd.Stdout = os.Stdout
if err := cmd.Run(); err != nil {
t.Fatalf("build: %v", err)
return sharedBin
}

// TestMain runs after all tests in the package finish and cleans up the
// shared binary dir. Without this the tmp dir leaks across `go test` runs.
func TestMain(m *testing.M) {
code := m.Run()
if sharedBinDir != "" {
_ = os.RemoveAll(sharedBinDir)
}
return bin
os.Exit(code)
}

func run(t *testing.T, bin, home string, args ...string) (string, string, int) {
Expand Down
26 changes: 6 additions & 20 deletions tests/integration/create_credits_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"net/http"
"net/http/httptest"
"os"
"os/exec"
"strings"
"sync"
"testing"
Expand Down Expand Up @@ -85,28 +84,15 @@ func TestCreate_InsufficientCreditsOnInit_Exits5(t *testing.T) {
"vectoria": srv.URL,
})

cmd := exec.Command(bin, "create", "--from", tmpFile)
var stdout, stderr strings.Builder
cmd.Stdout = &stdout
cmd.Stderr = &stderr
cmd.Env = append(os.Environ(),
"VIBEKNOW_TOKEN=fake-token",
"VIBEKNOW_CONFIG_HOME="+configHome,
_, stderr, code := runVideoCmd(t, bin, configHome,
"create", "--from", tmpFile,
)

err := cmd.Run()
code := 0
if ee, ok := err.(*exec.ExitError); ok {
code = ee.ExitCode()
} else if err != nil {
t.Fatalf("run: %v\nstderr: %s", err, stderr.String())
}

if code != 5 {
t.Fatalf("exit code = %d, want 5 (business failure)\nstderr: %s", code, stderr.String())
t.Fatalf("exit code = %d, want 5 (business failure)\nstderr: %s", code, stderr)
}
if !strings.Contains(stderr.String(), "insufficient credits") {
t.Fatalf("stderr missing insufficient-credits message:\n%s", stderr.String())
if !strings.Contains(stderr, "insufficient credits") {
t.Fatalf("stderr missing insufficient-credits message:\n%s", stderr)
}

mu.Lock()
Expand All @@ -115,7 +101,7 @@ func TestCreate_InsufficientCreditsOnInit_Exits5(t *testing.T) {
t.Fatalf("expected kb to be created by CLI but no POST /v1/knowledgebases received")
}
if !kbDeleted {
t.Fatalf("expected orphan kb cleanup: DELETE /v1/knowledgebases/<id> was never called.\nstderr:%s", stderr.String())
t.Fatalf("expected orphan kb cleanup: DELETE /v1/knowledgebases/<id> was never called.\nstderr:%s", stderr)
}
if deletedID != createdID {
t.Fatalf("deleted kb = %q, want %q", deletedID, createdID)
Expand Down
23 changes: 4 additions & 19 deletions tests/integration/create_engine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ import (
"fmt"
"net/http"
"net/http/httptest"
"os"
"os/exec"
"strings"
"sync"
"testing"
Expand Down Expand Up @@ -81,25 +79,12 @@ func TestCreate_EngineAgent_WiresV2AndSurfacesProgress(t *testing.T) {
bin := build(t)
configHome := buildVideoProfile(t, srv.URL)

cmd := exec.Command(bin, "create", "--engine", "agent", "--from", "doc_abc12345")
var stdout, stderr strings.Builder
cmd.Stdout = &stdout
cmd.Stderr = &stderr
cmd.Env = append(os.Environ(),
"VIBEKNOW_TOKEN=fake-token",
"VIBEKNOW_CONFIG_HOME="+configHome,
stdout, stderr, code := runVideoCmd(t, bin, configHome,
"create", "--engine", "agent", "--from", "doc_abc12345",
)

err := cmd.Run()
code := 0
if ee, ok := err.(*exec.ExitError); ok {
code = ee.ExitCode()
} else if err != nil {
t.Fatalf("run: %v\nstderr: %s", err, stderr.String())
}

if code != 0 {
t.Fatalf("create exit %d\nstdout:%s\nstderr:%s", code, stdout.String(), stderr.String())
t.Fatalf("create exit %d\nstdout:%s\nstderr:%s", code, stdout, stderr)
}

mu.Lock()
Expand Down Expand Up @@ -129,7 +114,7 @@ func TestCreate_EngineAgent_WiresV2AndSurfacesProgress(t *testing.T) {
}

// 3. Progress visibility: stderr contains [agent] prefixed lines.
out := stdout.String() + stderr.String()
out := stdout + stderr
if !strings.Contains(out, "[agent] 正在调用知识库") {
t.Fatalf("missing [agent] progress prefix for first message:\n%s", out)
}
Expand Down
26 changes: 5 additions & 21 deletions tests/integration/create_mode_test.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
package integration

import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"os"
"os/exec"
"strings"
"sync"
"testing"
Expand Down Expand Up @@ -98,25 +95,12 @@ func TestCreate_ModeReplica_WiresVideoKind(t *testing.T) {
bin := build(t)
configHome := buildVideoProfile(t, figlens.URL)

cmd := exec.Command(bin, "create", "--mode", "replica", "--from", "doc_abc12345")
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
cmd.Env = append(os.Environ(),
"VIBEKNOW_TOKEN=fake-token",
"VIBEKNOW_CONFIG_HOME="+configHome,
stdout, stderr, code := runVideoCmd(t, bin, configHome,
"create", "--mode", "replica", "--from", "doc_abc12345",
)

err := cmd.Run()
code := 0
if ee, ok := err.(*exec.ExitError); ok {
code = ee.ExitCode()
} else if err != nil {
t.Fatalf("run: %v\nstderr: %s", err, stderr.String())
}

if code != 0 {
t.Fatalf("create exit %d\nstdout: %s\nstderr: %s", code, stdout.String(), stderr.String())
t.Fatalf("create exit %d\nstdout: %s\nstderr: %s", code, stdout, stderr)
}

mu.Lock()
Expand All @@ -134,8 +118,8 @@ func TestCreate_ModeReplica_WiresVideoKind(t *testing.T) {
if bodies["stream"]["video_kind"] != "replica" {
t.Fatalf("stream body video_kind = %v, want \"replica\"", bodies["stream"]["video_kind"])
}
out := stdout.String() + stderr.String()
out := stdout + stderr
if !strings.Contains(out, "doc_replica_plan") {
t.Fatalf("expected doc_replica_plan in output (proves stage map), got:\nstdout: %s\nstderr: %s", stdout.String(), stderr.String())
t.Fatalf("expected doc_replica_plan in output (proves stage map), got:\nstdout: %s\nstderr: %s", stdout, stderr)
}
}
56 changes: 17 additions & 39 deletions tests/integration/kb_prune_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import (
"encoding/json"
"net/http"
"net/http/httptest"
"os"
"os/exec"
"strings"
"sync"
"testing"
Expand Down Expand Up @@ -45,23 +43,18 @@ func TestKBPrune_DryRunDoesNotDelete(t *testing.T) {
bin := build(t)
configHome := buildProfile(t, map[string]string{"vectoria": srv.URL})

cmd := exec.Command(bin, "kb", "prune", "--pattern", "vibeknow-cli-*")
var stdout, stderr strings.Builder
cmd.Stdout = &stdout
cmd.Stderr = &stderr
cmd.Env = append(os.Environ(),
"VIBEKNOW_TOKEN=fake-token",
"VIBEKNOW_CONFIG_HOME="+configHome,
stdout, stderr, code := runVideoCmd(t, bin, configHome,
"kb", "prune", "--pattern", "vibeknow-cli-*",
)
if err := cmd.Run(); err != nil {
t.Fatalf("dry-run unexpected exit: %v\nstdout:%s\nstderr:%s", err, stdout.String(), stderr.String())
if code != 0 {
t.Fatalf("dry-run unexpected exit %d\nstdout:%s\nstderr:%s", code, stdout, stderr)
}
combined := stdout.String() + stderr.String()
combined := stdout + stderr
if !strings.Contains(combined, "vibeknow-cli-1") {
t.Fatalf("dry-run output should list matched kb:\nstdout:%s\nstderr:%s", stdout.String(), stderr.String())
t.Fatalf("dry-run output should list matched kb:\nstdout:%s\nstderr:%s", stdout, stderr)
}
if !strings.Contains(combined, "dry run") {
t.Fatalf("dry-run output should mention 'dry run' hint:\nstdout:%s\nstderr:%s", stdout.String(), stderr.String())
t.Fatalf("dry-run output should mention 'dry run' hint:\nstdout:%s\nstderr:%s", stdout, stderr)
}
mu.Lock()
dc := deleteCalls
Expand Down Expand Up @@ -106,16 +99,11 @@ func TestKBPrune_ApplyDeletesMatchedOnly(t *testing.T) {
bin := build(t)
configHome := buildProfile(t, map[string]string{"vectoria": srv.URL})

cmd := exec.Command(bin, "kb", "prune", "--pattern", "vibeknow-cli-*", "--yes")
var stdout, stderr strings.Builder
cmd.Stdout = &stdout
cmd.Stderr = &stderr
cmd.Env = append(os.Environ(),
"VIBEKNOW_TOKEN=fake-token",
"VIBEKNOW_CONFIG_HOME="+configHome,
_, stderr, code := runVideoCmd(t, bin, configHome,
"kb", "prune", "--pattern", "vibeknow-cli-*", "--yes",
)
if err := cmd.Run(); err != nil {
t.Fatalf("apply unexpected exit: %v\nstderr:%s", err, stderr.String())
if code != 0 {
t.Fatalf("apply unexpected exit %d\nstderr:%s", code, stderr)
}

mu.Lock()
Expand All @@ -142,23 +130,13 @@ func TestKBPrune_NoFilterExits2(t *testing.T) {
}
bin := build(t)
configHome := buildProfile(t, map[string]string{"vectoria": "http://example.invalid"})
cmd := exec.Command(bin, "kb", "prune", "--yes")
var stdout, stderr strings.Builder
cmd.Stdout = &stdout
cmd.Stderr = &stderr
cmd.Env = append(os.Environ(),
"VIBEKNOW_TOKEN=fake-token",
"VIBEKNOW_CONFIG_HOME="+configHome,
_, stderr, code := runVideoCmd(t, bin, configHome,
"kb", "prune", "--yes",
)
err := cmd.Run()
ee, ok := err.(*exec.ExitError)
if !ok {
t.Fatalf("expected exec.ExitError, got err=%v stderr=%s", err, stderr.String())
if code != 2 {
t.Fatalf("expected exit 2, got %d\nstderr:%s", code, stderr)
}
if ee.ExitCode() != 2 {
t.Fatalf("expected exit 2, got %d\nstderr:%s", ee.ExitCode(), stderr.String())
}
if !strings.Contains(stderr.String(), "--pattern") || !strings.Contains(stderr.String(), "--older-than") {
t.Fatalf("error message should mention --pattern AND --older-than, got: %s", stderr.String())
if !strings.Contains(stderr, "--pattern") || !strings.Contains(stderr, "--older-than") {
t.Fatalf("error message should mention --pattern AND --older-than, got: %s", stderr)
}
}
Loading
Loading