codiedev 0.8.1 → 0.8.2
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/dist/commands/shared.d.ts +14 -0
- package/dist/commands/shared.js +17 -0
- package/dist/connect.js +41 -46
- package/dist/hookTransport.d.ts +13 -0
- package/dist/hookTransport.js +136 -0
- package/package.json +1 -1
|
@@ -33,6 +33,20 @@ export declare function recordAgentEvent(config: CodiedevConfig, event: {
|
|
|
33
33
|
latencyMs: number;
|
|
34
34
|
error?: string;
|
|
35
35
|
}): void;
|
|
36
|
+
/**
|
|
37
|
+
* Per-step install telemetry for `npx codiedev connect`. One row per phase
|
|
38
|
+
* (started, detect, install_target, validate_token, completed, failed) so
|
|
39
|
+
* the admin company page can show a timeline when a customer says "it says
|
|
40
|
+
* connected but it doesn't work." Fire-and-forget, never throws. Auto-stamps
|
|
41
|
+
* platform + node + cli version so call sites only need step/target/ok/error.
|
|
42
|
+
*/
|
|
43
|
+
export type InstallTelemetryStep = "started" | "detect" | "install_target" | "validate_token" | "completed" | "failed";
|
|
44
|
+
export declare function recordInstallEvent(config: CodiedevConfig, event: {
|
|
45
|
+
step: InstallTelemetryStep;
|
|
46
|
+
target?: string;
|
|
47
|
+
ok: boolean;
|
|
48
|
+
error?: string;
|
|
49
|
+
}): void;
|
|
36
50
|
/**
|
|
37
51
|
* Wraps a CLI subcommand body so success and failure both record telemetry
|
|
38
52
|
* and the original error propagates after the event is queued. Use this in
|
package/dist/commands/shared.js
CHANGED
|
@@ -37,6 +37,7 @@ exports.applyConfigEnvOverrides = applyConfigEnvOverrides;
|
|
|
37
37
|
exports.requireConfig = requireConfig;
|
|
38
38
|
exports.apiRequest = apiRequest;
|
|
39
39
|
exports.recordAgentEvent = recordAgentEvent;
|
|
40
|
+
exports.recordInstallEvent = recordInstallEvent;
|
|
40
41
|
exports.withTelemetry = withTelemetry;
|
|
41
42
|
exports.timeAgo = timeAgo;
|
|
42
43
|
exports.stripQuotes = stripQuotes;
|
|
@@ -155,6 +156,22 @@ function recordAgentEvent(config, event) {
|
|
|
155
156
|
// Swallow — telemetry must never affect user-facing flow.
|
|
156
157
|
});
|
|
157
158
|
}
|
|
159
|
+
function recordInstallEvent(config, event) {
|
|
160
|
+
apiRequest("POST", "/api/telemetry/install-event", {
|
|
161
|
+
config,
|
|
162
|
+
body: {
|
|
163
|
+
step: event.step,
|
|
164
|
+
target: event.target,
|
|
165
|
+
ok: event.ok,
|
|
166
|
+
error: event.error,
|
|
167
|
+
platform: process.platform,
|
|
168
|
+
nodeVersion: process.version,
|
|
169
|
+
cliVersion: version_1.CLI_VERSION,
|
|
170
|
+
},
|
|
171
|
+
}).catch(() => {
|
|
172
|
+
// Swallow — telemetry must never affect connect flow.
|
|
173
|
+
});
|
|
174
|
+
}
|
|
158
175
|
/**
|
|
159
176
|
* Wraps a CLI subcommand body so success and failure both record telemetry
|
|
160
177
|
* and the original error propagates after the event is queued. Use this in
|
package/dist/connect.js
CHANGED
|
@@ -41,6 +41,7 @@ const utils_1 = require("./utils");
|
|
|
41
41
|
const connectFlow_1 = require("./connectFlow");
|
|
42
42
|
const version_1 = require("./version");
|
|
43
43
|
const upgradeNudge_1 = require("./upgradeNudge");
|
|
44
|
+
const shared_1 = require("./commands/shared");
|
|
44
45
|
const BACKEND_URL = process.env.CODIEDEV_URL || "https://judicious-falcon-861.convex.site";
|
|
45
46
|
/**
|
|
46
47
|
* Reads a line from stdin without echoing the typed/pasted characters —
|
|
@@ -214,6 +215,11 @@ async function runConnect(args = []) {
|
|
|
214
215
|
console.error(`\nError: Failed to save config — ${err.message}`);
|
|
215
216
|
process.exit(1);
|
|
216
217
|
}
|
|
218
|
+
// Once we have a valid token + config, every subsequent step posts a
|
|
219
|
+
// per-step row to /api/telemetry/install-event so the admin page can show
|
|
220
|
+
// a timeline when a customer says "it connected but it doesn't work."
|
|
221
|
+
// Fire-and-forget — never blocks the connect flow.
|
|
222
|
+
(0, shared_1.recordInstallEvent)(config, { step: "started", ok: true });
|
|
217
223
|
// Migration: strip MCP server entries written by pre-CLI-only versions
|
|
218
224
|
// of `codiedev connect`. These pointed at `npx codiedev-mcp`, a binary
|
|
219
225
|
// that was removed in 0.3.4 and that 0.7.11+ no longer ships. Without
|
|
@@ -242,64 +248,46 @@ async function runConnect(args = []) {
|
|
|
242
248
|
const hasClaude = (0, utils_1.claudeCodeInstalled)();
|
|
243
249
|
const hasCodex = (0, utils_1.codexInstalled)();
|
|
244
250
|
const hasCursor = (0, utils_1.cursorInstalled)();
|
|
251
|
+
(0, shared_1.recordInstallEvent)(config, { step: "detect", target: "claude-code", ok: hasClaude });
|
|
252
|
+
(0, shared_1.recordInstallEvent)(config, { step: "detect", target: "codex", ok: hasCodex });
|
|
253
|
+
(0, shared_1.recordInstallEvent)(config, { step: "detect", target: "cursor", ok: hasCursor });
|
|
245
254
|
const installed = [];
|
|
246
255
|
// Machine-readable target keys reported back to the backend via
|
|
247
256
|
// /api/cli/markInstalled so the portal's "Connected" state reflects
|
|
248
257
|
// what's actually wired up on this machine, not just "token validated."
|
|
249
258
|
const installedTargets = [];
|
|
250
|
-
|
|
259
|
+
// Wrap each per-target write so we emit one install_target row each. The
|
|
260
|
+
// existing user-visible behavior (console.warn on failure, continue) is
|
|
261
|
+
// unchanged — telemetry just observes the outcome.
|
|
262
|
+
const runInstall = (target, fn, label, targetKey) => {
|
|
251
263
|
try {
|
|
252
|
-
(
|
|
253
|
-
installed.push(
|
|
254
|
-
installedTargets.push(
|
|
255
|
-
|
|
256
|
-
catch (err) {
|
|
257
|
-
console.error(`\nWarning: Failed to install Claude Code hook — ${err.message}`);
|
|
258
|
-
}
|
|
259
|
-
try {
|
|
260
|
-
(0, utils_1.installClaudeCodeInstructions)();
|
|
261
|
-
installed.push("Claude Code agent instructions (~/.claude/CLAUDE.md)");
|
|
262
|
-
installedTargets.push("claudeCode-instructions");
|
|
264
|
+
fn();
|
|
265
|
+
installed.push(label);
|
|
266
|
+
installedTargets.push(targetKey);
|
|
267
|
+
(0, shared_1.recordInstallEvent)(config, { step: "install_target", target, ok: true });
|
|
263
268
|
}
|
|
264
269
|
catch (err) {
|
|
265
|
-
|
|
270
|
+
const message = err.message;
|
|
271
|
+
console.error(`\nWarning: Failed to install ${label} — ${message}`);
|
|
272
|
+
(0, shared_1.recordInstallEvent)(config, {
|
|
273
|
+
step: "install_target",
|
|
274
|
+
target,
|
|
275
|
+
ok: false,
|
|
276
|
+
error: message?.slice(0, 500),
|
|
277
|
+
});
|
|
266
278
|
}
|
|
279
|
+
};
|
|
280
|
+
if (hasClaude) {
|
|
281
|
+
runInstall("claude-code-hook", utils_1.installHook, "Claude Code SessionEnd hook (~/.claude/settings.json)", "claudeCode-hook");
|
|
282
|
+
runInstall("claude-code-instructions", utils_1.installClaudeCodeInstructions, "Claude Code agent instructions (~/.claude/CLAUDE.md)", "claudeCode-instructions");
|
|
267
283
|
}
|
|
268
284
|
if (hasCodex) {
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
installed.push("Codex Stop hook (~/.codex/hooks.json)");
|
|
272
|
-
installedTargets.push("codex-hook");
|
|
273
|
-
}
|
|
274
|
-
catch (err) {
|
|
275
|
-
console.error(`\nWarning: Failed to install Codex hook — ${err.message}`);
|
|
276
|
-
}
|
|
277
|
-
try {
|
|
278
|
-
(0, utils_1.installCodexInstructions)();
|
|
279
|
-
installed.push("Codex agent instructions (~/.codex/AGENTS.md)");
|
|
280
|
-
installedTargets.push("codex-instructions");
|
|
281
|
-
}
|
|
282
|
-
catch (err) {
|
|
283
|
-
console.error(`\nWarning: Failed to install Codex instructions — ${err.message}`);
|
|
284
|
-
}
|
|
285
|
+
runInstall("codex-hook", utils_1.installCodexHook, "Codex Stop hook (~/.codex/hooks.json)", "codex-hook");
|
|
286
|
+
runInstall("codex-instructions", utils_1.installCodexInstructions, "Codex agent instructions (~/.codex/AGENTS.md)", "codex-instructions");
|
|
285
287
|
}
|
|
286
288
|
if (hasCursor) {
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
installed.push("Cursor sessionEnd hook (~/.cursor/hooks.json)");
|
|
290
|
-
installedTargets.push("cursor-hook");
|
|
291
|
-
}
|
|
292
|
-
catch (err) {
|
|
293
|
-
console.error(`\nWarning: Failed to install Cursor hook — ${err.message}`);
|
|
294
|
-
}
|
|
295
|
-
try {
|
|
296
|
-
(0, utils_1.installCursorInstructions)();
|
|
297
|
-
installed.push("Cursor agent instructions (~/.cursor/rules/codiedev.mdc)");
|
|
298
|
-
installedTargets.push("cursor-instructions");
|
|
299
|
-
}
|
|
300
|
-
catch (err) {
|
|
301
|
-
console.error(`\nWarning: Failed to install Cursor instructions — ${err.message}`);
|
|
302
|
-
}
|
|
289
|
+
runInstall("cursor-hook", utils_1.installCursorHook, "Cursor sessionEnd hook (~/.cursor/hooks.json)", "cursor-hook");
|
|
290
|
+
runInstall("cursor-instructions", utils_1.installCursorInstructions, "Cursor agent instructions (~/.cursor/rules/codiedev.mdc)", "cursor-instructions");
|
|
303
291
|
}
|
|
304
292
|
// Tell the backend which targets we wired up so the portal's onboarding
|
|
305
293
|
// card flips to "Connected" only when something actually installed —
|
|
@@ -310,10 +298,17 @@ async function runConnect(args = []) {
|
|
|
310
298
|
await postJson(`${BACKEND_URL}/api/cli/markInstalled`, {
|
|
311
299
|
installedTargets,
|
|
312
300
|
}, { Authorization: `Bearer ${token}` });
|
|
301
|
+
(0, shared_1.recordInstallEvent)(config, { step: "completed", ok: true });
|
|
313
302
|
}
|
|
314
303
|
catch (err) {
|
|
315
|
-
|
|
304
|
+
const message = err.message;
|
|
305
|
+
console.error(`\nWarning: Failed to report install to backend — ${message}`);
|
|
316
306
|
console.error("Local install succeeded; portal may not reflect it until you re-run.");
|
|
307
|
+
(0, shared_1.recordInstallEvent)(config, {
|
|
308
|
+
step: "failed",
|
|
309
|
+
ok: false,
|
|
310
|
+
error: message?.slice(0, 500),
|
|
311
|
+
});
|
|
317
312
|
}
|
|
318
313
|
}
|
|
319
314
|
if (!hasClaude && !hasCodex && !hasCursor) {
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import * as http from "http";
|
|
2
|
+
export declare const DEFAULT_HOOK_HTTP_TIMEOUT_MS = 8000;
|
|
3
|
+
/** Hard cap for GET requests used in sync-context (e.g. auto-pull skill list).
|
|
4
|
+
* If the backend accepts the connection but never writes a response the hook
|
|
5
|
+
* would stall every Claude Code session start — we cap it well below any
|
|
6
|
+
* reasonable UX threshold. */
|
|
7
|
+
export declare const GET_JSON_TIMEOUT_MS = 5000;
|
|
8
|
+
export declare function getHookHttpTimeoutMs(): number;
|
|
9
|
+
type RequestImpl = typeof http.request;
|
|
10
|
+
export declare function getJson<T>(url: string, bearerToken: string): Promise<T>;
|
|
11
|
+
export declare function postJson(url: string, body: Record<string, unknown>, bearerToken: string, requestImpl?: RequestImpl): Promise<void>;
|
|
12
|
+
export declare function postJsonWithHookTimeout(url: string, body: Record<string, unknown>, bearerToken: string, requestImpl?: RequestImpl): Promise<void>;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.GET_JSON_TIMEOUT_MS = exports.DEFAULT_HOOK_HTTP_TIMEOUT_MS = void 0;
|
|
37
|
+
exports.getHookHttpTimeoutMs = getHookHttpTimeoutMs;
|
|
38
|
+
exports.getJson = getJson;
|
|
39
|
+
exports.postJson = postJson;
|
|
40
|
+
exports.postJsonWithHookTimeout = postJsonWithHookTimeout;
|
|
41
|
+
const http = __importStar(require("http"));
|
|
42
|
+
const https = __importStar(require("https"));
|
|
43
|
+
const version_1 = require("./version");
|
|
44
|
+
exports.DEFAULT_HOOK_HTTP_TIMEOUT_MS = 8_000;
|
|
45
|
+
/** Hard cap for GET requests used in sync-context (e.g. auto-pull skill list).
|
|
46
|
+
* If the backend accepts the connection but never writes a response the hook
|
|
47
|
+
* would stall every Claude Code session start — we cap it well below any
|
|
48
|
+
* reasonable UX threshold. */
|
|
49
|
+
exports.GET_JSON_TIMEOUT_MS = 5_000;
|
|
50
|
+
function getHookHttpTimeoutMs() {
|
|
51
|
+
const raw = process.env.CODIEDEV_HOOK_HTTP_TIMEOUT_MS;
|
|
52
|
+
if (!raw)
|
|
53
|
+
return exports.DEFAULT_HOOK_HTTP_TIMEOUT_MS;
|
|
54
|
+
const parsed = Number(raw);
|
|
55
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
56
|
+
return exports.DEFAULT_HOOK_HTTP_TIMEOUT_MS;
|
|
57
|
+
}
|
|
58
|
+
return parsed;
|
|
59
|
+
}
|
|
60
|
+
function requestJson(method, url, bearerToken, body, requestImpl, useTimeout = false,
|
|
61
|
+
/** When set, overrides getHookHttpTimeoutMs() for this request. */
|
|
62
|
+
explicitTimeoutMs) {
|
|
63
|
+
return new Promise((resolve, reject) => {
|
|
64
|
+
const parsed = new URL(url);
|
|
65
|
+
const data = body === undefined ? undefined : JSON.stringify(body);
|
|
66
|
+
const headers = {
|
|
67
|
+
Authorization: `Bearer ${bearerToken}`,
|
|
68
|
+
"X-Codiedev-Cli-Version": version_1.CLI_VERSION,
|
|
69
|
+
};
|
|
70
|
+
if (data !== undefined) {
|
|
71
|
+
headers["Content-Type"] = "application/json";
|
|
72
|
+
headers["Content-Length"] = Buffer.byteLength(data);
|
|
73
|
+
}
|
|
74
|
+
const options = {
|
|
75
|
+
hostname: parsed.hostname,
|
|
76
|
+
port: parsed.port || (parsed.protocol === "https:" ? 443 : 80),
|
|
77
|
+
path: parsed.pathname + parsed.search,
|
|
78
|
+
method,
|
|
79
|
+
headers,
|
|
80
|
+
};
|
|
81
|
+
const lib = parsed.protocol === "https:" ? https : http;
|
|
82
|
+
const request = requestImpl ?? lib.request;
|
|
83
|
+
let settled = false;
|
|
84
|
+
const timeoutMs = explicitTimeoutMs ?? getHookHttpTimeoutMs();
|
|
85
|
+
let timer;
|
|
86
|
+
const finish = (fn) => {
|
|
87
|
+
if (settled)
|
|
88
|
+
return;
|
|
89
|
+
settled = true;
|
|
90
|
+
if (timer)
|
|
91
|
+
clearTimeout(timer);
|
|
92
|
+
fn();
|
|
93
|
+
};
|
|
94
|
+
const req = request(options, (res) => {
|
|
95
|
+
let responseData = "";
|
|
96
|
+
res.on("data", (chunk) => {
|
|
97
|
+
responseData += chunk;
|
|
98
|
+
});
|
|
99
|
+
res.on("end", () => {
|
|
100
|
+
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
|
|
101
|
+
try {
|
|
102
|
+
const parsedBody = responseData.length === 0 ? undefined : JSON.parse(responseData);
|
|
103
|
+
finish(() => resolve(parsedBody));
|
|
104
|
+
}
|
|
105
|
+
catch (e) {
|
|
106
|
+
finish(() => reject(e));
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
finish(() => reject(new Error(`HTTP ${res.statusCode}: ${responseData}`)));
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
if (useTimeout) {
|
|
115
|
+
timer = setTimeout(() => {
|
|
116
|
+
req.destroy(new Error(`request timed out after ${timeoutMs}ms`));
|
|
117
|
+
}, timeoutMs);
|
|
118
|
+
}
|
|
119
|
+
req.on("error", (err) => finish(() => reject(err)));
|
|
120
|
+
if (data !== undefined) {
|
|
121
|
+
req.write(data);
|
|
122
|
+
}
|
|
123
|
+
req.end();
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
function getJson(url, bearerToken) {
|
|
127
|
+
// Always apply a 5-second cap so a hanging backend cannot stall a
|
|
128
|
+
// SessionStart hook (which would freeze every Claude Code session start).
|
|
129
|
+
return requestJson("GET", url, bearerToken, undefined, undefined, true, exports.GET_JSON_TIMEOUT_MS);
|
|
130
|
+
}
|
|
131
|
+
function postJson(url, body, bearerToken, requestImpl) {
|
|
132
|
+
return requestJson("POST", url, bearerToken, body, requestImpl);
|
|
133
|
+
}
|
|
134
|
+
function postJsonWithHookTimeout(url, body, bearerToken, requestImpl) {
|
|
135
|
+
return requestJson("POST", url, bearerToken, body, requestImpl, true);
|
|
136
|
+
}
|
package/package.json
CHANGED