@tera-system/pro 0.1.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 (170) hide show
  1. package/LICENSE.md +59 -0
  2. package/MANIFEST.json +24 -0
  3. package/README.md +190 -0
  4. package/RELEASES.md +44 -0
  5. package/WELCOME.md +35 -0
  6. package/agents/application-blueprint.md +412 -0
  7. package/agents/auditor.md +670 -0
  8. package/agents/design-reviewer.md +397 -0
  9. package/agents/domain-expert-agent.md +510 -0
  10. package/agents/domain-research-agent.md +360 -0
  11. package/agents/engineering-agent-dotnet.md +222 -0
  12. package/agents/engineering-agent-phaser.md +279 -0
  13. package/agents/engineering-agent-typescript.md +304 -0
  14. package/agents/engineering-agent.md +147 -0
  15. package/agents/monitor.md +400 -0
  16. package/agents/production-erp-expert.md +506 -0
  17. package/agents/project-knowledge-agent.md +271 -0
  18. package/agents/qa-agent.md +503 -0
  19. package/agents/tera-business-transformation-consultant.md +290 -0
  20. package/agents/tera-client-engagement.md +891 -0
  21. package/agents/tera-software-designer.md +242 -0
  22. package/agents/tera-strategic-advisor.md +325 -0
  23. package/agents/tera-system-evolution.md +764 -0
  24. package/agents/tera.md +526 -0
  25. package/agents/ui-designer.md +431 -0
  26. package/commands/tera-approve.md +36 -0
  27. package/commands/tera-diagnose.md +49 -0
  28. package/commands/tera-gate.md +42 -0
  29. package/commands/tera-help.md +43 -0
  30. package/commands/tera-new-project.md +37 -0
  31. package/commands/tera-plan.md +36 -0
  32. package/commands/tera-request-build.md +47 -0
  33. package/commands/tera-resume.md +36 -0
  34. package/commands/tera-review.md +54 -0
  35. package/commands/tera-status.md +40 -0
  36. package/commands/tera-update.md +50 -0
  37. package/core/project-control/templates/TASK_TEMPLATE.md +146 -0
  38. package/core/tera-system/AGENT_ACTIVATION_MATRIX.md +285 -0
  39. package/core/tera-system/AGENT_DEPENDENCY_MAP.md +117 -0
  40. package/core/tera-system/AGENT_GENERATION_TEMPLATE.md +312 -0
  41. package/core/tera-system/AGENT_PERMISSION_MODEL.md +343 -0
  42. package/core/tera-system/AIS_PROTOCOL.md +191 -0
  43. package/core/tera-system/TERA_AGENT_CONDUCT.md +102 -0
  44. package/core/tera-system/TERA_CONTINUOUS_IMPROVEMENT_POLICY.md +111 -0
  45. package/core/tera-system/TERA_DISTRIBUTION_POLICY.md +291 -0
  46. package/core/tera-system/TERA_LICENSE_GUARD.md +144 -0
  47. package/core/tera-system/TERA_PROJECT_DECISION.md +281 -0
  48. package/core/tera-system/TERA_USER_GUIDE.md +462 -0
  49. package/core/tera-system/TOOLING_AND_MCP_POLICY.md +285 -0
  50. package/core/tera-system/TeraApplicationQuestionBank.md +362 -0
  51. package/core/tera-system/TeraArchitectureMap.md +91 -0
  52. package/core/tera-system/TeraClientPolicy.md +366 -0
  53. package/core/tera-system/TeraHelperAgents.md +970 -0
  54. package/core/tera-system/TeraPolicyMap.md +131 -0
  55. package/core/tera-system/TeraPreExecutionGate.md +818 -0
  56. package/core/tera-system/TeraPreparationDocumentationGovernance.md +370 -0
  57. package/core/tera-system/TeraPricingPolicy.md +674 -0
  58. package/core/tera-system/TeraProjectIntakePolicy.md +164 -0
  59. package/core/tera-system/TeraScenarioStressTests.md +168 -0
  60. package/core/tera-system/TeraSubAgents.md +854 -0
  61. package/core/tera-system/TeraSystemMaintenanceChecklist.md +80 -0
  62. package/core/tera-system/TeraTokenPolicy.md +362 -0
  63. package/core/tera-system/Tera_Project_Preparation_Files.md +1045 -0
  64. package/core/tera-system/agent-helpers/application-blueprint-details.md +177 -0
  65. package/core/tera-system/client-helpers/tera-client-engagement-discovery-domains.md +99 -0
  66. package/core/tera-system/client-helpers/tera-client-engagement-gates.md +258 -0
  67. package/core/tera-system/client-helpers/tera-client-engagement-pricing.md +341 -0
  68. package/core/tera-system/client-helpers/tera-client-engagement-protocols.md +692 -0
  69. package/core/tera-system/consulting-helpers/BTCA_METHODOLOGY_FRAMEWORK.md +195 -0
  70. package/core/tera-system/consulting-helpers/BTCA_REPORT_TEMPLATES.md +266 -0
  71. package/core/tera-system/design-system/ACCESSIBILITY_RULES.md +31 -0
  72. package/core/tera-system/design-system/COMPONENT_LIBRARY_SCHEMA.md +46 -0
  73. package/core/tera-system/design-system/DESIGN_MD_INTEGRATION.md +59 -0
  74. package/core/tera-system/design-system/DESIGN_REVIEW_STANDARDS.md +241 -0
  75. package/core/tera-system/design-system/DESIGN_SOURCE_PROTOCOL.md +61 -0
  76. package/core/tera-system/design-system/DESIGN_SYSTEM_OVERVIEW.md +66 -0
  77. package/core/tera-system/design-system/DESIGN_TOKENS_SCHEMA.md +66 -0
  78. package/core/tera-system/design-system/EXTERNAL_REFERENCE_ANALYSIS.md +52 -0
  79. package/core/tera-system/design-system/FIGMA_INTEGRATION.md +138 -0
  80. package/core/tera-system/design-system/INTERNAL_KITS_INDEX.md +26 -0
  81. package/core/tera-system/design-system/LAYOUT_PATTERNS.md +52 -0
  82. package/core/tera-system/design-system/MOBILE_UI_UX_STANDARDS.md +342 -0
  83. package/core/tera-system/design-system/RTL_LTR_RULES.md +39 -0
  84. package/core/tera-system/design-system/UI_ACCEPTANCE_GATE.md +80 -0
  85. package/core/tera-system/design-system/kits/KIT_ADMIN_DASHBOARD.md +102 -0
  86. package/core/tera-system/engineering-governance/ENGINEERING_AGENT_RESPONSIBILITIES.md +210 -0
  87. package/core/tera-system/engineering-governance/ENGINEERING_BEST_PRACTICES.md +468 -0
  88. package/core/tera-system/engineering-governance/ENGINEERING_GOVERNANCE_GATE.md +131 -0
  89. package/core/tera-system/engineering-governance/ENGINEERING_REVIEW_CHECKLIST.md +129 -0
  90. package/core/tera-system/engineering-governance/QUALITY_GATE_THRESHOLDS.md +159 -0
  91. package/core/tera-system/engineering-helpers/engineering-agent-core.md +171 -0
  92. package/core/tera-system/knowledge-base/OPENHANDS_ARCHITECTURE_REFERENCE.md +243 -0
  93. package/core/tera-system/knowledge-base/manufacturing/00_INDEX.md +32 -0
  94. package/core/tera-system/knowledge-base/manufacturing/01_MANUFACTURING_ERP_CORE_CONCEPTS.md +7 -0
  95. package/core/tera-system/knowledge-base/manufacturing/02_SAP_MANUFACTURING_RESEARCH.md +7 -0
  96. package/core/tera-system/knowledge-base/manufacturing/03_DYNAMICS_365_MANUFACTURING_RESEARCH.md +7 -0
  97. package/core/tera-system/knowledge-base/manufacturing/04_ORACLE_MANUFACTURING_RESEARCH.md +7 -0
  98. package/core/tera-system/knowledge-base/manufacturing/05_ODOO_MANUFACTURING_RESEARCH.md +7 -0
  99. package/core/tera-system/knowledge-base/manufacturing/06_ERPNEXT_MANUFACTURING_RESEARCH.md +7 -0
  100. package/core/tera-system/knowledge-base/manufacturing/07_MANUFACTURING_COSTING_GUIDE.md +7 -0
  101. package/core/tera-system/knowledge-base/manufacturing/08_PRODUCTION_DISCOVERY_QUESTIONS.md +7 -0
  102. package/core/tera-system/knowledge-base/manufacturing/09_MANUFACTURING_BLUEPRINT_CHECKLIST.md +7 -0
  103. package/core/tera-system/knowledge-base/manufacturing/10_PRODUCTION_TEST_SCENARIOS.md +7 -0
  104. package/core/tera-system/knowledge-base/manufacturing/11_QUALITY_REWORK_AND_SCRAP_GUIDE.md +7 -0
  105. package/core/tera-system/knowledge-base/manufacturing/12_MRP_AND_PLANNING_GUIDE.md +7 -0
  106. package/core/tera-system/knowledge-base/manufacturing/13_LOCAL_AND_REGIONAL_MANUFACTURING_CONTEXT.md +7 -0
  107. package/core/tera-system/knowledge-base/manufacturing/14_VENDOR_COMPARISON_MATRIX.md +7 -0
  108. package/core/tera-system/product-standards/maintenance-apps/BEST_PRACTICES_DOMAIN.md +325 -0
  109. package/core/tera-system/product-standards/maintenance-apps/STANDARD_DEFINITION.md +142 -0
  110. package/core/tera-system/profiles/PROFILES_INDEX.md +57 -0
  111. package/core/tera-system/profiles/TEMPLATE.md +47 -0
  112. package/core/tera-system/profiles/dotnet-blazor-ef.md +76 -0
  113. package/core/tera-system/profiles/dotnet-razorpages-adonet.md +137 -0
  114. package/core/tera-system/profiles/dotnet-wpf-sqlite.md +159 -0
  115. package/core/tera-system/profiles/effect-bun-opencode.md +109 -0
  116. package/core/tera-system/profiles/flutter-mobile.md +369 -0
  117. package/core/tera-system/profiles/nextjs-prisma.md +110 -0
  118. package/core/tera-system/profiles/phaser-react-node.md +302 -0
  119. package/core/tera-system/profiles/react-pwa.md +97 -0
  120. package/core/tera-system/runtime/CLIENT_DISCOVERY_PROTOCOL.md +145 -0
  121. package/core/tera-system/runtime/DOMAIN_INTELLIGENCE_PROTOCOL.md +124 -0
  122. package/core/tera-system/runtime/MVP_DEFINITION_PROTOCOL.md +176 -0
  123. package/core/tera-system/runtime/TERA_RUNTIME_CHECKLISTS.md +646 -0
  124. package/core/tera-system/runtime/TERA_RUNTIME_PROTOCOLS.md +50 -0
  125. package/core/tera-system/runtime/TERA_RUNTIME_PROTOCOLS_CLIENT.md +355 -0
  126. package/core/tera-system/runtime/TERA_RUNTIME_PROTOCOLS_CORE.md +799 -0
  127. package/core/tera-system/runtime/TERA_RUNTIME_TEMPLATES.md +908 -0
  128. package/core/tera-system/runtime/TERA_RUNTIME_TEMPLATES_DELIVERY.md +584 -0
  129. package/core/tera-system/runtime/TERA_RUNTIME_TEMPLATES_PREPARATION.md +376 -0
  130. package/core/tera-system/runtime/TERA_SOLUTION_PREPARATION_PROTOCOL.md +335 -0
  131. package/core/tera-system/runtime/TERA_SOLUTION_PREPARATION_TEMPLATES.md +397 -0
  132. package/core/tera-system/runtime/VERSION_LIFECYCLE_PROTOCOL.md +296 -0
  133. package/core/tera-system/semgrep-rules/README.md +32 -0
  134. package/core/tera-system/semgrep-rules/tera-security.yml +66 -0
  135. package/core/tera-system/semgrep-rules/tera-standards.yml +49 -0
  136. package/core/tera-system/teranoo-ui/README.md +48 -0
  137. package/core/tera-system/teranoo-ui/components/button.tsx +51 -0
  138. package/core/tera-system/teranoo-ui/components/card.tsx +49 -0
  139. package/core/tera-system/teranoo-ui/components/dashboard-layout.tsx +36 -0
  140. package/core/tera-system/teranoo-ui/components/data-table.tsx +146 -0
  141. package/core/tera-system/teranoo-ui/components/empty-state.tsx +31 -0
  142. package/core/tera-system/teranoo-ui/components/kpi-card.tsx +42 -0
  143. package/core/tera-system/teranoo-ui/components/page-header.tsx +25 -0
  144. package/core/tera-system/teranoo-ui/components/search-input.tsx +40 -0
  145. package/core/tera-system/teranoo-ui/components/sidebar.tsx +66 -0
  146. package/core/tera-system/teranoo-ui/components/stats-card.tsx +37 -0
  147. package/core/tera-system/teranoo-ui/registry.json +77 -0
  148. package/core/tera-system/teranoo-ui/styles/teranoo-theme.css +61 -0
  149. package/opencode.tera.example.json +30 -0
  150. package/package.json +43 -0
  151. package/scripts/build-bundle.mjs +118 -0
  152. package/scripts/build.mjs +110 -0
  153. package/scripts/dev/mock-server.mjs +124 -0
  154. package/scripts/install.js +406 -0
  155. package/scripts/lib/bundle.mjs +120 -0
  156. package/scripts/lib/integrity.mjs +195 -0
  157. package/scripts/lib/license-gate.mjs +263 -0
  158. package/scripts/lib/license.mjs +90 -0
  159. package/scripts/lib/public-key.pem +3 -0
  160. package/scripts/preflight.mjs +55 -0
  161. package/scripts/tera-heartbeat.mjs +132 -0
  162. package/scripts/tera-license.mjs +63 -0
  163. package/scripts/tera-sync.mjs +116 -0
  164. package/tools/tera-clean.ps1 +97 -0
  165. package/tools/tera-fetch.ps1 +165 -0
  166. package/tools/tera-release.ps1 +96 -0
  167. package/tools/tera-schedule.ps1 +59 -0
  168. package/tools/tera-update.ps1 +472 -0
  169. package/tools/tera-watch.ps1 +154 -0
  170. package/tools/update-client-repositories.ps1 +92 -0
