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.
@@ -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
@@ -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
- if (hasClaude) {
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
- (0, utils_1.installHook)();
253
- installed.push("Claude Code SessionEnd hook (~/.claude/settings.json)");
254
- installedTargets.push("claudeCode-hook");
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
- console.error(`\nWarning: Failed to install Claude Code instructions — ${err.message}`);
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
- try {
270
- (0, utils_1.installCodexHook)();
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
- try {
288
- (0, utils_1.installCursorHook)();
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
- console.error(`\nWarning: Failed to report install to backend — ${err.message}`);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codiedev",
3
- "version": "0.8.1",
3
+ "version": "0.8.2",
4
4
  "description": "Connect Claude Code, Codex, Cursor, or VS Code Copilot to CodieDev for org-wide session capture and artifact collaboration",
5
5
  "bin": {
6
6
  "codiedev": "dist/cli.js",