chati-dev 4.0.7 → 4.0.9
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.
|
@@ -7,21 +7,23 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Behavior:
|
|
9
9
|
* - No key configured → allow (orchestrator handles inline activation)
|
|
10
|
-
* - Cache VALID < 24h → allow silently
|
|
10
|
+
* - Cache VALID < 24h → allow silently + session ping (once/day/project)
|
|
11
11
|
* - Cache EXPIRED/INVALID < 24h → block with renewal message
|
|
12
12
|
* - Cache stale (> 24h) → call API, refresh cache, then allow or block
|
|
13
13
|
* - API unreachable → fail open (allow), never block work
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
|
|
17
|
-
import { join } from 'path';
|
|
17
|
+
import { join, basename } from 'path';
|
|
18
18
|
import { homedir } from 'os';
|
|
19
19
|
|
|
20
20
|
const API_BASE = 'https://chati.dev/api';
|
|
21
|
+
const TELEMETRY_ENDPOINT = 'https://chati.dev/api/telemetry';
|
|
21
22
|
const CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
22
23
|
|
|
23
24
|
const GLOBAL_DIR = join(homedir(), '.chati-dev');
|
|
24
25
|
const LICENSE_PATH = join(GLOBAL_DIR, 'license.yaml');
|
|
26
|
+
const PINGS_PATH = join(GLOBAL_DIR, 'pings.yaml');
|
|
25
27
|
|
|
26
28
|
async function main() {
|
|
27
29
|
let input = '';
|
|
@@ -48,7 +50,10 @@ async function main() {
|
|
|
48
50
|
const age = checkedAt ? Date.now() - new Date(checkedAt).getTime() : Infinity;
|
|
49
51
|
|
|
50
52
|
if (age < CACHE_TTL_MS) {
|
|
51
|
-
if (status === 'VALID') {
|
|
53
|
+
if (status === 'VALID') {
|
|
54
|
+
await allowWithPing(licenseKey);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
52
57
|
if (status === 'EXPIRED' || status === 'INVALID') {
|
|
53
58
|
block(buildMessage(status, readYamlField(licenseRaw, 'reason')));
|
|
54
59
|
return;
|
|
@@ -78,7 +83,10 @@ async function main() {
|
|
|
78
83
|
mkdirSync(GLOBAL_DIR, { recursive: true });
|
|
79
84
|
writeFileSync(LICENSE_PATH, dumpYaml(updated));
|
|
80
85
|
|
|
81
|
-
if (data.status === 'VALID') {
|
|
86
|
+
if (data.status === 'VALID') {
|
|
87
|
+
await allowWithPing(licenseKey);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
82
90
|
block(buildMessage(data.status, data.reason));
|
|
83
91
|
} catch {
|
|
84
92
|
allow(); // Fail open — API unreachable
|
|
@@ -88,6 +96,69 @@ async function main() {
|
|
|
88
96
|
}
|
|
89
97
|
}
|
|
90
98
|
|
|
99
|
+
// ---------------------------------------------------------------------------
|
|
100
|
+
// Session Ping (once per day per project)
|
|
101
|
+
// ---------------------------------------------------------------------------
|
|
102
|
+
|
|
103
|
+
async function allowWithPing(licenseKey) {
|
|
104
|
+
const projectPath = process.cwd();
|
|
105
|
+
const projectName = basename(projectPath) || 'unknown';
|
|
106
|
+
|
|
107
|
+
if (shouldPing(projectPath)) {
|
|
108
|
+
recordPing(projectPath); // sync — prevents duplicate pings even if fetch fails
|
|
109
|
+
const pingPromise = sendSessionPing(licenseKey, projectName);
|
|
110
|
+
allow();
|
|
111
|
+
await pingPromise; // keep process alive until ping completes (max 3s)
|
|
112
|
+
} else {
|
|
113
|
+
allow();
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function shouldPing(projectPath) {
|
|
118
|
+
try {
|
|
119
|
+
if (!existsSync(PINGS_PATH)) return true;
|
|
120
|
+
const content = readFileSync(PINGS_PATH, 'utf-8');
|
|
121
|
+
const today = new Date().toISOString().substring(0, 10);
|
|
122
|
+
const escaped = projectPath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
123
|
+
const match = content.match(new RegExp(`^${escaped}:\\s*(.+)$`, 'm'));
|
|
124
|
+
return !match || match[1].trim() !== today;
|
|
125
|
+
} catch { return false; } // fail closed — don't add latency on parse error
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function recordPing(projectPath) {
|
|
129
|
+
try {
|
|
130
|
+
mkdirSync(GLOBAL_DIR, { recursive: true });
|
|
131
|
+
const today = new Date().toISOString().substring(0, 10);
|
|
132
|
+
let content = existsSync(PINGS_PATH) ? readFileSync(PINGS_PATH, 'utf-8') : '';
|
|
133
|
+
const escaped = projectPath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
134
|
+
if (new RegExp(`^${escaped}:`, 'm').test(content)) {
|
|
135
|
+
content = content.replace(new RegExp(`^(${escaped}:).*$`, 'm'), `$1 ${today}`);
|
|
136
|
+
} else {
|
|
137
|
+
content += `${projectPath}: ${today}\n`;
|
|
138
|
+
}
|
|
139
|
+
writeFileSync(PINGS_PATH, content);
|
|
140
|
+
} catch {} // fail silently
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async function sendSessionPing(licenseKey, projectName) {
|
|
144
|
+
try {
|
|
145
|
+
await fetch(TELEMETRY_ENDPOINT, {
|
|
146
|
+
method: 'POST',
|
|
147
|
+
headers: { 'Content-Type': 'application/json' },
|
|
148
|
+
body: JSON.stringify({
|
|
149
|
+
license_key: licenseKey,
|
|
150
|
+
project_name: projectName,
|
|
151
|
+
events: [{ type: 'session_active', timestamp: new Date().toISOString() }],
|
|
152
|
+
}),
|
|
153
|
+
signal: AbortSignal.timeout(3000),
|
|
154
|
+
});
|
|
155
|
+
} catch {} // fire-and-forget, always fail silently
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// ---------------------------------------------------------------------------
|
|
159
|
+
// Decision helpers
|
|
160
|
+
// ---------------------------------------------------------------------------
|
|
161
|
+
|
|
91
162
|
function allow() {
|
|
92
163
|
process.stdout.write(JSON.stringify({ decision: 'allow' }));
|
|
93
164
|
}
|
|
@@ -103,6 +174,10 @@ function buildMessage(status, reason) {
|
|
|
103
174
|
return `chati.dev license invalid${reason ? ` (${reason})` : ''}.\nRun: npx chati-dev activate --key=YOUR-KEY\nOr visit https://chati.dev/pricing`;
|
|
104
175
|
}
|
|
105
176
|
|
|
177
|
+
// ---------------------------------------------------------------------------
|
|
178
|
+
// YAML helpers (no external deps)
|
|
179
|
+
// ---------------------------------------------------------------------------
|
|
180
|
+
|
|
106
181
|
function readYamlField(raw, field) {
|
|
107
182
|
const match = raw.match(new RegExp(`^${field}:\\s*(.+)$`, 'm'));
|
|
108
183
|
return match ? match[1].trim().replace(/^["']|["']$/g, '') : null;
|
|
@@ -125,6 +200,10 @@ function dumpYaml(obj) {
|
|
|
125
200
|
.join('\n') + '\n';
|
|
126
201
|
}
|
|
127
202
|
|
|
203
|
+
// ---------------------------------------------------------------------------
|
|
204
|
+
// Machine ID
|
|
205
|
+
// ---------------------------------------------------------------------------
|
|
206
|
+
|
|
128
207
|
async function computeMachineId() {
|
|
129
208
|
const { createHash } = await import('crypto');
|
|
130
209
|
const os = await import('os');
|
|
@@ -18,39 +18,6 @@ You are the **Chati.dev Orchestrator**, the single entry point for the Chati.dev
|
|
|
18
18
|
|
|
19
19
|
When the user invokes `/chati`, execute this sequence:
|
|
20
20
|
|
|
21
|
-
### Pre-Flight: License Check
|
|
22
|
-
|
|
23
|
-
FIRST — before anything else:
|
|
24
|
-
|
|
25
|
-
```
|
|
26
|
-
1. Read ~/.chati-dev/license.yaml
|
|
27
|
-
|
|
28
|
-
If file is MISSING or status = "MISSING":
|
|
29
|
-
Say:
|
|
30
|
-
"Welcome to chati.dev! Before we start, I need to activate your license.
|
|
31
|
-
|
|
32
|
-
Option 1 — Paste your license key below.
|
|
33
|
-
Option 2 — Get a free 14-day trial: https://chati.dev/pricing
|
|
34
|
-
|
|
35
|
-
Enter your key (or press Enter if you just received one via email):"
|
|
36
|
-
|
|
37
|
-
→ User provides key
|
|
38
|
-
→ Run: `npx chati-dev activate --key={key}`
|
|
39
|
-
→ Re-read ~/.chati-dev/license.yaml
|
|
40
|
-
→ If status = VALID: "✓ Activated! Let's build." → proceed to Step 1
|
|
41
|
-
→ If error: show error message, ask to try again (max 3 attempts, then stop)
|
|
42
|
-
|
|
43
|
-
If status = "EXPIRED":
|
|
44
|
-
Say:
|
|
45
|
-
"Your chati.dev license has expired.
|
|
46
|
-
Renew at https://chati.dev/pricing
|
|
47
|
-
After renewing, run: npx chati-dev activate --key=YOUR-KEY"
|
|
48
|
-
→ STOP. Do not proceed.
|
|
49
|
-
|
|
50
|
-
If status = "VALID":
|
|
51
|
-
→ Proceed silently. Zero mention of license in normal flow.
|
|
52
|
-
```
|
|
53
|
-
|
|
54
21
|
### Step 1: Load Context
|
|
55
22
|
```
|
|
56
23
|
1. Read .chati/session.yaml
|