@@ -0,0 +1,132 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @tera-system/pro — Heartbeat command
4
+ * --------------------------------------------------------------
5
+ * Checks in with the license server. If more than HEARTBEAT_DAYS
6
+ * have passed since the last successful heartbeat, the client
7
+ * contacts the server to re-validate the license key and detect
8
+ * revocations.
9
+ *
10
+ * Can be invoked:
11
+ * - manually : node scripts/tera-heartbeat.mjs
12
+ * - post-install : called by install.js
13
+ * - before commands : called by preflight.mjs (if due)
14
+ *
15
+ * Env:
16
+ * TERA_LICENSE_API — API base URL (default https://teranoo.com/api/license)
17
+ * TERA_TARGET_DIR — target workspace
18
+ */
19
+ import fs from "node:fs";
20
+ import path from "node:path";
21
+ import { readLicenseState, writeLicenseState } from "./lib/license.mjs";
22
+
23
+ const HEARTBEAT_DAYS = 3;
24
+ const HEARTBEAT_MS = HEARTBEAT_DAYS * 86400000;
25
+ const API_URL = process.env.TERA_LICENSE_API || "https://teranoo.com/api/license";
26
+
27
+ function resolveTarget() {
28
+ const args = process.argv.slice(2);
29
+ for (let i = 0; i < args.length; i++) {
30
+ if (args[i] === "--target" && args[i + 1]) return path.resolve(args[i + 1]);
31
+ }
32
+ return path.resolve(process.env.TERA_TARGET_DIR || process.env.INIT_CWD || process.cwd());
33
+ }
34
+
35
+ async function serverVerify(licenseKey) {
36
+ if (!licenseKey) return null;
37
+ try {
38
+ const controller = new AbortController();
39
+ const timeout = setTimeout(() => controller.abort(), 8000);
40
+ const res = await fetch(`${API_URL}/verify`, {
41
+ method: "POST",
42
+ headers: { "Content-Type": "application/json" },
43
+ body: JSON.stringify({ licenseKey }),
44
+ signal: controller.signal,
45
+ });
46
+ clearTimeout(timeout);
47
+ if (!res.ok) return null;
48
+ return await res.json();
49
+ } catch {
50
+ return null;
51
+ }
52
+ }
53
+
54
+ const target = resolveTarget();
55
+ const stateFile = path.join(target, ".tera", "license.state.json");
56
+ const heartbeatFile = path.join(target, ".tera", "heartbeat.state.json");
57
+
58
+ (async () => {
59
+ const now = Date.now();
60
+
61
+ // Read previous heartbeat
62
+ let hb = {};
63
+ try {
64
+ hb = JSON.parse(fs.readFileSync(heartbeatFile, "utf8"));
65
+ } catch {
66
+ hb = {};
67
+ }
68
+ const lastHb = hb.lastHeartbeat ? new Date(hb.lastHeartbeat).getTime() : 0;
69
+ const due = now - lastHb > HEARTBEAT_MS;
70
+
71
+ const license = readLicenseState(stateFile) || {};
72
+
73
+ if (!due) {
74
+ console.log(`✔ heartbeat not due (last: ${hb.lastHeartbeat || "never"}, every ${HEARTBEAT_DAYS}d)`);
75
+ process.exit(0);
76
+ }
77
+
78
+ if (license.state !== "Active" && license.state !== "Grace") {
79
+ console.log(`⚠ heartbeat skipped — license state is "${license.state}" (not Active)`);
80
+ process.exit(0);
81
+ }
82
+
83
+ console.log(`heartbeat due (last ${hb.lastHeartbeat || "never"}) — contacting server...`);
84
+ const server = await serverVerify(license.licenseKey);
85
+
86
+ if (!server) {
87
+ console.log(`⚠ server unreachable — keeping current license state (will retry in ${HEARTBEAT_DAYS}d)`);
88
+ fs.mkdirSync(path.dirname(heartbeatFile), { recursive: true });
89
+ fs.writeFileSync(heartbeatFile, JSON.stringify({
90
+ lastHeartbeat: new Date().toISOString(),
91
+ lastResult: "unreachable",
92
+ }, null, 2));
93
+ process.exit(2); // non-zero: available but not verified
94
+ }
95
+
96
+ if (server.valid === false) {
97
+ console.log(`❌ license revoked/invalid by server: ${server.reason || "revoked"}`);
98
+ writeLicenseState(stateFile, {
99
+ ...license,
100
+ state: "Invalid",
101
+ reason: server.reason || "server-revoked",
102
+ checkedAt: new Date().toISOString(),
103
+ });
104
+ fs.mkdirSync(path.dirname(heartbeatFile), { recursive: true });
105
+ fs.writeFileSync(heartbeatFile, JSON.stringify({
106
+ lastHeartbeat: new Date().toISOString(),
107
+ lastResult: "revoked",
108
+ }, null, 2));
109
+ process.exit(1);
110
+ }
111
+
112
+ // Valid — refresh local state
113
+ writeLicenseState(stateFile, {
114
+ ...license,
115
+ state: "Active",
116
+ reason: "heartbeat-verified",
117
+ expiresAt: server.expiresAt || license.expiresAt || null,
118
+ checkedAt: new Date().toISOString(),
119
+ });
120
+ fs.mkdirSync(path.dirname(heartbeatFile), { recursive: true });
121
+ fs.writeFileSync(heartbeatFile, JSON.stringify({
122
+ lastHeartbeat: new Date().toISOString(),
123
+ lastResult: "ok",
124
+ expiresAt: server.expiresAt || null,
125
+ }, null, 2));
126
+
127
+ console.log(`✔ heartbeat verified — state=Active (expires ${server.expiresAt || "?"})`);
128
+ process.exit(0);
129
+ })().catch((err) => {
130
+ console.error(`❌ heartbeat error: ${err.message}`);
131
+ process.exit(1);
132
+ });
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env node
2
+ // @tera/core license CLI — issue & verify license keys (SCP-005)
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import { issueLicenseKey, assessLicenseState, readLicenseState, STATES } from "./lib/license.mjs";
7
+
8
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
9
+ const DEV_KEY = path.join(__dirname, "..", "license", "dev", "dev-private-key.pem");
10
+
11
+ function usage() {
12
+ console.log(`Usage:
13
+ tera-license gen --tier <basic|premium> --project <name> --expires <YYYY-MM-DD> [--seats N]
14
+ (private key: env TERA_LICENSE_PRIVATE or the dev key under license/dev/)
15
+ tera-license check [--key <licenseKey> | --state-file <path>]
16
+ (or env TERA_LICENSE_KEY)`);
17
+ }
18
+
19
+ const args = process.argv.slice(2);
20
+ const cmd = args[0];
21
+ function flag(name) {
22
+ const i = args.indexOf(name);
23
+ return i >= 0 && args[i + 1] ? args[i + 1] : undefined;
24
+ }
25
+
26
+ if (cmd === "gen") {
27
+ const tier = flag("--tier") || "basic";
28
+ const project = flag("--project");
29
+ const expires = flag("--expires");
30
+ const seats = flag("--seats") ? Number(flag("--seats")) : 1;
31
+ if (!project || !expires) {
32
+ usage();
33
+ process.exit(1);
34
+ }
35
+ const privatePem = process.env.TERA_LICENSE_PRIVATE || fs.readFileSync(DEV_KEY, "utf8");
36
+ const key = issueLicenseKey(
37
+ { tier, project, issuedAt: new Date().toISOString(), expiresAt: new Date(expires).toISOString(), seats },
38
+ privatePem
39
+ );
40
+ console.log(key);
41
+ } else if (cmd === "check") {
42
+ let key = flag("--key");
43
+ if (!key) {
44
+ const stateFile = flag("--state-file");
45
+ if (stateFile) {
46
+ const st = readLicenseState(stateFile);
47
+ key = st && st.licenseKey;
48
+ if (!key) {
49
+ console.log(JSON.stringify({ state: STATES.UNLICENSED, reason: "no-key-trial" }, null, 2));
50
+ process.exit(0);
51
+ }
52
+ } else {
53
+ key = process.env.TERA_LICENSE_KEY;
54
+ }
55
+ }
56
+ if (!key) {
57
+ console.log(JSON.stringify({ state: STATES.UNLICENSED, reason: "no-key-trial" }, null, 2));
58
+ process.exit(0);
59
+ }
60
+ console.log(JSON.stringify(assessLicenseState(key), null, 2));
61
+ } else {
62
+ usage();
63
+ }
@@ -0,0 +1,116 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @tera-system/pro — Sync command
4
+ * --------------------------------------------------------------
5
+ * Downloads the signed core bundle from the remote server (or a
6
+ * configured URL), verifies its Ed25519 signature, validates every
7
+ * file hash, and applies the files into the target workspace.
8
+ *
9
+ * Usage:
10
+ * node scripts/tera-sync.mjs [--target <dir>] [--url <bundleUrl>]
11
+ *
12
+ * Env:
13
+ * TERA_SYNC_URL — bundle URL (default: https://teranoo.com/api/tera-system/bundle)
14
+ * TERA_TARGET_DIR — target workspace (fallback: INIT_CWD / cwd)
15
+ */
16
+ import fs from "node:fs";
17
+ import path from "node:path";
18
+ import { verifyBundle, applyBundle } from "./lib/bundle.mjs";
19
+
20
+ const DEFAULT_URL = "https://teranoo.com/api/tera-system/bundle";
21
+
22
+ function resolveArgs() {
23
+ const args = process.argv.slice(2);
24
+ let target = process.env.TERA_TARGET_DIR || process.env.INIT_CWD || process.cwd();
25
+ let url = process.env.TERA_SYNC_URL || DEFAULT_URL;
26
+ for (let i = 0; i < args.length; i++) {
27
+ if (args[i] === "--target" && args[i + 1]) target = path.resolve(args[i + 1]);
28
+ if (args[i] === "--url" && args[i + 1]) url = args[i + 1];
29
+ if (args[i] === "--help") {
30
+ console.log("Usage: node scripts/tera-sync.mjs [--target <dir>] [--url <bundleUrl>]");
31
+ process.exit(0);
32
+ }
33
+ }
34
+ return { target: path.resolve(target), url };
35
+ }
36
+
37
+ async function downloadBundle(url) {
38
+ const controller = new AbortController();
39
+ const timeout = setTimeout(() => controller.abort(), 15000);
40
+ try {
41
+ const res = await fetch(url, { signal: controller.signal });
42
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
43
+ const bundle = await res.json();
44
+ clearTimeout(timeout);
45
+ return bundle;
46
+ } catch (err) {
47
+ clearTimeout(timeout);
48
+ throw new Error(`download-failed: ${err.message}`);
49
+ }
50
+ }
51
+
52
+ const { target, url } = resolveArgs();
53
+ const syncStateFile = path.join(target, ".tera", "sync.state.json");
54
+
55
+ (async () => {
56
+ console.log(`\n@tera-system/pro — sync`);
57
+ console.log(` target : ${target}`);
58
+ console.log(` url : ${url}\n`);
59
+
60
+ let bundle;
61
+ try {
62
+ bundle = await downloadBundle(url);
63
+ } catch (err) {
64
+ console.log(`❌ ${err.message}`);
65
+ console.log(" The system will retry later (background) or on next install.");
66
+ fs.mkdirSync(path.dirname(syncStateFile), { recursive: true });
67
+ fs.writeFileSync(
68
+ syncStateFile,
69
+ JSON.stringify({
70
+ status: "pending",
71
+ lastError: err.message,
72
+ lastAttempt: new Date().toISOString(),
73
+ }, null, 2)
74
+ );
75
+ process.exit(1);
76
+ }
77
+
78
+ // Verify signature + hashes
79
+ const verification = verifyBundle(bundle);
80
+ if (!verification.ok) {
81
+ console.log(`❌ Bundle verification failed: ${verification.reason}`);
82
+ fs.mkdirSync(path.dirname(syncStateFile), { recursive: true });
83
+ fs.writeFileSync(syncStateFile, JSON.stringify({
84
+ status: "failed",
85
+ reason: verification.reason,
86
+ lastAttempt: new Date().toISOString(),
87
+ }, null, 2));
88
+ process.exit(1);
89
+ }
90
+
91
+ console.log(`✔ Signature verified (contentSha256=${verification.contentSha256.slice(0, 16)}...)`);
92
+ console.log(`✔ Bundle version : ${verification.packageVersion} (created ${verification.createdAt?.slice(0, 10)})`);
93
+
94
+ // Apply files
95
+ const result = applyBundle(bundle, target);
96
+ console.log(`✔ Files applied : ${result.applied}`);
97
+ if (result.errors.length) {
98
+ console.log(`⚠ Errors : ${result.errors.join(", ")}`);
99
+ }
100
+
101
+ // Record sync state
102
+ fs.mkdirSync(path.dirname(syncStateFile), { recursive: true });
103
+ fs.writeFileSync(syncStateFile, JSON.stringify({
104
+ status: "completed",
105
+ packageVersion: verification.packageVersion,
106
+ contentSha256: verification.contentSha256,
107
+ filesApplied: result.applied,
108
+ syncedAt: new Date().toISOString(),
109
+ }, null, 2));
110
+
111
+ console.log(`✔ Sync state : .tera/sync.state.json`);
112
+ console.log("\n✔ sync complete");
113
+ })().catch((err) => {
114
+ console.error(`❌ unexpected error: ${err.message}`);
115
+ process.exit(1);
116
+ });
@@ -0,0 +1,97 @@
1
+ <#
2
+ Tera Clean - release-readiness whitespace cleaner for Tera-managed roots.
3
+ Finds and removes trailing whitespace and extra blank lines at EOF so that
4
+ the repo passes `git diff --check` (the gate enforced by tera-update.ps1).
5
+
6
+ Scope: only Tera-managed roots: .opencode/, tera-system/, tera-workshop/.
7
+ Mode:
8
+ -DryRun : report only, no writes (default)
9
+ (no -DryRun) : clean files in place
10
+
11
+ Source of truth: tera-system/TERA_DISTRIBUTION_POLICY.md (SCP-2026-08-15-010)
12
+ #>
13
+ [CmdletBinding()]
14
+ param(
15
+ [string]$RepoPath = (Get-Location).Path,
16
+ [switch]$DryRun
17
+ )
18
+
19
+ Set-StrictMode -Version 5
20
+ $ErrorActionPreference = 'Stop'
21
+
22
+ function Test-TrailingWhitespace([string]$Text) {
23
+ # [ \t]+ at end of line (optional \r before \n for CRLF)
24
+ return $Text -match '(?m)[ \t]+\r?$'
25
+ }
26
+
27
+ function Test-ExtraBlankAtEof([string]$Text) {
28
+ # more than one line terminator at EOF => extra blank lines
29
+ return $Text -match '(\r?\n){2,}$'
30
+ }
31
+
32
+ function Get-LineEnding([string]$Text) {
33
+ if ($Text.Contains("`r`n")) { return "`r`n" }
34
+ return "`n"
35
+ }
36
+
37
+ $RepoPath = [System.IO.Path]::GetFullPath($RepoPath)
38
+ if (-not (Test-Path -LiteralPath $RepoPath -PathType Container)) { throw "RepoPath does not exist: $RepoPath" }
39
+ $gitRoot = (& git -C $RepoPath rev-parse --show-toplevel 2>$null).Trim()
40
+ if ($LASTEXITCODE -ne 0 -or [string]::IsNullOrWhiteSpace($gitRoot)) { throw "RepoPath must be a Git repository." }
41
+ $gitRoot = [System.IO.Path]::GetFullPath($gitRoot)
42
+
43
+ $binaryExt = @('.png','.jpg','.jpeg','.gif','.ico','.bmp','.woff','.woff2','.ttf','.otf','.node')
44
+ $managedRoots = @('.opencode', 'tera-system', 'tera-workshop')
45
+
46
+ $tracked = @(& git -C $RepoPath ls-files)
47
+ $targets = @()
48
+ foreach ($file in $tracked) {
49
+ $isManaged = $false
50
+ foreach ($root in $managedRoots) {
51
+ if ($file -eq $root -or $file.StartsWith($root + '/')) { $isManaged = $true; break }
52
+ }
53
+ if (-not $isManaged) { continue }
54
+ $ext = [System.IO.Path]::GetExtension($file).ToLowerInvariant()
55
+ if ($binaryExt -contains $ext) { continue }
56
+ $full = Join-Path $gitRoot ($file -replace '/', '\')
57
+ if (-not (Test-Path -LiteralPath $full)) { continue }
58
+ $targets += $file
59
+ }
60
+
61
+ $changed = @()
62
+ foreach ($file in $targets) {
63
+ $full = Join-Path $gitRoot ($file -replace '/', '\')
64
+ $raw = [System.IO.File]::ReadAllText($full)
65
+ $hasTrailing = Test-TrailingWhitespace $raw
66
+ $hasExtraBlank = Test-ExtraBlankAtEof $raw
67
+ if (-not ($hasTrailing -or $hasExtraBlank)) { continue }
68
+
69
+ if ($DryRun) {
70
+ $changed += [pscustomobject]@{ File = $file; Trailing = $hasTrailing; ExtraBlank = $hasExtraBlank }
71
+ continue
72
+ }
73
+
74
+ $eol = Get-LineEnding $raw
75
+ $lines = $raw -split '\r?\n'
76
+ $clean = @()
77
+ foreach ($ln in $lines) { $clean += $ln -replace '[ \t]+$', '' }
78
+ # remove trailing empty lines but keep exactly one final line terminator
79
+ while ($clean.Count -gt 1 -and $clean[$clean.Count - 1].Trim() -eq '') {
80
+ $clean = $clean[0..($clean.Count - 2)]
81
+ }
82
+ $newText = ($clean -join $eol) + $eol
83
+ if ($newText -ne $raw) {
84
+ [System.IO.File]::WriteAllText($full, $newText, (New-Object System.Text.UTF8Encoding $false))
85
+ $changed += [pscustomobject]@{ File = $file; Trailing = $hasTrailing; ExtraBlank = $hasExtraBlank }
86
+ }
87
+ }
88
+
89
+ if ($DryRun) {
90
+ Write-Host "Tera Clean [DRY RUN] - files that would be changed: $($changed.Count)"
91
+ $changed | ForEach-Object { Write-Host (" {0} (trailing={1}, extraBlank={2})" -f $_.File, $_.Trailing, $_.ExtraBlank) }
92
+ Write-Host "Run without -DryRun to apply."
93
+ } else {
94
+ Write-Host "Tera Clean - changed files: $($changed.Count)"
95
+ $changed | ForEach-Object { Write-Host (" {0}" -f $_.File) }
96
+ Write-Host "Next: git add -A && git diff --cached --check"
97
+ }
@@ -0,0 +1,165 @@
1
+ <#
2
+ Tera Fetch — fetch release metadata, check for updates, or build a verified
3
+ release package from the origin repository.
4
+ Modes:
5
+ -List : list available version tags from the origin repository
6
+ -Check : compare installed version (lock.json) with latest from origin
7
+ -Fetch : create a verified release package (Manifest + SHA256SUMS)
8
+ from the exact tag via tera-release.ps1
9
+ Source of truth: tera-system/TERA_DISTRIBUTION_POLICY.md (SCP-2026-08-15-009, 012)
10
+ #>
11
+ [CmdletBinding()]
12
+ param(
13
+ [Parameter(Mandatory = $true)]
14
+ [string]$RepositoryUrl,
15
+
16
+ [switch]$List,
17
+
18
+ [switch]$Check,
19
+
20
+ [string]$Version,
21
+
22
+ [string]$OutPath,
23
+
24
+ [string]$RepoPath = (Get-Location).Path,
25
+
26
+ [ValidateSet('stable', 'beta')]
27
+ [string]$Channel = 'stable',
28
+
29
+ [string]$CachePath
30
+ )
31
+
32
+ Set-StrictMode -Version 5
33
+ # Continue وليس Stop: هذا السكربت يعتمد على فحوص $LASTEXITCODE الصريحة بعد كل
34
+ # استدعاء git، و stderr الطبيعي لـ git (مثل رسائل التقدم) يجب ألا يوقف التنفيذ.
35
+ $ErrorActionPreference = 'Continue'
36
+
37
+ function Fail([string]$Message) {
38
+ throw $Message
39
+ }
40
+
41
+ # مقارنة إصدارات رقمية X.Y.Z — ترجع 1 إذا a أحدث، -1 إذا b أحدث، 0 إذا متساويان
42
+ function Compare-TeraVersion([string]$a, [string]$b) {
43
+ $aParts = ($a -replace '^tera-v', '').Split('.')
44
+ $bParts = ($b -replace '^tera-v', '').Split('.')
45
+ for ($i = 0; $i -lt [Math]::Max($aParts.Count, $bParts.Count); $i++) {
46
+ $pa = 0; $pb = 0
47
+ if ($i -lt $aParts.Count) { [void][int]::TryParse($aParts[$i], [ref]$pa) }
48
+ if ($i -lt $bParts.Count) { [void][int]::TryParse($bParts[$i], [ref]$pb) }
49
+ if ($pa -gt $pb) { return 1 }
50
+ if ($pa -lt $pb) { return -1 }
51
+ }
52
+ return 0
53
+ }
54
+
55
+ # جلب قائمة إصدارات tera-vX.Y.Z من الأصل (مشترك بين List و Check)
56
+ function Get-OriginVersions([string]$Url) {
57
+ $lines = @(& git ls-remote --tags $Url 2>$null)
58
+ if ($LASTEXITCODE -ne 0) { Fail "Could not reach origin repository: $Url" }
59
+ $versions = New-Object System.Collections.Generic.List[string]
60
+ foreach ($line in $lines) {
61
+ if ($line -match 'refs/tags/(tera-v[0-9]+\.[0-9]+\.[0-9]+)(\^\{\})?$') {
62
+ $versions.Add($matches[1])
63
+ }
64
+ }
65
+ $sorted = @($versions | Sort-Object -Unique)
66
+ return , $sorted
67
+ }
68
+
69
+ #---------------------------------------------------------------------
70
+ # 1. List mode — عرض الإصدارات المتاحة من المستودع الأصلي
71
+ #---------------------------------------------------------------------
72
+ if ($List) {
73
+ $versions = Get-OriginVersions $RepositoryUrl
74
+ if ($versions.Count -eq 0) {
75
+ Write-Host "No tera-vX.Y.Z tags found in: $RepositoryUrl"
76
+ } else {
77
+ Write-Host "Available Tera versions (from origin):"
78
+ $versions | ForEach-Object { Write-Host " $_" }
79
+ }
80
+ return
81
+ }
82
+
83
+ #---------------------------------------------------------------------
84
+ # 2. Check mode — هل يوجد إصدار أحدث من المثبت؟
85
+ #---------------------------------------------------------------------
86
+ if ($Check) {
87
+ $versions = Get-OriginVersions $RepositoryUrl
88
+ if ($versions.Count -eq 0) {
89
+ Write-Host "No tera-vX.Y.Z tags found in: $RepositoryUrl"
90
+ return
91
+ }
92
+ $latest = $versions[-1]
93
+
94
+ $lockPath = Join-Path $RepoPath '.tera\lock.json'
95
+ $installed = $null
96
+ if (Test-Path -LiteralPath $lockPath) {
97
+ try {
98
+ $lock = Get-Content -LiteralPath $lockPath -Raw | ConvertFrom-Json
99
+ if ($lock.tag) { $installed = [string]$lock.tag }
100
+ } catch {
101
+ Write-Warning "lock.json unreadable at $lockPath — treating as no installation."
102
+ }
103
+ }
104
+
105
+ if (-not $installed) {
106
+ Write-Host "No prior installation detected in $RepoPath."
107
+ Write-Host "Latest available: $latest"
108
+ Write-Host "Install with: tera-update.ps1 -Version $latest -Mode Apply -Initialize -RepositoryUrl `"$RepositoryUrl`""
109
+ return
110
+ }
111
+
112
+ $cmp = Compare-TeraVersion $installed $latest
113
+ if ($cmp -ge 0) {
114
+ Write-Host "You are up to date: $installed (latest in origin: $latest)"
115
+ } else {
116
+ Write-Host "Update available!"
117
+ Write-Host " Installed: $installed"
118
+ Write-Host " Latest: $latest"
119
+ Write-Host "Upgrade with: tera-update.ps1 -Version $latest -Mode Apply -RepositoryUrl `"$RepositoryUrl`""
120
+ }
121
+ return
122
+ }
123
+
124
+ #---------------------------------------------------------------------
125
+ # 3. Fetch mode — بناء حزمة إصدار موثقة من المستودع الأصلي
126
+ #---------------------------------------------------------------------
127
+ if (-not $Version) { Fail 'Fetch mode requires -Version (e.g. tera-v2.5.0).' }
128
+ if (-not $OutPath) { Fail 'Fetch mode requires -OutPath.' }
129
+
130
+ if (-not $CachePath) {
131
+ $CachePath = Join-Path $env:LOCALAPPDATA 'Tera\release-cache'
132
+ }
133
+ New-Item -ItemType Directory -Path $CachePath -Force | Out-Null
134
+
135
+ # إنشاء استنساخ موقت (shallow) عند الـ Tag المطلوب من المستودع الأصلي
136
+ $tempCheckout = Join-Path $CachePath ("checkout-" + [guid]::NewGuid().ToString('N'))
137
+ try {
138
+ $null = & git init $tempCheckout 2>$null
139
+ if ($LASTEXITCODE -ne 0) { Fail "Could not initialize temp checkout." }
140
+ $null = & git -C $tempCheckout remote add origin $RepositoryUrl 2>$null
141
+ if ($LASTEXITCODE -ne 0) { Fail "Could not add origin remote: $RepositoryUrl" }
142
+ $refspec = ('refs/tags/{0}:refs/tags/{0}' -f $Version)
143
+ $null = & git -C $tempCheckout fetch --depth 1 origin $refspec 2>$null
144
+ if ($LASTEXITCODE -ne 0) { Fail "Could not fetch tag $Version from origin." }
145
+ $null = & git -C $tempCheckout checkout --quiet $Version 2>$null
146
+ if ($LASTEXITCODE -ne 0) { Fail "Could not checkout tag $Version." }
147
+
148
+ $commit = (& git -C $tempCheckout rev-list -n 1 $Version 2>$null).Trim()
149
+ if ($commit -notmatch '^[0-9a-fA-F]{40}$') { Fail "Could not resolve commit for $Version." }
150
+
151
+ Write-Host "Fetched $Version from origin (commit $commit)."
152
+ Write-Host "Building verified release package via tera-release.ps1 ..."
153
+
154
+ $releaseBuilder = Join-Path (Split-Path -Parent $MyInvocation.MyCommand.Path) 'tera-release.ps1'
155
+ & $releaseBuilder -Version $Version -Tag $Version -Channel $Channel -RepoPath $tempCheckout -OutputPath $OutPath
156
+ if ($LASTEXITCODE -ne 0) { Fail "tera-release.ps1 failed for $Version." }
157
+
158
+ Write-Host "Fetched and built verified package: $OutPath"
159
+ Write-Host "Next: run tera-update.ps1 -SourcePath $OutPath"
160
+ }
161
+ finally {
162
+ if (Test-Path -LiteralPath $tempCheckout) {
163
+ Remove-Item -LiteralPath $tempCheckout -Recurse -Force -ErrorAction SilentlyContinue
164
+ }
165
+ }
@@ -0,0 +1,96 @@
1
+ <#
2
+ Build a deterministic Tera release directory from the exact Git tag.
3
+ The working tree is never used as the release content.
4
+ #>
5
+ [CmdletBinding()]
6
+ param(
7
+ [Parameter(Mandatory = $true)]
8
+ [string]$Version,
9
+
10
+ [string]$Tag,
11
+
12
+ [ValidateSet('stable', 'beta')]
13
+ [string]$Channel = 'stable',
14
+
15
+ [Parameter(Mandatory = $true)]
16
+ [string]$OutputPath,
17
+
18
+ [string]$RepoPath = (Get-Location).Path
19
+ )
20
+
21
+ Set-StrictMode -Version 5
22
+ $ErrorActionPreference = 'Stop'
23
+
24
+ if (-not $Tag) { $Tag = "tera-v$Version" }
25
+ $RepoPath = [System.IO.Path]::GetFullPath($RepoPath)
26
+ $OutputPath = [System.IO.Path]::GetFullPath($OutputPath)
27
+ if (-not (Test-Path -LiteralPath $RepoPath -PathType Container)) { throw "RepoPath not found: $RepoPath" }
28
+ $top = (git -C $RepoPath rev-parse --show-toplevel 2>$null).Trim()
29
+ if ($LASTEXITCODE -ne 0 -or [string]::IsNullOrWhiteSpace($top)) { throw 'RepoPath must be a Git repository.' }
30
+ $commit = (git -C $RepoPath rev-list -n 1 "$Tag^{commit}" 2>$null).Trim()
31
+ if ($LASTEXITCODE -ne 0 -or $commit -notmatch '^[0-9a-fA-F]{40}$') { throw "Tag does not resolve to a full commit: $Tag" }
32
+ $normalizedVersion = ($Version -replace '^tera-', '') -replace '^v', ''
33
+
34
+ # التحقق من اتساق Version مع Tag — رفض الحزم المتناقضة
35
+ $expectedTag = "tera-v$normalizedVersion"
36
+ if ($Tag -ne $expectedTag) {
37
+ throw "Tag $Tag does not match Version $Version (expected $expectedTag)."
38
+ }
39
+
40
+ if (Test-Path -LiteralPath $OutputPath) {
41
+ $children = @(Get-ChildItem -LiteralPath $OutputPath -Force)
42
+ if ($children.Count -gt 0) { throw "OutputPath must be empty: $OutputPath" }
43
+ } else {
44
+ New-Item -ItemType Directory -Path $OutputPath -Force | Out-Null
45
+ }
46
+
47
+ $tempRoot = Join-Path ([System.IO.Path]::GetTempPath()) ("tera-release-" + [guid]::NewGuid().ToString('N'))
48
+ $archivePath = Join-Path $tempRoot 'tag.zip'
49
+ $snapshotRoot = Join-Path $tempRoot 'snapshot'
50
+ $managedRoots = @('.opencode', 'tera-system', 'tera-workshop')
51
+ $files = New-Object System.Collections.Generic.List[string]
52
+
53
+ try {
54
+ New-Item -ItemType Directory -Path $tempRoot -Force | Out-Null
55
+ $null = & git -C $RepoPath archive --format=zip "--output=$archivePath" $Tag 2>$null
56
+ if ($LASTEXITCODE -ne 0 -or -not (Test-Path -LiteralPath $archivePath -PathType Leaf)) {
57
+ throw "Could not archive exact Git tag: $Tag"
58
+ }
59
+ Expand-Archive -LiteralPath $archivePath -DestinationPath $snapshotRoot -Force
60
+
61
+ foreach ($root in $managedRoots) {
62
+ $sourceRoot = Join-Path $snapshotRoot $root
63
+ if (-not (Test-Path -LiteralPath $sourceRoot -PathType Container)) { continue }
64
+ foreach ($file in Get-ChildItem -LiteralPath $sourceRoot -File -Recurse) {
65
+ $relative = ($file.FullName.Substring($snapshotRoot.Length + 1) -replace '\\', '/')
66
+ $target = Join-Path $OutputPath $relative
67
+ $targetDir = Split-Path -Parent $target
68
+ if (-not (Test-Path -LiteralPath $targetDir)) { New-Item -ItemType Directory -Path $targetDir -Force | Out-Null }
69
+ Copy-Item -LiteralPath $file.FullName -Destination $target -Force
70
+ $files.Add($relative)
71
+ }
72
+ }
73
+ if ($files.Count -eq 0) { throw 'No managed files found in the tagged snapshot.' }
74
+
75
+ $manifest = [ordered]@{
76
+ version = $normalizedVersion
77
+ tag = $Tag
78
+ commit = $commit
79
+ channel = $Channel
80
+ files = @($files | Sort-Object)
81
+ }
82
+ $manifest | ConvertTo-Json -Depth 5 | Set-Content -LiteralPath (Join-Path $OutputPath 'TERA_RELEASE_MANIFEST.json') -Encoding UTF8
83
+
84
+ $sumLines = New-Object System.Collections.Generic.List[string]
85
+ foreach ($relative in ($files | Sort-Object)) {
86
+ $hash = (Get-FileHash -LiteralPath (Join-Path $OutputPath $relative) -Algorithm SHA256).Hash.ToLowerInvariant()
87
+ $sumLines.Add("$hash $relative")
88
+ }
89
+ $sumLines | Set-Content -LiteralPath (Join-Path $OutputPath 'SHA256SUMS') -Encoding ASCII
90
+ }
91
+ finally {
92
+ if (Test-Path -LiteralPath $tempRoot) { Remove-Item -LiteralPath $tempRoot -Recurse -Force -ErrorAction SilentlyContinue }
93
+ }
94
+
95
+ Write-Host "Release created from exact tag: $OutputPath"
96
+ Write-Host "Tag=$Tag Commit=$commit Channel=$Channel Files=$($files.Count)"