-
Notifications
You must be signed in to change notification settings - Fork 127
Expand file tree
/
Copy pathMakefile
More file actions
1325 lines (1261 loc) · 66.7 KB
/
Copy pathMakefile
File metadata and controls
1325 lines (1261 loc) · 66.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# Makefile for Atomic Chat Electron App - Build, Lint, Test, and Clean
REPORT_PORTAL_URL ?= ""
REPORT_PORTAL_API_KEY ?= ""
REPORT_PORTAL_PROJECT_NAME ?= ""
REPORT_PORTAL_LAUNCH_NAME ?= "Atomic Chat App"
REPORT_PORTAL_DESCRIPTION ?= "Atomic Chat App report"
# Default target, does nothing
all:
@echo "Specify a target to run"
# Installs yarn dependencies and builds core and extensions
install-and-build:
ifeq ($(OS),Windows_NT)
echo "skip"
else ifeq ($(shell uname -s),Linux)
chmod +x src-tauri/build-utils/*
endif
yarn install
yarn build:tauri:plugin:api
yarn build:core
yarn build:extensions
# Install required Rust targets for macOS universal builds
install-rust-targets:
ifeq ($(shell uname -s),Darwin)
@echo "Detected macOS, installing universal build targets..."
rustup target add x86_64-apple-darwin
rustup target add aarch64-apple-darwin
@echo "Rust targets installed successfully!"
else
@echo "Not macOS; skipping Rust target installation."
endif
# Install required Rust targets for Android builds
install-android-rust-targets:
@echo "Checking and installing Android Rust targets..."
@rustup target list --installed | grep -q "aarch64-linux-android" || rustup target add aarch64-linux-android
@rustup target list --installed | grep -q "armv7-linux-androideabi" || rustup target add armv7-linux-androideabi
@rustup target list --installed | grep -q "i686-linux-android" || rustup target add i686-linux-android
@rustup target list --installed | grep -q "x86_64-linux-android" || rustup target add x86_64-linux-android
@echo "Android Rust targets ready!"
# Install required Rust targets for iOS builds
install-ios-rust-targets:
@echo "Checking and installing iOS Rust targets..."
@rustup target list --installed | grep -q "aarch64-apple-ios" || rustup target add aarch64-apple-ios
@rustup target list --installed | grep -q "aarch64-apple-ios-sim" || rustup target add aarch64-apple-ios-sim
@rustup target list --installed | grep -q "x86_64-apple-ios" || rustup target add x86_64-apple-ios
@echo "iOS Rust targets ready!"
dev: install-and-build
yarn download:bin
make download-llamacpp-backend
make download-llamacpp-upstream-backend
make build-mlx-server
make build-foundation-models-server-if-exists
make build-cli-dev
yarn dev
# Same as `dev`, but skips (re)installing backends if they are already present.
# Uses the `-if-exists` targets for llamacpp / mlx-server / foundation-models-server.
dev-fast: install-and-build
yarn download:bin
make download-llamacpp-backend-if-exists
make download-llamacpp-upstream-backend-if-exists
make build-mlx-server-if-exists
make build-foundation-models-server-if-exists
make build-cli-dev
yarn dev
# Dev-режим с форсированным SetupScreen (онбординг) без удаления моделей.
# Флаг FORCE_ONBOARDING прокидывается в vite как compile-time константа.
dev-onboarding: install-and-build
yarn download:bin
make download-llamacpp-backend
make download-llamacpp-upstream-backend
make build-mlx-server
make build-foundation-models-server-if-exists
make build-cli-dev
FORCE_ONBOARDING=true yarn dev
# ──────────────────────────────────────────────────────────────
# Windows Development
# ──────────────────────────────────────────────────────────────
# One-time setup: installs Rust, nvm-windows, Node.js 20, Python, jq, Yarn
setup-windows:
ifeq ($(OS),Windows_NT)
powershell -ExecutionPolicy Bypass -File scripts/setup-windows.ps1
else
@echo "This target is for Windows only. Use 'make dev' instead."
endif
# Full dev workflow for Windows (mirrors CI pipeline)
dev-windows:
ifeq ($(OS),Windows_NT)
powershell -ExecutionPolicy Bypass -File scripts/dev-windows.ps1
else
@echo "This target is for Windows only. Use 'make dev' instead."
endif
# Same as `dev-windows`, but reuses the llama.cpp backend already downloaded
# under src-tauri/resources/llamacpp-backend-upstream (analogue of `dev-fast`
# for macOS). Skips the GitHub release fetch — fast iteration on the currently
# installed backend without re-downloading.
dev-windows-fast:
ifeq ($(OS),Windows_NT)
powershell -ExecutionPolicy Bypass -File scripts/dev-windows.ps1 -SkipBackendDownload
else
@echo "This target is for Windows only. Use 'make dev-fast' instead."
endif
# Dev workflow with CPU-only backend to test runtime GPU auto-download.
# Clears downloaded backends from the Atomic Chat data folder
# (data\llamacpp-upstream\backends), starts with the upstream `win-cpu-x64`
# build, then the llamacpp-upstream extension detects the GPU and downloads
# the optimal backend (CUDA 12.4 / 13.1 / Vulkan) in the background — and
# shows the UI popup.
dev-windows-cpu:
ifeq ($(OS),Windows_NT)
powershell -ExecutionPolicy Bypass -Command "\
Get-Process llama-server -ErrorAction SilentlyContinue | Stop-Process -Force; \
Get-Process -Name 'Atomic Chat','atomic-chat' -ErrorAction SilentlyContinue | Stop-Process -Force; \
Get-Process -Name 'msedgewebview2' -ErrorAction SilentlyContinue | Where-Object { try { $$_.MainModule.FileName -like '*Atomic Chat*' } catch { $$false } } | Stop-Process -Force; \
Start-Sleep -Seconds 2; \
$$settingsFile = Join-Path $$env:APPDATA 'chat.atomic.app\settings.json'; \
$$dataDir = $$null; \
if (Test-Path $$settingsFile) { \
$$s = Get-Content $$settingsFile -Raw | ConvertFrom-Json; \
$$dataDir = $$s.data_folder; \
}; \
if (-not $$dataDir) { $$dataDir = Join-Path $$env:APPDATA 'Atomic Chat\data' }; \
$$backendsDir = Join-Path $$dataDir 'llamacpp-upstream\backends'; \
if (Test-Path $$backendsDir) { \
Write-Host ('Clearing downloaded backends from ' + $$backendsDir) -ForegroundColor Yellow; \
Remove-Item $$backendsDir -Recurse -Force; \
} else { \
Write-Host 'No downloaded backends to clear.' -ForegroundColor Gray; \
}; \
$$webviewCandidates = @( \
(Join-Path $$env:LOCALAPPDATA 'chat.atomic.app\EBWebView\Default\Local Storage'), \
(Join-Path $$env:APPDATA 'chat.atomic.app\EBWebView\Default\Local Storage') \
); \
$$wiped = $$false; \
foreach ($$path in $$webviewCandidates) { \
if (Test-Path $$path) { \
Write-Host ('Clearing WebView2 Local Storage from ' + $$path) -ForegroundColor Yellow; \
Remove-Item $$path -Recurse -Force -ErrorAction SilentlyContinue; \
if (-not (Test-Path $$path)) { $$wiped = $$true } else { Write-Host (' WARN: failed to remove ' + $$path + ' (process still locked?)') -ForegroundColor Red } \
} \
}; \
if (-not $$wiped) { Write-Host 'No WebView2 Local Storage was cleared (paths missing or locked).' -ForegroundColor Gray }; \
$$env:LLAMACPP_BACKEND = 'win-cpu-x64'; \
Write-Host ''; \
Write-Host 'Tip: for a full wipe (all data, models, settings, WebView2 cache) run:' -ForegroundColor Cyan; \
Write-Host ' make clean-windows-all CONFIRM=1' -ForegroundColor Cyan; \
Write-Host ''; \
& '$(CURDIR)/scripts/dev-windows.ps1'; \
"
else
@echo "This target is for Windows only."
endif
# Full wipe of all Atomic Chat data on Windows — used to simulate a true
# first-launch as if the app had never been installed. Removes the four
# default APPDATA / LOCALAPPDATA directories (see DEVELOP.md → "Where Atomic
# Chat stores data on Windows"). Does NOT touch a custom data_folder if the
# user relocated it via the in-app setting — that is the user's responsibility.
#
# Guarded by CONFIRM=1 so an accidental `make clean-windows-all` only prints
# what would be removed.
clean-windows-all:
ifeq ($(OS),Windows_NT)
ifeq ($(CONFIRM),1)
powershell -ExecutionPolicy Bypass -Command "\
Get-Process llama-server -ErrorAction SilentlyContinue | Stop-Process -Force; \
Get-Process -Name 'Atomic Chat','atomic-chat' -ErrorAction SilentlyContinue | Stop-Process -Force; \
Get-Process -Name 'msedgewebview2' -ErrorAction SilentlyContinue | Where-Object { try { $$_.MainModule.FileName -like '*chat.atomic.app*' -or $$_.MainModule.FileName -like '*Atomic Chat*' } catch { $$false } } | Stop-Process -Force; \
Start-Sleep -Seconds 2; \
$$paths = @( \
(Join-Path $$env:APPDATA 'Atomic Chat'), \
(Join-Path $$env:APPDATA 'Atomic-Chat'), \
(Join-Path $$env:APPDATA 'chat.atomic.app'), \
(Join-Path $$env:LOCALAPPDATA 'chat.atomic.app') \
); \
foreach ($$p in $$paths) { \
if (Test-Path $$p) { \
Write-Host ('Removing ' + $$p) -ForegroundColor Yellow; \
Remove-Item $$p -Recurse -Force -ErrorAction SilentlyContinue; \
if (Test-Path $$p) { Write-Host (' WARN: failed to fully remove ' + $$p) -ForegroundColor Red } \
} else { \
Write-Host ('Not present: ' + $$p) -ForegroundColor Gray; \
} \
}; \
Write-Host 'Atomic Chat: full data wipe done.' -ForegroundColor Green; \
"
else
@powershell -NoProfile -ExecutionPolicy Bypass -Command "\
Write-Host 'DRY RUN. Nothing was deleted.' -ForegroundColor Yellow; \
Write-Host 'These paths WOULD be removed when re-run with CONFIRM=1:' -ForegroundColor Yellow; \
$$paths = @( \
(Join-Path $$env:APPDATA 'Atomic Chat'), \
(Join-Path $$env:APPDATA 'Atomic-Chat'), \
(Join-Path $$env:APPDATA 'chat.atomic.app'), \
(Join-Path $$env:LOCALAPPDATA 'chat.atomic.app') \
); \
foreach ($$p in $$paths) { \
$$exists = if (Test-Path $$p) { '[exists]' } else { '[not present]' }; \
Write-Host (' ' + $$p + ' ' + $$exists) -ForegroundColor Gray; \
}; \
Write-Host ''; \
Write-Host 'Run again with CONFIRM=1 to actually delete:' -ForegroundColor Yellow; \
Write-Host ' make clean-windows-all CONFIRM=1' -ForegroundColor Cyan; \
"
endif
else
@echo "This target is for Windows only."
endif
# Web application targets
install-web-app:
yarn install
dev-web-app: install-web-app
yarn build:core
yarn dev:web-app
build-web-app: install-web-app
yarn build:core
yarn build:web-app
serve-web-app:
yarn serve:web-app
build-serve-web-app: build-web-app
yarn serve:web-app
# Mobile
dev-android: install-and-build install-android-rust-targets
@echo "Setting up Android development environment..."
@if [ ! -d "src-tauri/gen/android" ]; then \
echo "Android app not initialized. Initializing..."; \
yarn tauri android init; \
fi
@echo "Sourcing Android environment setup..."
@bash autoqa/scripts/setup-android-env.sh echo "Android environment ready"
@echo "Starting Android development server..."
yarn dev:android
dev-ios: install-and-build install-ios-rust-targets
@echo "Setting up iOS development environment..."
ifeq ($(shell uname -s),Darwin)
@if [ ! -d "src-tauri/gen/ios" ]; then \
echo "iOS app not initialized. Initializing..."; \
yarn tauri ios init; \
fi
@echo "Checking iOS development requirements..."
@xcrun --version > /dev/null 2>&1 || (echo "❌ Xcode command line tools not found. Install with: xcode-select --install" && exit 1)
@xcrun simctl list devices available | grep -q "iPhone\|iPad" || (echo "❌ No iOS simulators found. Install simulators through Xcode." && exit 1)
@echo "Starting iOS development server..."
yarn dev:ios
else
@echo "❌ iOS development is only supported on macOS"
@exit 1
endif
# Linting
lint: install-and-build
yarn lint
# Testing
.PHONY: test test-all test-local test-web test-extensions test-rust stub-resources \
verify-fast verify test-quality test-hardening-contracts \
test-coverage-critical capture-capabilities capture-hw-profile \
test-live test-live-cloud mutants
test-web:
yarn test
test-extensions:
yarn --cwd extensions workspaces foreach -A \
--include '@janhq/llamacpp-extension' \
--include '@janhq/llamacpp-upstream-extension' \
--include '@janhq/mlx-extension' \
--include '@janhq/download-extension' \
run test:run
# Tauri validates bundle.resources and externalBin paths while compiling the
# test target. Tests never execute these artefacts, so create only missing
# placeholders and never overwrite a real local build.
stub-resources:
ifeq ($(OS),Windows_NT)
powershell -NoProfile -Command "\
$$files = @( \
'src-tauri/resources/LICENSE', \
'src-tauri/resources/pre-install/test-placeholder', \
'src-tauri/resources/bin/jan-cli.exe', \
'src-tauri/resources/bin/bun-x86_64-pc-windows-msvc.exe', \
'src-tauri/resources/bin/uv-x86_64-pc-windows-msvc.exe', \
'src-tauri/resources/llamacpp-backend/test-placeholder', \
'src-tauri/resources/llamacpp-backend-upstream/test-placeholder' \
); \
foreach ($$file in $$files) { \
$$parent = Split-Path -Parent $$file; \
New-Item -ItemType Directory -Force -Path $$parent | Out-Null; \
if (-not (Test-Path $$file)) { New-Item -ItemType File -Path $$file | Out-Null } \
}"
else ifeq ($(shell uname -s),Darwin)
@mkdir -p src-tauri/resources/bin src-tauri/resources/pre-install src-tauri/resources/llamacpp-backend src-tauri/resources/llamacpp-backend-upstream
@[ -e src-tauri/resources/LICENSE ] || touch src-tauri/resources/LICENSE
@[ -e src-tauri/resources/pre-install/test-placeholder ] || touch src-tauri/resources/pre-install/test-placeholder
@[ -e src-tauri/resources/bin/jan-cli ] || touch src-tauri/resources/bin/jan-cli
@[ -e src-tauri/resources/bin/mlx-server ] || touch src-tauri/resources/bin/mlx-server
@[ -e src-tauri/resources/bin/mlx-server-version.txt ] || touch src-tauri/resources/bin/mlx-server-version.txt
@[ -e src-tauri/resources/bin/mlx-server-backend.txt ] || touch src-tauri/resources/bin/mlx-server-backend.txt
@[ -e src-tauri/resources/bin/foundation-models-server ] || touch src-tauri/resources/bin/foundation-models-server
@[ -e src-tauri/resources/bin/bun-aarch64-apple-darwin ] || touch src-tauri/resources/bin/bun-aarch64-apple-darwin
@[ -e src-tauri/resources/bin/bun-x86_64-apple-darwin ] || touch src-tauri/resources/bin/bun-x86_64-apple-darwin
@[ -e src-tauri/resources/bin/uv-aarch64-apple-darwin ] || touch src-tauri/resources/bin/uv-aarch64-apple-darwin
@[ -e src-tauri/resources/bin/uv-x86_64-apple-darwin ] || touch src-tauri/resources/bin/uv-x86_64-apple-darwin
else
@mkdir -p src-tauri/resources/bin src-tauri/resources/pre-install src-tauri/resources/llamacpp-backend src-tauri/resources/llamacpp-backend-upstream
@[ -e src-tauri/resources/LICENSE ] || touch src-tauri/resources/LICENSE
@[ -e src-tauri/resources/pre-install/test-placeholder ] || touch src-tauri/resources/pre-install/test-placeholder
@[ -e src-tauri/resources/bin/jan-cli ] || touch src-tauri/resources/bin/jan-cli
@[ -e src-tauri/resources/bin/sqlite-vec.so ] || touch src-tauri/resources/bin/sqlite-vec.so
@[ -e src-tauri/resources/bin/uv-x86_64-unknown-linux-gnu ] || touch src-tauri/resources/bin/uv-x86_64-unknown-linux-gnu
@[ -e src-tauri/resources/llamacpp-backend/test-placeholder ] || touch src-tauri/resources/llamacpp-backend/test-placeholder
@[ -e src-tauri/resources/llamacpp-backend-upstream/test-placeholder ] || touch src-tauri/resources/llamacpp-backend-upstream/test-placeholder
endif
test-rust: export TAURI_CONFIG := {"bundle":{"icon":["icons/icon.png"]}}
test-rust: stub-resources
cargo test --manifest-path src-tauri/Cargo.toml --no-default-features --features test-tauri -- --test-threads=1
cargo test --manifest-path src-tauri/plugins/tauri-plugin-hardware/Cargo.toml
cargo test --manifest-path src-tauri/plugins/tauri-plugin-llamacpp/Cargo.toml
cargo test --manifest-path src-tauri/plugins/tauri-plugin-llamacpp-upstream/Cargo.toml -- --test-threads=1
ifeq ($(shell uname -s),Darwin)
cargo test --manifest-path src-tauri/plugins/tauri-plugin-mlx/Cargo.toml
endif
cargo test --manifest-path src-tauri/utils/Cargo.toml
# Fast local suite: root Vitest, extension Vitest, and every test-bearing
# Rust crate supported on the current platform.
test-local: test-web test-extensions test-rust
# Deterministic local gate for agent-authored changes. Coverage replaces the
# ordinary Vitest runs here, so the suites execute once while also producing
# the critical-flow summaries consumed by check-coverage-floor.mjs.
test-quality:
node scripts/check-test-quality.mjs
test-hardening-contracts:
node --test tests/capabilities.test.mjs \
tests/registry-contracts.test.mjs \
tests/hardware-profiles.test.mjs
test-coverage-critical:
yarn test:coverage
yarn --cwd extensions workspaces foreach -A \
--include '@janhq/llamacpp-extension' \
--include '@janhq/llamacpp-upstream-extension' \
run test:coverage
node scripts/check-coverage-floor.mjs
verify-fast:
yarn lint
"$(MAKE)" test-quality
"$(MAKE)" test-hardening-contracts
"$(MAKE)" test-coverage-critical
verify: verify-fast test-rust
# Explicitly live capture commands. The caller supplies paths/identity so these
# never download artifacts or mutate fixtures during a normal verification run.
capture-capabilities:
@test -n "$(PROVIDER)" || (echo "PROVIDER is required" && exit 2)
@test -n "$(BINARY)" || (echo "BINARY is required" && exit 2)
@test -n "$(OUTPUT)" || (echo "OUTPUT is required" && exit 2)
node scripts/capture-capabilities.mjs "$(PROVIDER)" "$(BINARY)" "$(OUTPUT)" "$(VERSION)"
capture-hw-profile:
@test -n "$(OUTPUT)" || (echo "OUTPUT is required" && exit 2)
node scripts/capture-hw-profile.mjs "$(OUTPUT)"
# Opt-in acceptance against live local binaries and moving external registries.
# Missing sidecar env vars are reported as skips; use REQUIRE=1 to make them
# mandatory. These targets are intentionally excluded from verify/verify-fast.
test-live:
python3 scripts/test-local-sidecars.py $(if $(filter 1,$(REQUIRE)),--require,)
ATOMIC_TEST_LIVE_REGISTRIES=1 yarn workspace @janhq/web-app vitest --run \
src/services/__tests__/external-contracts.test.ts
test-live-cloud:
python3 scripts/record-cloud-live.py $(if $(filter 1,$(REQUIRE)),--require,)
mutants:
bash scripts/test-cargo-mutants.sh
test-agent:
cargo test --manifest-path src-tauri/Cargo.toml -p Atomic-Chat core::agent
ATOMIC_AGENT_E2E_LLAMA_SERVER ?= $(CURDIR)/src-tauri/resources/llamacpp-backend/build/bin/llama-server
ATOMIC_AGENT_E2E_MODEL ?= $(HOME)/Library/Application Support/Atomic Chat/data/llamacpp/models/unsloth/Qwen3_5-9B-GGUF-Qwen3_5-9B-IQ4_XS/model.gguf
ifeq ($(OS),Windows_NT)
GAIA_LLAMA_SERVER ?= $(CURDIR)/src-tauri/resources/llamacpp-backend-upstream/build/bin/llama-server.exe
else
GAIA_LLAMA_SERVER ?= $(ATOMIC_AGENT_E2E_LLAMA_SERVER)
endif
GAIA_MODEL ?= $(ATOMIC_AGENT_E2E_MODEL)
test-agent-model:
@test -x "$(ATOMIC_AGENT_E2E_LLAMA_SERVER)" || (echo "llama-server not found: $(ATOMIC_AGENT_E2E_LLAMA_SERVER)" && exit 1)
@test -f "$(ATOMIC_AGENT_E2E_MODEL)" || (echo "model not found: $(ATOMIC_AGENT_E2E_MODEL)" && exit 1)
ATOMIC_AGENT_E2E_LLAMA_SERVER="$(ATOMIC_AGENT_E2E_LLAMA_SERVER)" \
ATOMIC_AGENT_E2E_MODEL="$(ATOMIC_AGENT_E2E_MODEL)" \
cargo test --manifest-path src-tauri/Cargo.toml -p Atomic-Chat \
managed_model_agent_scenarios -- --ignored --nocapture --test-threads=1
.PHONY: gaia-eval
gaia-eval:
@test -x "$(GAIA_LLAMA_SERVER)" || (echo "llama-server not found or not executable: $(GAIA_LLAMA_SERVER)" && exit 1)
@test -f "$(GAIA_MODEL)" || (echo "model not found: $(GAIA_MODEL)" && exit 1)
GAIA_LLAMA_SERVER="$(GAIA_LLAMA_SERVER)" \
GAIA_MODEL="$(GAIA_MODEL)" \
cargo run --manifest-path src-tauri/Cargo.toml -p Atomic-Chat \
--features gaia-eval --example gaia-eval
test: lint install-rust-targets
yarn download:bin
ifeq ($(OS),Windows_NT)
endif
yarn copy:assets:tauri
yarn build:icon
yarn build:mlx-server
make build-foundation-models-server-if-exists
make build-cli
$(MAKE) test-local
# Exhaustive developer verification: prepare every bundled artefact, run the
# deterministic quality/coverage/Rust gate, then exercise live contracts.
# Unconfigured sidecars and cloud providers are reported as skips. REQUIRE=1
# makes those live prerequisites mandatory.
test-all: install-and-build install-rust-targets
yarn download:bin
yarn copy:assets:tauri
yarn build:icon
yarn build:mlx-server
$(MAKE) build-foundation-models-server-if-exists
$(MAKE) build-cli
python3 scripts/run_test_all.py \
--make "$(MAKE)" \
$(if $(filter 1,$(REQUIRE)),--require-live,)
# Download MLX server binary (mlx-vlm fork) from GitHub releases (macOS only)
# Supports GH_TOKEN env var for authenticated GitHub API requests (avoids rate limits in CI)
# Pinned compatibility baseline. Override only for a dedicated compatibility
# validation run; normal builds never resolve a moving latest release.
# Example:
# make build-mlx-server MLXVLM_TAG=mlxvlm-macos-arm64-abc1234
MLXVLM_TAG ?= mlxvlm-macos-arm64-addaf9f
build-mlx-server:
ifeq ($(shell uname -s),Darwin)
@mkdir -p src-tauri/resources/bin
@echo "Downloading MLX server binary (mlx-vlm)..."; \
if [ -n "$(MLXVLM_TAG)" ]; then \
TAG="$(MLXVLM_TAG)"; \
echo "Using pinned release: $$TAG"; \
else \
echo "Fetching latest mlx-vlm release..."; \
API_URL="https://api.github.com/repos/AtomicBot-ai/mlx-vlm/releases?per_page=50"; \
TMPREL=$$(mktemp /tmp/mlxvlm-releases-XXXXXX.json); \
_gh_get() { \
if [ "$$1" = "1" ] && [ -n "$$GH_TOKEN" ]; then \
curl -sS -H "Authorization: Bearer $$GH_TOKEN" -H "Accept: application/vnd.github+json" -H "User-Agent: atomic-chat-ci" -o "$$2" -w "%{http_code}" "$$3" || echo "000"; \
else \
curl -sS -H "Accept: application/vnd.github+json" -H "User-Agent: atomic-chat-ci" -o "$$2" -w "%{http_code}" "$$3" || echo "000"; \
fi; \
}; \
_gh_fetch() { \
HTTP_CODE=""; \
for attempt in 1 2 3 4 5; do \
HTTP_CODE=$$(_gh_get "$$1" "$$2" "$$3"); \
case "$$HTTP_CODE" in \
2*) return 0 ;; \
403|429|5*|000) \
echo " GitHub API attempt $$attempt/5 (auth=$$1): HTTP $$HTTP_CODE, retrying in $$((attempt * 2))s..."; \
sleep $$((attempt * 2)) ;; \
*) return 1 ;; \
esac; \
done; \
return 1; \
}; \
_response_ok() { \
[ -s "$$1" ] && jq -e 'type == "array" and length > 0' "$$1" >/dev/null 2>&1; \
}; \
USE_TOKEN=0; [ -n "$$GH_TOKEN" ] && USE_TOKEN=1; \
_gh_fetch "$$USE_TOKEN" "$$TMPREL" "$$API_URL" || true; \
FIRST_CODE="$$HTTP_CODE"; \
if ! _response_ok "$$TMPREL" && [ "$$USE_TOKEN" = "1" ]; then \
echo "Token-authenticated request did not yield usable releases (HTTP $$FIRST_CODE); retrying unauthenticated..."; \
_gh_fetch "0" "$$TMPREL" "$$API_URL" || true; \
fi; \
case "$$HTTP_CODE" in \
2*) ;; \
*) echo "Error: GitHub API failed (last HTTP $$HTTP_CODE)"; \
echo " body (first 500 bytes):"; head -c 500 "$$TMPREL" 2>/dev/null || true; echo; \
rm -f "$$TMPREL"; exit 1 ;; \
esac; \
if [ ! -s "$$TMPREL" ] || ! jq -e 'type == "array"' "$$TMPREL" >/dev/null 2>&1; then \
echo "Error: GitHub API returned non-array or empty response (HTTP $$HTTP_CODE):"; \
head -c 500 "$$TMPREL" 2>/dev/null || true; echo; \
rm -f "$$TMPREL"; exit 1; \
fi; \
REL_COUNT=$$(jq 'length' "$$TMPREL"); \
echo "GitHub API returned $$REL_COUNT release(s)"; \
TAG=$$(jq -r '[.[] | select(.tag_name | startswith("mlxvlm-macos-arm64"))] | sort_by(.published_at // .created_at) | reverse | .[0].tag_name // empty' "$$TMPREL"); \
if [ -z "$$TAG" ]; then \
echo "Error: No mlx-vlm release found matching 'mlxvlm-macos-arm64*'. First 10 tags in response:"; \
jq -r '.[0:10] | .[].tag_name' "$$TMPREL" || true; \
rm -f "$$TMPREL"; exit 1; \
fi; \
rm -f "$$TMPREL"; \
fi; \
echo "Release: $$TAG"; \
URL="https://github.com/AtomicBot-ai/mlx-vlm/releases/download/$$TAG/mlxvlm-mlx-server-macos-arm64.tar.gz"; \
echo "Downloading: $$URL"; \
curl -fSL "$$URL" -o /tmp/mlxvlm-mlx-server.tar.gz; \
tar -xzf /tmp/mlxvlm-mlx-server.tar.gz -C src-tauri/resources/bin/; \
rm -f /tmp/mlxvlm-mlx-server.tar.gz; \
chmod +x src-tauri/resources/bin/mlx-server; \
echo "$$TAG" > src-tauri/resources/bin/mlx-server-version.txt; \
echo "macos-arm64" > src-tauri/resources/bin/mlx-server-backend.txt; \
echo "MLX server (mlx-vlm) downloaded and extracted successfully ($$TAG)"
@SIGNING_IDENTITY=$$(security find-identity -v -p codesigning | grep "Developer ID Application" | head -1 | sed 's/.*"\(.*\)".*/\1/'); \
if [ -n "$$SIGNING_IDENTITY" ]; then \
echo "Signing mlx-server with identity: $$SIGNING_IDENTITY"; \
codesign --force --options runtime --timestamp --entitlements src-tauri/Entitlements.plist --sign "$$SIGNING_IDENTITY" src-tauri/resources/bin/mlx-server; \
echo "Code signing completed successfully"; \
else \
echo "Warning: No Developer ID Application identity found. Applying ad-hoc signature."; \
codesign --force --deep --sign - src-tauri/resources/bin/mlx-server; \
fi
@mkdir -p src-tauri/target/debug/resources/bin; \
cp src-tauri/resources/bin/mlx-server src-tauri/target/debug/resources/bin/mlx-server; \
cp src-tauri/resources/bin/mlx-server-version.txt src-tauri/target/debug/resources/bin/mlx-server-version.txt; \
cp src-tauri/resources/bin/mlx-server-backend.txt src-tauri/target/debug/resources/bin/mlx-server-backend.txt; \
echo "Debug copy updated with signed binary"
else
@echo "Skipping MLX server download (macOS only)"
endif
# Download MLX server if missing or different from the verified pin.
build-mlx-server-if-exists:
ifeq ($(shell uname -s),Darwin)
@if [ ! -f "src-tauri/resources/bin/mlx-server" ] || [ ! -f "src-tauri/resources/bin/mlx-server-version.txt" ]; then \
echo "MLX server binary or version file missing — downloading..."; \
make build-mlx-server; \
else \
LOCAL_TAG=$$(cat src-tauri/resources/bin/mlx-server-version.txt 2>/dev/null); \
if [ "$$LOCAL_TAG" = "$(MLXVLM_TAG)" ]; then \
echo "MLX server is up-to-date ($$LOCAL_TAG)"; \
else \
echo "MLX server differs from verified pin: local=$$LOCAL_TAG pinned=$(MLXVLM_TAG) — updating..."; \
make build-mlx-server; \
fi; \
fi
else
@echo "Skipping MLX server build (macOS only)"
endif
# Build Apple Foundation Models server (macOS 26+ only) - always builds
build-foundation-models-server:
ifeq ($(shell uname -s),Darwin)
@echo "Building Foundation Models server for macOS 26+..."
cd foundation-models-server && swift build -c release
@echo "Copying foundation-models-server binary..."
@cp foundation-models-server/.build/release/foundation-models-server src-tauri/resources/bin/foundation-models-server
@chmod +x src-tauri/resources/bin/foundation-models-server
@echo "Foundation Models server built and copied successfully"
@echo "Checking for code signing identity..."
@SIGNING_IDENTITY=$$(security find-identity -v -p codesigning | grep "Developer ID Application" | head -1 | sed 's/.*"\(.*\)".*/\1/'); \
if [ -n "$$SIGNING_IDENTITY" ]; then \
echo "Signing foundation-models-server with identity: $$SIGNING_IDENTITY"; \
codesign --force --options runtime --timestamp --sign "$$SIGNING_IDENTITY" src-tauri/resources/bin/foundation-models-server; \
echo "Code signing completed successfully"; \
else \
echo "Warning: No Developer ID Application identity found. Skipping code signing."; \
fi
else
@echo "Skipping Foundation Models server build (macOS only)"
endif
# Build Foundation Models server only if not already present (for dev)
build-foundation-models-server-if-exists:
ifeq ($(shell uname -s),Darwin)
@if [ -f "src-tauri/resources/bin/foundation-models-server" ]; then \
echo "Foundation Models server already exists at src-tauri/resources/bin/foundation-models-server, skipping build..."; \
else \
make build-foundation-models-server; \
fi
else
@echo "Skipping Foundation Models server build (macOS only)"
endif
# Download llamacpp turboquant backend for bundling
# Supports GH_TOKEN env var for authenticated GitHub API requests (avoids rate limits in CI)
# Verified Apple Silicon compatibility baseline. LLAMACPP_TAG remains the
# explicit developer override used by non-macOS legacy targets.
# Example:
# make download-llamacpp-backend LLAMACPP_TAG=turboquant-macos-arm64-7c01058
ATOMIC_CHAT_CONF_BACKEND_REV ?= f149c30e55a3dfcdcbcc5d4a72be7c691db8f5e2
LLAMACPP_MACOS_ARM64_TAG ?= turboquant-macos-arm64-066cc29
LLAMACPP_TAG ?= $(if $(filter Darwin,$(shell uname -s)),$(if $(filter arm64,$(shell uname -m)),$(LLAMACPP_MACOS_ARM64_TAG),),)
download-llamacpp-backend:
ifeq ($(shell uname -s),Darwin)
@mkdir -p src-tauri/resources/llamacpp-backend
@ARCH=$$(uname -m); \
if [ "$$ARCH" != "arm64" ]; then \
echo "Skipping TurboQuant backend: no verified macOS x64 release exists"; \
exit 0; \
fi; \
if [ "$$ARCH" = "arm64" ]; then BACKEND="macos-arm64"; else BACKEND="macos-x64"; fi; \
echo "Platform: $$BACKEND"; \
if [ -n "$(LLAMACPP_TAG)" ]; then \
TAG="$(LLAMACPP_TAG)"; \
echo "Using pinned release: $$TAG"; \
else \
echo "Fetching latest llamacpp turboquant release..."; \
TMPREL=$$(mktemp /tmp/llamacpp-releases-XXXXXX.json); \
API_URL="https://api.github.com/repos/AtomicBot-ai/atomic-llama-cpp-turboquant/releases?per_page=50"; \
_gh_get() { \
if [ "$$1" = "1" ] && [ -n "$$GH_TOKEN" ]; then \
curl -sS -H "Authorization: Bearer $$GH_TOKEN" -H "Accept: application/vnd.github+json" -H "User-Agent: atomic-chat-ci" -o "$$2" -w "%{http_code}" "$$3" || echo "000"; \
else \
curl -sS -H "Accept: application/vnd.github+json" -H "User-Agent: atomic-chat-ci" -o "$$2" -w "%{http_code}" "$$3" || echo "000"; \
fi; \
}; \
_gh_fetch() { \
HTTP_CODE=""; \
for attempt in 1 2 3 4 5; do \
HTTP_CODE=$$(_gh_get "$$1" "$$2" "$$3"); \
case "$$HTTP_CODE" in \
2*) return 0 ;; \
403|429|5*|000) \
echo " GitHub API attempt $$attempt/5 (auth=$$1): HTTP $$HTTP_CODE, retrying in $$((attempt * 2))s..."; \
sleep $$((attempt * 2)) ;; \
*) return 1 ;; \
esac; \
done; \
return 1; \
}; \
_response_ok() { \
[ -s "$$1" ] && jq -e 'type == "array" and length > 0' "$$1" >/dev/null 2>&1; \
}; \
USE_TOKEN=0; [ -n "$$GH_TOKEN" ] && USE_TOKEN=1; \
_gh_fetch "$$USE_TOKEN" "$$TMPREL" "$$API_URL" || true; \
FIRST_CODE="$$HTTP_CODE"; \
case "$$HTTP_CODE" in \
2*) \
if jq -e 'type == "array"' "$$TMPREL" >/dev/null 2>&1; then \
REL_COUNT=$$(jq 'length' "$$TMPREL"); \
echo "GitHub API returned $$REL_COUNT release(s) (auth=$$USE_TOKEN)"; \
else \
REL_COUNT=-1; \
fi ;; \
*) \
echo " GitHub API request failed (auth=$$USE_TOKEN, HTTP $$HTTP_CODE)"; \
REL_COUNT=-1 ;; \
esac; \
if ! _response_ok "$$TMPREL" && [ "$$USE_TOKEN" = "1" ]; then \
echo "Token-authenticated request did not yield usable releases (HTTP $$FIRST_CODE); retrying unauthenticated..."; \
_gh_fetch "0" "$$TMPREL" "$$API_URL" || true; \
fi; \
case "$$HTTP_CODE" in \
2*) ;; \
*) echo "Error: GitHub API failed (last HTTP $$HTTP_CODE)"; \
echo " body (first 500 bytes):"; head -c 500 "$$TMPREL" 2>/dev/null || true; echo; \
rm -f "$$TMPREL"; exit 1 ;; \
esac; \
if [ ! -s "$$TMPREL" ] || ! jq -e 'type == "array"' "$$TMPREL" >/dev/null 2>&1; then \
echo "Error: GitHub API returned non-array or empty response (HTTP $$HTTP_CODE):"; \
head -c 500 "$$TMPREL" 2>/dev/null || true; echo; \
rm -f "$$TMPREL"; exit 1; \
fi; \
REL_COUNT=$$(jq 'length' "$$TMPREL"); \
echo "Final response: $$REL_COUNT release(s)"; \
TAG=$$(jq -r --arg b "$$BACKEND" '[.[] | select(.tag_name | startswith("turboquant-" + $$b))][0].tag_name // empty' "$$TMPREL"); \
if [ -z "$$TAG" ]; then \
echo "No turboquant release found for $$BACKEND, trying legacy release..."; \
TAG=$$(jq -r '[.[] | select(.tag_name | startswith("turboquant-") | not)][0].tag_name // empty' "$$TMPREL"); \
fi; \
if [ -z "$$TAG" ]; then \
echo "Error: No matching release found for backend=$$BACKEND. First 10 tags in response:"; \
jq -r '.[0:10] | .[].tag_name' "$$TMPREL" || true; \
rm -f "$$TMPREL"; exit 1; \
fi; \
rm -f "$$TMPREL"; \
fi; \
echo "Release: $$TAG"; \
case "$$TAG" in \
turboquant-*) URL="https://github.com/AtomicBot-ai/atomic-llama-cpp-turboquant/releases/download/$$TAG/llama-turboquant-$$BACKEND.tar.gz" ;; \
*) URL="https://github.com/AtomicBot-ai/atomic-llama-cpp-turboquant/releases/download/$$TAG/llama-$$TAG-bin-$$BACKEND.tar.gz" ;; \
esac; \
echo "$$TAG" > src-tauri/resources/llamacpp-backend/version.txt; \
echo "$$BACKEND" > src-tauri/resources/llamacpp-backend/backend.txt; \
echo "Downloading: $$URL"; \
curl -fSL "$$URL" -o /tmp/llamacpp-backend.tar.gz; \
tar -xzf /tmp/llamacpp-backend.tar.gz -C src-tauri/resources/llamacpp-backend/; \
rm -f /tmp/llamacpp-backend.tar.gz; \
echo "Downloaded and extracted llamacpp backend successfully"
@SIGNING_IDENTITY=$$(security find-identity -v -p codesigning | grep "Developer ID Application" | head -1 | sed 's/.*"\(.*\)".*/\1/'); \
if [ -n "$$SIGNING_IDENTITY" ]; then \
echo "Signing llamacpp backend binaries..."; \
for bin in src-tauri/resources/llamacpp-backend/build/bin/*; do \
if [ -f "$$bin" ] && file "$$bin" | grep -q "Mach-O"; then \
codesign --force --options runtime --timestamp --entitlements src-tauri/Entitlements.plist --sign "$$SIGNING_IDENTITY" "$$bin"; \
fi; \
done; \
echo "Code signing completed"; \
else \
echo "Warning: No Developer ID Application identity found. Skipping code signing."; \
fi
else ifeq ($(OS),Windows_NT)
@$(MAKE) download-llamacpp-backend-win-cpu
else ifeq ($(shell uname -s),Linux)
@mkdir -p src-tauri/resources/llamacpp-backend
@# TurboQuant ships on Linux as the second provider alongside
@# llamacpp-upstream. The single Linux build (linux-x64-vulkan) serves
@# both CPU and GPU via GGML_BACKEND_DL, so it is the offline-fallback
@# bundle. The backend index is resolved from the static turboquant
@# manifest in atomic-chat-conf (raw.githubusercontent.com — no
@# api.github.com rate limit); the archive download itself comes from
@# the AtomicBot-ai releases CDN.
@BACKEND="linux-x64-vulkan"; \
echo "Platform: $$BACKEND (turboquant / Linux)"; \
if [ -n "$(LLAMACPP_TAG)" ]; then \
TAG="$(LLAMACPP_TAG)"; \
ASSET="llama-turboquant-$$BACKEND.tar.gz"; \
echo "Using pinned release: $$TAG"; \
else \
echo "Resolving TurboQuant backend index from atomic-chat-conf manifest..."; \
TMPREL=$$(mktemp /tmp/turboquant-manifest-XXXXXX.json); \
MANIFEST_URL="https://raw.githubusercontent.com/AtomicBot-ai/atomic-chat-conf/$(ATOMIC_CHAT_CONF_BACKEND_REV)/backends/turboquant-manifest.json"; \
if ! curl -sS --retry 5 --retry-delay 3 -H "User-Agent: atomic-chat-ci" -o "$$TMPREL" "$$MANIFEST_URL"; then \
echo "Error: failed to fetch turboquant manifest from $$MANIFEST_URL"; \
rm -f "$$TMPREL"; exit 1; \
fi; \
if ! jq -e '.backends' "$$TMPREL" >/dev/null 2>&1; then \
echo "Error: turboquant manifest did not parse or lacks backends[]:"; \
head -c 500 "$$TMPREL" 2>/dev/null || true; echo; \
rm -f "$$TMPREL"; exit 1; \
fi; \
TAG=$$(jq -r --arg id "$$BACKEND" '.backends[] | select(.id == $$id) | .tag' "$$TMPREL"); \
ASSET=$$(jq -r --arg id "$$BACKEND" '.backends[] | select(.id == $$id) | .asset' "$$TMPREL"); \
if [ -z "$$TAG" ] || [ "$$TAG" = "null" ] || [ -z "$$ASSET" ] || [ "$$ASSET" = "null" ]; then \
echo "Error: turboquant manifest does not list backend $$BACKEND (update atomic-chat-conf/backends/turboquant-manifest.json):"; \
head -c 500 "$$TMPREL" 2>/dev/null || true; echo; \
rm -f "$$TMPREL"; exit 1; \
fi; \
rm -f "$$TMPREL"; \
fi; \
URL="https://github.com/AtomicBot-ai/atomic-llama-cpp-turboquant/releases/download/$$TAG/$$ASSET"; \
echo "$$TAG" > src-tauri/resources/llamacpp-backend/version.txt; \
echo "$$BACKEND" > src-tauri/resources/llamacpp-backend/backend.txt; \
echo "Release: $$TAG Backend: $$BACKEND"; \
echo "Downloading: $$URL"; \
curl -fSL --retry 5 --retry-delay 3 "$$URL" -o /tmp/llamacpp-backend.tar.gz; \
tar -xzf /tmp/llamacpp-backend.tar.gz -C src-tauri/resources/llamacpp-backend/; \
rm -f /tmp/llamacpp-backend.tar.gz; \
if [ ! -f "src-tauri/resources/llamacpp-backend/build/bin/llama-server" ]; then \
if [ -f "src-tauri/resources/llamacpp-backend/bin/llama-server" ]; then \
echo "Relocating bin/ → build/bin/ to match expected layout..."; \
mkdir -p src-tauri/resources/llamacpp-backend/build; \
mv src-tauri/resources/llamacpp-backend/bin src-tauri/resources/llamacpp-backend/build/bin; \
elif [ -f "src-tauri/resources/llamacpp-backend/llama-server" ]; then \
echo "Relocating flat layout → build/bin/..."; \
mkdir -p src-tauri/resources/llamacpp-backend/build/bin; \
find src-tauri/resources/llamacpp-backend -maxdepth 1 -type f \( -name "llama-*" -o -name "*.so" -o -name "*.so.*" \) -exec mv {} src-tauri/resources/llamacpp-backend/build/bin/ \;; \
fi; \
fi; \
echo "Downloaded and extracted turboquant llamacpp backend ($$BACKEND) for Linux successfully"
else
@echo "Skipping llamacpp backend download (unsupported platform)"
endif
# Download CPU fallback backend for Windows (pure PowerShell, no bash needed).
# Sources the official upstream ggml-org/llama.cpp release into the upstream
# backend resource dir. The app will auto-detect GPU and download the optimal
# backend (CUDA/Vulkan) at runtime via the llamacpp-upstream extension.
# Upstream remains the Windows default; this target owns its CPU fallback.
# TurboQuant uses the separate `download-llamacpp-backend-win-cpu` target.
download-llamacpp-upstream-backend-win-cpu:
powershell -NoProfile -Command " \
$$ErrorActionPreference = 'Stop'; \
$$dir = 'src-tauri/resources/llamacpp-backend-upstream'; \
if (Test-Path $$dir) { Remove-Item $$dir -Recurse -Force }; \
New-Item -ItemType Directory -Path $$dir -Force | Out-Null; \
Write-Host 'Resolving backend index from atomic-chat-conf manifest (ATO-199)...'; \
$$headers = @{ 'User-Agent' = 'atomic-chat' }; \
$$backend = 'win-cpu-x64'; \
$$manifest = Invoke-RestMethod -Uri 'https://raw.githubusercontent.com/AtomicBot-ai/atomic-chat-conf/main/backends/manifest.json' -Headers $$headers; \
$$tag = ''; \
$$want = \"llama-$$($$manifest.tag_name)-bin-$${backend}.zip\"; \
if ($$manifest.assets | Where-Object { $$_.name -eq $$want }) { $$tag = $$manifest.tag_name }; \
if (-not $$tag) { throw 'atomic-chat-conf backend manifest does not list the win-cpu-x64 asset (update backends/manifest.json)' }; \
$$url = \"https://github.com/ggml-org/llama.cpp/releases/download/$$tag/llama-$${tag}-bin-$${backend}.zip\"; \
[System.IO.File]::WriteAllText(\"$$dir/version.txt\", $$tag); \
[System.IO.File]::WriteAllText(\"$$dir/backend.txt\", $$backend); \
Write-Host \"Release: $$tag Backend: $$backend\"; \
Write-Host \"Downloading: $$url\"; \
$$tmp = \"$$env:TEMP\\llamacpp-upstream-backend.zip\"; \
$$ok = $$false; \
for ($$i = 1; $$i -le 5; $$i++) { \
try { Invoke-WebRequest -Uri $$url -OutFile $$tmp -UseBasicParsing; $$ok = $$true; break } \
catch { Write-Host \"Download attempt $$i/5 failed: $$($$_.Exception.Message); retrying...\"; Start-Sleep -Seconds 3 } \
}; \
if (-not $$ok) { throw \"Failed to download $$url after 5 attempts\" }; \
Expand-Archive -Path $$tmp -DestinationPath $$dir -Force; \
Remove-Item $$tmp -Force -ErrorAction SilentlyContinue; \
if (-not (Test-Path \"$$dir/build/bin/llama-server.exe\")) { \
if (Test-Path \"$$dir/llama-server.exe\") { \
Write-Host 'Relocating flat-extracted binaries into build/bin/...'; \
New-Item -ItemType Directory -Path \"$$dir/build/bin\" -Force | Out-Null; \
Get-ChildItem \"$$dir\" -File | Where-Object { $$_.Name -ne 'version.txt' -and $$_.Name -ne 'backend.txt' } | Move-Item -Destination \"$$dir/build/bin/\"; \
} \
}; \
Write-Host \"CPU backend ($$backend) downloaded successfully. App will auto-download GPU backend at runtime.\"; \
"
# Download the bundled CPU fallback for the TurboQuant provider on Windows
# (pure PowerShell, no bash needed). TurboQuant ships on Windows as the second
# provider alongside llamacpp-upstream; this target bundles the offline-fallback
# `windows-x64-cpu` build into the turboquant resource dir. The app auto-detects
# GPU and downloads the optimal CUDA/Vulkan backend at runtime via the
# llamacpp-extension. Backend index is resolved from the static turboquant
# manifest in atomic-chat-conf (raw.githubusercontent.com — no api.github.com
# rate limit); the archive download comes from the AtomicBot-ai releases CDN.
download-llamacpp-backend-win-cpu:
powershell -NoProfile -Command " \
$$ErrorActionPreference = 'Stop'; \
$$dir = 'src-tauri/resources/llamacpp-backend'; \
if (Test-Path $$dir) { Remove-Item $$dir -Recurse -Force }; \
New-Item -ItemType Directory -Path $$dir -Force | Out-Null; \
Write-Host 'Resolving TurboQuant backend index from atomic-chat-conf manifest...'; \
$$headers = @{ 'User-Agent' = 'atomic-chat' }; \
$$backend = 'windows-x64-cpu'; \
$$manifest = Invoke-RestMethod -Uri 'https://raw.githubusercontent.com/AtomicBot-ai/atomic-chat-conf/$(ATOMIC_CHAT_CONF_BACKEND_REV)/backends/turboquant-manifest.json' -Headers $$headers; \
$$entry = $$manifest.backends | Where-Object { $$_.id -eq $$backend } | Select-Object -First 1; \
if (-not $$entry) { throw 'atomic-chat-conf turboquant manifest does not list the windows-x64-cpu backend (update backends/turboquant-manifest.json)' }; \
$$tag = $$entry.tag; \
$$asset = $$entry.asset; \
$$url = \"https://github.com/AtomicBot-ai/atomic-llama-cpp-turboquant/releases/download/$$tag/$$asset\"; \
[System.IO.File]::WriteAllText(\"$$dir/version.txt\", $$tag); \
[System.IO.File]::WriteAllText(\"$$dir/backend.txt\", $$backend); \
Write-Host \"Release: $$tag Backend: $$backend\"; \
Write-Host \"Downloading: $$url\"; \
$$tmp = \"$$env:TEMP\\llamacpp-turboquant-backend.zip\"; \
$$ok = $$false; \
for ($$i = 1; $$i -le 5; $$i++) { \
try { Invoke-WebRequest -Uri $$url -OutFile $$tmp -UseBasicParsing; $$ok = $$true; break } \
catch { Write-Host \"Download attempt $$i/5 failed: $$($$_.Exception.Message); retrying...\"; Start-Sleep -Seconds 3 } \
}; \
if (-not $$ok) { throw \"Failed to download $$url after 5 attempts\" }; \
Expand-Archive -Path $$tmp -DestinationPath $$dir -Force; \
Remove-Item $$tmp -Force -ErrorAction SilentlyContinue; \
if (-not (Test-Path \"$$dir/build/bin/llama-server.exe\")) { \
if (Test-Path \"$$dir/llama-server.exe\") { \
Write-Host 'Relocating flat-extracted binaries into build/bin/...'; \
New-Item -ItemType Directory -Path \"$$dir/build/bin\" -Force | Out-Null; \
Get-ChildItem \"$$dir\" -File | Where-Object { $$_.Name -ne 'version.txt' -and $$_.Name -ne 'backend.txt' } | Move-Item -Destination \"$$dir/build/bin/\"; \
} \
}; \
Write-Host \"TurboQuant CPU backend ($$backend) downloaded successfully. App will auto-download GPU backend at runtime.\"; \
"
# Full Windows release build (local, no code signing).
# Mirrors CI pipeline from release.yml: CPU-only backend, NSIS + MSI installers.
# Output: src-tauri/target/release/bundle/nsis/*.exe
build-windows-release:
ifeq ($(OS),Windows_NT)
powershell -ExecutionPolicy Bypass -File scripts/build-windows-release.ps1
else
@echo "This target is for Windows only."
endif
# Download upstream ggml-org/llama.cpp backend for bundling alongside the
# turboquant fork on macOS. We ship BOTH backends in the DMG so users can pick
# the "Llama.cpp" provider (vanilla upstream) or the "llama.cpp" provider
# (TurboQuant fork) at runtime. The upstream binary is NOT a fork — we just
# re-codesign the official release with our Developer ID so it survives
# notarization. See ADR in AGENTS.md §7.
#
# Backend-index source (ATO-199): the Windows and Linux branches resolve the
# release tag + asset names from the static manifest in atomic-chat-conf
# (raw.githubusercontent.com — no per-IP rate limit), mirroring the runtime
# `fetchRemoteBackends()` source. The archive downloads themselves still come
# from the ggml-org CDN (LLAMACPP_DOWNLOAD_BASE). macOS stays on
# api.github.com — its macos-arm64/-x64 assets are not in the manifest
# (macOS is bundle-only at runtime). GH_TOKEN only matters for the macOS
# branch now.
# Pinned to a known-good upstream release while we hold off on auto-tracking
# new ggml-org tags (2026-07-03). Override to pin a different release, or set
# to empty to resume auto-resolving the latest release (macOS) / the tag from
# the atomic-chat-conf manifest (Windows/Linux), e.g.:
# make download-llamacpp-upstream-backend LLAMACPP_UPSTREAM_TAG=b9222
# make download-llamacpp-upstream-backend LLAMACPP_UPSTREAM_TAG=
LLAMACPP_UPSTREAM_TAG ?= b9937
download-llamacpp-upstream-backend:
ifeq ($(shell uname -s),Darwin)
@rm -rf src-tauri/resources/llamacpp-backend-upstream
@mkdir -p src-tauri/resources/llamacpp-backend-upstream
@ARCH=$$(uname -m); \
if [ "$$ARCH" = "arm64" ]; then BACKEND="macos-arm64"; else BACKEND="macos-x64"; fi; \
echo "Platform: $$BACKEND (upstream)"; \
if [ -n "$(LLAMACPP_UPSTREAM_TAG)" ]; then \
TAG="$(LLAMACPP_UPSTREAM_TAG)"; \
echo "Using pinned upstream release: $$TAG"; \
else \
echo "Fetching latest upstream llama.cpp release..."; \
TMPREL=$$(mktemp /tmp/llamacpp-upstream-XXXXXX.json); \
API_URL="https://api.github.com/repos/ggml-org/llama.cpp/releases?per_page=20"; \
_gh_get() { \
if [ "$$1" = "1" ] && [ -n "$$GH_TOKEN" ]; then \
curl -sS -H "Authorization: Bearer $$GH_TOKEN" -H "Accept: application/vnd.github+json" -H "User-Agent: atomic-chat-ci" -o "$$2" -w "%{http_code}" "$$3" || echo "000"; \
else \
curl -sS -H "Accept: application/vnd.github+json" -H "User-Agent: atomic-chat-ci" -o "$$2" -w "%{http_code}" "$$3" || echo "000"; \
fi; \
}; \
_gh_fetch() { \
HTTP_CODE=""; \
for attempt in 1 2 3 4 5; do \
HTTP_CODE=$$(_gh_get "$$1" "$$2" "$$3"); \
case "$$HTTP_CODE" in \
2*) return 0 ;; \
403|429|5*|000) \
echo " GitHub API attempt $$attempt/5 (auth=$$1): HTTP $$HTTP_CODE, retrying in $$((attempt * 2))s..."; \
sleep $$((attempt * 2)) ;; \
*) return 1 ;; \
esac; \
done; \
return 1; \
}; \
_tag_ok() { \
[ -s "$$1" ] && [ "$$(jq -r 'if type=="array" then length else 0 end' "$$1" 2>/dev/null)" -gt 0 ]; \
}; \
USE_TOKEN=0; [ -n "$$GH_TOKEN" ] && USE_TOKEN=1; \
_gh_fetch "$$USE_TOKEN" "$$TMPREL" "$$API_URL" || true; \
FIRST_CODE="$$HTTP_CODE"; \
if ! _tag_ok "$$TMPREL" && [ "$$USE_TOKEN" = "1" ]; then \
echo "Token-authenticated request did not yield a tag_name (HTTP $$FIRST_CODE); retrying unauthenticated..."; \
_gh_fetch "0" "$$TMPREL" "$$API_URL" || true; \
fi; \
case "$$HTTP_CODE" in \
2*) ;; \
*) echo "Error: GitHub API failed (last HTTP $$HTTP_CODE)"; \
echo " body (first 500 bytes):"; head -c 500 "$$TMPREL" 2>/dev/null || true; echo; \
rm -f "$$TMPREL"; exit 1 ;; \
esac; \
TAG=$$(jq -r --arg suf "-bin-$$BACKEND.tar.gz" '[ .[] | select((.draft // false)|not) | select((.prerelease // false)|not) | . as $$r | select(($$r.assets // []) | any(.name == ("llama-" + $$r.tag_name + $$suf))) ] | .[0].tag_name // empty' "$$TMPREL"); \
if [ -z "$$TAG" ] || [ "$$TAG" = "null" ]; then \
echo "Error: no recent release carries asset llama-<tag>-bin-$$BACKEND.tar.gz (upstream asset upload may be in progress):"; \
head -c 500 "$$TMPREL" 2>/dev/null || true; echo; \
rm -f "$$TMPREL"; exit 1; \
fi; \
rm -f "$$TMPREL"; \
fi; \
URL="https://github.com/ggml-org/llama.cpp/releases/download/$$TAG/llama-$$TAG-bin-$$BACKEND.tar.gz"; \
echo "$$TAG" > src-tauri/resources/llamacpp-backend-upstream/version.txt; \
echo "$$BACKEND" > src-tauri/resources/llamacpp-backend-upstream/backend.txt; \
echo "Release: $$TAG Backend: $$BACKEND"; \
echo "Downloading: $$URL"; \
curl -fSL --retry 5 --retry-delay 3 "$$URL" -o /tmp/llamacpp-upstream-backend.tar.gz; \
tar -xzf /tmp/llamacpp-upstream-backend.tar.gz -C src-tauri/resources/llamacpp-backend-upstream/; \
rm -f /tmp/llamacpp-upstream-backend.tar.gz; \
if [ ! -f "src-tauri/resources/llamacpp-backend-upstream/build/bin/llama-server" ]; then \
if [ -f "src-tauri/resources/llamacpp-backend-upstream/bin/llama-server" ]; then \
echo "Relocating bin/ → build/bin/ to match expected layout..."; \
mkdir -p src-tauri/resources/llamacpp-backend-upstream/build; \
mv src-tauri/resources/llamacpp-backend-upstream/bin src-tauri/resources/llamacpp-backend-upstream/build/bin; \
elif [ -f "src-tauri/resources/llamacpp-backend-upstream/llama-server" ]; then \
echo "Relocating flat layout → build/bin/..."; \
mkdir -p src-tauri/resources/llamacpp-backend-upstream/build/bin; \
find src-tauri/resources/llamacpp-backend-upstream -maxdepth 1 -type f \( -name "llama-*" -o -name "*.dylib" -o -name "*.metal" \) -exec mv {} src-tauri/resources/llamacpp-backend-upstream/build/bin/ \;; \
else \
NESTED_DIR=$$(find src-tauri/resources/llamacpp-backend-upstream -maxdepth 1 -type d -name 'llama-*' | head -1); \
if [ -n "$$NESTED_DIR" ] && [ -f "$$NESTED_DIR/llama-server" ]; then \
echo "Relocating $$NESTED_DIR/ → build/bin/ ..."; \
mkdir -p src-tauri/resources/llamacpp-backend-upstream/build/bin; \
find "$$NESTED_DIR" -maxdepth 1 -type f -exec mv {} src-tauri/resources/llamacpp-backend-upstream/build/bin/ \;; \
find "$$NESTED_DIR" -maxdepth 1 -type l -exec mv {} src-tauri/resources/llamacpp-backend-upstream/build/bin/ \;; \
rmdir "$$NESTED_DIR" 2>/dev/null || rm -rf "$$NESTED_DIR"; \
fi; \
fi; \
fi; \
echo "Downloaded and extracted upstream llamacpp backend successfully"
@SIGNING_IDENTITY=$$(security find-identity -v -p codesigning | grep "Developer ID Application" | head -1 | sed 's/.*"\(.*\)".*/\1/'); \
if [ -n "$$SIGNING_IDENTITY" ]; then \