@thinkingai/ae-cli 6.1.10 → 6.1.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -3
- package/README.zh.md +25 -3
- package/dist/{auth-56Z45UVR.js → auth-GDV3H5I4.js} +18 -8
- package/dist/{auth-ENM3FE6L.js → auth-NN55553T.js} +3 -3
- package/dist/{capability-2H6PAOA3.js → capability-DRLGDVS4.js} +24 -17
- package/dist/{capability-YPOQX6PL.js → capability-P6GK3AQH.js} +24 -17
- package/dist/chunk-4NN5IWVN.js +26 -0
- package/dist/{chunk-DB4Q3ANU.js → chunk-6A2FUCIS.js} +3 -3
- package/dist/{chunk-PVBYJWC2.js → chunk-7KQWSBSL.js} +4 -4
- package/dist/{chunk-24BAVOX3.js → chunk-AXDXJTPC.js} +3 -1
- package/dist/{chunk-CPVTECJ3.js → chunk-DT6Y3TD7.js} +5 -5
- package/dist/{chunk-EGEIXA2Z.js → chunk-DWO43OIB.js} +0 -248
- package/dist/{chunk-V6FR6WTW.js → chunk-GJJA4CQZ.js} +34 -2
- package/dist/{chunk-RGXCNC4N.js → chunk-GS2P7LFD.js} +50 -17
- package/dist/{chunk-LYVNONC4.js → chunk-JHENBQ5B.js} +35 -0
- package/dist/{chunk-3P3562ZX.js → chunk-MVDZ7DBQ.js} +5 -5
- package/dist/{chunk-LCXU3AAT.js → chunk-NBPKWKRA.js} +2 -2
- package/dist/{chunk-HQ2A7ITL.js → chunk-RBNKI5ZW.js} +50 -17
- package/dist/{chunk-LHVM35J4.js → chunk-TS6BUGUY.js} +4 -4
- package/dist/chunk-TUKQZTMI.js +250 -0
- package/dist/{chunk-J7MZHDHQ.js → chunk-VKD5WQKN.js} +5 -5
- package/dist/{chunk-3KI3RRXX.js → chunk-VTXHDCBW.js} +3 -3
- package/dist/{chunk-KTYR3U6D.js → chunk-YTG6Q75E.js} +10 -6
- package/dist/{chunk-IR4ZLVPW.js → chunk-YV52FB5G.js} +25 -3
- package/dist/{chunk-E7UXXHO3.js → chunk-Z3OXWCIA.js} +3 -3
- package/dist/{cli-token-4SBMXAUK.js → cli-token-GL5MS5FK.js} +4 -4
- package/dist/{client-PP5FETMW.js → client-DAIPF7XN.js} +4 -4
- package/dist/{community-report-client-KK2QBANO.js → community-report-client-M2RW4MXD.js} +2 -2
- package/dist/config-4VZNLBKF.js +489 -0
- package/dist/index.js +94 -35
- package/dist/memory-RWJW4XFO.js +892 -0
- package/dist/memory-VO2ZJCRT.js +892 -0
- package/dist/{metadata-JIQ77HFY.js → metadata-YGTHR2XJ.js} +8 -8
- package/dist/{metadata-AN3YFZEV.js → metadata-ZRN2GHPN.js} +8 -8
- package/dist/{model-JASQVOFD.js → model-CLUIK3K5.js} +7 -5
- package/dist/{raw-XJCAT3HX.js → raw-52B4UKO4.js} +8 -7
- package/dist/sync-3REDHGY6.js +10259 -0
- package/dist/{te-agent-JHUG6DVV.js → te-agent-XNPELAKX.js} +580 -84
- package/dist/{te-analysis-ITKTO6JS.js → te-analysis-GJI5FZUL.js} +1152 -13
- package/dist/{te-analysis-RAC67YYD.js → te-analysis-N2BRDJZ5.js} +1152 -13
- package/dist/{te-community-QOYIYEJI.js → te-community-SQXKE5OO.js} +7 -7
- package/dist/{te-community-UFKI6ONP.js → te-community-TYSNU3NQ.js} +7 -7
- package/dist/{te-dataops-PZQ5NQLY.js → te-dataops-5TM7WZDI.js} +370 -189
- package/dist/{te-dataops-XTWVTJCA.js → te-dataops-OWIADNSM.js} +370 -189
- package/dist/{te-engage-NLZUPSBK.js → te-engage-L72HWRGO.js} +716 -118
- package/dist/{te-engage-E7F4HTXU.js → te-engage-QWM4GFS7.js} +716 -118
- package/dist/{te-experiment-N63WF7XA.js → te-experiment-2T2HEZML.js} +189 -10
- package/dist/{te-experiment-D32TB6ZB.js → te-experiment-PVEY7AEZ.js} +189 -10
- package/dist/{te-kb-E7NSCBRB.js → te-kb-VRMEY3D4.js} +6 -6
- package/dist/{te-meta-GBDTMPEL.js → te-meta-53BVXPFI.js} +7 -7
- package/dist/{te-meta-ZTLTSHXC.js → te-meta-TOCBPBXI.js} +7 -7
- package/dist/{te-system-AH7DMCAQ.js → te-system-XGS5EQIQ.js} +4 -4
- package/dist/{te-team-BQ3SKSZV.js → te-team-BZRDV2CM.js} +7 -7
- package/dist/{update-DKG6UXEM.js → update-HEDXGOJH.js} +8 -6
- package/package.json +5 -2
- package/skills/ae-agent/SKILL.md +178 -16
- package/skills/ae-agent/references/add-skill.md +22 -10
- package/skills/ae-agent/references/edit-skill.md +24 -14
- package/skills/ae-agent/references/find-archived-conversations.md +82 -0
- package/skills/ae-agent/references/restore-conversation.md +54 -0
- package/skills/ae-agent/references/upload-skill.md +24 -14
- package/skills/ae-analysis/SKILL.md +1 -1
- package/skills/ae-analysis/references/command_index.md +104 -42
- package/skills/ae-analysis/references/entity_id_import_options.md +1 -1
- package/skills/ae-analysis/references/project_access_detail_get.md +3 -3
- package/skills/ae-analysis/references/project_data_power_delete.md +3 -3
- package/skills/ae-analysis/references/project_data_power_get.md +3 -3
- package/skills/ae-analysis/references/project_data_power_list.md +3 -3
- package/skills/ae-analysis/references/project_data_power_upsert.md +3 -3
- package/skills/ae-analysis/references/project_entity_create.md +3 -3
- package/skills/ae-analysis/references/project_entity_delete.md +3 -3
- package/skills/ae-analysis/references/project_entity_event_list.md +3 -3
- package/skills/ae-analysis/references/project_entity_get.md +3 -3
- package/skills/ae-analysis/references/project_entity_list.md +3 -3
- package/skills/ae-analysis/references/project_entity_update.md +3 -3
- package/skills/ae-analysis/references/project_function_list.md +3 -3
- package/skills/ae-analysis/references/project_info_create.md +25 -0
- package/skills/ae-analysis/references/project_info_delete.md +24 -0
- package/skills/ae-analysis/references/project_info_get.md +3 -3
- package/skills/ae-analysis/references/project_info_list.md +3 -3
- package/skills/ae-analysis/references/project_info_update.md +3 -3
- package/skills/ae-analysis/references/project_mark_time_create.md +3 -3
- package/skills/ae-analysis/references/project_mark_time_delete.md +3 -3
- package/skills/ae-analysis/references/project_mark_time_list.md +3 -3
- package/skills/ae-analysis/references/project_mark_time_update.md +3 -3
- package/skills/ae-analysis/references/project_member_add.md +3 -3
- package/skills/ae-analysis/references/project_member_batch_update.md +3 -3
- package/skills/ae-analysis/references/project_member_candidate_list.md +3 -3
- package/skills/ae-analysis/references/project_member_handover_export.md +3 -3
- package/skills/ae-analysis/references/project_member_handover_run.md +3 -3
- package/skills/ae-analysis/references/project_member_import.md +3 -3
- package/skills/ae-analysis/references/project_member_list.md +3 -3
- package/skills/ae-analysis/references/project_member_receiver_list.md +3 -3
- package/skills/ae-analysis/references/project_member_remove.md +3 -3
- package/skills/ae-analysis/references/project_member_update.md +3 -3
- package/skills/ae-analysis/references/project_owner_update.md +3 -3
- package/skills/ae-analysis/references/project_permission_binding_list.md +3 -3
- package/skills/ae-analysis/references/project_receive_status_update.md +3 -3
- package/skills/ae-analysis/references/project_role_delete.md +3 -3
- package/skills/ae-analysis/references/project_role_function_list.md +3 -3
- package/skills/ae-analysis/references/project_role_get.md +3 -3
- package/skills/ae-analysis/references/project_role_list.md +3 -3
- package/skills/ae-analysis/references/project_role_upsert.md +3 -3
- package/skills/ae-analysis/references/project_role_user_list.md +3 -3
- package/skills/ae-analysis/references/project_space_list.md +1 -1
- package/skills/ae-analysis/references/project_timezone_get.md +3 -3
- package/skills/ae-analysis/references/project_timezone_overview.md +3 -3
- package/skills/ae-analysis/references/project_timezone_update.md +3 -3
- package/skills/ae-analysis/references/project_user_id_items_update.md +3 -3
- package/skills/ae-analysis/references/system_admin_function_list.md +22 -0
- package/skills/ae-analysis/references/system_admin_function_update.md +26 -0
- package/skills/ae-analysis/references/system_admin_list.md +21 -0
- package/skills/ae-analysis/references/system_admin_remove.md +25 -0
- package/skills/ae-analysis/references/system_admin_upsert.md +25 -0
- package/skills/ae-analysis/references/system_function_list.md +21 -0
- package/skills/ae-analysis/references/system_member_add.md +25 -0
- package/skills/ae-analysis/references/system_member_candidate_list.md +22 -0
- package/skills/ae-analysis/references/system_member_delete.md +25 -0
- package/skills/ae-analysis/references/system_member_list.md +24 -0
- package/skills/ae-analysis/references/system_member_mfa_unbind.md +25 -0
- package/skills/ae-analysis/references/system_member_password_reset.md +32 -0
- package/skills/ae-analysis/references/system_member_project_batch_update.md +27 -0
- package/skills/ae-analysis/references/system_member_status_update.md +26 -0
- package/skills/ae-analysis/references/system_member_update.md +23 -0
- package/skills/ae-analysis/references/system_mfa_get.md +21 -0
- package/skills/ae-analysis/references/system_mfa_update.md +25 -0
- package/skills/ae-analysis/references/system_node_monitor_list.md +24 -0
- package/skills/ae-analysis/references/system_oauth2_update.md +22 -0
- package/skills/ae-analysis/references/system_ops_alert_contact_delete.md +25 -0
- package/skills/ae-analysis/references/system_ops_alert_contact_list.md +23 -0
- package/skills/ae-analysis/references/system_ops_alert_contact_test.md +29 -0
- package/skills/ae-analysis/references/system_ops_alert_contact_upsert.md +39 -0
- package/skills/ae-analysis/references/system_preference_get.md +21 -0
- package/skills/ae-analysis/references/system_preference_update.md +22 -0
- package/skills/ae-analysis/references/system_project_usage_list.md +28 -0
- package/skills/ae-analysis/references/system_query_alert_rule_list.md +21 -0
- package/skills/ae-analysis/references/system_query_alert_rule_update.md +26 -0
- package/skills/ae-analysis/references/system_query_monitor_overview.md +26 -0
- package/skills/ae-analysis/references/system_query_task_cancel.md +25 -0
- package/skills/ae-analysis/references/system_query_task_export.md +49 -0
- package/skills/ae-analysis/references/system_query_task_get.md +23 -0
- package/skills/ae-analysis/references/system_query_task_list.md +35 -0
- package/skills/ae-analysis/references/system_query_task_options.md +27 -0
- package/skills/ae-analysis/references/system_receiver_address_delete.md +26 -0
- package/skills/ae-analysis/references/system_receiver_address_overview.md +21 -0
- package/skills/ae-analysis/references/system_receiver_address_project_list.md +21 -0
- package/skills/ae-analysis/references/system_receiver_address_promote.md +24 -0
- package/skills/ae-analysis/references/system_receiver_address_upsert.md +27 -0
- package/skills/ae-analysis/references/system_receiver_detection_get.md +22 -0
- package/skills/ae-analysis/references/system_receiver_detection_run.md +22 -0
- package/skills/ae-analysis/references/system_receiver_detection_update.md +25 -0
- package/skills/ae-analysis/references/system_role_delete.md +26 -0
- package/skills/ae-analysis/references/system_role_function_list.md +22 -0
- package/skills/ae-analysis/references/system_role_get.md +22 -0
- package/skills/ae-analysis/references/system_role_list.md +24 -0
- package/skills/ae-analysis/references/system_role_upsert.md +27 -0
- package/skills/ae-analysis/references/system_role_user_list.md +22 -0
- package/skills/ae-analysis/references/system_seat_list.md +25 -0
- package/skills/ae-analysis/references/system_seat_update.md +26 -0
- package/skills/ae-analysis/references/system_smtp_delete.md +24 -0
- package/skills/ae-analysis/references/system_smtp_get.md +21 -0
- package/skills/ae-analysis/references/system_smtp_test.md +22 -0
- package/skills/ae-analysis/references/system_smtp_upsert.md +31 -0
- package/skills/ae-analysis/references/system_third_party_login_disable.md +25 -0
- package/skills/ae-analysis/references/system_third_party_login_list.md +21 -0
- package/skills/ae-analysis/references/system_third_party_login_upsert.md +33 -0
- package/skills/ae-analysis/references/system_usage_overview.md +21 -0
- package/skills/ae-analysis/references/system_usage_trend_export.md +44 -0
- package/skills/ae-analysis/references/system_usage_trend_query.md +28 -0
- package/skills/ae-dataops/SKILL.md +1 -1
- package/skills/ae-dataops/references/dataops-flow-create.md +51 -16
- package/skills/ae-engage/SKILL.md +67 -27
- package/skills/ae-engage/references/activity-activity.md +3 -0
- package/skills/ae-engage/references/activity-approval.md +12 -4
- package/skills/ae-engage/references/activity-data-detail.md +61 -0
- package/skills/ae-engage/references/activity-task.md +21 -6
- package/skills/ae-engage/references/activity-topic.md +31 -13
- package/skills/ae-engage/references/add-channel.md +170 -41
- package/skills/ae-engage/references/build-task-save-guide.md +66 -29
- package/skills/ae-engage/references/channel-update-config.md +3 -2
- package/skills/ae-engage/references/common-metric.md +88 -115
- package/skills/ae-engage/references/flow-detail.md +13 -0
- package/skills/ae-engage/references/preset-event.md +14 -32
- package/skills/ae-engage/references/save-flow.md +141 -64
- package/skills/ae-engage/references/save-task.md +231 -58
- package/skills/ae-engage/references/scene-config-metric.md +3 -0
- package/skills/ae-engage/references/scene-preset-metric.md +8 -37
- package/skills/ae-engage/references/scene-strategy-audience.md +56 -643
- package/skills/ae-engage/references/scene-strategy.md +6 -6
- package/skills/ae-engage/references/task-detail.md +10 -0
- package/skills/ae-engage/references/task-submit-approval.md +45 -0
- package/skills/ae-engage/references/validate-flow-node-config.md +1 -1
- package/skills/ae-experiment/SKILL.md +38 -5
- package/skills/ae-experiment/references/check_experiment_ready.md +2 -0
- package/skills/ae-experiment/references/delete_metric.md +2 -0
- package/skills/ae-experiment/references/query_experiment_detail.md +6 -0
- package/skills/ae-experiment/references/query_experiment_list.md +4 -0
- package/skills/ae-experiment/references/query_experiment_list_archived.md +3 -0
- package/skills/ae-experiment/references/query_experiment_metric_trend.md +5 -5
- package/skills/ae-experiment/references/query_experiment_report_summary.md +4 -3
- package/skills/ae-experiment/references/query_experiment_sample_size_report.md +6 -5
- package/skills/ae-experiment/references/query_metric_detail.md +5 -0
- package/skills/ae-experiment/references/query_metric_list.md +3 -0
- package/skills/ae-experiment/references/save_build_guide.md +39 -0
- package/skills/ae-experiment/references/save_experiment.md +53 -3
- package/skills/ae-experiment/references/save_metric.md +67 -1
- package/skills/ae-experiment/references/save_submit_experiment.md +3 -0
- package/skills/ae-experiment/references/save_validate.md +33 -0
- package/skills/ae-experiment-design/SKILL.md +149 -0
- package/skills/ae-experiment-design/agents/openai.yaml +4 -0
- package/skills/ae-experiment-design/references/client-experiment-sdk.md +147 -0
- package/skills/ae-experiment-design/references/experiment-creation.md +108 -0
- package/skills/ae-experiment-design/references/experiment-sdk-contract.md +100 -0
- package/skills/ae-experiment-design/references/exposure-contract.md +91 -0
- package/skills/ae-experiment-design/references/hybrid-experiment-sdk.md +74 -0
- package/skills/ae-experiment-design/references/metric-readiness.md +143 -0
- package/skills/ae-experiment-design/references/platform-operations.md +105 -0
- package/skills/ae-experiment-design/references/sdk-index.md +76 -0
- package/skills/ae-experiment-design/references/sdk-integration.md +114 -0
- package/skills/ae-experiment-design/references/sdk-troubleshooting.md +139 -0
- package/skills/ae-experiment-design/references/server-experiment-sdk.md +78 -0
- package/skills/ae-experiment-design/scripts/calculate_experiment_plan.py +450 -0
- package/skills/ae-experiment-insight/SKILL.md +149 -0
- package/skills/ae-experiment-insight/agents/openai.yaml +4 -0
- package/skills/ae-experiment-insight/references/decision-framework.md +69 -0
- package/skills/ae-experiment-insight/references/diagnostic-playbook.md +225 -0
- package/skills/ae-experiment-insight/references/platform-operations.md +82 -0
- package/skills/ae-experiment-insight/scripts/analyze_experiment.py +478 -0
- package/skills/ae-generate-tracking-code/SKILL.md +2 -2
- package/skills/ae-metadata/SKILL.md +1 -1
- package/dist/config-BSSALXEN.js +0 -128
- package/dist/sync-QFP4XFN3.js +0 -485
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Calculate deterministic SRM and treatment-versus-control comparisons."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import json
|
|
7
|
+
import math
|
|
8
|
+
import sys
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def normal_ppf(probability: float) -> float:
|
|
13
|
+
"""Return the inverse standard-normal CDF using Acklam's approximation."""
|
|
14
|
+
if not 0.0 < probability < 1.0:
|
|
15
|
+
raise ValueError("probability must be between 0 and 1")
|
|
16
|
+
a = (
|
|
17
|
+
-3.969683028665376e01,
|
|
18
|
+
2.209460984245205e02,
|
|
19
|
+
-2.759285104469687e02,
|
|
20
|
+
1.383577518672690e02,
|
|
21
|
+
-3.066479806614716e01,
|
|
22
|
+
2.506628277459239e00,
|
|
23
|
+
)
|
|
24
|
+
b = (
|
|
25
|
+
-5.447609879822406e01,
|
|
26
|
+
1.615858368580409e02,
|
|
27
|
+
-1.556989798598866e02,
|
|
28
|
+
6.680131188771972e01,
|
|
29
|
+
-1.328068155288572e01,
|
|
30
|
+
)
|
|
31
|
+
c = (
|
|
32
|
+
-7.784894002430293e-03,
|
|
33
|
+
-3.223964580411365e-01,
|
|
34
|
+
-2.400758277161838e00,
|
|
35
|
+
-2.549732539343734e00,
|
|
36
|
+
4.374664141464968e00,
|
|
37
|
+
2.938163982698783e00,
|
|
38
|
+
)
|
|
39
|
+
d = (
|
|
40
|
+
7.784695709041462e-03,
|
|
41
|
+
3.224671290700398e-01,
|
|
42
|
+
2.445134137142996e00,
|
|
43
|
+
3.754408661907416e00,
|
|
44
|
+
)
|
|
45
|
+
low = 0.02425
|
|
46
|
+
high = 1.0 - low
|
|
47
|
+
if probability < low:
|
|
48
|
+
q = math.sqrt(-2.0 * math.log(probability))
|
|
49
|
+
return (
|
|
50
|
+
(((((c[0] * q + c[1]) * q + c[2]) * q + c[3]) * q + c[4]) * q + c[5])
|
|
51
|
+
/ ((((d[0] * q + d[1]) * q + d[2]) * q + d[3]) * q + 1.0)
|
|
52
|
+
)
|
|
53
|
+
if probability <= high:
|
|
54
|
+
q = probability - 0.5
|
|
55
|
+
r = q * q
|
|
56
|
+
return (
|
|
57
|
+
(((((a[0] * r + a[1]) * r + a[2]) * r + a[3]) * r + a[4]) * r + a[5])
|
|
58
|
+
* q
|
|
59
|
+
/ (((((b[0] * r + b[1]) * r + b[2]) * r + b[3]) * r + b[4]) * r + 1.0)
|
|
60
|
+
)
|
|
61
|
+
q = math.sqrt(-2.0 * math.log(1.0 - probability))
|
|
62
|
+
return -(
|
|
63
|
+
(((((c[0] * q + c[1]) * q + c[2]) * q + c[3]) * q + c[4]) * q + c[5])
|
|
64
|
+
/ ((((d[0] * q + d[1]) * q + d[2]) * q + d[3]) * q + 1.0)
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def regularized_gamma_q(shape: float, value: float) -> float:
|
|
69
|
+
"""Return the regularized upper incomplete gamma function Q(shape, value)."""
|
|
70
|
+
if shape <= 0.0 or value < 0.0:
|
|
71
|
+
raise ValueError("invalid gamma arguments")
|
|
72
|
+
if value == 0.0:
|
|
73
|
+
return 1.0
|
|
74
|
+
epsilon = 3e-14
|
|
75
|
+
minimum = 1e-300
|
|
76
|
+
max_iterations = 10000
|
|
77
|
+
log_gamma = math.lgamma(shape)
|
|
78
|
+
|
|
79
|
+
if value < shape + 1.0:
|
|
80
|
+
term = 1.0 / shape
|
|
81
|
+
total = term
|
|
82
|
+
current = shape
|
|
83
|
+
for _ in range(max_iterations):
|
|
84
|
+
current += 1.0
|
|
85
|
+
term *= value / current
|
|
86
|
+
total += term
|
|
87
|
+
if abs(term) < abs(total) * epsilon:
|
|
88
|
+
lower = total * math.exp(-value + shape * math.log(value) - log_gamma)
|
|
89
|
+
return max(0.0, min(1.0, 1.0 - lower))
|
|
90
|
+
raise ValueError("gamma series did not converge")
|
|
91
|
+
|
|
92
|
+
b = value + 1.0 - shape
|
|
93
|
+
c = 1.0 / minimum
|
|
94
|
+
d = 1.0 / b
|
|
95
|
+
h = d
|
|
96
|
+
for iteration in range(1, max_iterations + 1):
|
|
97
|
+
coefficient = -iteration * (iteration - shape)
|
|
98
|
+
b += 2.0
|
|
99
|
+
d = coefficient * d + b
|
|
100
|
+
if abs(d) < minimum:
|
|
101
|
+
d = minimum
|
|
102
|
+
c = b + coefficient / c
|
|
103
|
+
if abs(c) < minimum:
|
|
104
|
+
c = minimum
|
|
105
|
+
d = 1.0 / d
|
|
106
|
+
delta = d * c
|
|
107
|
+
h *= delta
|
|
108
|
+
if abs(delta - 1.0) < epsilon:
|
|
109
|
+
result = math.exp(-value + shape * math.log(value) - log_gamma) * h
|
|
110
|
+
return max(0.0, min(1.0, result))
|
|
111
|
+
raise ValueError("gamma continued fraction did not converge")
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def beta_continued_fraction(a: float, b: float, x: float) -> float:
|
|
115
|
+
max_iterations = 10000
|
|
116
|
+
epsilon = 3e-14
|
|
117
|
+
minimum = 1e-300
|
|
118
|
+
qab = a + b
|
|
119
|
+
qap = a + 1.0
|
|
120
|
+
qam = a - 1.0
|
|
121
|
+
c = 1.0
|
|
122
|
+
d = 1.0 - qab * x / qap
|
|
123
|
+
if abs(d) < minimum:
|
|
124
|
+
d = minimum
|
|
125
|
+
d = 1.0 / d
|
|
126
|
+
h = d
|
|
127
|
+
for iteration in range(1, max_iterations + 1):
|
|
128
|
+
even = 2 * iteration
|
|
129
|
+
coefficient = iteration * (b - iteration) * x / ((qam + even) * (a + even))
|
|
130
|
+
d = 1.0 + coefficient * d
|
|
131
|
+
if abs(d) < minimum:
|
|
132
|
+
d = minimum
|
|
133
|
+
c = 1.0 + coefficient / c
|
|
134
|
+
if abs(c) < minimum:
|
|
135
|
+
c = minimum
|
|
136
|
+
d = 1.0 / d
|
|
137
|
+
h *= d * c
|
|
138
|
+
|
|
139
|
+
coefficient = -(a + iteration) * (qab + iteration) * x / (
|
|
140
|
+
(a + even) * (qap + even)
|
|
141
|
+
)
|
|
142
|
+
d = 1.0 + coefficient * d
|
|
143
|
+
if abs(d) < minimum:
|
|
144
|
+
d = minimum
|
|
145
|
+
c = 1.0 + coefficient / c
|
|
146
|
+
if abs(c) < minimum:
|
|
147
|
+
c = minimum
|
|
148
|
+
d = 1.0 / d
|
|
149
|
+
delta = d * c
|
|
150
|
+
h *= delta
|
|
151
|
+
if abs(delta - 1.0) < epsilon:
|
|
152
|
+
return h
|
|
153
|
+
raise ValueError("beta continued fraction did not converge")
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def regularized_beta(a: float, b: float, x: float) -> float:
|
|
157
|
+
if a <= 0.0 or b <= 0.0 or not 0.0 <= x <= 1.0:
|
|
158
|
+
raise ValueError("invalid beta arguments")
|
|
159
|
+
if x == 0.0:
|
|
160
|
+
return 0.0
|
|
161
|
+
if x == 1.0:
|
|
162
|
+
return 1.0
|
|
163
|
+
front = math.exp(
|
|
164
|
+
math.lgamma(a + b) - math.lgamma(a) - math.lgamma(b)
|
|
165
|
+
+ a * math.log(x) + b * math.log(1.0 - x)
|
|
166
|
+
)
|
|
167
|
+
if x < (a + 1.0) / (a + b + 2.0):
|
|
168
|
+
return front * beta_continued_fraction(a, b, x) / a
|
|
169
|
+
return 1.0 - front * beta_continued_fraction(b, a, 1.0 - x) / b
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def student_t_two_sided_p(t_value: float, degrees_freedom: float) -> float:
|
|
173
|
+
x = degrees_freedom / (degrees_freedom + t_value * t_value)
|
|
174
|
+
return regularized_beta(degrees_freedom / 2.0, 0.5, x)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def student_t_critical(alpha: float, degrees_freedom: float) -> float:
|
|
178
|
+
low = 0.0
|
|
179
|
+
high = 1.0
|
|
180
|
+
while student_t_two_sided_p(high, degrees_freedom) > alpha:
|
|
181
|
+
high *= 2.0
|
|
182
|
+
if high > 1e6:
|
|
183
|
+
raise ValueError("could not bracket t critical value")
|
|
184
|
+
for _ in range(100):
|
|
185
|
+
middle = (low + high) / 2.0
|
|
186
|
+
if student_t_two_sided_p(middle, degrees_freedom) > alpha:
|
|
187
|
+
low = middle
|
|
188
|
+
else:
|
|
189
|
+
high = middle
|
|
190
|
+
return (low + high) / 2.0
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def validate_groups(params: dict[str, Any]) -> list[dict[str, Any]]:
|
|
194
|
+
groups = params.get("groups")
|
|
195
|
+
if not isinstance(groups, list) or len(groups) < 2:
|
|
196
|
+
raise ValueError("groups must contain at least two groups")
|
|
197
|
+
names: set[str] = set()
|
|
198
|
+
for group in groups:
|
|
199
|
+
if not isinstance(group, dict):
|
|
200
|
+
raise ValueError("every group must be an object")
|
|
201
|
+
name = group.get("name")
|
|
202
|
+
if not isinstance(name, str) or not name:
|
|
203
|
+
raise ValueError("every group must have a non-empty name")
|
|
204
|
+
if name in names:
|
|
205
|
+
raise ValueError(f"duplicate group name: {name}")
|
|
206
|
+
names.add(name)
|
|
207
|
+
return groups
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def calculate_srm(
|
|
211
|
+
groups: list[dict[str, Any]],
|
|
212
|
+
expected_allocations: dict[str, Any],
|
|
213
|
+
alpha_srm: float,
|
|
214
|
+
) -> dict[str, Any]:
|
|
215
|
+
names = [group["name"] for group in groups]
|
|
216
|
+
if set(expected_allocations) != set(names):
|
|
217
|
+
raise ValueError("expected_allocations keys must exactly match group names")
|
|
218
|
+
allocations = {name: float(expected_allocations[name]) for name in names}
|
|
219
|
+
if any(value <= 0.0 for value in allocations.values()):
|
|
220
|
+
raise ValueError("expected allocations must be positive")
|
|
221
|
+
if not math.isclose(sum(allocations.values()), 1.0, rel_tol=0.0, abs_tol=1e-9):
|
|
222
|
+
raise ValueError("expected allocations must sum to 1.0")
|
|
223
|
+
|
|
224
|
+
observed: dict[str, int] = {}
|
|
225
|
+
for group in groups:
|
|
226
|
+
assigned = group.get("assigned")
|
|
227
|
+
if isinstance(assigned, bool) or not isinstance(assigned, int) or assigned < 0:
|
|
228
|
+
raise ValueError("every group must have a non-negative integer assigned count")
|
|
229
|
+
observed[group["name"]] = assigned
|
|
230
|
+
total = sum(observed.values())
|
|
231
|
+
if total == 0:
|
|
232
|
+
raise ValueError("total assigned count must be greater than 0")
|
|
233
|
+
|
|
234
|
+
expected = {name: total * allocations[name] for name in names}
|
|
235
|
+
chi_square = sum(
|
|
236
|
+
(observed[name] - expected[name]) ** 2 / expected[name] for name in names
|
|
237
|
+
)
|
|
238
|
+
degrees_freedom = len(names) - 1
|
|
239
|
+
p_value = regularized_gamma_q(degrees_freedom / 2.0, chi_square / 2.0)
|
|
240
|
+
return {
|
|
241
|
+
"chi_square": chi_square,
|
|
242
|
+
"degrees_freedom": degrees_freedom,
|
|
243
|
+
"p_value": p_value,
|
|
244
|
+
"alpha": alpha_srm,
|
|
245
|
+
"srm_detected": p_value < alpha_srm,
|
|
246
|
+
"observed": observed,
|
|
247
|
+
"expected": expected,
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def binary_comparison(
|
|
252
|
+
control: dict[str, Any],
|
|
253
|
+
treatment: dict[str, Any],
|
|
254
|
+
alpha: float,
|
|
255
|
+
) -> dict[str, Any]:
|
|
256
|
+
n0 = int(control.get("sample_size", 0))
|
|
257
|
+
n1 = int(treatment.get("sample_size", 0))
|
|
258
|
+
x0 = int(control.get("successes", -1))
|
|
259
|
+
x1 = int(treatment.get("successes", -1))
|
|
260
|
+
if n0 <= 0 or n1 <= 0:
|
|
261
|
+
raise ValueError("binary sample_size must be greater than 0")
|
|
262
|
+
if not 0 <= x0 <= n0 or not 0 <= x1 <= n1:
|
|
263
|
+
raise ValueError("binary successes must be between 0 and sample_size")
|
|
264
|
+
p0 = x0 / n0
|
|
265
|
+
p1 = x1 / n1
|
|
266
|
+
difference = p1 - p0
|
|
267
|
+
pooled = (x0 + x1) / (n0 + n1)
|
|
268
|
+
pooled_se = math.sqrt(pooled * (1.0 - pooled) * (1.0 / n0 + 1.0 / n1))
|
|
269
|
+
if pooled_se == 0.0:
|
|
270
|
+
z_value = 0.0 if difference == 0.0 else math.copysign(math.inf, difference)
|
|
271
|
+
p_value = 1.0 if difference == 0.0 else 0.0
|
|
272
|
+
else:
|
|
273
|
+
z_value = difference / pooled_se
|
|
274
|
+
p_value = math.erfc(abs(z_value) / math.sqrt(2.0))
|
|
275
|
+
interval_se = math.sqrt(
|
|
276
|
+
p0 * (1.0 - p0) / n0 + p1 * (1.0 - p1) / n1
|
|
277
|
+
)
|
|
278
|
+
critical = normal_ppf(1.0 - alpha / 2.0)
|
|
279
|
+
relative_lift = None if p0 == 0.0 else difference / p0
|
|
280
|
+
return {
|
|
281
|
+
"control_value": p0,
|
|
282
|
+
"treatment_value": p1,
|
|
283
|
+
"absolute_difference": difference,
|
|
284
|
+
"relative_lift": relative_lift,
|
|
285
|
+
"test": "two_sided_pooled_two_proportion_z",
|
|
286
|
+
"statistic": z_value,
|
|
287
|
+
"p_value": p_value,
|
|
288
|
+
"confidence_interval_absolute": [
|
|
289
|
+
difference - critical * interval_se,
|
|
290
|
+
difference + critical * interval_se,
|
|
291
|
+
],
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def continuous_comparison(
|
|
296
|
+
control: dict[str, Any],
|
|
297
|
+
treatment: dict[str, Any],
|
|
298
|
+
alpha: float,
|
|
299
|
+
) -> dict[str, Any]:
|
|
300
|
+
n0 = int(control.get("sample_size", 0))
|
|
301
|
+
n1 = int(treatment.get("sample_size", 0))
|
|
302
|
+
mean0 = float(control.get("mean"))
|
|
303
|
+
mean1 = float(treatment.get("mean"))
|
|
304
|
+
sd0 = float(control.get("standard_deviation"))
|
|
305
|
+
sd1 = float(treatment.get("standard_deviation"))
|
|
306
|
+
if n0 < 2 or n1 < 2:
|
|
307
|
+
raise ValueError("continuous sample_size must be at least 2")
|
|
308
|
+
if sd0 < 0.0 or sd1 < 0.0:
|
|
309
|
+
raise ValueError("standard_deviation must be non-negative")
|
|
310
|
+
variance0 = sd0 * sd0 / n0
|
|
311
|
+
variance1 = sd1 * sd1 / n1
|
|
312
|
+
standard_error = math.sqrt(variance0 + variance1)
|
|
313
|
+
difference = mean1 - mean0
|
|
314
|
+
if standard_error == 0.0:
|
|
315
|
+
t_value = 0.0 if difference == 0.0 else math.copysign(math.inf, difference)
|
|
316
|
+
p_value = 1.0 if difference == 0.0 else 0.0
|
|
317
|
+
degrees_freedom = math.inf
|
|
318
|
+
critical = normal_ppf(1.0 - alpha / 2.0)
|
|
319
|
+
else:
|
|
320
|
+
t_value = difference / standard_error
|
|
321
|
+
denominator = (
|
|
322
|
+
variance0 * variance0 / (n0 - 1)
|
|
323
|
+
+ variance1 * variance1 / (n1 - 1)
|
|
324
|
+
)
|
|
325
|
+
degrees_freedom = (
|
|
326
|
+
math.inf if denominator == 0.0 else (variance0 + variance1) ** 2 / denominator
|
|
327
|
+
)
|
|
328
|
+
if math.isinf(degrees_freedom):
|
|
329
|
+
p_value = math.erfc(abs(t_value) / math.sqrt(2.0))
|
|
330
|
+
critical = normal_ppf(1.0 - alpha / 2.0)
|
|
331
|
+
else:
|
|
332
|
+
p_value = student_t_two_sided_p(t_value, degrees_freedom)
|
|
333
|
+
critical = student_t_critical(alpha, degrees_freedom)
|
|
334
|
+
relative_lift = None if mean0 == 0.0 else difference / mean0
|
|
335
|
+
return {
|
|
336
|
+
"control_value": mean0,
|
|
337
|
+
"treatment_value": mean1,
|
|
338
|
+
"absolute_difference": difference,
|
|
339
|
+
"relative_lift": relative_lift,
|
|
340
|
+
"test": "two_sided_welch_t",
|
|
341
|
+
"statistic": t_value,
|
|
342
|
+
"degrees_freedom": degrees_freedom,
|
|
343
|
+
"p_value": p_value,
|
|
344
|
+
"confidence_interval_absolute": [
|
|
345
|
+
difference - critical * standard_error,
|
|
346
|
+
difference + critical * standard_error,
|
|
347
|
+
],
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
def calculate(params: dict[str, Any]) -> dict[str, Any]:
|
|
352
|
+
groups = validate_groups(params)
|
|
353
|
+
metric_type = params.get("metric_type")
|
|
354
|
+
if metric_type not in {"binary", "continuous"}:
|
|
355
|
+
raise ValueError("metric_type must be binary or continuous")
|
|
356
|
+
control_name = params.get("control")
|
|
357
|
+
by_name = {group["name"]: group for group in groups}
|
|
358
|
+
if control_name not in by_name:
|
|
359
|
+
raise ValueError("control must match one group name")
|
|
360
|
+
|
|
361
|
+
alpha = float(params.get("alpha", 0.05))
|
|
362
|
+
alpha_srm = float(params.get("alpha_srm", 0.01))
|
|
363
|
+
if not 0.0 < alpha < 0.5 or not 0.0 < alpha_srm < 0.5:
|
|
364
|
+
raise ValueError("alpha and alpha_srm must be between 0 and 0.5")
|
|
365
|
+
multiple_testing = params.get("multiple_testing", "bonferroni")
|
|
366
|
+
if multiple_testing not in {"bonferroni", "none"}:
|
|
367
|
+
raise ValueError("multiple_testing must be bonferroni or none")
|
|
368
|
+
|
|
369
|
+
srm = None
|
|
370
|
+
expected_allocations = params.get("expected_allocations")
|
|
371
|
+
if expected_allocations is not None:
|
|
372
|
+
if not isinstance(expected_allocations, dict):
|
|
373
|
+
raise ValueError("expected_allocations must be an object")
|
|
374
|
+
srm = calculate_srm(groups, expected_allocations, alpha_srm)
|
|
375
|
+
|
|
376
|
+
control = by_name[control_name]
|
|
377
|
+
comparisons: list[dict[str, Any]] = []
|
|
378
|
+
number_of_comparisons = len(groups) - 1
|
|
379
|
+
comparison_alpha = (
|
|
380
|
+
alpha / number_of_comparisons
|
|
381
|
+
if multiple_testing == "bonferroni"
|
|
382
|
+
else alpha
|
|
383
|
+
)
|
|
384
|
+
for treatment in groups:
|
|
385
|
+
if treatment["name"] == control_name:
|
|
386
|
+
continue
|
|
387
|
+
result = (
|
|
388
|
+
binary_comparison(control, treatment, comparison_alpha)
|
|
389
|
+
if metric_type == "binary"
|
|
390
|
+
else continuous_comparison(control, treatment, comparison_alpha)
|
|
391
|
+
)
|
|
392
|
+
difference = result["absolute_difference"]
|
|
393
|
+
significant = result["p_value"] < comparison_alpha
|
|
394
|
+
if significant and difference > 0:
|
|
395
|
+
classification = "statistically_significant_positive"
|
|
396
|
+
elif significant and difference < 0:
|
|
397
|
+
classification = "statistically_significant_negative"
|
|
398
|
+
else:
|
|
399
|
+
classification = "inconclusive_at_selected_alpha"
|
|
400
|
+
result.update(
|
|
401
|
+
{
|
|
402
|
+
"control": control_name,
|
|
403
|
+
"treatment": treatment["name"],
|
|
404
|
+
"comparison_alpha": comparison_alpha,
|
|
405
|
+
"statistically_significant": significant,
|
|
406
|
+
"classification": classification,
|
|
407
|
+
}
|
|
408
|
+
)
|
|
409
|
+
comparisons.append(result)
|
|
410
|
+
|
|
411
|
+
exposure_coverage: dict[str, float] = {}
|
|
412
|
+
for group in groups:
|
|
413
|
+
assigned = group.get("assigned")
|
|
414
|
+
sample_size = group.get("sample_size")
|
|
415
|
+
if (
|
|
416
|
+
isinstance(assigned, int)
|
|
417
|
+
and assigned > 0
|
|
418
|
+
and isinstance(sample_size, int)
|
|
419
|
+
and sample_size >= 0
|
|
420
|
+
):
|
|
421
|
+
exposure_coverage[group["name"]] = sample_size / assigned
|
|
422
|
+
|
|
423
|
+
warnings: list[str] = []
|
|
424
|
+
if len(comparisons) > 1 and multiple_testing == "none":
|
|
425
|
+
warnings.append(
|
|
426
|
+
"P-values are unadjusted across treatments; confirm that this matches the pre-registered policy."
|
|
427
|
+
)
|
|
428
|
+
if srm and srm["srm_detected"]:
|
|
429
|
+
warnings.append(
|
|
430
|
+
"SRM was detected from assignment counts; diagnose allocation before using causal comparisons."
|
|
431
|
+
)
|
|
432
|
+
|
|
433
|
+
return {
|
|
434
|
+
"ok": True,
|
|
435
|
+
"data": {
|
|
436
|
+
"metric_type": metric_type,
|
|
437
|
+
"control": control_name,
|
|
438
|
+
"familywise_alpha": alpha,
|
|
439
|
+
"multiple_testing": multiple_testing,
|
|
440
|
+
"comparison_alpha": comparison_alpha,
|
|
441
|
+
"srm": srm,
|
|
442
|
+
"exposure_coverage": exposure_coverage,
|
|
443
|
+
"comparisons": comparisons,
|
|
444
|
+
"warnings": warnings,
|
|
445
|
+
},
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
def main() -> int:
|
|
450
|
+
try:
|
|
451
|
+
if len(sys.argv) != 2:
|
|
452
|
+
raise ValueError("usage: analyze_experiment.py '<json-object>'")
|
|
453
|
+
raw = sys.argv[1]
|
|
454
|
+
params = json.load(sys.stdin) if raw == "-" else json.loads(raw)
|
|
455
|
+
if not isinstance(params, dict):
|
|
456
|
+
raise ValueError("input must be a JSON object")
|
|
457
|
+
print(json.dumps(calculate(params), ensure_ascii=False, indent=2, allow_nan=False))
|
|
458
|
+
return 0
|
|
459
|
+
except (ValueError, TypeError, KeyError, json.JSONDecodeError) as error:
|
|
460
|
+
print(
|
|
461
|
+
json.dumps(
|
|
462
|
+
{
|
|
463
|
+
"ok": False,
|
|
464
|
+
"error": {
|
|
465
|
+
"type": "validation",
|
|
466
|
+
"code": "INVALID_INPUT",
|
|
467
|
+
"message": str(error),
|
|
468
|
+
},
|
|
469
|
+
},
|
|
470
|
+
ensure_ascii=False,
|
|
471
|
+
indent=2,
|
|
472
|
+
)
|
|
473
|
+
)
|
|
474
|
+
return 2
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
if __name__ == "__main__":
|
|
478
|
+
raise SystemExit(main())
|
|
@@ -91,12 +91,12 @@ First, check if `.ae-cli/draft.json` exists and read existing configuration:
|
|
|
91
91
|
**⚠️ Key: SERVER_URL and APP_ID are independent config items. Even if draft.json has project_id and host, you MUST resolve and confirm them. `project_id` ≠ `APP_ID`, `host` ≠ `SERVER_URL`.**
|
|
92
92
|
|
|
93
93
|
1. **APP_ID** — Prefer automatic lookup before asking:
|
|
94
|
-
- If `meta.project_id` is known, run `ae-cli
|
|
94
|
+
- If `meta.project_id` is known, run `ae-cli project info list` once for the current host.
|
|
95
95
|
- Find the project whose `projectId` matches `meta.project_id`.
|
|
96
96
|
- If the matched project has `appId`, ask: **"I found APP_ID `<appId>` for project `<projectId>`. Use it? yes / enter new value"**
|
|
97
97
|
- If the project is missing, ambiguous, or has no `appId`, ask the user to copy APP_ID from AE Admin → "Project Settings" → "Integration Config".
|
|
98
98
|
2. **SERVER_URL** — Data ingestion endpoint (**different from web URL**; go to AE Admin → "Project Settings" → "Integration Config" → fill in "Public URL")
|
|
99
|
-
- If `meta.project_id` is known, you may try `ae-cli
|
|
99
|
+
- If `meta.project_id` is known, you may try `ae-cli project info get --project-id <project_id>` once.
|
|
100
100
|
- Use the returned value only if the response explicitly contains a receiver URL field such as `serverUrl`, `pushUrl`, `push_url`, `receiverUrl`, `publicUrl`, `publicReceiverAddress`, `privateReceiverAddress`, or equivalent ingestion endpoint field.
|
|
101
101
|
- If both `publicReceiverAddress` and `privateReceiverAddress` are present, prefer `publicReceiverAddress` as `SERVER_URL` for generated snippets unless the user explicitly needs an internal/private-network receiver.
|
|
102
102
|
- If a value is found, ask: **"I found SERVER_URL `<url>` for project `<projectId>`. Use it? yes / enter new value"**
|
|
@@ -53,7 +53,7 @@ ae-cli metadata property <dimension-table-action> [options]
|
|
|
53
53
|
|
|
54
54
|
## PROJECT_ID_GATE
|
|
55
55
|
|
|
56
|
-
Same rules as `ae-analysis`: reuse verified project context in one conversation; otherwise `ae-cli
|
|
56
|
+
Same rules as `ae-analysis`: reuse verified project context in one conversation; otherwise `ae-cli project info list` (or `ae-analysis` skill) to resolve `project_id`.
|
|
57
57
|
|
|
58
58
|
## Commands (10)
|
|
59
59
|
|
package/dist/config-BSSALXEN.js
DELETED
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
GLOBAL_QUERY_CONFIG_KEY,
|
|
3
|
-
getAnalysisMappingPathForClusterMode,
|
|
4
|
-
getClusterInfoFilePath,
|
|
5
|
-
isGlobalQueryModeEnabled,
|
|
6
|
-
setGlobalQueryModeEnabled
|
|
7
|
-
} from "./chunk-LCXU3AAT.js";
|
|
8
|
-
import {
|
|
9
|
-
printError,
|
|
10
|
-
printOutput
|
|
11
|
-
} from "./chunk-KTYR3U6D.js";
|
|
12
|
-
import "./chunk-E7UXXHO3.js";
|
|
13
|
-
import "./chunk-IR4ZLVPW.js";
|
|
14
|
-
import {
|
|
15
|
-
addHost,
|
|
16
|
-
loadConfig,
|
|
17
|
-
normalizeUrl,
|
|
18
|
-
setActiveHost
|
|
19
|
-
} from "./chunk-V6FR6WTW.js";
|
|
20
|
-
import "./chunk-LYVNONC4.js";
|
|
21
|
-
|
|
22
|
-
// src/commands/config.ts
|
|
23
|
-
function registerConfig(program) {
|
|
24
|
-
const configCmd = program.command("config").description("AE host and environment configuration").action(() => {
|
|
25
|
-
printError("config", "Missing subcommand.", "Run: ae-cli config --help");
|
|
26
|
-
process.exit(1);
|
|
27
|
-
});
|
|
28
|
-
configCmd.command("list").description("List configured AE environments").action(async () => {
|
|
29
|
-
await printConfigList(program);
|
|
30
|
-
});
|
|
31
|
-
configCmd.command("current").description("Show the active AE environment").action(async () => {
|
|
32
|
-
await printCurrentConfig(program);
|
|
33
|
-
});
|
|
34
|
-
configCmd.command("use <env>").description("Switch active AE environment by exact URL or unique label").action(async (env) => {
|
|
35
|
-
await useConfigEnv(program, env);
|
|
36
|
-
});
|
|
37
|
-
configCmd.command("set-host <url>").description("Set the active AE host non-interactively (adds it if not already configured)").option("--label <label>", "Display label for this host (defaults to the host URL)").action(async (url, opts) => {
|
|
38
|
-
const normalized = normalizeUrl(url);
|
|
39
|
-
const label = opts.label || normalized;
|
|
40
|
-
addHost(normalized, label);
|
|
41
|
-
setActiveHost(normalized);
|
|
42
|
-
await printOutput({ activeHost: normalized, label }, program.opts().format || "json");
|
|
43
|
-
});
|
|
44
|
-
const clusterMode = configCmd.command("cluster-mode").description("Manage local multi-cluster analysis MCP mode").action(async () => {
|
|
45
|
-
await printClusterModeStatus(program);
|
|
46
|
-
});
|
|
47
|
-
clusterMode.command("status").description("Show local multi-cluster analysis MCP mode").action(async () => {
|
|
48
|
-
await printClusterModeStatus(program);
|
|
49
|
-
});
|
|
50
|
-
clusterMode.command("enable").description("Enable local multi-cluster analysis MCP mode").action(async () => {
|
|
51
|
-
setGlobalQueryModeEnabled(true);
|
|
52
|
-
await printClusterModeStatus(program);
|
|
53
|
-
});
|
|
54
|
-
clusterMode.command("disable").description("Disable local multi-cluster analysis MCP mode").action(async () => {
|
|
55
|
-
setGlobalQueryModeEnabled(false);
|
|
56
|
-
await printClusterModeStatus(program);
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
async function printClusterModeStatus(program) {
|
|
60
|
-
const enabled = isGlobalQueryModeEnabled();
|
|
61
|
-
await printOutput({
|
|
62
|
-
[GLOBAL_QUERY_CONFIG_KEY]: enabled,
|
|
63
|
-
path: getClusterInfoFilePath(),
|
|
64
|
-
analysisMappingPath: getAnalysisMappingPathForClusterMode(enabled)
|
|
65
|
-
}, program.opts().format || "json", program.opts().jq);
|
|
66
|
-
}
|
|
67
|
-
function readHostViews() {
|
|
68
|
-
const config = loadConfig();
|
|
69
|
-
const hosts = Object.entries(config.hosts).map(([url, entry]) => ({
|
|
70
|
-
url,
|
|
71
|
-
label: entry.label,
|
|
72
|
-
active: url === config.activeHost
|
|
73
|
-
}));
|
|
74
|
-
return { activeHost: config.activeHost, hosts };
|
|
75
|
-
}
|
|
76
|
-
async function printConfigList(program) {
|
|
77
|
-
const { activeHost, hosts } = readHostViews();
|
|
78
|
-
await printOutput({ activeHost, hosts }, program.opts().format || "json", program.opts().jq);
|
|
79
|
-
}
|
|
80
|
-
async function printCurrentConfig(program) {
|
|
81
|
-
const { activeHost, hosts } = readHostViews();
|
|
82
|
-
const current = hosts.find((h) => h.url === activeHost);
|
|
83
|
-
if (!current) {
|
|
84
|
-
failConfig("No active AE environment configured.", "Run: ae-cli config use <env> or ae-cli config list");
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
await printOutput({ activeHost: current.url, label: current.label }, program.opts().format || "json", program.opts().jq);
|
|
88
|
-
}
|
|
89
|
-
async function useConfigEnv(program, env) {
|
|
90
|
-
const target = resolveHost(env);
|
|
91
|
-
if (target.status === "not-found") {
|
|
92
|
-
failConfig(
|
|
93
|
-
`No AE environment matched: ${env}`,
|
|
94
|
-
target.hosts.length > 0 ? `Available environments: ${formatHostOptions(target.hosts)}` : "Run: ae-cli config list"
|
|
95
|
-
);
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
if (target.status === "duplicate-label") {
|
|
99
|
-
failConfig(
|
|
100
|
-
`Duplicate label: ${env}`,
|
|
101
|
-
`Rename duplicate labels first. Run: ae-cli config list \u2014 ${formatHostOptions(target.matches)}`
|
|
102
|
-
);
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
setActiveHost(target.host.url);
|
|
106
|
-
await printOutput({ activeHost: target.host.url, label: target.host.label }, program.opts().format || "json", program.opts().jq);
|
|
107
|
-
}
|
|
108
|
-
function resolveHost(env) {
|
|
109
|
-
const raw = env.trim();
|
|
110
|
-
const selector = raw.startsWith("http://") || raw.startsWith("https://") ? normalizeUrl(raw) : raw;
|
|
111
|
-
const { hosts } = readHostViews();
|
|
112
|
-
const byUrl = hosts.find((h) => h.url === selector);
|
|
113
|
-
if (byUrl) return { status: "found", host: byUrl };
|
|
114
|
-
const byLabel = hosts.filter((h) => h.label === raw);
|
|
115
|
-
if (byLabel.length === 1) return { status: "found", host: byLabel[0] };
|
|
116
|
-
if (byLabel.length > 1) return { status: "duplicate-label", matches: byLabel };
|
|
117
|
-
return { status: "not-found", hosts };
|
|
118
|
-
}
|
|
119
|
-
function formatHostOptions(hosts) {
|
|
120
|
-
return hosts.map((h) => `${h.label} (${h.url})`).join(", ");
|
|
121
|
-
}
|
|
122
|
-
function failConfig(message, hint) {
|
|
123
|
-
printError("config", message, hint);
|
|
124
|
-
process.exitCode = 1;
|
|
125
|
-
}
|
|
126
|
-
export {
|
|
127
|
-
registerConfig
|
|
128
|
-
};
|