superlocalmemory 3.6.23 → 3.7.1
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/CHANGELOG.md +61 -0
- package/README.md +271 -71
- package/bin/slm-npm +43 -89
- package/ide/configs/antigravity-mcp.json +2 -2
- package/ide/configs/chatgpt-desktop-mcp.json +1 -1
- package/ide/configs/claude-desktop-mcp.json +2 -2
- package/ide/configs/windsurf-mcp.json +2 -2
- package/ide/hooks/context-hook.js +6 -2
- package/ide/hooks/post-recall-hook.js +7 -3
- package/ide/hooks/tool-event-hook.sh +2 -1
- package/package.json +17 -9
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/_GENERATED.md +1 -1
- package/plugin/agents/slm-memory-advisor.md +1 -1
- package/plugin/requirements.txt +1 -1
- package/plugin/skills/slm-session/SKILL.md +1 -1
- package/plugin-src/agents/slm-memory-advisor.md +1 -1
- package/plugin-src/manifest.json +1 -1
- package/plugin-src/requirements.txt +1 -1
- package/plugin-src/rules/AGENTS.md +1 -1
- package/plugin-src/skills/slm-session/SKILL.md +1 -1
- package/pyproject.toml +40 -8
- package/scripts/postinstall-interactive.js +17 -94
- package/scripts/postinstall.js +185 -258
- package/scripts/preuninstall.js +9 -50
- package/src/superlocalmemory/__init__.py +2 -2
- package/src/superlocalmemory/attribution/mathematical_dna.py +1 -1
- package/src/superlocalmemory/attribution/signer.py +34 -19
- package/src/superlocalmemory/attribution/watermark.py +1 -1
- package/src/superlocalmemory/cli/_lazy_init.py +7 -7
- package/src/superlocalmemory/cli/commands.py +453 -191
- package/src/superlocalmemory/cli/context_commands.py +5 -4
- package/src/superlocalmemory/cli/daemon.py +282 -187
- package/src/superlocalmemory/cli/db_migrate.py +3 -1
- package/src/superlocalmemory/cli/diagnostics_cmd.py +28 -0
- package/src/superlocalmemory/cli/evidence_cmd.py +103 -0
- package/src/superlocalmemory/cli/ingest_cmd.py +7 -3
- package/src/superlocalmemory/cli/main.py +128 -31
- package/src/superlocalmemory/cli/pending_store.py +54 -38
- package/src/superlocalmemory/cli/scale_engine_cmd.py +37 -0
- package/src/superlocalmemory/cli/service_installer.py +57 -52
- package/src/superlocalmemory/cli/setup_wizard.py +142 -88
- package/src/superlocalmemory/cli/version_banner.py +2 -1
- package/src/superlocalmemory/code_graph/config.py +3 -1
- package/src/superlocalmemory/core/backend_orchestrator.py +81 -21
- package/src/superlocalmemory/core/config.py +65 -20
- package/src/superlocalmemory/core/consolidation_engine.py +9 -7
- package/src/superlocalmemory/core/context_cache.py +56 -8
- package/src/superlocalmemory/core/derivation_lineage.py +246 -0
- package/src/superlocalmemory/core/embedding_worker.py +32 -20
- package/src/superlocalmemory/core/embeddings.py +54 -18
- package/src/superlocalmemory/core/engine.py +150 -104
- package/src/superlocalmemory/core/engine_ingestion.py +513 -0
- package/src/superlocalmemory/core/engine_wiring.py +32 -23
- package/src/superlocalmemory/core/evidence_bundle.py +526 -0
- package/src/superlocalmemory/core/fact_consolidator.py +5 -11
- package/src/superlocalmemory/core/graph_analyzer.py +2 -2
- package/src/superlocalmemory/core/health_monitor.py +4 -2
- package/src/superlocalmemory/core/ingestion_command.py +636 -0
- package/src/superlocalmemory/core/injection.py +69 -18
- package/src/superlocalmemory/core/lifecycle_state.py +153 -0
- package/src/superlocalmemory/core/maintenance.py +1 -1
- package/src/superlocalmemory/core/maintenance_scheduler.py +51 -35
- package/src/superlocalmemory/core/mutations.py +143 -0
- package/src/superlocalmemory/core/platform_utils.py +7 -4
- package/src/superlocalmemory/core/ram_lock.py +16 -5
- package/src/superlocalmemory/core/rate_limit.py +1 -1
- package/src/superlocalmemory/core/recall_pipeline.py +60 -101
- package/src/superlocalmemory/core/recall_worker.py +76 -59
- package/src/superlocalmemory/core/registry.py +1 -1
- package/src/superlocalmemory/core/scale_engine.py +293 -0
- package/src/superlocalmemory/core/score_contract.py +62 -0
- package/src/superlocalmemory/core/security_primitives.py +3 -1
- package/src/superlocalmemory/core/slm_disabled.py +3 -5
- package/src/superlocalmemory/core/store_pipeline.py +172 -40
- package/src/superlocalmemory/core/tier_manager.py +32 -20
- package/src/superlocalmemory/core/worker_pool.py +13 -4
- package/src/superlocalmemory/dynamics/activation_guided_quantization.py +1 -1
- package/src/superlocalmemory/dynamics/eap_scheduler.py +10 -3
- package/src/superlocalmemory/dynamics/ebbinghaus_langevin_coupling.py +1 -1
- package/src/superlocalmemory/dynamics/fisher_langevin_coupling.py +1 -1
- package/src/superlocalmemory/encoding/auto_linker.py +1 -1
- package/src/superlocalmemory/encoding/cognitive_consolidator.py +7 -16
- package/src/superlocalmemory/encoding/consolidator.py +22 -5
- package/src/superlocalmemory/encoding/fact_extractor.py +1 -1
- package/src/superlocalmemory/encoding/foresight.py +2 -0
- package/src/superlocalmemory/encoding/graph_builder.py +1 -1
- package/src/superlocalmemory/encoding/temporal_parser.py +2 -0
- package/src/superlocalmemory/evaluation/__init__.py +13 -0
- package/src/superlocalmemory/evaluation/calibration.py +308 -0
- package/src/superlocalmemory/evolution/skill_evolver.py +2 -1
- package/src/superlocalmemory/graph/cozo_backend.py +256 -23
- package/src/superlocalmemory/hooks/_outcome_common.py +21 -11
- package/src/superlocalmemory/hooks/antigravity_adapter.py +10 -31
- package/src/superlocalmemory/hooks/auto_invoker.py +25 -27
- package/src/superlocalmemory/hooks/auto_recall.py +31 -6
- package/src/superlocalmemory/hooks/auto_recall_hook.py +13 -33
- package/src/superlocalmemory/hooks/before_web_hook.py +9 -7
- package/src/superlocalmemory/hooks/claude_code_hooks.py +123 -35
- package/src/superlocalmemory/hooks/codex_assets.py +59 -0
- package/src/superlocalmemory/hooks/codex_hooks.py +186 -0
- package/src/superlocalmemory/hooks/context_payload.py +1 -1
- package/src/superlocalmemory/hooks/copilot_adapter.py +9 -24
- package/src/superlocalmemory/hooks/cursor_adapter.py +10 -32
- package/src/superlocalmemory/hooks/hook_daemon.py +4 -2
- package/src/superlocalmemory/hooks/hook_handlers.py +219 -32
- package/src/superlocalmemory/hooks/memory_protocol.py +5 -3
- package/src/superlocalmemory/hooks/post_tool_async_hook.py +4 -2
- package/src/superlocalmemory/hooks/session_registry.py +15 -8
- package/src/superlocalmemory/hooks/stop_outcome_hook.py +10 -6
- package/src/superlocalmemory/hooks/topic_shift_hook.py +42 -12
- package/src/superlocalmemory/hooks/user_prompt_hook.py +9 -14
- package/src/superlocalmemory/hooks/user_prompt_rehash_hook.py +19 -11
- package/src/superlocalmemory/infra/auth_middleware.py +38 -5
- package/src/superlocalmemory/infra/backup.py +7 -5
- package/src/superlocalmemory/infra/cloud_backup.py +18 -8
- package/src/superlocalmemory/infra/daemon_identity.py +248 -0
- package/src/superlocalmemory/infra/data_root.py +199 -0
- package/src/superlocalmemory/infra/event_bus.py +3 -1
- package/src/superlocalmemory/infra/local_diagnostics.py +327 -0
- package/src/superlocalmemory/infra/process_reaper.py +23 -0
- package/src/superlocalmemory/ingestion/adapter_manager.py +27 -9
- package/src/superlocalmemory/ingestion/base_adapter.py +25 -31
- package/src/superlocalmemory/ingestion/calendar_adapter.py +13 -4
- package/src/superlocalmemory/ingestion/credentials.py +14 -7
- package/src/superlocalmemory/ingestion/gmail_adapter.py +13 -4
- package/src/superlocalmemory/ingestion/transcript_adapter.py +7 -2
- package/src/superlocalmemory/learning/consolidation_quantization_worker.py +1 -1
- package/src/superlocalmemory/learning/ensemble.py +11 -0
- package/src/superlocalmemory/learning/entity_compiler.py +1 -1
- package/src/superlocalmemory/learning/feedback.py +1 -1
- package/src/superlocalmemory/learning/forgetting_scheduler.py +12 -7
- package/src/superlocalmemory/learning/quantization_scheduler.py +1 -1
- package/src/superlocalmemory/learning/ranker.py +4 -1
- package/src/superlocalmemory/learning/source_quality.py +1 -1
- package/src/superlocalmemory/learning/trigram_index.py +3 -2
- package/src/superlocalmemory/llm/backbone.py +1 -1
- package/src/superlocalmemory/math/ebbinghaus.py +1 -1
- package/src/superlocalmemory/math/fisher.py +1 -1
- package/src/superlocalmemory/math/fisher_quantized.py +1 -1
- package/src/superlocalmemory/math/hopfield.py +1 -1
- package/src/superlocalmemory/math/langevin.py +1 -1
- package/src/superlocalmemory/math/polar_quant.py +3 -4
- package/src/superlocalmemory/math/qjl.py +1 -1
- package/src/superlocalmemory/math/sheaf.py +1 -1
- package/src/superlocalmemory/math/turbo_quant.py +3 -2
- package/src/superlocalmemory/mcp/_daemon_proxy.py +12 -11
- package/src/superlocalmemory/mcp/_pool_adapter.py +27 -0
- package/src/superlocalmemory/mcp/http_transport.py +53 -0
- package/src/superlocalmemory/mcp/server.py +39 -13
- package/src/superlocalmemory/mcp/shared.py +69 -3
- package/src/superlocalmemory/mcp/tools_active.py +141 -31
- package/src/superlocalmemory/mcp/tools_core.py +130 -31
- package/src/superlocalmemory/mcp/tools_evolution.py +5 -7
- package/src/superlocalmemory/mcp/tools_learning.py +42 -2
- package/src/superlocalmemory/mcp/tools_mesh.py +7 -23
- package/src/superlocalmemory/mcp/tools_optimize.py +8 -1
- package/src/superlocalmemory/mcp/tools_v28.py +23 -2
- package/src/superlocalmemory/mcp/tools_v3.py +26 -1
- package/src/superlocalmemory/mcp/tools_v33.py +56 -17
- package/src/superlocalmemory/mesh/broker.py +2 -0
- package/src/superlocalmemory/mesh/remote_sync.py +50 -12
- package/src/superlocalmemory/optimize/cache/manager.py +77 -1
- package/src/superlocalmemory/optimize/cache/semantic.py +23 -3
- package/src/superlocalmemory/optimize/compress/ccr.py +4 -0
- package/src/superlocalmemory/optimize/compress/router.py +6 -1
- package/src/superlocalmemory/optimize/config/__init__.py +5 -0
- package/src/superlocalmemory/optimize/config/store.py +6 -4
- package/src/superlocalmemory/optimize/proxy/_helpers.py +15 -5
- package/src/superlocalmemory/optimize/proxy/capture.py +3 -2
- package/src/superlocalmemory/optimize/proxy/server.py +2 -2
- package/src/superlocalmemory/optimize/storage/db.py +12 -12
- package/src/superlocalmemory/retrieval/agentic.py +1 -1
- package/src/superlocalmemory/retrieval/ann_index.py +1 -1
- package/src/superlocalmemory/retrieval/bm25_channel.py +35 -11
- package/src/superlocalmemory/retrieval/bridge_discovery.py +73 -8
- package/src/superlocalmemory/retrieval/engine.py +169 -79
- package/src/superlocalmemory/retrieval/entity_channel.py +289 -67
- package/src/superlocalmemory/retrieval/forgetting_filter.py +1 -1
- package/src/superlocalmemory/retrieval/fusion.py +1 -1
- package/src/superlocalmemory/retrieval/hopfield_channel.py +118 -30
- package/src/superlocalmemory/retrieval/profile_channel.py +1 -1
- package/src/superlocalmemory/retrieval/quantization_aware_search.py +16 -10
- package/src/superlocalmemory/retrieval/reranker.py +56 -20
- package/src/superlocalmemory/retrieval/scope_policy.py +85 -0
- package/src/superlocalmemory/retrieval/semantic_channel.py +122 -14
- package/src/superlocalmemory/retrieval/spreading_activation.py +141 -25
- package/src/superlocalmemory/retrieval/strategy.py +1 -1
- package/src/superlocalmemory/retrieval/temporal_channel.py +30 -15
- package/src/superlocalmemory/retrieval/vector_store.py +1 -1
- package/src/superlocalmemory/server/api.py +10 -7
- package/src/superlocalmemory/server/bandit_loops.py +4 -2
- package/src/superlocalmemory/server/recall_serializer.py +24 -0
- package/src/superlocalmemory/server/route_mutations.py +84 -0
- package/src/superlocalmemory/server/routes/agents.py +8 -6
- package/src/superlocalmemory/server/routes/brain.py +14 -12
- package/src/superlocalmemory/server/routes/chat.py +29 -12
- package/src/superlocalmemory/server/routes/data_io.py +55 -24
- package/src/superlocalmemory/server/routes/helpers.py +8 -63
- package/src/superlocalmemory/server/routes/ingest.py +53 -36
- package/src/superlocalmemory/server/routes/memories.py +104 -43
- package/src/superlocalmemory/server/routes/mesh.py +31 -0
- package/src/superlocalmemory/server/routes/profiles.py +26 -4
- package/src/superlocalmemory/server/routes/tiers.py +43 -11
- package/src/superlocalmemory/server/routes/timeline.py +5 -1
- package/src/superlocalmemory/server/routes/v3_api.py +76 -21
- package/src/superlocalmemory/server/security_middleware.py +1 -1
- package/src/superlocalmemory/server/unified_daemon.py +680 -293
- package/src/superlocalmemory/server/write_identity.py +147 -0
- package/src/superlocalmemory/storage/access_log.py +4 -3
- package/src/superlocalmemory/storage/database.py +118 -25
- package/src/superlocalmemory/storage/migration_runner.py +84 -1
- package/src/superlocalmemory/storage/migration_v33.py +1 -1
- package/src/superlocalmemory/storage/migrations/M002_model_state_history.py +6 -60
- package/src/superlocalmemory/storage/migrations/M018_ingestion_operations.py +120 -0
- package/src/superlocalmemory/storage/migrations/M019_derivation_lineage.py +54 -0
- package/src/superlocalmemory/storage/migrations/M020_model_state_integrity.py +52 -0
- package/src/superlocalmemory/storage/migrations/__init__.py +5 -0
- package/src/superlocalmemory/storage/models.py +16 -0
- package/src/superlocalmemory/storage/quantized_store.py +20 -3
- package/src/superlocalmemory/storage/v2_migrator.py +5 -3
- package/src/superlocalmemory/ui/favicon.svg +5 -0
- package/src/superlocalmemory/ui/index.html +1 -0
- package/src/superlocalmemory/ui/js/compliance.js +1 -1
- package/src/superlocalmemory/ui/js/core.js +49 -8
- package/src/superlocalmemory/ui/js/dashboard.js +23 -2
- package/src/superlocalmemory/ui/js/feedback.js +1 -1
- package/src/superlocalmemory/ui/js/graph-filters.js +1 -1
- package/src/superlocalmemory/ui/js/graph-ui.js +1 -1
- package/src/superlocalmemory/ui/js/lifecycle.js +1 -1
- package/src/superlocalmemory/ui/js/ng-mesh.js +15 -49
- package/src/superlocalmemory/ui/js/settings.js +4 -2
- package/src/superlocalmemory/vector/lancedb_backend.py +57 -9
- package/bin/slm +0 -59
- package/bin/slm.bat +0 -77
- package/bin/slm.cmd +0 -5
- package/ide/integrations/langchain/README.md +0 -106
- package/ide/integrations/langchain/langchain_superlocalmemory/__init__.py +0 -9
- package/ide/integrations/langchain/langchain_superlocalmemory/chat_message_history.py +0 -201
- package/ide/integrations/langchain/pyproject.toml +0 -38
- package/ide/integrations/langchain/tests/__init__.py +0 -3
- package/ide/integrations/langchain/tests/test_chat_message_history.py +0 -215
- package/ide/integrations/langchain/tests/test_security.py +0 -117
- package/ide/integrations/llamaindex/README.md +0 -81
- package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/__init__.py +0 -9
- package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/base.py +0 -316
- package/ide/integrations/llamaindex/pyproject.toml +0 -43
- package/ide/integrations/llamaindex/tests/__init__.py +0 -3
- package/ide/integrations/llamaindex/tests/test_chat_store.py +0 -294
- package/ide/integrations/llamaindex/tests/test_security.py +0 -241
- package/scripts/__tests__/build-plugin.test.mjs +0 -613
- package/scripts/_savings_math.py +0 -270
- package/scripts/build-dmg.sh +0 -417
- package/scripts/build-plugin.js +0 -742
- package/scripts/build-slm-hook.ps1 +0 -40
- package/scripts/build-slm-hook.sh +0 -45
- package/scripts/build_entry.py +0 -452
- package/scripts/ci/stage5b_gate.sh +0 -50
- package/scripts/dogfood_savings.py +0 -490
- package/scripts/generate-thumbnails.py +0 -218
- package/scripts/install-skills.ps1 +0 -4
- package/scripts/install-skills.sh +0 -5
- package/scripts/install.ps1 +0 -701
- package/scripts/install.sh +0 -1015
- package/scripts/postinstall_binary.js +0 -287
- package/scripts/prepack.js +0 -33
- package/scripts/release_manifest.py +0 -273
- package/scripts/slm-hook.spec +0 -56
- package/scripts/start-dashboard.ps1 +0 -52
- package/scripts/start-dashboard.sh +0 -41
- package/scripts/sync-wiki.ps1 +0 -127
- package/scripts/sync-wiki.sh +0 -82
- package/scripts/test-dmg.sh +0 -161
- package/scripts/test-npm-package.ps1 +0 -252
- package/scripts/test-npm-package.sh +0 -207
- package/scripts/verify-install.ps1 +0 -294
- package/scripts/verify-install.sh +0 -266
- package/scripts/verify-v27.ps1 +0 -301
- package/scripts/verify-v27.sh +0 -233
- package/src/superlocalmemory.egg-info/PKG-INFO +0 -516
- package/src/superlocalmemory.egg-info/SOURCES.txt +0 -529
- package/src/superlocalmemory.egg-info/dependency_links.txt +0 -1
- package/src/superlocalmemory.egg-info/entry_points.txt +0 -2
- package/src/superlocalmemory.egg-info/requires.txt +0 -71
- package/src/superlocalmemory.egg-info/top_level.txt +0 -1
package/scripts/_savings_math.py
DELETED
|
@@ -1,270 +0,0 @@
|
|
|
1
|
-
# Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
|
|
2
|
-
# Licensed under AGPL-3.0-or-later - see LICENSE file
|
|
3
|
-
# Part of SuperLocalMemory V3 | https://qualixar.com | https://varunpratap.com
|
|
4
|
-
|
|
5
|
-
"""_savings_math.py — PURE math module for WP-14 metered-pipeline $-savings dogfood.
|
|
6
|
-
|
|
7
|
-
Zero I/O. No network. No side effects.
|
|
8
|
-
|
|
9
|
-
Prices used here come from R6 (2026-06-16):
|
|
10
|
-
Anthropic Sonnet 4.6: $3.00/MTok input, $15.00/MTok output — VERIFIED
|
|
11
|
-
Anthropic Opus 4.8: $5.00/MTok input, $25.00/MTok output — VERIFIED
|
|
12
|
-
Anthropic Haiku 4.5: $1.00/MTok input, $5.00/MTok output — VERIFIED
|
|
13
|
-
Cache-read multiplier: 0.10x (90% off cached portion) — VERIFIED
|
|
14
|
-
Source: https://platform.claude.com/docs/en/about-claude/pricing (fetched 2026-06-16)
|
|
15
|
-
|
|
16
|
-
OpenAI prices: UNVERIFIED-OFFICIAL (openai.com/api/pricing unreachable at fetch time;
|
|
17
|
-
data from aipricing.guru aggregator). Do not cite as authoritative.
|
|
18
|
-
|
|
19
|
-
CRIT notes (from LLD §9):
|
|
20
|
-
1. Blended input-price is a conservative lower-bound (output tokens cost more).
|
|
21
|
-
2. tokens_saved_compress is a word-count proxy (not a real tokenizer).
|
|
22
|
-
3. chars//4 is used in sim for input tokens (char-exact by construction).
|
|
23
|
-
"""
|
|
24
|
-
|
|
25
|
-
from __future__ import annotations
|
|
26
|
-
|
|
27
|
-
import dataclasses
|
|
28
|
-
import json
|
|
29
|
-
from datetime import datetime, timezone
|
|
30
|
-
from typing import Any
|
|
31
|
-
|
|
32
|
-
# ---------------------------------------------------------------------------
|
|
33
|
-
# Public types
|
|
34
|
-
# ---------------------------------------------------------------------------
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
@dataclasses.dataclass(frozen=False)
|
|
38
|
-
class MetricsDelta:
|
|
39
|
-
"""Difference between two MetricsSnapshots, clamped to ≥0.
|
|
40
|
-
|
|
41
|
-
Negative values indicate the DB was reset or shared between runs —
|
|
42
|
-
clamped to 0 (conservative, never overclaim).
|
|
43
|
-
"""
|
|
44
|
-
|
|
45
|
-
hits: int
|
|
46
|
-
misses: int
|
|
47
|
-
tokens_saved_input: int
|
|
48
|
-
tokens_saved_output: int
|
|
49
|
-
tokens_saved_compress: int
|
|
50
|
-
|
|
51
|
-
def __post_init__(self) -> None:
|
|
52
|
-
# Clamp all fields to ≥ 0 (immutable-safe: assign back to self)
|
|
53
|
-
self.hits = max(0, self.hits)
|
|
54
|
-
self.misses = max(0, self.misses)
|
|
55
|
-
self.tokens_saved_input = max(0, self.tokens_saved_input)
|
|
56
|
-
self.tokens_saved_output = max(0, self.tokens_saved_output)
|
|
57
|
-
self.tokens_saved_compress = max(0, self.tokens_saved_compress)
|
|
58
|
-
|
|
59
|
-
@property
|
|
60
|
-
def total_tokens_saved(self) -> int:
|
|
61
|
-
return (
|
|
62
|
-
self.tokens_saved_input
|
|
63
|
-
+ self.tokens_saved_output
|
|
64
|
-
+ self.tokens_saved_compress
|
|
65
|
-
)
|
|
66
|
-
|
|
67
|
-
@property
|
|
68
|
-
def hit_rate(self) -> float:
|
|
69
|
-
total = self.hits + self.misses
|
|
70
|
-
return self.hits / total if total > 0 else 0.0
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
@dataclasses.dataclass(frozen=True)
|
|
74
|
-
class Report:
|
|
75
|
-
"""Immutable dogfood savings report."""
|
|
76
|
-
|
|
77
|
-
mode: str # "sim" or "live"
|
|
78
|
-
provider: str
|
|
79
|
-
prompts: int
|
|
80
|
-
repeat: int
|
|
81
|
-
delta: MetricsDelta
|
|
82
|
-
price_per_1m: float
|
|
83
|
-
price_source: str # e.g. "DEFAULT_COST_PER_MILLION_INPUT_TOKENS[anthropic], 2026-06-07 (R6)"
|
|
84
|
-
savings_usd: float
|
|
85
|
-
savings_inr: float
|
|
86
|
-
inr_rate: float
|
|
87
|
-
|
|
88
|
-
def to_json(self) -> str:
|
|
89
|
-
"""Serialize to raw JSON ledger format per LLD §5."""
|
|
90
|
-
d = self.delta
|
|
91
|
-
formula_str = (
|
|
92
|
-
f"({d.tokens_saved_input} + {d.tokens_saved_output} + "
|
|
93
|
-
f"{d.tokens_saved_compress}) / 1_000_000 * {self.price_per_1m}"
|
|
94
|
-
)
|
|
95
|
-
payload: dict[str, Any] = {
|
|
96
|
-
"mode": self.mode,
|
|
97
|
-
"provider": self.provider,
|
|
98
|
-
"workload": {
|
|
99
|
-
"prompts": self.prompts,
|
|
100
|
-
"repeat": self.repeat,
|
|
101
|
-
"total_requests": self.prompts * (1 + self.repeat),
|
|
102
|
-
},
|
|
103
|
-
"delta": {
|
|
104
|
-
"hits": d.hits,
|
|
105
|
-
"misses": d.misses,
|
|
106
|
-
"tokens_saved_input": d.tokens_saved_input,
|
|
107
|
-
"tokens_saved_output": d.tokens_saved_output,
|
|
108
|
-
"tokens_saved_compress": d.tokens_saved_compress,
|
|
109
|
-
"total_tokens_saved": d.total_tokens_saved,
|
|
110
|
-
"hit_rate": round(d.hit_rate, 4),
|
|
111
|
-
},
|
|
112
|
-
"pricing": {
|
|
113
|
-
"price_per_1m": self.price_per_1m,
|
|
114
|
-
"source": self.price_source,
|
|
115
|
-
"date": "2026-06-07",
|
|
116
|
-
"inr_rate": self.inr_rate,
|
|
117
|
-
},
|
|
118
|
-
"computed": {
|
|
119
|
-
"savings_usd": round(self.savings_usd, 6),
|
|
120
|
-
"savings_inr": round(self.savings_inr, 4),
|
|
121
|
-
"formula": formula_str,
|
|
122
|
-
},
|
|
123
|
-
"generated_at": datetime.now(timezone.utc).isoformat(),
|
|
124
|
-
}
|
|
125
|
-
return json.dumps(payload, indent=2)
|
|
126
|
-
|
|
127
|
-
def to_markdown(self) -> str:
|
|
128
|
-
"""Render human-readable Markdown report per LLD §5 / AC3."""
|
|
129
|
-
d = self.delta
|
|
130
|
-
mode_label = "simulated" if self.mode == "sim" else "live"
|
|
131
|
-
openai_note = (
|
|
132
|
-
"\n> **UNVERIFIED-OFFICIAL:** OpenAI prices sourced from third-party "
|
|
133
|
-
"aggregator (openai.com/api/pricing was unreachable). Do not cite externally."
|
|
134
|
-
if self.provider.lower() == "openai"
|
|
135
|
-
else ""
|
|
136
|
-
)
|
|
137
|
-
|
|
138
|
-
return f"""# SLM Optimize — Metered-Pipeline $-Savings Report ({mode_label.upper()})
|
|
139
|
-
|
|
140
|
-
> Generated by `scripts/dogfood_savings.py` — WP-14 evidence artifact.
|
|
141
|
-
> Mode: **{self.mode}** | Provider: **{self.provider}** | Date: {datetime.now(timezone.utc).strftime("%Y-%m-%d")}
|
|
142
|
-
|
|
143
|
-
## Workload
|
|
144
|
-
|
|
145
|
-
| Parameter | Value |
|
|
146
|
-
|-----------|-------|
|
|
147
|
-
| Prompts (N) | {self.prompts} |
|
|
148
|
-
| Repeats per prompt (R) | {self.repeat} |
|
|
149
|
-
| Total requests | {self.prompts * (1 + self.repeat)} |
|
|
150
|
-
| Hit rate | {d.hit_rate * 100:.1f}% (R/{1 + self.repeat} = {self.repeat}/{1 + self.repeat}) |
|
|
151
|
-
|
|
152
|
-
## Token Savings (delta from MetricsSnapshot)
|
|
153
|
-
|
|
154
|
-
| Component | Tokens saved |
|
|
155
|
-
|-----------|-------------|
|
|
156
|
-
| Input (cache hit) | {d.tokens_saved_input:,} |
|
|
157
|
-
| Output (cache hit) | {d.tokens_saved_output:,} |
|
|
158
|
-
| Compress (word-count proxy) | {d.tokens_saved_compress:,} |
|
|
159
|
-
| **Total** | **{d.total_tokens_saved:,}** |
|
|
160
|
-
|
|
161
|
-
> Tokens saved: produced by SHIPPED CacheManager.on_hit recovery (M-01/M-02), not invented.
|
|
162
|
-
|
|
163
|
-
## Pricing
|
|
164
|
-
|
|
165
|
-
| Field | Value |
|
|
166
|
-
|-------|-------|
|
|
167
|
-
| Price per 1M tokens | ${self.price_per_1m:.2f} (blended input, conservative lower bound) |
|
|
168
|
-
| Source | {self.price_source} |
|
|
169
|
-
| USD→INR rate | {self.inr_rate} |
|
|
170
|
-
{openai_note}
|
|
171
|
-
|
|
172
|
-
## Computed Savings
|
|
173
|
-
|
|
174
|
-
**Formula:** `total_tokens_saved / 1_000_000 × price_per_1m`
|
|
175
|
-
|
|
176
|
-
| Metric | Value |
|
|
177
|
-
|--------|-------|
|
|
178
|
-
| Savings (USD) | **${self.savings_usd:.4f}** |
|
|
179
|
-
| Savings (INR) | **₹{self.savings_inr:.2f}** |
|
|
180
|
-
|
|
181
|
-
> Formula: `({d.tokens_saved_input} + {d.tokens_saved_output} + {d.tokens_saved_compress}) / 1_000_000 × {self.price_per_1m} = {self.savings_usd:.6f} USD`
|
|
182
|
-
|
|
183
|
-
## Methodology & Caveats
|
|
184
|
-
|
|
185
|
-
1. **Simulated workload (this run):** synthetic prompts with char-exact content (chars//4 = exact tokens by construction). No API key or network required.
|
|
186
|
-
2. **Savings metric is REAL:** tokens_saved_* fields come from `CacheManager.on_hit` M-01/M-02 recovery in the SHIPPED proxy — not a hardcoded percentage.
|
|
187
|
-
3. **Blended input price is a conservative lower bound:** output tokens cost 5× more ($15/MTok for Sonnet 4.6 output vs $3 input). This report uses input price only → understates true savings.
|
|
188
|
-
4. **Hit rate is workload-dependent:** this run simulates R={self.repeat} repeats per prompt → hit_rate = R/(1+R) = {self.repeat/(1+self.repeat)*100:.1f}%. Real workloads vary.
|
|
189
|
-
5. **tokens_saved_compress is a word-count proxy** (not a real tokenizer). Field name "bytes" in DB schema is legacy; unit is word count.
|
|
190
|
-
6. **Pricing is a point-in-time snapshot** (source date: 2026-06-07). Verify before citing externally.
|
|
191
|
-
7. **Local/self-hosted usage = $0 savings** (no metered API). Savings apply only to metered API workloads (Anthropic, OpenAI, Gemini).
|
|
192
|
-
8. **Live mode:** `--mode live --i-will-spend-money + SLM_DOGFOOD_API_KEY` routes real HTTP through proxy at 127.0.0.1:8765. ~$0.05–$0.20 per run. Never run in CI.
|
|
193
|
-
|
|
194
|
-
## Verification
|
|
195
|
-
|
|
196
|
-
Cross-check with: `slm optimize savings --provider {self.provider} --json`
|
|
197
|
-
(same formula as `optimize_cmd.py:145-155`; numbers should agree within rounding).
|
|
198
|
-
"""
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
# ---------------------------------------------------------------------------
|
|
202
|
-
# Pure functions
|
|
203
|
-
# ---------------------------------------------------------------------------
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
def compute_savings_usd(
|
|
207
|
-
tokens_saved_input: int,
|
|
208
|
-
tokens_saved_output: int,
|
|
209
|
-
tokens_saved_compress: int,
|
|
210
|
-
*,
|
|
211
|
-
price_per_1m: float,
|
|
212
|
-
) -> float:
|
|
213
|
-
"""Compute estimated USD savings from token counts.
|
|
214
|
-
|
|
215
|
-
Contract-twin of optimize_cmd.py:145-155:
|
|
216
|
-
tokens_saved = ti + to + tc
|
|
217
|
-
estimated_savings_usd = tokens_saved / 1_000_000 * rate
|
|
218
|
-
|
|
219
|
-
CRIT-1: This uses the blended input price — a conservative lower bound.
|
|
220
|
-
Output tokens cost 5× more than input (e.g. $15 vs $3/MTok for Sonnet 4.6).
|
|
221
|
-
"""
|
|
222
|
-
total = tokens_saved_input + tokens_saved_output + tokens_saved_compress
|
|
223
|
-
return total / 1_000_000 * price_per_1m
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
def resolve_price(
|
|
227
|
-
provider: str,
|
|
228
|
-
default_table: dict[str, float],
|
|
229
|
-
overrides: dict[str, Any],
|
|
230
|
-
) -> tuple[float, str]:
|
|
231
|
-
"""Resolve price per 1M tokens for a provider.
|
|
232
|
-
|
|
233
|
-
Priority: overrides[provider].input_per_1m_usd > default_table[provider] > default_table["default"].
|
|
234
|
-
|
|
235
|
-
Returns (price, source_description).
|
|
236
|
-
"""
|
|
237
|
-
# 1. Check config overrides
|
|
238
|
-
if provider in overrides:
|
|
239
|
-
override_val = overrides[provider]
|
|
240
|
-
if isinstance(override_val, dict) and "input_per_1m_usd" in override_val:
|
|
241
|
-
price = float(override_val["input_per_1m_usd"])
|
|
242
|
-
return price, f"pricing_override[{provider}].input_per_1m_usd"
|
|
243
|
-
|
|
244
|
-
# 2. Default table lookup
|
|
245
|
-
if provider in default_table:
|
|
246
|
-
price = float(default_table[provider])
|
|
247
|
-
return price, f"default_table[{provider}]"
|
|
248
|
-
|
|
249
|
-
# 3. Fallback to "default"
|
|
250
|
-
price = float(default_table.get("default", 3.00))
|
|
251
|
-
return price, "default_table[default]"
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
def to_inr(usd: float, rate: float) -> float:
|
|
255
|
-
"""Convert USD to INR at the given exchange rate."""
|
|
256
|
-
return usd * rate
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
def metrics_delta(before: Any, after: Any) -> MetricsDelta:
|
|
260
|
-
"""Compute the delta between two MetricsSnapshots, clamping to ≥0.
|
|
261
|
-
|
|
262
|
-
Uses attribute access (compatible with MetricsSnapshot dataclass).
|
|
263
|
-
"""
|
|
264
|
-
return MetricsDelta(
|
|
265
|
-
hits=after.hits - before.hits,
|
|
266
|
-
misses=after.misses - before.misses,
|
|
267
|
-
tokens_saved_input=after.tokens_saved_input - before.tokens_saved_input,
|
|
268
|
-
tokens_saved_output=after.tokens_saved_output - before.tokens_saved_output,
|
|
269
|
-
tokens_saved_compress=after.tokens_saved_compress - before.tokens_saved_compress,
|
|
270
|
-
)
|
package/scripts/build-dmg.sh
DELETED
|
@@ -1,417 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
#
|
|
3
|
-
# SuperLocalMemory V2 - DMG Installer Build Script
|
|
4
|
-
#
|
|
5
|
-
# Copyright (c) 2026 Varun Pratap Bhardwaj
|
|
6
|
-
# Solution Architect & Original Creator
|
|
7
|
-
#
|
|
8
|
-
# Licensed under MIT License (see LICENSE file)
|
|
9
|
-
# Repository: https://github.com/qualixar/superlocalmemory
|
|
10
|
-
#
|
|
11
|
-
# ATTRIBUTION REQUIRED: This notice must be preserved in all copies.
|
|
12
|
-
#
|
|
13
|
-
|
|
14
|
-
set -e # Exit on any error
|
|
15
|
-
|
|
16
|
-
# ============================================================================
|
|
17
|
-
# CONFIGURATION
|
|
18
|
-
# ============================================================================
|
|
19
|
-
|
|
20
|
-
VERSION="2.7.4"
|
|
21
|
-
APP_NAME="SuperLocalMemory"
|
|
22
|
-
DMG_NAME="SuperLocalMemory-v${VERSION}-macos"
|
|
23
|
-
BUILD_DIR="build/dmg"
|
|
24
|
-
INSTALLER_DIR="${BUILD_DIR}/${APP_NAME}"
|
|
25
|
-
TEMP_DMG="${BUILD_DIR}/temp.dmg"
|
|
26
|
-
FINAL_DMG="${DMG_NAME}.dmg"
|
|
27
|
-
|
|
28
|
-
# Colors for output
|
|
29
|
-
RED='\033[0;31m'
|
|
30
|
-
GREEN='\033[0;32m'
|
|
31
|
-
YELLOW='\033[1;33m'
|
|
32
|
-
BLUE='\033[0;34m'
|
|
33
|
-
NC='\033[0m' # No Color
|
|
34
|
-
|
|
35
|
-
# ============================================================================
|
|
36
|
-
# FUNCTIONS
|
|
37
|
-
# ============================================================================
|
|
38
|
-
|
|
39
|
-
log_info() {
|
|
40
|
-
echo -e "${BLUE}[INFO]${NC} $1"
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
log_success() {
|
|
44
|
-
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
log_warning() {
|
|
48
|
-
echo -e "${YELLOW}[WARNING]${NC} $1"
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
log_error() {
|
|
52
|
-
echo -e "${RED}[ERROR]${NC} $1"
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
cleanup_build() {
|
|
56
|
-
log_info "Cleaning up previous build artifacts..."
|
|
57
|
-
rm -rf "${BUILD_DIR}"
|
|
58
|
-
rm -f "${FINAL_DMG}"
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
create_directory_structure() {
|
|
62
|
-
log_info "Creating directory structure..."
|
|
63
|
-
mkdir -p "${INSTALLER_DIR}"
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
copy_installer_files() {
|
|
67
|
-
log_info "Copying installer files..."
|
|
68
|
-
|
|
69
|
-
# Core installation script
|
|
70
|
-
cp install.sh "${INSTALLER_DIR}/"
|
|
71
|
-
|
|
72
|
-
# Source files
|
|
73
|
-
log_info " → Copying src/ directory..."
|
|
74
|
-
cp -r src "${INSTALLER_DIR}/"
|
|
75
|
-
|
|
76
|
-
# Binary files
|
|
77
|
-
log_info " → Copying bin/ directory..."
|
|
78
|
-
cp -r bin "${INSTALLER_DIR}/"
|
|
79
|
-
|
|
80
|
-
# MCP configurations
|
|
81
|
-
log_info " → Copying configs/ directory..."
|
|
82
|
-
cp -r configs "${INSTALLER_DIR}/"
|
|
83
|
-
|
|
84
|
-
# Universal skills
|
|
85
|
-
log_info " → Copying skills/ directory..."
|
|
86
|
-
cp -r skills "${INSTALLER_DIR}/"
|
|
87
|
-
|
|
88
|
-
# MCP server
|
|
89
|
-
log_info " → Copying mcp_server.py..."
|
|
90
|
-
cp mcp_server.py "${INSTALLER_DIR}/"
|
|
91
|
-
|
|
92
|
-
# Skills installer
|
|
93
|
-
log_info " → Copying install-skills.sh..."
|
|
94
|
-
cp install-skills.sh "${INSTALLER_DIR}/"
|
|
95
|
-
|
|
96
|
-
# Requirements files
|
|
97
|
-
log_info " → Copying requirements files..."
|
|
98
|
-
cp requirements.txt "${INSTALLER_DIR}/"
|
|
99
|
-
cp requirements-core.txt "${INSTALLER_DIR}/"
|
|
100
|
-
cp requirements-full.txt "${INSTALLER_DIR}/"
|
|
101
|
-
cp requirements-search.txt "${INSTALLER_DIR}/"
|
|
102
|
-
cp requirements-ui.txt "${INSTALLER_DIR}/"
|
|
103
|
-
cp requirements-learning.txt "${INSTALLER_DIR}/" 2>/dev/null || true
|
|
104
|
-
|
|
105
|
-
# UI server
|
|
106
|
-
log_info " → Copying ui_server.py..."
|
|
107
|
-
cp ui_server.py "${INSTALLER_DIR}/"
|
|
108
|
-
|
|
109
|
-
# Start scripts
|
|
110
|
-
log_info " → Copying start scripts..."
|
|
111
|
-
cp start-dashboard.sh "${INSTALLER_DIR}/"
|
|
112
|
-
|
|
113
|
-
# Verification script
|
|
114
|
-
log_info " → Copying verify-install.sh..."
|
|
115
|
-
cp verify-install.sh "${INSTALLER_DIR}/"
|
|
116
|
-
|
|
117
|
-
# Documentation
|
|
118
|
-
log_info " → Copying documentation..."
|
|
119
|
-
cp README.md "${INSTALLER_DIR}/" 2>/dev/null || true
|
|
120
|
-
cp LICENSE "${INSTALLER_DIR}/" 2>/dev/null || true
|
|
121
|
-
cp ATTRIBUTION.md "${INSTALLER_DIR}/" 2>/dev/null || true
|
|
122
|
-
cp CHANGELOG.md "${INSTALLER_DIR}/" 2>/dev/null || true
|
|
123
|
-
if [ -d "docs" ]; then
|
|
124
|
-
cp -r docs "${INSTALLER_DIR}/"
|
|
125
|
-
fi
|
|
126
|
-
|
|
127
|
-
# Shell completions
|
|
128
|
-
if [ -d "completions" ]; then
|
|
129
|
-
log_info " → Copying shell completions..."
|
|
130
|
-
cp -r completions "${INSTALLER_DIR}/"
|
|
131
|
-
fi
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
create_readme() {
|
|
135
|
-
log_info "Creating README-INSTALLATION.txt..."
|
|
136
|
-
|
|
137
|
-
cat > "${INSTALLER_DIR}/README-INSTALLATION.txt" << 'READMEEOF'
|
|
138
|
-
================================================================================
|
|
139
|
-
SuperLocalMemory V2.7.0 - macOS Installation
|
|
140
|
-
================================================================================
|
|
141
|
-
|
|
142
|
-
Thank you for downloading SuperLocalMemory V2!
|
|
143
|
-
|
|
144
|
-
QUICK START (2 minutes):
|
|
145
|
-
------------------------
|
|
146
|
-
|
|
147
|
-
1. Open Terminal (Applications → Utilities → Terminal)
|
|
148
|
-
|
|
149
|
-
2. Navigate to this folder:
|
|
150
|
-
cd /Volumes/SuperLocalMemory
|
|
151
|
-
|
|
152
|
-
3. Run the installer:
|
|
153
|
-
./install.sh
|
|
154
|
-
|
|
155
|
-
4. Follow the on-screen prompts
|
|
156
|
-
|
|
157
|
-
5. Restart your AI tools (Claude Desktop, Cursor, etc.)
|
|
158
|
-
|
|
159
|
-
WHAT GETS INSTALLED:
|
|
160
|
-
-------------------
|
|
161
|
-
• Core memory system → ~/.superlocalmemory/
|
|
162
|
-
• MCP server auto-configured for 11+ IDEs
|
|
163
|
-
• 6 universal skills (Claude Code, Continue.dev, Cody, etc.)
|
|
164
|
-
• CLI command: slm
|
|
165
|
-
• Dashboard server: start-dashboard.sh
|
|
166
|
-
|
|
167
|
-
VERIFICATION:
|
|
168
|
-
------------
|
|
169
|
-
After installation, verify by running:
|
|
170
|
-
slm status
|
|
171
|
-
|
|
172
|
-
NEED HELP?
|
|
173
|
-
---------
|
|
174
|
-
• Documentation: README.md
|
|
175
|
-
• Quick Start: docs/CLI-COMMANDS-REFERENCE.md
|
|
176
|
-
• GitHub Wiki: https://github.com/qualixar/superlocalmemory/wiki
|
|
177
|
-
• Issues: https://github.com/qualixar/superlocalmemory/issues
|
|
178
|
-
|
|
179
|
-
UNINSTALL:
|
|
180
|
-
---------
|
|
181
|
-
To uninstall:
|
|
182
|
-
rm -rf ~/.superlocalmemory
|
|
183
|
-
rm -f /usr/local/bin/slm
|
|
184
|
-
|
|
185
|
-
================================================================================
|
|
186
|
-
Created by: Varun Pratap Bhardwaj
|
|
187
|
-
License: MIT (Attribution Required)
|
|
188
|
-
Repository: https://github.com/qualixar/superlocalmemory
|
|
189
|
-
================================================================================
|
|
190
|
-
READMEEOF
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
create_installer_wrapper() {
|
|
194
|
-
log_info "Creating installer wrapper script..."
|
|
195
|
-
|
|
196
|
-
cat > "${INSTALLER_DIR}/INSTALL" << 'WRAPPEREOF'
|
|
197
|
-
#!/usr/bin/env bash
|
|
198
|
-
#
|
|
199
|
-
# SuperLocalMemory V2 - Installer Wrapper
|
|
200
|
-
# Double-click this file to install, or run from Terminal
|
|
201
|
-
#
|
|
202
|
-
|
|
203
|
-
# Detect if running from Finder (double-click)
|
|
204
|
-
if [ -t 0 ]; then
|
|
205
|
-
# Running in terminal
|
|
206
|
-
TERMINAL_MODE=true
|
|
207
|
-
else
|
|
208
|
-
# Running from Finder
|
|
209
|
-
TERMINAL_MODE=false
|
|
210
|
-
# Open Terminal and run installer
|
|
211
|
-
osascript <<APPLESCRIPT
|
|
212
|
-
tell application "Terminal"
|
|
213
|
-
activate
|
|
214
|
-
do script "cd \"$(dirname \"$0\")\" && ./install.sh && echo '\\n\\nPress Enter to close...' && read"
|
|
215
|
-
end tell
|
|
216
|
-
APPLESCRIPT
|
|
217
|
-
exit 0
|
|
218
|
-
fi
|
|
219
|
-
|
|
220
|
-
# If in terminal mode, just run installer
|
|
221
|
-
cd "$(dirname "$0")"
|
|
222
|
-
./install.sh
|
|
223
|
-
|
|
224
|
-
echo ""
|
|
225
|
-
echo "Installation complete!"
|
|
226
|
-
echo "Press Enter to close..."
|
|
227
|
-
read
|
|
228
|
-
WRAPPEREOF
|
|
229
|
-
|
|
230
|
-
chmod +x "${INSTALLER_DIR}/INSTALL"
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
create_dmg_background() {
|
|
234
|
-
log_info "Creating DMG background image..."
|
|
235
|
-
|
|
236
|
-
# Create a simple background with instructions
|
|
237
|
-
# (This uses ImageMagick if available, otherwise creates a simple text file)
|
|
238
|
-
|
|
239
|
-
if command -v convert &> /dev/null; then
|
|
240
|
-
log_info " → Using ImageMagick to create background..."
|
|
241
|
-
convert -size 640x480 xc:white \
|
|
242
|
-
-font Helvetica -pointsize 20 \
|
|
243
|
-
-fill black -gravity north \
|
|
244
|
-
-annotate +0+50 "SuperLocalMemory" \
|
|
245
|
-
-pointsize 14 -gravity center \
|
|
246
|
-
-annotate +0+0 "Drag to Applications folder\nor double-click INSTALL to begin" \
|
|
247
|
-
"${BUILD_DIR}/.background.png" 2>/dev/null || true
|
|
248
|
-
else
|
|
249
|
-
log_warning " → ImageMagick not found, skipping background image"
|
|
250
|
-
fi
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
create_symlink() {
|
|
254
|
-
log_info "Creating Applications folder symlink..."
|
|
255
|
-
ln -s /Applications "${BUILD_DIR}/Applications"
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
create_dmg() {
|
|
259
|
-
log_info "Creating temporary DMG..."
|
|
260
|
-
|
|
261
|
-
# Calculate size needed (add 50MB buffer)
|
|
262
|
-
SIZE=$(du -sm "${INSTALLER_DIR}" | awk '{print $1}')
|
|
263
|
-
SIZE=$((SIZE + 50))
|
|
264
|
-
|
|
265
|
-
log_info " → Allocating ${SIZE}MB for DMG..."
|
|
266
|
-
|
|
267
|
-
# Create writable DMG
|
|
268
|
-
hdiutil create \
|
|
269
|
-
-volname "${APP_NAME}" \
|
|
270
|
-
-srcfolder "${BUILD_DIR}" \
|
|
271
|
-
-ov \
|
|
272
|
-
-format UDRW \
|
|
273
|
-
-size ${SIZE}m \
|
|
274
|
-
"${TEMP_DMG}"
|
|
275
|
-
|
|
276
|
-
log_info "Mounting temporary DMG..."
|
|
277
|
-
DEVICE=$(hdiutil attach -readwrite -noverify -noautoopen "${TEMP_DMG}" | \
|
|
278
|
-
grep -E '^/dev/' | sed 1q | awk '{print $1}')
|
|
279
|
-
|
|
280
|
-
log_info " → Mounted at: ${DEVICE}"
|
|
281
|
-
|
|
282
|
-
# Wait for mount
|
|
283
|
-
sleep 2
|
|
284
|
-
|
|
285
|
-
MOUNT_POINT="/Volumes/${APP_NAME}"
|
|
286
|
-
|
|
287
|
-
# Set custom icon positions (if possible)
|
|
288
|
-
log_info "Configuring Finder view settings..."
|
|
289
|
-
|
|
290
|
-
osascript <<APPLESCRIPT || log_warning "Could not set Finder view settings"
|
|
291
|
-
tell application "Finder"
|
|
292
|
-
tell disk "${APP_NAME}"
|
|
293
|
-
open
|
|
294
|
-
set current view of container window to icon view
|
|
295
|
-
set toolbar visible of container window to false
|
|
296
|
-
set statusbar visible of container window to false
|
|
297
|
-
set the bounds of container window to {100, 100, 740, 580}
|
|
298
|
-
set theViewOptions to the icon view options of container window
|
|
299
|
-
set arrangement of theViewOptions to not arranged
|
|
300
|
-
set icon size of theViewOptions to 128
|
|
301
|
-
set background picture of theViewOptions to file ".background.png"
|
|
302
|
-
|
|
303
|
-
-- Position installer folder
|
|
304
|
-
set position of item "${APP_NAME}" of container window to {160, 240}
|
|
305
|
-
|
|
306
|
-
-- Position Applications symlink
|
|
307
|
-
set position of item "Applications" of container window to {480, 240}
|
|
308
|
-
|
|
309
|
-
-- Position README
|
|
310
|
-
if exists item "README-INSTALLATION.txt" then
|
|
311
|
-
set position of item "README-INSTALLATION.txt" of container window to {320, 400}
|
|
312
|
-
end if
|
|
313
|
-
|
|
314
|
-
update without registering applications
|
|
315
|
-
delay 2
|
|
316
|
-
close
|
|
317
|
-
end tell
|
|
318
|
-
end tell
|
|
319
|
-
APPLESCRIPT
|
|
320
|
-
|
|
321
|
-
# Ensure everything is written
|
|
322
|
-
sync
|
|
323
|
-
sleep 2
|
|
324
|
-
|
|
325
|
-
log_info "Unmounting temporary DMG..."
|
|
326
|
-
hdiutil detach "${DEVICE}" -quiet || {
|
|
327
|
-
log_warning "Normal unmount failed, forcing..."
|
|
328
|
-
hdiutil detach "${DEVICE}" -force -quiet
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
log_info "Converting to compressed read-only DMG..."
|
|
332
|
-
hdiutil convert "${TEMP_DMG}" \
|
|
333
|
-
-format UDZO \
|
|
334
|
-
-imagekey zlib-level=9 \
|
|
335
|
-
-o "${FINAL_DMG}"
|
|
336
|
-
|
|
337
|
-
# Clean up temp DMG
|
|
338
|
-
rm -f "${TEMP_DMG}"
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
verify_dmg() {
|
|
342
|
-
log_info "Verifying DMG..."
|
|
343
|
-
|
|
344
|
-
if [ ! -f "${FINAL_DMG}" ]; then
|
|
345
|
-
log_error "DMG file not found: ${FINAL_DMG}"
|
|
346
|
-
return 1
|
|
347
|
-
fi
|
|
348
|
-
|
|
349
|
-
# Check DMG integrity
|
|
350
|
-
hdiutil verify "${FINAL_DMG}" && log_success "DMG verification passed"
|
|
351
|
-
|
|
352
|
-
# Show file size
|
|
353
|
-
SIZE=$(du -h "${FINAL_DMG}" | awk '{print $1}')
|
|
354
|
-
log_info "DMG size: ${SIZE}"
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
show_summary() {
|
|
358
|
-
echo ""
|
|
359
|
-
echo "============================================================================"
|
|
360
|
-
log_success "DMG BUILD COMPLETE!"
|
|
361
|
-
echo "============================================================================"
|
|
362
|
-
echo ""
|
|
363
|
-
echo " Output file: ${FINAL_DMG}"
|
|
364
|
-
echo " Size: $(du -h "${FINAL_DMG}" | awk '{print $1}')"
|
|
365
|
-
echo ""
|
|
366
|
-
echo "NEXT STEPS:"
|
|
367
|
-
echo " 1. Test the DMG:"
|
|
368
|
-
echo " open ${FINAL_DMG}"
|
|
369
|
-
echo ""
|
|
370
|
-
echo " 2. Upload to GitHub Releases:"
|
|
371
|
-
echo " gh release create v${VERSION} ${FINAL_DMG} \\"
|
|
372
|
-
echo " --title \"SuperLocalMemory v${VERSION}\" \\"
|
|
373
|
-
echo " --notes \"See CHANGELOG.md for details\""
|
|
374
|
-
echo ""
|
|
375
|
-
echo " 3. Or upload manually:"
|
|
376
|
-
echo " https://github.com/qualixar/superlocalmemory/releases/new"
|
|
377
|
-
echo ""
|
|
378
|
-
echo "============================================================================"
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
# ============================================================================
|
|
382
|
-
# MAIN EXECUTION
|
|
383
|
-
# ============================================================================
|
|
384
|
-
|
|
385
|
-
main() {
|
|
386
|
-
log_info "Starting DMG build for SuperLocalMemory v${VERSION}..."
|
|
387
|
-
echo ""
|
|
388
|
-
|
|
389
|
-
# Check we're in the right directory
|
|
390
|
-
if [ ! -f "install.sh" ]; then
|
|
391
|
-
log_error "Must run from superlocalmemory repository root"
|
|
392
|
-
exit 1
|
|
393
|
-
fi
|
|
394
|
-
|
|
395
|
-
# Check required tools
|
|
396
|
-
if ! command -v hdiutil &> /dev/null; then
|
|
397
|
-
log_error "hdiutil not found (required for macOS DMG creation)"
|
|
398
|
-
exit 1
|
|
399
|
-
fi
|
|
400
|
-
|
|
401
|
-
# Build process
|
|
402
|
-
cleanup_build
|
|
403
|
-
create_directory_structure
|
|
404
|
-
copy_installer_files
|
|
405
|
-
create_readme
|
|
406
|
-
create_installer_wrapper
|
|
407
|
-
create_dmg_background
|
|
408
|
-
create_symlink
|
|
409
|
-
create_dmg
|
|
410
|
-
verify_dmg
|
|
411
|
-
show_summary
|
|
412
|
-
|
|
413
|
-
log_success "Build complete!"
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
# Run main function
|
|
417
|
-
main "$@"
|