From 2fd1392f9c6057c873eb7feb8a84ce461b1c2955 Mon Sep 17 00:00:00 2001 From: nullkey Date: Thu, 14 May 2026 22:53:25 +0800 Subject: [PATCH 1/2] refactor(tests): shared binary build + runVideoCmd consolidation (0.6.1) Three pieces of integration-test infra hygiene that /simplify reviewers flagged across the 0.4.x / 0.5.x / 0.6.0 PRs but kept getting deferred. Bundled into a focused patch release. 1. Shared binary build via sync.Once + TestMain (tests/integration/ cli_smoke_test.go). 15+ callers of build(t) now share a single compile per `go test` run instead of each rebuilding. 2. runVideoCmd signature changed from (stdout, combined, exitCode) to (stdout, stderr, exitCode). Callers that want the combined string compute `stdout + stderr` explicitly. The 3 existing callers in video_flow_test.go and the 4 hand-rollers (create_credits, create_engine, create_mode, kb_prune) all migrated to use this single helper. Removes ~80 lines of duplicated exec.Command / env / ExitError boilerplate. 3. .gitignore covers *.pdf and *.docx so local smoke-test files (real-backend manual testing) don't sit as untracked commit-bait. test.pdf was already ignored; broader catch-all subsumes it. No behavior change for end users. Internal test refactor. --- .gitignore | 5 +- CHANGELOG.md | 22 ++++++++ package.json | 2 +- skills/vibeknow-core/SKILL.md | 2 +- skills/vibeknow-create/SKILL.md | 2 +- skills/vibeknow-doc/SKILL.md | 2 +- tests/integration/cli_smoke_test.go | 69 ++++++++++++++++++------ tests/integration/create_credits_test.go | 26 +++------ tests/integration/create_engine_test.go | 23 ++------ tests/integration/create_mode_test.go | 26 ++------- tests/integration/kb_prune_test.go | 56 ++++++------------- tests/integration/video_flow_test.go | 24 ++++++--- 12 files changed, 130 insertions(+), 129 deletions(-) diff --git a/.gitignore b/.gitignore index 8422278..da23a45 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/CHANGELOG.md b/CHANGELOG.md index b812ea1..51c2a90 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/package.json b/package.json index 81c084c..e3c6b78 100644 --- a/package.json +++ b/package.json @@ -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": { diff --git a/skills/vibeknow-core/SKILL.md b/skills/vibeknow-core/SKILL.md index 9e0ee90..8f438b1 100644 --- a/skills/vibeknow-core/SKILL.md +++ b/skills/vibeknow-core/SKILL.md @@ -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: diff --git a/skills/vibeknow-create/SKILL.md b/skills/vibeknow-create/SKILL.md index 01ca265..0682e1d 100644 --- a/skills/vibeknow-create/SKILL.md +++ b/skills/vibeknow-create/SKILL.md @@ -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: diff --git a/skills/vibeknow-doc/SKILL.md b/skills/vibeknow-doc/SKILL.md index 89a8c6b..fefd60e 100644 --- a/skills/vibeknow-doc/SKILL.md +++ b/skills/vibeknow-doc/SKILL.md @@ -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: diff --git a/tests/integration/cli_smoke_test.go b/tests/integration/cli_smoke_test.go index ac3c5ea..5e573fe 100644 --- a/tests/integration/cli_smoke_test.go +++ b/tests/integration/cli_smoke_test.go @@ -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) { diff --git a/tests/integration/create_credits_test.go b/tests/integration/create_credits_test.go index c8af797..0c5fa6d 100644 --- a/tests/integration/create_credits_test.go +++ b/tests/integration/create_credits_test.go @@ -5,7 +5,6 @@ import ( "net/http" "net/http/httptest" "os" - "os/exec" "strings" "sync" "testing" @@ -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() @@ -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/ was never called.\nstderr:%s", stderr.String()) + t.Fatalf("expected orphan kb cleanup: DELETE /v1/knowledgebases/ was never called.\nstderr:%s", stderr) } if deletedID != createdID { t.Fatalf("deleted kb = %q, want %q", deletedID, createdID) diff --git a/tests/integration/create_engine_test.go b/tests/integration/create_engine_test.go index 68e2bc9..e0b8f04 100644 --- a/tests/integration/create_engine_test.go +++ b/tests/integration/create_engine_test.go @@ -5,8 +5,6 @@ import ( "fmt" "net/http" "net/http/httptest" - "os" - "os/exec" "strings" "sync" "testing" @@ -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() @@ -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) } diff --git a/tests/integration/create_mode_test.go b/tests/integration/create_mode_test.go index 545ef63..d7688b6 100644 --- a/tests/integration/create_mode_test.go +++ b/tests/integration/create_mode_test.go @@ -1,13 +1,10 @@ package integration import ( - "bytes" "encoding/json" "fmt" "net/http" "net/http/httptest" - "os" - "os/exec" "strings" "sync" "testing" @@ -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() @@ -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) } } diff --git a/tests/integration/kb_prune_test.go b/tests/integration/kb_prune_test.go index 7e08152..150b2e7 100644 --- a/tests/integration/kb_prune_test.go +++ b/tests/integration/kb_prune_test.go @@ -4,8 +4,6 @@ import ( "encoding/json" "net/http" "net/http/httptest" - "os" - "os/exec" "strings" "sync" "testing" @@ -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 @@ -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() @@ -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) } } diff --git a/tests/integration/video_flow_test.go b/tests/integration/video_flow_test.go index 638ff66..182addf 100644 --- a/tests/integration/video_flow_test.go +++ b/tests/integration/video_flow_test.go @@ -57,15 +57,21 @@ func buildVideoProfile(t *testing.T, figlensURL string) string { return buildProfile(t, map[string]string{"figlens": figlensURL}) } -// runVideoCmd runs the binary with the given args, capturing stdout and stderr -// separately. Returns stdout, combined output, and exit code. +// runVideoCmd runs the binary with the given args, capturing stdout and +// stderr separately. Returns stdout, stderr, and exit code. Also used by +// non-video tests (kb prune, create-credits, create-mode, create-engine) +// despite the legacy name; rename to runCLI is a follow-up. +// +// Sets test-friendly env vars: VIBEKNOW_TOKEN=fake-token (most mock +// backends ignore auth), VIBEKNOW_CONFIG_HOME from the caller, and +// VIBEKNOW_EXPORT_TIMEOUT=10s so tests that don't care about export +// polling don't get stuck on the default 5min timeout. func runVideoCmd(t *testing.T, bin, configHome string, args ...string) (string, string, int) { t.Helper() cmd := exec.Command(bin, args...) cmd.Env = append(os.Environ(), "VIBEKNOW_TOKEN=fake-token", "VIBEKNOW_CONFIG_HOME="+configHome, - // Keep export timeout short in tests. "VIBEKNOW_EXPORT_TIMEOUT=10s", ) var stdoutBuf, stderrBuf strings.Builder @@ -77,8 +83,7 @@ func runVideoCmd(t *testing.T, bin, configHome string, args ...string) (string, if ee, ok := err.(*exec.ExitError); ok { code = ee.ExitCode() } - combined := stdoutBuf.String() + stderrBuf.String() - return stdoutBuf.String(), combined, code + return stdoutBuf.String(), stderrBuf.String(), code } // TestDownload_BeforeExport_Exits2 verifies that `vk video download` exits @@ -112,9 +117,10 @@ func TestDownload_BeforeExport_Exits2(t *testing.T) { bin := build(t) configHome := buildVideoProfile(t, figlens.URL) - _, combined, code := runVideoCmd(t, bin, configHome, + stdout, stderr, code := runVideoCmd(t, bin, configHome, "video", "download", "42", "--session-id", "s_integ", ) + combined := stdout + stderr if code != 2 { t.Fatalf("expected exit 2, got %d\ncombined:\n%s", code, combined) @@ -173,13 +179,14 @@ func TestExport_AsyncReturnsImmediately(t *testing.T) { bin := build(t) configHome := buildVideoProfile(t, figlens.URL) - stdout, combined, code := runVideoCmd(t, bin, configHome, + stdout, stderr, code := runVideoCmd(t, bin, configHome, "video", "export", "42", "--session-id", "s_integ", "--async", "--yes", "--output", "json", ) + combined := stdout + stderr if code != 0 { t.Fatalf("expected exit 0, got %d\ncombined:\n%s", code, combined) @@ -267,13 +274,14 @@ func TestExport_NDJSON(t *testing.T) { bin := build(t) configHome := buildVideoProfile(t, figlens.URL) - stdout, combined, code := runVideoCmd(t, bin, configHome, + stdout, stderr, code := runVideoCmd(t, bin, configHome, "video", "export", "42", "--session-id", "s_integ", "--yes", "--output", "ndjson", "--poll-interval", "1ms", ) + combined := stdout + stderr if code != 0 { t.Fatalf("expected exit 0, got %d\ncombined:\n%s", code, combined) From a80abdee637a4e653c9e9d128c60113acd7c7a87 Mon Sep 17 00:00:00 2001 From: nullkey Date: Thu, 14 May 2026 23:12:49 +0800 Subject: [PATCH 2/2] fix(create): accept vectoria UUID as doc_id (0.6.2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The docIDRe only matched a hypothetical `doc_<8+ alnum>` form, but vectoria actually returns UUIDs (`xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`, lowercase hex) — which is what `vk create` prints back to the user on every run as `doc_id: `. Copying that line into `--from` for a follow-up invocation got treated as a local file path and failed with `stat: no such file or directory`. Real-backend smoke verified: passing a prior run's UUID now resolves to `using doc_id: `, skips upload, and creates the figlens task. Tests cover legacy `doc_` (backward compat), the real UUID form, file paths, URLs, and malformed UUIDs. --- CHANGELOG.md | 13 ++++++++++++ cmd/create.go | 11 +++++++++- cmd/create_test.go | 36 +++++++++++++++++++++++++++++++++ package.json | 2 +- skills/vibeknow-core/SKILL.md | 2 +- skills/vibeknow-create/SKILL.md | 2 +- skills/vibeknow-doc/SKILL.md | 2 +- 7 files changed, 63 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 51c2a90..d450d7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## 0.6.2 — 2026-05-14 + +### Fixed + +- `vk create --from ` now recognizes the UUID form + vectoria actually returns (`xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`, + lowercase hex) as a doc-id and skips the upload step. Previously + the `docIDRe` only accepted a hypothetical `doc_` form that + vectoria has never used, so re-passing the `doc_id:` line printed + by a prior `vk create` run got treated as a local file path and + failed with "stat: no such file or directory". Backward compatible: + the legacy `doc_` form still matches. + ## 0.6.1 — 2026-05-14 ### Changed diff --git a/cmd/create.go b/cmd/create.go index 0ea6723..abb41f1 100644 --- a/cmd/create.go +++ b/cmd/create.go @@ -37,7 +37,16 @@ var ( flagCreateEngine string ) -var docIDRe = regexp.MustCompile(`^doc_[a-zA-Z0-9]{8,}$`) +// docIDRe matches a vectoria document identifier supplied via `--from`. +// Two forms are accepted: +// - Legacy CLI-coined form `doc_<8+ alnum>` (used by older callers). +// - Vectoria's native UUID form `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` +// (lowercase hex, what `vk create` itself prints on every run). +// +// Without the UUID branch, a user copying the `doc_id:` line from a prior +// successful run and re-passing it to `--from` would have the CLI treat +// it as a local file path, ending in "stat: no such file or directory". +var docIDRe = regexp.MustCompile(`^(doc_[a-zA-Z0-9]{8,}|[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$`) var createCmd = &cobra.Command{ Use: "create", diff --git a/cmd/create_test.go b/cmd/create_test.go index 7ed491c..9a27f2e 100644 --- a/cmd/create_test.go +++ b/cmd/create_test.go @@ -134,3 +134,39 @@ func TestEnginePipelineAllowsAllModes(t *testing.T) { } } +func TestDocIDRegex(t *testing.T) { + tests := []struct { + in string + match bool + desc string + }{ + // Legacy form (kept for backward compat). + {"doc_abc12345", true, "legacy doc_"}, + {"doc_a1b2c3d4e5f6", true, "legacy long"}, + {"doc_short", false, "legacy too-short suffix"}, + + // Vectoria native UUID form (what `vk create` actually prints). + {"3c498964-2c54-4ac0-97e8-1125d3bed640", true, "real vectoria UUID"}, + {"00000000-0000-0000-0000-000000000000", true, "all-zero UUID"}, + + // Negative: file paths and URLs must not match. + {"./my-file.docx", false, "relative path"}, + {"/abs/path.pdf", false, "absolute path"}, + {"my-file.docx", false, "filename with extension"}, + {"https://example.com/x", false, "URL"}, + + // Negative: malformed UUIDs. + {"3c498964-2c54-4ac0-97e8", false, "truncated UUID"}, + {"3C498964-2C54-4AC0-97E8-1125D3BED640", false, "uppercase (vectoria emits lowercase)"}, + {"3c498964_2c54_4ac0_97e8_1125d3bed640", false, "underscores not hyphens"}, + {"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", false, "non-hex chars"}, + } + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + got := docIDRe.MatchString(tt.in) + if got != tt.match { + t.Fatalf("docIDRe.MatchString(%q) = %v, want %v", tt.in, got, tt.match) + } + }) + } +} diff --git a/package.json b/package.json index e3c6b78..a1271c0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vibeknow-cli", - "version": "0.6.1", + "version": "0.6.2", "description": "VibeKnow CLI — turn docs / URLs into videos", "license": "MIT", "bin": { diff --git a/skills/vibeknow-core/SKILL.md b/skills/vibeknow-core/SKILL.md index 8f438b1..ee2fa33 100644 --- a/skills/vibeknow-core/SKILL.md +++ b/skills/vibeknow-core/SKILL.md @@ -1,6 +1,6 @@ --- name: vibeknow-core -version: 0.6.1 +version: 0.6.2 description: "vibeknow CLI setup, authentication, profile management, and diagnostics. Use when: first-time setup, auth errors, switching environments, diagnosing connection issues." metadata: requires: diff --git a/skills/vibeknow-create/SKILL.md b/skills/vibeknow-create/SKILL.md index 0682e1d..b456cc9 100644 --- a/skills/vibeknow-create/SKILL.md +++ b/skills/vibeknow-create/SKILL.md @@ -1,6 +1,6 @@ --- name: vibeknow-create -version: 0.6.1 +version: 0.6.2 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: diff --git a/skills/vibeknow-doc/SKILL.md b/skills/vibeknow-doc/SKILL.md index fefd60e..195eaf9 100644 --- a/skills/vibeknow-doc/SKILL.md +++ b/skills/vibeknow-doc/SKILL.md @@ -1,6 +1,6 @@ --- name: vibeknow-doc -version: 0.6.1 +version: 0.6.2 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: