@tera-system/pro 0.1.3 → 0.2.0

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.
Files changed (155) hide show
  1. package/MANIFEST.json +5 -3
  2. package/package.json +3 -6
  3. package/scripts/build.mjs +4 -3
  4. package/scripts/install.js +83 -15
  5. package/scripts/lib/license-gate.mjs +4 -4
  6. package/scripts/tera-heartbeat.mjs +4 -4
  7. package/agents/application-blueprint.md +0 -411
  8. package/agents/auditor.md +0 -669
  9. package/agents/design-reviewer.md +0 -396
  10. package/agents/domain-expert-agent.md +0 -514
  11. package/agents/domain-research-agent.md +0 -364
  12. package/agents/engineering-agent-dotnet.md +0 -222
  13. package/agents/engineering-agent-phaser.md +0 -279
  14. package/agents/engineering-agent-typescript.md +0 -304
  15. package/agents/engineering-agent.md +0 -147
  16. package/agents/monitor.md +0 -399
  17. package/agents/production-erp-expert.md +0 -510
  18. package/agents/project-knowledge-agent.md +0 -275
  19. package/agents/qa-agent.md +0 -502
  20. package/agents/tera-business-transformation-consultant.md +0 -294
  21. package/agents/tera-client-engagement.md +0 -895
  22. package/agents/tera-software-designer.md +0 -241
  23. package/agents/tera-strategic-advisor.md +0 -329
  24. package/agents/tera-system-evolution.md +0 -763
  25. package/agents/tera.md +0 -524
  26. package/agents/ui-designer.md +0 -430
  27. package/commands/tera-approve.md +0 -36
  28. package/commands/tera-diagnose.md +0 -49
  29. package/commands/tera-gate.md +0 -42
  30. package/commands/tera-help.md +0 -43
  31. package/commands/tera-new-project.md +0 -37
  32. package/commands/tera-plan.md +0 -36
  33. package/commands/tera-request-build.md +0 -47
  34. package/commands/tera-resume.md +0 -36
  35. package/commands/tera-review.md +0 -54
  36. package/commands/tera-status.md +0 -40
  37. package/commands/tera-update.md +0 -50
  38. package/core/tera-system/AGENT_ACTIVATION_MATRIX.md +0 -285
  39. package/core/tera-system/AGENT_DEPENDENCY_MAP.md +0 -117
  40. package/core/tera-system/AGENT_GENERATION_TEMPLATE.md +0 -312
  41. package/core/tera-system/AGENT_PERMISSION_MODEL.md +0 -343
  42. package/core/tera-system/AIS_PROTOCOL.md +0 -191
  43. package/core/tera-system/TERA_AGENT_CONDUCT.md +0 -102
  44. package/core/tera-system/TERA_CONTINUOUS_IMPROVEMENT_POLICY.md +0 -111
  45. package/core/tera-system/TERA_DISTRIBUTION_POLICY.md +0 -291
  46. package/core/tera-system/TERA_LICENSE_GUARD.md +0 -144
  47. package/core/tera-system/TERA_PROJECT_DECISION.md +0 -281
  48. package/core/tera-system/TERA_USER_GUIDE.md +0 -462
  49. package/core/tera-system/TOOLING_AND_MCP_POLICY.md +0 -285
  50. package/core/tera-system/TeraApplicationQuestionBank.md +0 -362
  51. package/core/tera-system/TeraArchitectureMap.md +0 -91
  52. package/core/tera-system/TeraClientPolicy.md +0 -366
  53. package/core/tera-system/TeraHelperAgents.md +0 -970
  54. package/core/tera-system/TeraPolicyMap.md +0 -131
  55. package/core/tera-system/TeraPreExecutionGate.md +0 -818
  56. package/core/tera-system/TeraPreparationDocumentationGovernance.md +0 -370
  57. package/core/tera-system/TeraPricingPolicy.md +0 -674
  58. package/core/tera-system/TeraProjectIntakePolicy.md +0 -164
  59. package/core/tera-system/TeraScenarioStressTests.md +0 -168
  60. package/core/tera-system/TeraSubAgents.md +0 -854
  61. package/core/tera-system/TeraSystemMaintenanceChecklist.md +0 -80
  62. package/core/tera-system/TeraTokenPolicy.md +0 -362
  63. package/core/tera-system/Tera_Project_Preparation_Files.md +0 -1045
  64. package/core/tera-system/agent-helpers/application-blueprint-details.md +0 -177
  65. package/core/tera-system/client-helpers/tera-client-engagement-discovery-domains.md +0 -99
  66. package/core/tera-system/client-helpers/tera-client-engagement-gates.md +0 -258
  67. package/core/tera-system/client-helpers/tera-client-engagement-pricing.md +0 -341
  68. package/core/tera-system/client-helpers/tera-client-engagement-protocols.md +0 -692
  69. package/core/tera-system/consulting-helpers/BTCA_METHODOLOGY_FRAMEWORK.md +0 -195
  70. package/core/tera-system/consulting-helpers/BTCA_REPORT_TEMPLATES.md +0 -266
  71. package/core/tera-system/design-system/ACCESSIBILITY_RULES.md +0 -31
  72. package/core/tera-system/design-system/COMPONENT_LIBRARY_SCHEMA.md +0 -46
  73. package/core/tera-system/design-system/DESIGN_MD_INTEGRATION.md +0 -59
  74. package/core/tera-system/design-system/DESIGN_REVIEW_STANDARDS.md +0 -241
  75. package/core/tera-system/design-system/DESIGN_SOURCE_PROTOCOL.md +0 -61
  76. package/core/tera-system/design-system/DESIGN_SYSTEM_OVERVIEW.md +0 -66
  77. package/core/tera-system/design-system/DESIGN_TOKENS_SCHEMA.md +0 -66
  78. package/core/tera-system/design-system/EXTERNAL_REFERENCE_ANALYSIS.md +0 -52
  79. package/core/tera-system/design-system/FIGMA_INTEGRATION.md +0 -138
  80. package/core/tera-system/design-system/INTERNAL_KITS_INDEX.md +0 -26
  81. package/core/tera-system/design-system/LAYOUT_PATTERNS.md +0 -52
  82. package/core/tera-system/design-system/MOBILE_UI_UX_STANDARDS.md +0 -342
  83. package/core/tera-system/design-system/RTL_LTR_RULES.md +0 -39
  84. package/core/tera-system/design-system/UI_ACCEPTANCE_GATE.md +0 -80
  85. package/core/tera-system/design-system/kits/KIT_ADMIN_DASHBOARD.md +0 -102
  86. package/core/tera-system/engineering-governance/ENGINEERING_AGENT_RESPONSIBILITIES.md +0 -210
  87. package/core/tera-system/engineering-governance/ENGINEERING_BEST_PRACTICES.md +0 -468
  88. package/core/tera-system/engineering-governance/ENGINEERING_GOVERNANCE_GATE.md +0 -131
  89. package/core/tera-system/engineering-governance/ENGINEERING_REVIEW_CHECKLIST.md +0 -129
  90. package/core/tera-system/engineering-governance/QUALITY_GATE_THRESHOLDS.md +0 -159
  91. package/core/tera-system/engineering-helpers/engineering-agent-core.md +0 -171
  92. package/core/tera-system/knowledge-base/OPENHANDS_ARCHITECTURE_REFERENCE.md +0 -243
  93. package/core/tera-system/knowledge-base/manufacturing/00_INDEX.md +0 -32
  94. package/core/tera-system/knowledge-base/manufacturing/01_MANUFACTURING_ERP_CORE_CONCEPTS.md +0 -7
  95. package/core/tera-system/knowledge-base/manufacturing/02_SAP_MANUFACTURING_RESEARCH.md +0 -7
  96. package/core/tera-system/knowledge-base/manufacturing/03_DYNAMICS_365_MANUFACTURING_RESEARCH.md +0 -7
  97. package/core/tera-system/knowledge-base/manufacturing/04_ORACLE_MANUFACTURING_RESEARCH.md +0 -7
  98. package/core/tera-system/knowledge-base/manufacturing/05_ODOO_MANUFACTURING_RESEARCH.md +0 -7
  99. package/core/tera-system/knowledge-base/manufacturing/06_ERPNEXT_MANUFACTURING_RESEARCH.md +0 -7
  100. package/core/tera-system/knowledge-base/manufacturing/07_MANUFACTURING_COSTING_GUIDE.md +0 -7
  101. package/core/tera-system/knowledge-base/manufacturing/08_PRODUCTION_DISCOVERY_QUESTIONS.md +0 -7
  102. package/core/tera-system/knowledge-base/manufacturing/09_MANUFACTURING_BLUEPRINT_CHECKLIST.md +0 -7
  103. package/core/tera-system/knowledge-base/manufacturing/10_PRODUCTION_TEST_SCENARIOS.md +0 -7
  104. package/core/tera-system/knowledge-base/manufacturing/11_QUALITY_REWORK_AND_SCRAP_GUIDE.md +0 -7
  105. package/core/tera-system/knowledge-base/manufacturing/12_MRP_AND_PLANNING_GUIDE.md +0 -7
  106. package/core/tera-system/knowledge-base/manufacturing/13_LOCAL_AND_REGIONAL_MANUFACTURING_CONTEXT.md +0 -7
  107. package/core/tera-system/knowledge-base/manufacturing/14_VENDOR_COMPARISON_MATRIX.md +0 -7
  108. package/core/tera-system/product-standards/maintenance-apps/BEST_PRACTICES_DOMAIN.md +0 -325
  109. package/core/tera-system/product-standards/maintenance-apps/STANDARD_DEFINITION.md +0 -142
  110. package/core/tera-system/profiles/PROFILES_INDEX.md +0 -57
  111. package/core/tera-system/profiles/TEMPLATE.md +0 -47
  112. package/core/tera-system/profiles/dotnet-blazor-ef.md +0 -76
  113. package/core/tera-system/profiles/dotnet-razorpages-adonet.md +0 -137
  114. package/core/tera-system/profiles/dotnet-wpf-sqlite.md +0 -159
  115. package/core/tera-system/profiles/effect-bun-opencode.md +0 -109
  116. package/core/tera-system/profiles/flutter-mobile.md +0 -369
  117. package/core/tera-system/profiles/nextjs-prisma.md +0 -110
  118. package/core/tera-system/profiles/phaser-react-node.md +0 -302
  119. package/core/tera-system/profiles/react-pwa.md +0 -97
  120. package/core/tera-system/runtime/CLIENT_DISCOVERY_PROTOCOL.md +0 -145
  121. package/core/tera-system/runtime/DOMAIN_INTELLIGENCE_PROTOCOL.md +0 -124
  122. package/core/tera-system/runtime/MVP_DEFINITION_PROTOCOL.md +0 -176
  123. package/core/tera-system/runtime/TERA_RUNTIME_CHECKLISTS.md +0 -646
  124. package/core/tera-system/runtime/TERA_RUNTIME_PROTOCOLS.md +0 -50
  125. package/core/tera-system/runtime/TERA_RUNTIME_PROTOCOLS_CLIENT.md +0 -355
  126. package/core/tera-system/runtime/TERA_RUNTIME_PROTOCOLS_CORE.md +0 -799
  127. package/core/tera-system/runtime/TERA_RUNTIME_TEMPLATES.md +0 -908
  128. package/core/tera-system/runtime/TERA_RUNTIME_TEMPLATES_DELIVERY.md +0 -584
  129. package/core/tera-system/runtime/TERA_RUNTIME_TEMPLATES_PREPARATION.md +0 -376
  130. package/core/tera-system/runtime/TERA_SOLUTION_PREPARATION_PROTOCOL.md +0 -335
  131. package/core/tera-system/runtime/TERA_SOLUTION_PREPARATION_TEMPLATES.md +0 -397
  132. package/core/tera-system/runtime/VERSION_LIFECYCLE_PROTOCOL.md +0 -296
  133. package/core/tera-system/semgrep-rules/README.md +0 -32
  134. package/core/tera-system/semgrep-rules/tera-security.yml +0 -66
  135. package/core/tera-system/semgrep-rules/tera-standards.yml +0 -49
  136. package/core/tera-system/teranoo-ui/README.md +0 -48
  137. package/core/tera-system/teranoo-ui/components/button.tsx +0 -51
  138. package/core/tera-system/teranoo-ui/components/card.tsx +0 -49
  139. package/core/tera-system/teranoo-ui/components/dashboard-layout.tsx +0 -36
  140. package/core/tera-system/teranoo-ui/components/data-table.tsx +0 -146
  141. package/core/tera-system/teranoo-ui/components/empty-state.tsx +0 -31
  142. package/core/tera-system/teranoo-ui/components/kpi-card.tsx +0 -42
  143. package/core/tera-system/teranoo-ui/components/page-header.tsx +0 -25
  144. package/core/tera-system/teranoo-ui/components/search-input.tsx +0 -40
  145. package/core/tera-system/teranoo-ui/components/sidebar.tsx +0 -66
  146. package/core/tera-system/teranoo-ui/components/stats-card.tsx +0 -37
  147. package/core/tera-system/teranoo-ui/registry.json +0 -77
  148. package/core/tera-system/teranoo-ui/styles/teranoo-theme.css +0 -61
  149. package/tools/tera-clean.ps1 +0 -97
  150. package/tools/tera-fetch.ps1 +0 -165
  151. package/tools/tera-release.ps1 +0 -96
  152. package/tools/tera-schedule.ps1 +0 -59
  153. package/tools/tera-update.ps1 +0 -472
  154. package/tools/tera-watch.ps1 +0 -154
  155. package/tools/update-client-repositories.ps1 +0 -92
package/MANIFEST.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "@tera-system/pro",
3
- "version": "0.1.3",
4
- "description": "Build manifest for the Tera System Pro commercial plugin component.",
3
+ "version": "0.2.0",
4
+ "description": "Build manifest for the Tera System Pro commercial plugin component (THIN mode — content served from teranoo.com signed bundle).",
5
5
  "compatibleEnvironments": [
6
6
  "opencode"
7
7
  ],
8
8
  "minOpencodeVersion": "1.18.16",
9
+ "mode": "thin",
9
10
  "content": {
10
11
  "agents": 20,
11
12
  "commands": 11,
@@ -13,7 +14,8 @@
13
14
  "tools": 7,
14
15
  "projectControlTemplates": 1
15
16
  },
16
- "builtAt": "2026-08-26",
17
+ "bundleRef": "core-bundle-v0.1.4.signed.json (SHA256 4C87BAB833AEC0ACE184A790819B51D58427C51F29124CE2B02DA3AAE98C3F13)",
18
+ "builtAt": "2026-08-27",
17
19
  "sha256": {
18
20
  "agents": "ef634a3bd0a1fb3739b59b74b1e58c150a6beed73c48ce48ac12416b15b6a6a6",
19
21
  "commands": "f8c91dab42e41d1f8362edfd816964ceabdbd95e8ebdd02e83ea36f7f805a259",
package/package.json CHANGED
@@ -1,18 +1,15 @@
1
1
  {
2
2
  "name": "@tera-system/pro",
3
- "version": "0.1.3",
4
- "description": "Tera System Pro أ¢â‚¬â€‌ commercial edition with enforced license gate, server sync, and heartbeat verification",
3
+ "version": "0.2.0",
4
+ "description": "Tera System Pro commercial edition (thin): system files are fetched from teranoo.com as a signed bundle; enforced license gate + heartbeat",
5
5
  "license": "SEE LICENSE IN LICENSE.md",
6
6
  "type": "module",
7
7
  "engines": {
8
8
  "node": ">=18"
9
9
  },
10
10
  "files": [
11
- "agents",
12
- "commands",
13
- "core",
14
- "tools",
15
11
  "scripts",
12
+ "core/project-control",
16
13
  "MANIFEST.json",
17
14
  "opencode.tera.example.json",
18
15
  "WELCOME.md",
package/scripts/build.mjs CHANGED
@@ -57,8 +57,9 @@ const groupSha = (dir) => {
57
57
  return createHash("sha256").update(lines.join("\n")).digest("hex");
58
58
  };
59
59
 
60
- /* 1. self-check counts vs manifest */
60
+ /* 1. self-check counts vs manifest (thin mode: content dirs absent is OK) */
61
61
  console.log("── self-check: content counts vs MANIFEST.json");
62
+ const thinMode = manifest.mode === "thin";
62
63
  const checks = [
63
64
  ["agents", "agents"],
64
65
  ["commands", "commands"],
@@ -68,8 +69,8 @@ let fail = false;
68
69
  for (const [dir, key] of checks) {
69
70
  const n = countFiles(dir);
70
71
  const expected = manifest.content[key];
71
- const ok = n === expected;
72
- console.log(` ${dir.padEnd(16)} ${n} (manifest ${expected}) ${ok ? "OK" : "MISMATCH"}`);
72
+ const ok = n === expected || (thinMode && n === 0);
73
+ console.log(` ${dir.padEnd(16)} ${n} (manifest ${expected}) ${ok ? "OK" : "MISMATCH"}${thinMode && n === 0 ? " [thin — served via bundle]" : ""}`);
73
74
  if (!ok) fail = true;
74
75
  }
75
76
  if (fail) {
@@ -21,6 +21,7 @@
21
21
  */
22
22
  import { createHash } from "node:crypto";
23
23
  import fs from "node:fs";
24
+ import os from "node:os";
24
25
  import path from "node:path";
25
26
  import { fileURLToPath } from "node:url";
26
27
  import { assessLicenseState, readLicenseState, writeLicenseState } from "./lib/license.mjs";
@@ -32,6 +33,12 @@ const PKG_ROOT = path.resolve(__dirname, "..");
32
33
  const MANIFEST_NAME = "tera-core.manifest.json";
33
34
  const CONFIG_EXAMPLE = "opencode.tera.example.json";
34
35
 
36
+ /** Generate a stable device id from hostname + target path. */
37
+ function createDeviceId(target) {
38
+ const seed = `${os.hostname()}::${target}`;
39
+ return createHash("sha256").update(seed).digest("hex").slice(0, 16);
40
+ }
41
+
35
42
  /* ---------------------------------- helpers ---------------------------------- */
36
43
 
37
44
  const sha256 = (buf) => createHash("sha256").update(buf).digest("hex");
@@ -243,10 +250,12 @@ const LICENSE_TIMEOUT_MS = 5000; // 5 seconds max for API call
243
250
 
244
251
  /**
245
252
  * Verify license via remote API (online).
253
+ * Requires BOTH licenseKey and accountEmail (the API validates both).
246
254
  * Returns: { ok: boolean, state: string, reason: string, expiresAt?: string, source: "api" }
247
255
  */
248
- async function verifyViaAPI(licenseKey) {
256
+ async function verifyViaAPI(licenseKey, accountEmail) {
249
257
  if (!licenseKey) return { ok: false, state: "Unlicensed", reason: "no-key", source: "api" };
258
+ if (!accountEmail) return { ok: false, state: "Unknown", reason: "no-account-email", source: "api-fallback" };
250
259
 
251
260
  try {
252
261
  const controller = new AbortController();
@@ -255,7 +264,7 @@ async function verifyViaAPI(licenseKey) {
255
264
  const res = await fetch(`${LICENSE_API_URL}/verify`, {
256
265
  method: "POST",
257
266
  headers: { "Content-Type": "application/json" },
258
- body: JSON.stringify({ licenseKey }),
267
+ body: JSON.stringify({ licenseKey, accountEmail }),
259
268
  signal: controller.signal,
260
269
  });
261
270
  clearTimeout(timeout);
@@ -270,7 +279,8 @@ async function verifyViaAPI(licenseKey) {
270
279
  const data = await res.json();
271
280
  return {
272
281
  ok: data.valid === true,
273
- state: data.state || "Invalid",
282
+ // The API does not return a `state` field — derive it from `valid` + `reason`.
283
+ state: data.valid === true ? "Active" : data.state || "Invalid",
274
284
  reason: data.reason || "api-response",
275
285
  expiresAt: data.expiresAt || null,
276
286
  source: "api",
@@ -281,6 +291,29 @@ async function verifyViaAPI(licenseKey) {
281
291
  }
282
292
  }
283
293
 
294
+ /**
295
+ * Try to ACTIVATE the license on this device (best-effort, never breaks install).
296
+ * Requires licenseKey + accountEmail + deviceId.
297
+ */
298
+ async function activateViaAPI(licenseKey, accountEmail, deviceId) {
299
+ if (!licenseKey || !accountEmail || !deviceId) return null;
300
+ try {
301
+ const controller = new AbortController();
302
+ const timeout = setTimeout(() => controller.abort(), LICENSE_TIMEOUT_MS);
303
+ const res = await fetch(`${LICENSE_API_URL}/activate`, {
304
+ method: "POST",
305
+ headers: { "Content-Type": "application/json" },
306
+ body: JSON.stringify({ licenseKey, accountEmail, deviceId }),
307
+ signal: controller.signal,
308
+ });
309
+ clearTimeout(timeout);
310
+ if (!res.ok) return null;
311
+ return await res.json();
312
+ } catch {
313
+ return null;
314
+ }
315
+ }
316
+
284
317
  /**
285
318
  * Verify license offline (Ed25519 signature check).
286
319
  * Returns: { ok: boolean, state: string, reason: string, expiresAt?: string, source: "offline" }
@@ -298,16 +331,16 @@ function verifyOffline(licenseKey) {
298
331
  }
299
332
 
300
333
  /**
301
- * Combined verification: API-first, offline fallback.
334
+ * Combined verification: API-first (with email), offline fallback.
302
335
  * Always returns a result — never throws.
303
336
  */
304
- async function verifyLicense(licenseKey) {
337
+ async function verifyLicense(licenseKey, accountEmail) {
305
338
  // Step 1: Try API (unless explicitly disabled)
306
339
  if (process.env.TERA_OFFLINE_ONLY === "1") {
307
340
  return verifyOffline(licenseKey);
308
341
  }
309
342
 
310
- const apiResult = await verifyViaAPI(licenseKey);
343
+ const apiResult = await verifyViaAPI(licenseKey, accountEmail);
311
344
 
312
345
  // If API succeeded and gave a definitive answer, use it
313
346
  if (apiResult.source === "api" && apiResult.ok !== undefined) {
@@ -337,7 +370,7 @@ const SYNC_URL = process.env.TERA_SYNC_URL || "https://teranoo.com/api/tera-syst
337
370
  /**
338
371
  * Attempt server sync (best-effort, never breaks install).
339
372
  * Downloads the signed bundle, verifies signature + hashes, and applies.
340
- * If unreachable/rejected bundled content remains (thick mode).
373
+ * In THIN mode this is the only source of system files (required).
341
374
  */
342
375
  async function tryServerSync() {
343
376
  try {
@@ -346,20 +379,20 @@ async function tryServerSync() {
346
379
  const res = await fetch(SYNC_URL, { signal: controller.signal });
347
380
  clearTimeout(timeout);
348
381
  if (!res.ok) {
349
- console.log(` [sync ] server HTTP ${res.status} — using bundled content`);
382
+ console.log(` [sync ] server HTTP ${res.status} — no files applied`);
350
383
  return false;
351
384
  }
352
385
  const bundle = await res.json();
353
386
  const v = verifyBundle(bundle);
354
387
  if (!v.ok) {
355
- console.log(` [sync ] bundle rejected (${v.reason}) — using bundled content`);
388
+ console.log(` [sync ] bundle rejected (${v.reason}) — no files applied`);
356
389
  return false;
357
390
  }
358
391
  const r = applyBundle(bundle, target);
359
392
  console.log(` [sync ] server bundle applied — ${r.applied} files (signed & verified)`);
360
393
  return true;
361
394
  } catch {
362
- console.log(` [sync ] server unreachable — using bundled content`);
395
+ console.log(` [sync ] server unreachable — no files applied`);
363
396
  return false;
364
397
  }
365
398
  }
@@ -367,8 +400,28 @@ async function tryServerSync() {
367
400
  // Run: sync → integrity manifest → license check (async wrapper, non-blocking)
368
401
  (async () => {
369
402
  try {
370
- // Step 1: best-effort server sync (self-healing / fresh content)
371
- await tryServerSync();
403
+ // Thin-mode detection: no bundled content the server bundle is REQUIRED.
404
+ const thinMode = !fs.existsSync(path.join(PKG_ROOT, "agents"));
405
+
406
+ // Step 1: server sync (REQUIRED in thin mode; self-healing in thick mode)
407
+ const synced = await tryServerSync();
408
+ if (thinMode && !synced) {
409
+ // No local content and server unreachable → system cannot operate yet.
410
+ // Background retry per design: install completes, sync pending.
411
+ fs.mkdirSync(path.join(target, ".tera"), { recursive: true });
412
+ fs.writeFileSync(
413
+ path.join(target, ".tera", "sync.state.json"),
414
+ JSON.stringify({
415
+ status: "pending",
416
+ thinMode: true,
417
+ lastError: "server-unreachable-at-install",
418
+ lastAttempt: new Date().toISOString(),
419
+ }, null, 2)
420
+ );
421
+ console.log(" ⚠ [sync ] THIN MODE — server unreachable at install.");
422
+ console.log(" No system files were installed. Run later:");
423
+ console.log(" npm run tera-sync (or re-run install when online)");
424
+ }
372
425
 
373
426
  // Step 2: integrity manifest (after sync, covers server files too)
374
427
  try {
@@ -379,17 +432,32 @@ async function tryServerSync() {
379
432
  console.log(` [integrity] skipped (${err.message}) — install continues`);
380
433
  }
381
434
 
382
- // Step 3: license verification (API-first, offline fallback)
435
+ // Step 3: license verification (API-first with email, offline fallback)
383
436
  const stateFile = path.join(target, ".tera", "license.state.json");
384
- let licenseKey = (readLicenseState(stateFile) || {}).licenseKey;
437
+ const prevState = readLicenseState(stateFile) || {};
438
+ let licenseKey = prevState.licenseKey;
385
439
  if (!licenseKey) licenseKey = process.env.TERA_LICENSE_KEY || undefined;
440
+ let accountEmail = prevState.accountEmail;
441
+ if (!accountEmail) accountEmail = process.env.TERA_LICENSE_EMAIL || undefined;
442
+ const deviceId = prevState.deviceId || createDeviceId(target);
443
+
444
+ // 3a. best-effort activation (only when we have key + email and not activated yet)
445
+ if (licenseKey && accountEmail && !prevState.activated) {
446
+ const act = await activateViaAPI(licenseKey, accountEmail, deviceId);
447
+ if (act && act.ok === true) {
448
+ console.log(` [license ] ✔ activated on this device (${act.tier} / ${act.project})`);
449
+ }
450
+ }
386
451
 
387
- const result = await verifyLicense(licenseKey);
452
+ const result = await verifyLicense(licenseKey, accountEmail);
388
453
 
389
454
  writeLicenseState(stateFile, {
390
455
  state: result.state,
391
456
  reason: result.reason,
392
457
  licenseKey: licenseKey || null,
458
+ accountEmail: accountEmail || null,
459
+ deviceId,
460
+ activated: true,
393
461
  expiresAt: result.expiresAt || null,
394
462
  graceUntil: result.graceUntil || null,
395
463
  source: result.source,
@@ -22,15 +22,15 @@ const RECHECK_INTERVAL_MS = RECHECK_INTERVAL_DAYS * 86400000;
22
22
  * Try to verify license via API (online check).
23
23
  * Returns null if API unreachable.
24
24
  */
25
- async function verifyViaAPI(licenseKey, apiUrl) {
26
- if (!licenseKey || !apiUrl) return null;
25
+ async function verifyViaAPI(licenseKey, accountEmail, apiUrl) {
26
+ if (!licenseKey || !accountEmail || !apiUrl) return null;
27
27
  try {
28
28
  const controller = new AbortController();
29
29
  const timeout = setTimeout(() => controller.abort(), 5000);
30
30
  const res = await fetch(`${apiUrl}/verify`, {
31
31
  method: "POST",
32
32
  headers: { "Content-Type": "application/json" },
33
- body: JSON.stringify({ licenseKey }),
33
+ body: JSON.stringify({ licenseKey, accountEmail }),
34
34
  signal: controller.signal,
35
35
  });
36
36
  clearTimeout(timeout);
@@ -200,7 +200,7 @@ export async function enforceLicenseGate(target, options = {}) {
200
200
  const recheck = checkRecheckDue(lic);
201
201
  if (recheck.due && !offlineOnly) {
202
202
  // Try API re-verification
203
- const apiResult = await verifyViaAPI(lic.licenseKey, apiUrl);
203
+ const apiResult = await verifyViaAPI(lic.licenseKey, lic.accountEmail, apiUrl);
204
204
  if (apiResult && apiResult.valid === false) {
205
205
  return {
206
206
  allow: false,
@@ -32,15 +32,15 @@ function resolveTarget() {
32
32
  return path.resolve(process.env.TERA_TARGET_DIR || process.env.INIT_CWD || process.cwd());
33
33
  }
34
34
 
35
- async function serverVerify(licenseKey) {
36
- if (!licenseKey) return null;
35
+ async function serverVerify(licenseKey, accountEmail) {
36
+ if (!licenseKey || !accountEmail) return null;
37
37
  try {
38
38
  const controller = new AbortController();
39
39
  const timeout = setTimeout(() => controller.abort(), 8000);
40
40
  const res = await fetch(`${API_URL}/verify`, {
41
41
  method: "POST",
42
42
  headers: { "Content-Type": "application/json" },
43
- body: JSON.stringify({ licenseKey }),
43
+ body: JSON.stringify({ licenseKey, accountEmail }),
44
44
  signal: controller.signal,
45
45
  });
46
46
  clearTimeout(timeout);
@@ -81,7 +81,7 @@ const heartbeatFile = path.join(target, ".tera", "heartbeat.state.json");
81
81
  }
82
82
 
83
83
  console.log(`heartbeat due (last ${hb.lastHeartbeat || "never"}) — contacting server...`);
84
- const server = await serverVerify(license.licenseKey);
84
+ const server = await serverVerify(license.licenseKey, license.accountEmail);
85
85
 
86
86
  if (!server) {
87
87
  console.log(`⚠ server unreachable — keeping current license state (will retry in ${HEARTBEAT_DAYS}d)`);