badgr-cli 1.0.37 → 1.0.39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/package.json +5 -7
- package/src/badgr.js +23 -1
- package/src/catalog.js +479 -0
- package/src/commands/comfyui.js +1 -1
- package/src/commands/receipts.js +2 -1
- package/src/commands/run.js +134 -25
- package/src/commands/serve.js +100 -10
- package/src/commands/template.js +119 -0
- package/src/commands/test-run.js +4 -4
- package/src/commands/workload.js +197 -0
- package/src/commands/workspace.js +136 -0
- package/tests/commands.test.js +48 -0
- package/tests/run-lifecycle.test.js +55 -18
- package/tests/serve-lifecycle.test.js +165 -0
- package/tests/template.test.js +551 -0
- package/tests/workload-rerun.test.js +56 -0
- package/tests/workload-templates.test.js +1 -1
- package/tests/workload-workspace-paths.test.js +46 -0
|
@@ -498,3 +498,168 @@ describe('input validation for serve', () => {
|
|
|
498
498
|
expect(process.exitCode).toBe(1);
|
|
499
499
|
});
|
|
500
500
|
});
|
|
501
|
+
|
|
502
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
503
|
+
// 11. llama.cpp runtime (--runtime llama.cpp --gguf)
|
|
504
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
505
|
+
|
|
506
|
+
describe('--runtime llama.cpp', () => {
|
|
507
|
+
const HF_REPO = 'HauhauCS/Qwen3.6-35B-A3B-Uncensored-HauhauCS-Aggressive';
|
|
508
|
+
const HF_FILE = 'Qwen3.6-35B-A3B-Uncensored-HauhauCS-Aggressive-Q4_K_M.gguf';
|
|
509
|
+
|
|
510
|
+
it('errors when --hf-repo or --hf-file are missing', async () => {
|
|
511
|
+
await serveCommand(config, ['--runtime', 'llama.cpp', '--max-cost', '10'], chalk);
|
|
512
|
+
expect(process.exitCode).toBe(1);
|
|
513
|
+
expect(api.callApi).not.toHaveBeenCalled();
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
it('errors when only --hf-repo is given (missing --hf-file)', async () => {
|
|
517
|
+
await serveCommand(config, ['--runtime', 'llama.cpp', '--hf-repo', HF_REPO, '--max-cost', '10'], chalk);
|
|
518
|
+
expect(process.exitCode).toBe(1);
|
|
519
|
+
expect(api.callApi).not.toHaveBeenCalled();
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
it('uses llama.cpp image with LLAMA_ARG_HF_REPO and LLAMA_ARG_HF_FILE env vars', async () => {
|
|
523
|
+
// --no-wait: only POST /serve called (1 mock needed)
|
|
524
|
+
api.callApi.mockResolvedValueOnce(makeServeDep());
|
|
525
|
+
global.fetch = vi.fn().mockResolvedValue({ ok: true, status: 200 });
|
|
526
|
+
|
|
527
|
+
const p = serveCommand(
|
|
528
|
+
config,
|
|
529
|
+
['--runtime', 'llama.cpp', '--hf-repo', HF_REPO, '--hf-file', HF_FILE, '--max-cost', '10', '--no-wait'],
|
|
530
|
+
chalk,
|
|
531
|
+
);
|
|
532
|
+
await vi.advanceTimersByTimeAsync(100);
|
|
533
|
+
await p;
|
|
534
|
+
|
|
535
|
+
const body = api.callApi.mock.calls[0][1].body;
|
|
536
|
+
expect(body.image).toBe('michaelmanleyx/llama-cpp:server-cuda');
|
|
537
|
+
expect(body.env.LLAMA_ARG_HF_REPO).toBe(HF_REPO);
|
|
538
|
+
expect(body.env.LLAMA_ARG_HF_FILE).toBe(HF_FILE);
|
|
539
|
+
expect(body.model).toBeUndefined();
|
|
540
|
+
expect(process.exitCode).toBeFalsy();
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
it('health-checks /health (not /models)', async () => {
|
|
544
|
+
api.callApi
|
|
545
|
+
.mockResolvedValueOnce(makeServeDep())
|
|
546
|
+
.mockResolvedValueOnce({ status: 'running' });
|
|
547
|
+
|
|
548
|
+
const fetchedUrls = [];
|
|
549
|
+
global.fetch = vi.fn().mockImplementation((url) => {
|
|
550
|
+
fetchedUrls.push(url);
|
|
551
|
+
return Promise.resolve({ ok: true, status: 200 });
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
const p = serveCommand(
|
|
555
|
+
config,
|
|
556
|
+
['--runtime', 'llama.cpp', '--hf-repo', HF_REPO, '--hf-file', HF_FILE, '--max-cost', '10'],
|
|
557
|
+
chalk,
|
|
558
|
+
);
|
|
559
|
+
await vi.advanceTimersByTimeAsync(5000);
|
|
560
|
+
await p;
|
|
561
|
+
|
|
562
|
+
expect(fetchedUrls.some(u => u.includes('/health'))).toBe(true);
|
|
563
|
+
expect(fetchedUrls.some(u => u.endsWith('/models'))).toBe(false);
|
|
564
|
+
expect(process.exitCode).toBeFalsy();
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
it('records deployment and receipt as ready on success', async () => {
|
|
568
|
+
api.callApi
|
|
569
|
+
.mockResolvedValueOnce(makeServeDep())
|
|
570
|
+
.mockResolvedValueOnce({ status: 'running' });
|
|
571
|
+
|
|
572
|
+
global.fetch = vi.fn().mockResolvedValue({ ok: true, status: 200 });
|
|
573
|
+
|
|
574
|
+
const p = serveCommand(
|
|
575
|
+
config,
|
|
576
|
+
['--runtime', 'llama.cpp', '--hf-repo', HF_REPO, '--hf-file', HF_FILE, '--max-cost', '10'],
|
|
577
|
+
chalk,
|
|
578
|
+
);
|
|
579
|
+
await vi.advanceTimersByTimeAsync(5000);
|
|
580
|
+
await p;
|
|
581
|
+
|
|
582
|
+
expect(store.addDeployment).toHaveBeenCalledWith(expect.objectContaining({
|
|
583
|
+
id: 'dep-serve-001',
|
|
584
|
+
}));
|
|
585
|
+
expect(store.updateReceipt).toHaveBeenCalledWith('rcpt-serve-001', expect.objectContaining({
|
|
586
|
+
status: 'ready',
|
|
587
|
+
}));
|
|
588
|
+
expect(process.exitCode).toBeFalsy();
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
it('merges user --env with HF env vars', async () => {
|
|
592
|
+
// --no-wait: only POST /serve called (1 mock needed)
|
|
593
|
+
api.callApi.mockResolvedValueOnce(makeServeDep());
|
|
594
|
+
global.fetch = vi.fn().mockResolvedValue({ ok: true, status: 200 });
|
|
595
|
+
|
|
596
|
+
const p = serveCommand(
|
|
597
|
+
config,
|
|
598
|
+
['--runtime', 'llama.cpp', '--hf-repo', HF_REPO, '--hf-file', HF_FILE,
|
|
599
|
+
'--env', 'CTX_SIZE=8192', '--max-cost', '10', '--no-wait'],
|
|
600
|
+
chalk,
|
|
601
|
+
);
|
|
602
|
+
await vi.advanceTimersByTimeAsync(100);
|
|
603
|
+
await p;
|
|
604
|
+
|
|
605
|
+
const body = api.callApi.mock.calls[0][1].body;
|
|
606
|
+
expect(body.env.LLAMA_ARG_HF_REPO).toBe(HF_REPO);
|
|
607
|
+
expect(body.env.LLAMA_ARG_HF_FILE).toBe(HF_FILE);
|
|
608
|
+
expect(body.env.CTX_SIZE).toBe('8192');
|
|
609
|
+
});
|
|
610
|
+
});
|
|
611
|
+
|
|
612
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
613
|
+
// 12. E2E smoke test — tiny HF GGUF (ggml-org/tiny-llamas / stories260K.gguf)
|
|
614
|
+
//
|
|
615
|
+
// Uses a real, publicly-available tiny GGUF (~1 MB) to verify the full
|
|
616
|
+
// request lifecycle: arg parsing → correct serve body → /health polling →
|
|
617
|
+
// receipt recorded as ready → OpenAI-compatible base URL returned.
|
|
618
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
619
|
+
|
|
620
|
+
describe('llama.cpp E2E smoke — tiny GGUF (ggml-org/tiny-llamas)', () => {
|
|
621
|
+
const TINY_REPO = 'ggml-org/tiny-llamas';
|
|
622
|
+
const TINY_FILE = 'stories260K.gguf';
|
|
623
|
+
|
|
624
|
+
it('full lifecycle: arg parse → serve body → /health → ready receipt', async () => {
|
|
625
|
+
api.callApi
|
|
626
|
+
.mockResolvedValueOnce(makeServeDep({ model: undefined })) // POST /serve
|
|
627
|
+
.mockResolvedValueOnce({ status: 'running' }); // pre-health dep check
|
|
628
|
+
|
|
629
|
+
const fetchedUrls = [];
|
|
630
|
+
global.fetch = vi.fn().mockImplementation((url) => {
|
|
631
|
+
fetchedUrls.push(url);
|
|
632
|
+
return Promise.resolve({ ok: true, status: 200 });
|
|
633
|
+
});
|
|
634
|
+
|
|
635
|
+
const p = serveCommand(
|
|
636
|
+
config,
|
|
637
|
+
['--runtime', 'llama.cpp',
|
|
638
|
+
'--hf-repo', TINY_REPO,
|
|
639
|
+
'--hf-file', TINY_FILE,
|
|
640
|
+
'--max-cost', '1'],
|
|
641
|
+
chalk,
|
|
642
|
+
);
|
|
643
|
+
await vi.advanceTimersByTimeAsync(5000);
|
|
644
|
+
await p;
|
|
645
|
+
|
|
646
|
+
// Correct image and env vars in the serve request
|
|
647
|
+
const body = api.callApi.mock.calls[0][1].body;
|
|
648
|
+
expect(body.image).toBe('michaelmanleyx/llama-cpp:server-cuda');
|
|
649
|
+
expect(body.env.LLAMA_ARG_HF_REPO).toBe(TINY_REPO);
|
|
650
|
+
expect(body.env.LLAMA_ARG_HF_FILE).toBe(TINY_FILE);
|
|
651
|
+
expect(body.model).toBeUndefined();
|
|
652
|
+
|
|
653
|
+
// Readiness check hits /health, not /models
|
|
654
|
+
expect(fetchedUrls.some(u => u.includes('/health'))).toBe(true);
|
|
655
|
+
expect(fetchedUrls.some(u => u.endsWith('/models'))).toBe(false);
|
|
656
|
+
|
|
657
|
+
// Receipt finalised as ready with endpoint URL
|
|
658
|
+
expect(store.updateReceipt).toHaveBeenCalledWith('rcpt-serve-001', expect.objectContaining({
|
|
659
|
+
status: 'ready',
|
|
660
|
+
endpointUrl: ENDPOINT_URL,
|
|
661
|
+
}));
|
|
662
|
+
|
|
663
|
+
expect(process.exitCode).toBeFalsy();
|
|
664
|
+
});
|
|
665
|
+
});
|