2:I[7012,["4765","static/chunks/4765-f5afdf8061f456f3.js","9856","static/chunks/9856-3b185291364d9bef.js","6687","static/chunks/app/docs/%5B...slug%5D/page-e07536548216bee4.js"],"MarkdownRenderer"] 4:I[9856,["4765","static/chunks/4765-f5afdf8061f456f3.js","9856","static/chunks/9856-3b185291364d9bef.js","6687","static/chunks/app/docs/%5B...slug%5D/page-e07536548216bee4.js"],""] 5:I[4126,[],""] 7:I[9630,[],""] 8:I[4278,["9856","static/chunks/9856-3b185291364d9bef.js","8172","static/chunks/8172-b3a2d6fe4ae10d40.js","3185","static/chunks/app/layout-2814fa5d15b84fe4.js"],"HeadingProvider"] 9:I[1476,["9856","static/chunks/9856-3b185291364d9bef.js","8172","static/chunks/8172-b3a2d6fe4ae10d40.js","3185","static/chunks/app/layout-2814fa5d15b84fe4.js"],"Header"] a:I[3167,["9856","static/chunks/9856-3b185291364d9bef.js","8172","static/chunks/8172-b3a2d6fe4ae10d40.js","3185","static/chunks/app/layout-2814fa5d15b84fe4.js"],"Sidebar"] b:I[7409,["9856","static/chunks/9856-3b185291364d9bef.js","8172","static/chunks/8172-b3a2d6fe4ae10d40.js","3185","static/chunks/app/layout-2814fa5d15b84fe4.js"],"PageFrame"] 3:T28d7, # Feature Flags CLI Reference This document covers command-line tools and API calls for managing feature flags. ## Python Scripts ### Initialize Feature Flags The `init_feature_flags.py` script creates all flags from the shared definitions. ```bash cd /path/to/VoiceAssist/services/api-gateway # Preview what would be created (dry run) python scripts/init_feature_flags.py --dry-run # Initialize all flags (skips existing) python scripts/init_feature_flags.py # Force recreate all flags (WARNING: deletes existing!) python scripts/init_feature_flags.py --force # Initialize and migrate legacy flag names python scripts/init_feature_flags.py --migrate ``` **Options:** | Flag | Description | | ----------- | ----------------------------------------------------- | | `--dry-run` | Show what would be done without making changes | | `--force` | Delete and recreate all flags (requires confirmation) | | `--migrate` | Migrate legacy flag names to new dot-notation format | **Output Example:** ``` ✅ Feature flag initialization complete! ================================================== Created: 15 Skipped: 3 Errors: 0 ``` --- ## REST API (curl) All feature flag endpoints require admin authentication. Get a JWT token first: ```bash # Get admin token TOKEN=$(curl -s -X POST https://api.asimo.io/api/auth/login \ -H "Content-Type: application/json" \ -d '{"username": "admin", "password": "..."}' | jq -r '.access_token') ``` ### List All Flags ```bash curl -s https://api.asimo.io/api/admin/feature-flags \ -H "Authorization: Bearer $TOKEN" | jq ``` **Filter by environment:** ```bash curl -s "https://api.asimo.io/api/admin/feature-flags?environment=production" \ -H "Authorization: Bearer $TOKEN" | jq ``` **Include archived flags:** ```bash curl -s "https://api.asimo.io/api/admin/feature-flags?include_archived=true" \ -H "Authorization: Bearer $TOKEN" | jq ``` ### Get Specific Flag ```bash curl -s https://api.asimo.io/api/admin/feature-flags/ui.voice_mode_enabled \ -H "Authorization: Bearer $TOKEN" | jq ``` ### Create Flag ```bash curl -s -X POST https://api.asimo.io/api/admin/feature-flags \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "experiment.new_feature", "description": "Test new feature rollout", "flag_type": "boolean", "enabled": false, "default_value": false, "metadata": { "owner": "engineering", "criticality": "low" } }' | jq ``` ### Update Flag ```bash # Toggle enabled state curl -s -X PATCH https://api.asimo.io/api/admin/feature-flags/experiment.new_feature \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"enabled": true}' | jq # Update description curl -s -X PATCH https://api.asimo.io/api/admin/feature-flags/experiment.new_feature \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"description": "Updated description"}' | jq # Set rollout percentage curl -s -X PATCH https://api.asimo.io/api/admin/feature-flags/experiment.new_feature \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"rollout_percentage": 25}' | jq ``` ### Toggle Flag (Quick) ```bash curl -s -X POST https://api.asimo.io/api/admin/feature-flags/ui.voice_mode_enabled/toggle \ -H "Authorization: Bearer $TOKEN" | jq ``` ### Archive Flag (Soft Delete) ```bash curl -s -X POST https://api.asimo.io/api/admin/feature-flags/experiment.old_feature/archive \ -H "Authorization: Bearer $TOKEN" | jq ``` ### Delete Flag (Permanent) ```bash curl -s -X DELETE https://api.asimo.io/api/admin/feature-flags/experiment.test_flag \ -H "Authorization: Bearer $TOKEN" | jq ``` ### Update Variants (Multivariate Flags) ```bash curl -s -X PUT https://api.asimo.io/api/admin/feature-flags/experiment.pricing/variants \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "variants": [ {"name": "control", "value": "standard", "weight": 50}, {"name": "treatment_a", "value": "premium", "weight": 30}, {"name": "treatment_b", "value": "freemium", "weight": 20} ] }' | jq ``` ### Update Targeting Rules ```bash curl -s -X PUT https://api.asimo.io/api/admin/feature-flags/experiment.beta/targeting-rules \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "targeting_rules": { "rules": [ { "id": "beta-users", "priority": 1, "conditions": [ { "attribute": "userRole", "operator": "equals", "value": "beta_tester" } ], "resultEnabled": true } ], "defaultEnabled": false } }' | jq ``` --- ## Automation Scripts ### Bulk Enable/Disable by Category ```bash #!/bin/bash # enable-category.sh - Enable all flags in a category CATEGORY=$1 ENABLED=${2:-true} TOKEN=$(cat ~/.voiceassist_token) BASE_URL="https://api.asimo.io/api/admin/feature-flags" # Get all flags and filter by category FLAGS=$(curl -s "$BASE_URL" -H "Authorization: Bearer $TOKEN" | \ jq -r ".data.flags[] | select(.name | startswith(\"$CATEGORY.\")) | .name") for flag in $FLAGS; do echo "Setting $flag enabled=$ENABLED" curl -s -X PATCH "$BASE_URL/$flag" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d "{\"enabled\": $ENABLED}" > /dev/null done echo "Done! Updated flags in category: $CATEGORY" ``` **Usage:** ```bash ./enable-category.sh experiment true # Enable all experiment flags ./enable-category.sh experiment false # Disable all experiment flags ``` ### Export Flags to JSON ```bash #!/bin/bash # export-flags.sh - Export all flags to JSON backup TOKEN=$(cat ~/.voiceassist_token) OUTPUT="flags-backup-$(date +%Y%m%d).json" curl -s https://api.asimo.io/api/admin/feature-flags \ -H "Authorization: Bearer $TOKEN" | jq '.data.flags' > "$OUTPUT" echo "Exported flags to $OUTPUT" ``` ### Import Flags from JSON ```bash #!/bin/bash # import-flags.sh - Import flags from JSON backup INPUT=$1 TOKEN=$(cat ~/.voiceassist_token) BASE_URL="https://api.asimo.io/api/admin/feature-flags" for flag in $(jq -c '.[]' "$INPUT"); do name=$(echo "$flag" | jq -r '.name') echo "Importing $name..." curl -s -X POST "$BASE_URL" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d "$flag" > /dev/null 2>&1 || \ curl -s -X PATCH "$BASE_URL/$name" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d "$flag" > /dev/null done echo "Import complete!" ``` ### Check Flag Status ```bash #!/bin/bash # check-flag.sh - Quick flag status check FLAG=$1 TOKEN=$(cat ~/.voiceassist_token) curl -s "https://api.asimo.io/api/admin/feature-flags/$FLAG" \ -H "Authorization: Bearer $TOKEN" | \ jq '{name: .data.name, enabled: .data.enabled, type: .data.flag_type, value: .data.value}' ``` **Usage:** ```bash ./check-flag.sh ui.voice_mode_enabled # Output: {"name": "ui.voice_mode_enabled", "enabled": true, "type": "boolean", "value": null} ``` --- ## Testing Flag Definitions ### Run Sync Tests Verify TypeScript and Python flag definitions are in sync: ```bash cd /path/to/VoiceAssist/services/api-gateway # Run all sync tests pytest tests/test_flag_sync.py -v # Run specific test pytest tests/test_flag_sync.py::TestFlagSync::test_flag_names_match -v ``` **Test Output:** ``` tests/test_flag_sync.py::TestFlagSync::test_flag_names_match PASSED tests/test_flag_sync.py::TestFlagSync::test_categories_match PASSED tests/test_flag_sync.py::TestFlagSync::test_legacy_map_matches PASSED ... ======================== 14 passed in 0.42s ======================== ``` ### Validate Flag Definitions ```bash # Check all flags follow naming convention pytest tests/test_flag_sync.py::TestFlagSync::test_flag_naming_convention -v # Verify all flags have descriptions pytest tests/test_flag_sync.py::TestFlagSync::test_flag_descriptions_not_empty -v # Check docs URLs are valid pytest tests/test_flag_sync.py::TestFlagMetadata::test_docs_url_format -v ``` --- ## Deprecation Warnings ### Python When using deprecated flag names, warnings are logged: ```python from app.core.flag_definitions import resolve_flag_name # Using legacy name triggers deprecation warning resolved = resolve_flag_name("rag_strategy") # WARNING: Feature flag 'rag_strategy' is deprecated. Use 'backend.rag_strategy' instead. ``` ### TypeScript ```typescript import { resolveFlagName, isDeprecatedFlagName } from "@voiceassist/types"; // Check if name is deprecated if (isDeprecatedFlagName("rag_strategy")) { console.warn("Please migrate to new flag name"); } // Get new name with warning const newName = resolveFlagName("rag_strategy", true); // Console: [DEPRECATION] Feature flag 'rag_strategy' is deprecated... ``` --- ## Environment Variables | Variable | Default | Description | | ------------------------- | -------- | ---------------------------------- | | `FEATURE_FLAGS_CACHE_TTL` | `300` | Redis cache TTL in seconds | | `FEATURE_FLAGS_ENABLED` | `true` | Enable/disable feature flag system | | `REDIS_URL` | Required | Redis connection for flag caching | --- ## Troubleshooting ### Flag Not Found ```bash # Check if flag exists curl -s https://api.asimo.io/api/admin/feature-flags/backend.rag_strategy \ -H "Authorization: Bearer $TOKEN" | jq '.success' # If false, check available flags curl -s https://api.asimo.io/api/admin/feature-flags \ -H "Authorization: Bearer $TOKEN" | jq '.data.flags[].name' | grep rag ``` ### Cache Not Clearing ```bash # Manually invalidate Redis cache redis-cli -h redis DEL "feature_flag:ui.voice_mode_enabled" # Or wait for TTL (default 5 minutes) redis-cli -h redis TTL "feature_flag:ui.voice_mode_enabled" ``` ### Auth Token Expired ```bash # Refresh token TOKEN=$(curl -s -X POST https://api.asimo.io/api/auth/refresh \ -H "Content-Type: application/json" \ -d "{\"refresh_token\": \"$REFRESH_TOKEN\"}" | jq -r '.access_token') ``` --- ## Related Documentation - [Feature Flags Overview](./README.md) - [Admin Panel Guide](./admin-panel-guide.md) - [Backend Implementation](../../FEATURE_FLAGS.md) - [Naming Conventions](./naming-conventions.md) 6:["slug","admin-guide/feature-flags/cli-reference","c"] 0:["X7oMT3VrOffzp0qvbeOas",[[["",{"children":["docs",{"children":[["slug","admin-guide/feature-flags/cli-reference","c"],{"children":["__PAGE__?{\"slug\":[\"admin-guide\",\"feature-flags\",\"cli-reference\"]}",{}]}]}]},"$undefined","$undefined",true],["",{"children":["docs",{"children":[["slug","admin-guide/feature-flags/cli-reference","c"],{"children":["__PAGE__",{},[["$L1",["$","div",null,{"children":[["$","div",null,{"className":"mb-6 flex items-center justify-between gap-4","children":[["$","div",null,{"children":[["$","p",null,{"className":"text-sm text-gray-500 dark:text-gray-400","children":"Docs / Raw"}],["$","h1",null,{"className":"text-3xl font-bold text-gray-900 dark:text-white","children":"Feature Flags CLI Reference"}],["$","p",null,{"className":"text-sm text-gray-600 dark:text-gray-400","children":["Sourced from"," ",["$","code",null,{"className":"font-mono text-xs","children":["docs/","admin-guide/feature-flags/cli-reference.md"]}]]}]]}],["$","a",null,{"href":"https://github.com/mohammednazmy/VoiceAssist/edit/main/docs/admin-guide/feature-flags/cli-reference.md","target":"_blank","rel":"noreferrer","className":"inline-flex items-center gap-2 rounded-md border border-gray-200 dark:border-gray-700 px-3 py-1.5 text-sm text-gray-700 dark:text-gray-200 hover:border-primary-500 dark:hover:border-primary-400 hover:text-primary-700 dark:hover:text-primary-300","children":"Edit on GitHub"}]]}],["$","div",null,{"className":"rounded-lg border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900 p-6","children":["$","$L2",null,{"content":"$3"}]}],["$","div",null,{"className":"mt-6 flex flex-wrap gap-2 text-sm","children":[["$","$L4",null,{"href":"/reference/all-docs","className":"inline-flex items-center gap-1 rounded-md bg-gray-100 px-3 py-1 text-gray-700 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-200 dark:hover:bg-gray-700","children":"← All documentation"}],["$","$L4",null,{"href":"/","className":"inline-flex items-center gap-1 rounded-md bg-gray-100 px-3 py-1 text-gray-700 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-200 dark:hover:bg-gray-700","children":"Home"}]]}]]}],null],null],null]},[null,["$","$L5",null,{"parallelRouterKey":"children","segmentPath":["children","docs","children","$6","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L7",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[null,["$","$L5",null,{"parallelRouterKey":"children","segmentPath":["children","docs","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L7",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/_next/static/css/7f586cdbbaa33ff7.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","className":"h-full","children":["$","body",null,{"className":"__className_f367f3 h-full bg-white dark:bg-gray-900","children":[["$","a",null,{"href":"#main-content","className":"skip-to-content","children":"Skip to main content"}],["$","$L8",null,{"children":[["$","$L9",null,{}],["$","$La",null,{}],["$","main",null,{"id":"main-content","className":"lg:pl-64","role":"main","aria-label":"Documentation content","children":["$","$Lb",null,{"children":["$","$L5",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L7",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":"404"}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]]}]]}]}]],null],null],["$Lc",null]]]] c:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"Feature Flags CLI Reference | Docs | VoiceAssist Docs"}],["$","meta","3",{"name":"description","content":"Command-line interface reference for managing feature flags"}],["$","meta","4",{"name":"keywords","content":"VoiceAssist,documentation,medical AI,voice assistant,healthcare,HIPAA,API"}],["$","meta","5",{"name":"robots","content":"index, follow"}],["$","meta","6",{"name":"googlebot","content":"index, follow"}],["$","link","7",{"rel":"canonical","href":"https://assistdocs.asimo.io"}],["$","meta","8",{"property":"og:title","content":"VoiceAssist Documentation"}],["$","meta","9",{"property":"og:description","content":"Comprehensive documentation for VoiceAssist - Enterprise Medical AI Assistant"}],["$","meta","10",{"property":"og:url","content":"https://assistdocs.asimo.io"}],["$","meta","11",{"property":"og:site_name","content":"VoiceAssist Docs"}],["$","meta","12",{"property":"og:type","content":"website"}],["$","meta","13",{"name":"twitter:card","content":"summary"}],["$","meta","14",{"name":"twitter:title","content":"VoiceAssist Documentation"}],["$","meta","15",{"name":"twitter:description","content":"Comprehensive documentation for VoiceAssist - Enterprise Medical AI Assistant"}],["$","meta","16",{"name":"next-size-adjust"}]] 1:null