@0dai-dev/cli 4.3.6 → 4.3.7
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 +12 -11
- package/bin/0dai.js +127 -30
- package/lib/ai/manifest/mcp-exposure-contract.json +121 -0
- package/lib/ai/meta/manifest/mcp-tool-tiers.json +435 -0
- package/lib/ai/registry/mcp-catalog.json +98 -0
- package/lib/commands/auth.js +2 -1
- package/lib/commands/compliance.js +1 -1
- package/lib/commands/doctor.js +506 -12
- package/lib/commands/experience.js +40 -5
- package/lib/commands/feedback.js +157 -15
- package/lib/commands/gh.js +26 -0
- package/lib/commands/graph.js +9 -4
- package/lib/commands/heatmap.js +1 -1
- package/lib/commands/init.js +209 -27
- package/lib/commands/mcp.js +111 -33
- package/lib/commands/models.js +138 -41
- package/lib/commands/provider.js +30 -59
- package/lib/commands/quota.js +1 -1
- package/lib/commands/receipt.js +1 -1
- package/lib/commands/run.js +14 -6
- package/lib/commands/runner.js +31 -1
- package/lib/commands/status.js +38 -10
- package/lib/commands/swarm.js +130 -12
- package/lib/commands/update.js +184 -38
- package/lib/commands/usage.js +1 -1
- package/lib/commands/validate.js +32 -3
- package/lib/commands/vault.js +43 -8
- package/lib/python/__init__.py +0 -0
- package/lib/python/agent_quotas.py +525 -0
- package/lib/python/anomaly_alert.py +397 -0
- package/lib/python/anti_pattern_detector.py +799 -0
- package/lib/python/auth.py +443 -0
- package/lib/python/capi_profile_guard.py +477 -0
- package/lib/python/compliance_report.py +581 -0
- package/lib/python/drift_detector.py +388 -0
- package/lib/python/experience_pipeline.py +1130 -0
- package/lib/python/graph.py +19 -0
- package/lib/python/graph_core.py +293 -0
- package/lib/python/graph_io.py +179 -0
- package/lib/python/graph_legacy.py +2052 -0
- package/lib/python/graph_legacy_helpers.py +221 -0
- package/lib/python/graph_outcomes_core.py +85 -0
- package/lib/python/graph_queries.py +171 -0
- package/lib/python/graph_slice.py +198 -0
- package/lib/python/graph_slicer.py +576 -0
- package/lib/python/graph_slicer_cli.py +60 -0
- package/lib/python/graph_validation.py +64 -0
- package/lib/python/heatmap.py +934 -0
- package/lib/python/json_utils.py +193 -0
- package/lib/python/mcp_exposure_check.py +247 -0
- package/lib/python/model_router.py +1434 -0
- package/lib/python/project_manager.py +621 -0
- package/lib/python/provider_profiles.py +1618 -0
- package/lib/python/provider_registry.py +1211 -0
- package/lib/python/provider_registry_cli.py +125 -0
- package/lib/python/receipt_png.py +727 -0
- package/lib/python/structural_memory.py +325 -0
- package/lib/python/swarm_cost.py +177 -0
- package/lib/python/usage_ledger.py +569 -0
- package/lib/scripts/mcp_tier_config.py +240 -0
- package/lib/shared.js +95 -12
- package/lib/tui/index.mjs +35174 -0
- package/lib/utils/activation_telemetry.js +1 -4
- package/lib/utils/constants.js +7 -1
- package/lib/utils/identity.js +184 -0
- package/lib/utils/mcp-auth.js +81 -15
- package/lib/utils/plan.js +1 -1
- package/lib/vault/index.js +19 -3
- package/lib/vault/storage.js +21 -2
- package/lib/wizard.js +5 -2
- package/package.json +9 -3
- package/scripts/build-python-bundle.js +106 -0
- package/scripts/build-tui.js +14 -1
- package/scripts/harvest_experience.py +523 -0
- package/scripts/postinstall.js +15 -9
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
"""MCP tool tier configuration - single source of truth for Free vs Pro tool access.
|
|
2
|
+
|
|
3
|
+
FREE_TOOLS (49): Basic read-only tools and Mode A baseline helpers.
|
|
4
|
+
PRO_TOOLS (64): Advanced tools requiring Pro/Team plan ($15/mo).
|
|
5
|
+
|
|
6
|
+
Total: 113 unique tools. Must match all @mcp.tool registrations in mcp_server.py + mcp_tools/.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
PLAN_LEVELS = {"free": 0, "pro": 1, "team": 2, "enterprise": 3}
|
|
10
|
+
|
|
11
|
+
FREE_TOOLS: list[str] = [
|
|
12
|
+
# Core read tools — project context
|
|
13
|
+
"get_project_manifest",
|
|
14
|
+
"get_project_context",
|
|
15
|
+
"get_codebase_map",
|
|
16
|
+
"get_commands",
|
|
17
|
+
"get_environment",
|
|
18
|
+
"get_ai_version",
|
|
19
|
+
"get_project_health",
|
|
20
|
+
"mcp_tier_load",
|
|
21
|
+
"get_bulletins",
|
|
22
|
+
"get_applied_lock",
|
|
23
|
+
"get_custom_stacks",
|
|
24
|
+
"get_discovery",
|
|
25
|
+
"get_personas",
|
|
26
|
+
"get_registry",
|
|
27
|
+
"get_telemetry_summary",
|
|
28
|
+
"citation_audit",
|
|
29
|
+
"get_specs",
|
|
30
|
+
"master_plan_read",
|
|
31
|
+
# Knowledge tools
|
|
32
|
+
"search_experience",
|
|
33
|
+
"record_experience",
|
|
34
|
+
"score_candidates",
|
|
35
|
+
# Federation & graph
|
|
36
|
+
"get_federation",
|
|
37
|
+
"get_project_graph",
|
|
38
|
+
# Model selection
|
|
39
|
+
"get_model_ratings",
|
|
40
|
+
# Agent anomaly observability
|
|
41
|
+
"get_anomaly_taxonomy",
|
|
42
|
+
"get_anomaly_events",
|
|
43
|
+
"get_anomaly_summary",
|
|
44
|
+
"get_agent_activity",
|
|
45
|
+
"get_ghost_critiques",
|
|
46
|
+
"get_token_telemetry",
|
|
47
|
+
"verdict_count",
|
|
48
|
+
"idle_watchdog_status",
|
|
49
|
+
"get_pool_state",
|
|
50
|
+
"pool_query_free",
|
|
51
|
+
"pool_health",
|
|
52
|
+
# Solution utility tools (read-only, local-safe)
|
|
53
|
+
"debug_probe",
|
|
54
|
+
"parse_content",
|
|
55
|
+
# Memory tools — core feature, free tier (makes product sticky)
|
|
56
|
+
"memory_add",
|
|
57
|
+
"memory_search",
|
|
58
|
+
"memory_list",
|
|
59
|
+
"memory_delete",
|
|
60
|
+
"memory_verify",
|
|
61
|
+
"memory_inject",
|
|
62
|
+
# GitHub helpers (Tier 1, Mode A) - added 2026-04-26 per #1132.
|
|
63
|
+
"gh_label_create",
|
|
64
|
+
"gh_issue_edit_body",
|
|
65
|
+
"gh_pr_diff",
|
|
66
|
+
"propose_file_change",
|
|
67
|
+
# Session productivity scoring - added 2026-01-XX per #1727.
|
|
68
|
+
"session_productivity",
|
|
69
|
+
"session_productivity_pool",
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
CORE_DISCOVERY_TOOLS: list[str] = [
|
|
73
|
+
"mcp_tier_load",
|
|
74
|
+
"memory_search",
|
|
75
|
+
"get_project_health",
|
|
76
|
+
"get_session",
|
|
77
|
+
"get_bulletins",
|
|
78
|
+
"get_project_manifest",
|
|
79
|
+
"get_codebase_map",
|
|
80
|
+
"get_commands",
|
|
81
|
+
"get_discovery",
|
|
82
|
+
"get_specs",
|
|
83
|
+
"search_experience",
|
|
84
|
+
"record_experience",
|
|
85
|
+
"get_activity_feed",
|
|
86
|
+
"check_approval",
|
|
87
|
+
"get_role_policy",
|
|
88
|
+
]
|
|
89
|
+
|
|
90
|
+
HIDDEN_DISCOVERY_TOOLS: list[str] = [
|
|
91
|
+
"memory_delete",
|
|
92
|
+
"memory_inject",
|
|
93
|
+
"gh_label_create",
|
|
94
|
+
"gh_issue_edit_body",
|
|
95
|
+
"propose_file_change",
|
|
96
|
+
"create_spec",
|
|
97
|
+
"update_decision",
|
|
98
|
+
"submit_feedback",
|
|
99
|
+
"undo_mutation",
|
|
100
|
+
"record_anomaly",
|
|
101
|
+
"pool_checkin",
|
|
102
|
+
"pool_checkout",
|
|
103
|
+
"task_cancel",
|
|
104
|
+
"idle_watchdog_pause",
|
|
105
|
+
"gh_pr_merge_safe",
|
|
106
|
+
"rebase_orchestrator",
|
|
107
|
+
"fallback_chain_resolver",
|
|
108
|
+
]
|
|
109
|
+
|
|
110
|
+
PRO_TOOLS: list[str] = [
|
|
111
|
+
# Swarm tools (8)
|
|
112
|
+
"get_swarm_status",
|
|
113
|
+
"swarm_delegate",
|
|
114
|
+
"peer_delegate_request",
|
|
115
|
+
"peer_delegate_list",
|
|
116
|
+
"peer_delegate_status",
|
|
117
|
+
"peer_delegate_accept",
|
|
118
|
+
"peer_delegate_decline",
|
|
119
|
+
"peer_delegate_complete",
|
|
120
|
+
"peer_delegate_sessions",
|
|
121
|
+
"swarm_run",
|
|
122
|
+
"run_task",
|
|
123
|
+
"watch_tasks",
|
|
124
|
+
"get_portfolio",
|
|
125
|
+
"estimate_task_cost",
|
|
126
|
+
"recommend_model",
|
|
127
|
+
# Working group tools (4)
|
|
128
|
+
"get_working_group_profiles",
|
|
129
|
+
"working_group_deliberate",
|
|
130
|
+
"get_working_group_history",
|
|
131
|
+
"working_group_research",
|
|
132
|
+
# Audit tools (4)
|
|
133
|
+
"get_audit_log",
|
|
134
|
+
"scan_secrets",
|
|
135
|
+
"get_compliance_report",
|
|
136
|
+
"get_role_policy",
|
|
137
|
+
# Specs write tool
|
|
138
|
+
"create_spec",
|
|
139
|
+
# Write / mutation tools
|
|
140
|
+
"update_decision",
|
|
141
|
+
"save_session",
|
|
142
|
+
"submit_feedback",
|
|
143
|
+
"undo_mutation",
|
|
144
|
+
"record_anomaly",
|
|
145
|
+
"pool_checkin",
|
|
146
|
+
"pool_checkout",
|
|
147
|
+
"loop_kick",
|
|
148
|
+
"task_cancel",
|
|
149
|
+
"idle_watchdog_pause",
|
|
150
|
+
# Solution utility tools (external/browser providers)
|
|
151
|
+
"web_search",
|
|
152
|
+
"capture_screenshot",
|
|
153
|
+
# OpenRouter BYOK tools (SPEC-024 Phase 1)
|
|
154
|
+
"mcp__openrouter__list_models",
|
|
155
|
+
"mcp__openrouter__invoke",
|
|
156
|
+
# Advanced read tools
|
|
157
|
+
"get_org_policy",
|
|
158
|
+
"get_maturity_score",
|
|
159
|
+
"get_mcp_catalog",
|
|
160
|
+
"get_agent_teams",
|
|
161
|
+
"get_plugins",
|
|
162
|
+
"check_approval",
|
|
163
|
+
"list_projects",
|
|
164
|
+
"get_project_health_multi",
|
|
165
|
+
"get_daily_digest",
|
|
166
|
+
"get_session",
|
|
167
|
+
"check_conflicts",
|
|
168
|
+
"get_activity_feed",
|
|
169
|
+
"get_knowledge_base",
|
|
170
|
+
"get_wal",
|
|
171
|
+
"get_prompt_history",
|
|
172
|
+
"get_observability",
|
|
173
|
+
"get_orchestration",
|
|
174
|
+
"get_graph_context",
|
|
175
|
+
"trace_task",
|
|
176
|
+
"get_distilled_rules",
|
|
177
|
+
"get_substrate_health",
|
|
178
|
+
# Ops-wrappers suite (3) — added 2026-04-28 per ops-wrappers-suite task.
|
|
179
|
+
"gh_pr_merge_safe",
|
|
180
|
+
"rebase_orchestrator",
|
|
181
|
+
"fallback_chain_resolver",
|
|
182
|
+
# Graph memory (Phase D) — read-only Cypher over AGE memory_graph (#2104).
|
|
183
|
+
"memory_traverse",
|
|
184
|
+
# Hybrid pgvector+AGE search (#3638) — Pro; v1 short-circuit when flag off.
|
|
185
|
+
"memory_search_v2",
|
|
186
|
+
]
|
|
187
|
+
|
|
188
|
+
# Sentinel value for unknown/local-only plan (treated as Free)
|
|
189
|
+
UNKNOWN_PLAN = "free"
|
|
190
|
+
|
|
191
|
+
# Minimum plan level required for Pro tools
|
|
192
|
+
PRO_MIN_LEVEL = 1 # pro
|
|
193
|
+
|
|
194
|
+
UPGRADE_MESSAGE = "This tool requires Pro plan ($15/mo). Run: 0dai upgrade"
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def validate_completeness(all_tool_names: set[str]) -> list[str]:
|
|
198
|
+
"""Validate that FREE_TOOLS + PRO_TOOLS covers all registered tools.
|
|
199
|
+
|
|
200
|
+
Returns list of error messages (empty = valid).
|
|
201
|
+
"""
|
|
202
|
+
errors: list[str] = []
|
|
203
|
+
free_set = set(FREE_TOOLS)
|
|
204
|
+
pro_set = set(PRO_TOOLS)
|
|
205
|
+
|
|
206
|
+
# Check for duplicates between lists
|
|
207
|
+
overlap = free_set & pro_set
|
|
208
|
+
if overlap:
|
|
209
|
+
errors.append(f"Tools in both FREE and PRO lists: {sorted(overlap)}")
|
|
210
|
+
|
|
211
|
+
# Check for duplicates within lists
|
|
212
|
+
if len(FREE_TOOLS) != len(free_set):
|
|
213
|
+
from collections import Counter
|
|
214
|
+
dupes = [k for k, v in Counter(FREE_TOOLS).items() if v > 1]
|
|
215
|
+
errors.append(f"Duplicate tools in FREE_TOOLS: {dupes}")
|
|
216
|
+
|
|
217
|
+
if len(PRO_TOOLS) != len(pro_set):
|
|
218
|
+
from collections import Counter
|
|
219
|
+
dupes = [k for k, v in Counter(PRO_TOOLS).items() if v > 1]
|
|
220
|
+
errors.append(f"Duplicate tools in PRO_TOOLS: {dupes}")
|
|
221
|
+
|
|
222
|
+
# Check completeness
|
|
223
|
+
all_declared = free_set | pro_set
|
|
224
|
+
missing = all_tool_names - all_declared
|
|
225
|
+
extra = all_declared - all_tool_names
|
|
226
|
+
|
|
227
|
+
if missing:
|
|
228
|
+
errors.append(f"Tools not in any tier list: {sorted(missing)}")
|
|
229
|
+
if extra:
|
|
230
|
+
errors.append(f"Tools in tier list but not registered: {sorted(extra)}")
|
|
231
|
+
|
|
232
|
+
# Check counts (49 free / 64 pro / 113 total after memory_search_v2 #3718).
|
|
233
|
+
if len(free_set) != 49:
|
|
234
|
+
errors.append(f"FREE_TOOLS has {len(free_set)} items, expected 49")
|
|
235
|
+
if len(pro_set) != 64:
|
|
236
|
+
errors.append(f"PRO_TOOLS has {len(pro_set)} items, expected 64")
|
|
237
|
+
if len(free_set) + len(pro_set) != 113:
|
|
238
|
+
errors.append(f"Total tools: {len(free_set) + len(pro_set)}, expected 113")
|
|
239
|
+
|
|
240
|
+
return errors
|
package/lib/shared.js
CHANGED
|
@@ -17,11 +17,14 @@ const { spawnSync } = require("child_process");
|
|
|
17
17
|
const VERSION = require("../package.json").version;
|
|
18
18
|
|
|
19
19
|
// --- Re-export from extracted modules ---
|
|
20
|
-
const { SUPPORTED_CLIS, MANIFEST_FILES, PROBE_DIRS, SETTINGS_PRESERVE_FIELDS } = require("./utils/constants");
|
|
20
|
+
const { SUPPORTED_CLIS, MANIFEST_FILES, PROBE_DIRS, SETTINGS_PRESERVE_FIELDS, cliDisplayName } = require("./utils/constants");
|
|
21
21
|
const {
|
|
22
22
|
deviceFingerprint, registerProject, projectIdFor,
|
|
23
23
|
getGitRemoteOrigin, inferProjectName, detectStackHint,
|
|
24
24
|
collectMetadata, buildProjectIdentity, hashManifestFiles,
|
|
25
|
+
loadProjectBinding, validProjectId, validateProjectDisplayName,
|
|
26
|
+
bindingTargetMatches, projectTargetInfo, boundProjectIdentityFromBinding,
|
|
27
|
+
applyBoundProjectIdentity, writeProjectBinding,
|
|
25
28
|
} = require("./utils/identity");
|
|
26
29
|
const { PLAN_LEVELS, _detectPlanLocal, requirePlan, getSwarmQuotaLocal } = require("./utils/plan");
|
|
27
30
|
|
|
@@ -58,6 +61,32 @@ const DRIFT_TRACKED_CONFIGS = [
|
|
|
58
61
|
".windsurfrules",
|
|
59
62
|
".aider.conf.yml",
|
|
60
63
|
];
|
|
64
|
+
const LIVE_MANIFEST_DEFAULTS = Object.freeze({
|
|
65
|
+
"ai/manifest/current_state.json": JSON.stringify({
|
|
66
|
+
managed: true,
|
|
67
|
+
schema: "0dai.current_state.v1",
|
|
68
|
+
status: "initialized",
|
|
69
|
+
phase: "bootstrap",
|
|
70
|
+
current_milestone: "initialization",
|
|
71
|
+
current_sprint: "bootstrap",
|
|
72
|
+
release_posture: "local",
|
|
73
|
+
notes: "Initial 0dai live-state scaffold. Update this file as the project execution state changes.",
|
|
74
|
+
}, null, 2) + "\n",
|
|
75
|
+
"ai/manifest/current_task.json": JSON.stringify({
|
|
76
|
+
managed: true,
|
|
77
|
+
schema: "0dai.current_task.v1",
|
|
78
|
+
task_id: "bootstrap",
|
|
79
|
+
title: "Initialize 0dai project context",
|
|
80
|
+
status: "active",
|
|
81
|
+
phase: "bootstrap",
|
|
82
|
+
acceptance: [
|
|
83
|
+
"ai/manifest/project.yaml reflects project identity",
|
|
84
|
+
"ai/manifest/commands.yaml reflects safe project commands",
|
|
85
|
+
"ai/docs/roadmap.md captures the next work",
|
|
86
|
+
],
|
|
87
|
+
linked: [],
|
|
88
|
+
}, null, 2) + "\n",
|
|
89
|
+
});
|
|
61
90
|
|
|
62
91
|
// --- API ---
|
|
63
92
|
function apiCall(endpoint, data, options = {}) {
|
|
@@ -84,10 +113,10 @@ function apiCall(endpoint, data, options = {}) {
|
|
|
84
113
|
const opts = {
|
|
85
114
|
hostname: url.hostname,
|
|
86
115
|
port: url.port || (url.protocol === "https:" ? 443 : 80),
|
|
87
|
-
path: url.pathname,
|
|
116
|
+
path: url.pathname + url.search,
|
|
88
117
|
method: body ? "POST" : "GET",
|
|
89
118
|
headers,
|
|
90
|
-
timeout: 60000,
|
|
119
|
+
timeout: Number(options && options.timeoutMs) > 0 ? Number(options.timeoutMs) : 60000,
|
|
91
120
|
};
|
|
92
121
|
if (body) opts.headers["Content-Length"] = Buffer.byteLength(body);
|
|
93
122
|
const req = mod.request(opts, (res) => {
|
|
@@ -98,6 +127,11 @@ function apiCall(endpoint, data, options = {}) {
|
|
|
98
127
|
catch { resolve({ error: `HTTP ${res.statusCode}` }); }
|
|
99
128
|
});
|
|
100
129
|
});
|
|
130
|
+
if (options && options.background) {
|
|
131
|
+
req.on("socket", (socket) => {
|
|
132
|
+
if (socket && typeof socket.unref === "function") socket.unref();
|
|
133
|
+
});
|
|
134
|
+
}
|
|
101
135
|
req.on("error", (e) => resolve({ error: `${e.message}. Is ${API_URL} reachable?` }));
|
|
102
136
|
req.on("timeout", () => { req.destroy(); resolve({ error: "request timed out after 60s. Check your internet connection or try again." }); });
|
|
103
137
|
if (body) req.write(body);
|
|
@@ -345,33 +379,78 @@ function writeFiles(target, files) {
|
|
|
345
379
|
return created + updated;
|
|
346
380
|
}
|
|
347
381
|
|
|
382
|
+
function missingLiveManifestDefaults(target, pendingFiles = {}) {
|
|
383
|
+
const missing = {};
|
|
384
|
+
for (const [rel, content] of Object.entries(LIVE_MANIFEST_DEFAULTS)) {
|
|
385
|
+
if (Object.prototype.hasOwnProperty.call(pendingFiles, rel)) continue;
|
|
386
|
+
if (!fs.existsSync(path.join(target, rel))) missing[rel] = content;
|
|
387
|
+
}
|
|
388
|
+
return missing;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function ensureLiveManifestDefaults(target) {
|
|
392
|
+
const missing = missingLiveManifestDefaults(target);
|
|
393
|
+
for (const [rel, content] of Object.entries(missing)) {
|
|
394
|
+
const p = path.join(target, rel);
|
|
395
|
+
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
396
|
+
fs.writeFileSync(p, content, "utf8");
|
|
397
|
+
}
|
|
398
|
+
return Object.keys(missing);
|
|
399
|
+
}
|
|
400
|
+
|
|
348
401
|
// --- Repo Script Lookup ---
|
|
349
|
-
function
|
|
350
|
-
|
|
402
|
+
function repoScriptCandidates(target, scriptName) {
|
|
403
|
+
return [
|
|
351
404
|
path.join(target, "scripts", scriptName),
|
|
352
405
|
path.join(process.cwd(), "scripts", scriptName),
|
|
406
|
+
path.join(__dirname, "..", "scripts", scriptName),
|
|
353
407
|
path.join(__dirname, "..", "..", "..", "scripts", scriptName),
|
|
354
408
|
path.join(__dirname, "..", "..", "..", "..", "scripts", scriptName),
|
|
355
409
|
];
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function findRepoScript(target, scriptName) {
|
|
413
|
+
const candidates = repoScriptCandidates(target, scriptName);
|
|
356
414
|
for (const c of candidates) { if (fs.existsSync(c)) return c; }
|
|
357
415
|
return null;
|
|
358
416
|
}
|
|
359
417
|
|
|
418
|
+
// Python scripts copied into the package at lib/python/ on prepack
|
|
419
|
+
// (scripts/build-python-bundle.js). Lets pure-stdlib commands work for npm
|
|
420
|
+
// end-users who have no repo checkout of scripts/. Mirrors experience.js (#4360).
|
|
421
|
+
function resolveBundledScript(scriptName) {
|
|
422
|
+
const bundled = path.join(__dirname, "python", scriptName);
|
|
423
|
+
return fs.existsSync(bundled) ? bundled : null;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// Prefer a repo copy (operator/dev checkout); fall back to the bundled copy.
|
|
427
|
+
function resolvePythonScript(target, scriptName) {
|
|
428
|
+
return findRepoScript(target, scriptName) || resolveBundledScript(scriptName);
|
|
429
|
+
}
|
|
430
|
+
|
|
360
431
|
// --- Version Check ---
|
|
361
|
-
async function checkVersion() {
|
|
432
|
+
async function checkVersion(options = {}) {
|
|
362
433
|
try {
|
|
434
|
+
const force = Boolean(options && options.force);
|
|
435
|
+
const background = options && Object.prototype.hasOwnProperty.call(options, "background")
|
|
436
|
+
? Boolean(options.background)
|
|
437
|
+
: true;
|
|
363
438
|
const intervalSec = parseInt(process.env.ODAI_UPDATE_CHECK_INTERVAL || "3600");
|
|
364
439
|
let lastCheck = 0;
|
|
365
440
|
try { lastCheck = parseFloat(fs.readFileSync(VERSION_CHECK_FILE, "utf8")); } catch {}
|
|
366
|
-
if (Date.now() / 1000 - lastCheck < intervalSec) return;
|
|
441
|
+
if (!force && Date.now() / 1000 - lastCheck < intervalSec) return;
|
|
367
442
|
fs.mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 });
|
|
368
443
|
fs.writeFileSync(VERSION_CHECK_FILE, String(Date.now() / 1000));
|
|
369
|
-
const result = await apiCall("/v1/version"
|
|
444
|
+
const result = await apiCall("/v1/version", null, {
|
|
445
|
+
background,
|
|
446
|
+
timeoutMs: options && options.timeoutMs,
|
|
447
|
+
});
|
|
370
448
|
if (result.version && result.version !== VERSION) {
|
|
371
449
|
const cmp = (a, b) => { const [a1,a2,a3] = a.split(".").map(Number); const [b1,b2,b3] = b.split(".").map(Number); return a1 - b1 || a2 - b2 || a3 - b3; };
|
|
372
450
|
if (cmp(result.version, VERSION) > 0) {
|
|
373
451
|
log(`Update available: ${VERSION} → ${result.version}`);
|
|
374
|
-
|
|
452
|
+
const suffix = options && options.initHint ? ", then rerun 0dai init" : "";
|
|
453
|
+
console.log(` Run: npm update -g @0dai-dev/cli${suffix}\n`);
|
|
375
454
|
}
|
|
376
455
|
}
|
|
377
456
|
} catch {}
|
|
@@ -379,7 +458,7 @@ async function checkVersion() {
|
|
|
379
458
|
|
|
380
459
|
// --- Experience ---
|
|
381
460
|
function recordExperienceEvent(target, payload) {
|
|
382
|
-
const script =
|
|
461
|
+
const script = resolvePythonScript(target, "experience_pipeline.py");
|
|
383
462
|
if (!script) return;
|
|
384
463
|
try {
|
|
385
464
|
spawnSync("python3", [script, "record-json", "--target", target], {
|
|
@@ -394,7 +473,7 @@ module.exports = {
|
|
|
394
473
|
VERSION, API_URL, T, R, D, E, G, W,
|
|
395
474
|
log, CONFIG_DIR, AUTH_FILE, VERSION_CHECK_FILE, PROJECTS_FILE,
|
|
396
475
|
// Constants
|
|
397
|
-
PLAN_LEVELS, MANIFEST_FILES, PROBE_DIRS, SUPPORTED_CLIS, SETTINGS_PRESERVE_FIELDS,
|
|
476
|
+
PLAN_LEVELS, MANIFEST_FILES, PROBE_DIRS, SUPPORTED_CLIS, SETTINGS_PRESERVE_FIELDS, cliDisplayName,
|
|
398
477
|
// API
|
|
399
478
|
apiCall,
|
|
400
479
|
// Auth
|
|
@@ -402,6 +481,9 @@ module.exports = {
|
|
|
402
481
|
fetchAuthStatus, makeEnsureAuthenticated, ensureLicenseActivation,
|
|
403
482
|
// Identity
|
|
404
483
|
deviceFingerprint, registerProject, projectIdFor,
|
|
484
|
+
loadProjectBinding, validProjectId, validateProjectDisplayName,
|
|
485
|
+
bindingTargetMatches, projectTargetInfo, boundProjectIdentityFromBinding,
|
|
486
|
+
applyBoundProjectIdentity, writeProjectBinding,
|
|
405
487
|
getGitRemoteOrigin, inferProjectName, detectStackHint,
|
|
406
488
|
collectMetadata, buildProjectIdentity, hashManifestFiles,
|
|
407
489
|
// Plan / Tier
|
|
@@ -409,7 +491,8 @@ module.exports = {
|
|
|
409
491
|
// Project
|
|
410
492
|
sendProjectHeartbeat, recordExperienceEvent, logFirstRunSuccess,
|
|
411
493
|
// Files
|
|
412
|
-
mergeSettingsJson, writeFiles,
|
|
494
|
+
mergeSettingsJson, writeFiles, missingLiveManifestDefaults, ensureLiveManifestDefaults,
|
|
495
|
+
findRepoScript, repoScriptCandidates, resolveBundledScript, resolvePythonScript,
|
|
413
496
|
// Version
|
|
414
497
|
checkVersion,
|
|
415
498
|
// Re-exports for convenience
|