Skip to content

Commit 3eedab7

Browse files
dannyshmueliclaude
andcommitted
feat: Track session upload origin with explicit values
Implement origin tracking for Mixpanel analytics without requiring users to reinstall hooks: Changes: - Renamed hookTrigger → origin internally for clarity - Explicit origin values: manual-upload, hook-sessionstart, hook-precompact, hook-sessionend - No fallbacks - null values indicate bugs (visible in analytics) - Removed --origin CLI flag (never used, only added confusion) - Removed misleading [DEPRECATED] labels - Hook commands unchanged - continue using --hook-trigger - CLI maps --hook-trigger to origin format internally Header sent to server: - x-vibe-config-hook-trigger → x-vibe-config-origin Backwards compatibility: - Installed hooks continue using --hook-trigger flag - No reinstallation required - Mapping happens transparently in CLI All 716 tests passing. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 788831a commit 3eedab7

File tree

8 files changed

+31
-15
lines changed

8 files changed

+31
-15
lines changed

src/commands/send.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,11 @@ export async function send(options: SendOptions): Promise<void> {
9999
* Execute send in interactive mode with full UI
100100
*/
101101
async function executeInteractiveSend(options: SendOptions): Promise<void> {
102+
// Explicitly set origin for manual uploads (no fallback)
103+
if (!options.origin) {
104+
options.origin = 'manual-upload';
105+
}
106+
102107
const orchestrator = new SendOrchestrator();
103108
const progressUI = new SendProgressUI(options.silent);
104109
const summaryUI = new SendSummaryUI(options.silent);

src/index.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,12 +105,16 @@ program
105105
.option('-a, --all', 'Send sessions from all projects (default: current project only)')
106106
.option('--silent', 'Run in silent mode (for hook execution)')
107107
.option('--background', 'Run upload in background (for hooks)')
108-
.option('--hook-trigger <type>', 'Hook that triggered this command (sessionstart, precompact, sessionend)')
108+
.option('--hook-trigger <type>', 'Hook trigger type (used by installed hooks)')
109109
.option('--hook-version <version>', 'Hook version (for tracking hook updates)')
110110
.option('--test', 'Test mode for hook validation (exits without processing)')
111111
.option('--claude-project-dir <dir>', 'Claude project directory from $CLAUDE_PROJECT_DIR')
112112
.action(async (options) => {
113113
try {
114+
// Map hook-trigger to origin format for internal use
115+
if (options.hookTrigger) {
116+
options.origin = `hook-${options.hookTrigger}`;
117+
}
114118
await sendWithTimeout(options);
115119
} catch (error) {
116120
handleError(error);

src/lib/api-client.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ export interface UploadResult {
6161

6262
export interface CLIConfiguration {
6363
version: string;
64-
hookTrigger?: string | null;
64+
origin?: string | null;
6565
statusline: {
6666
personality: 'gordon' | 'vibe-log' | 'custom';
6767
customPersonality?: {
@@ -89,7 +89,7 @@ function generateRequestId(): string {
8989
}
9090

9191
// Gather current CLI configuration for sending to server
92-
async function gatherCLIConfiguration(hookTrigger?: string): Promise<CLIConfiguration | null> {
92+
async function gatherCLIConfiguration(origin?: string): Promise<CLIConfiguration | null> {
9393
try {
9494
// Get statusline configuration
9595
const statuslineConfig = getStatusLinePersonality();
@@ -99,7 +99,7 @@ async function gatherCLIConfiguration(hookTrigger?: string): Promise<CLIConfigur
9999

100100
return {
101101
version: require('../../package.json').version,
102-
hookTrigger: hookTrigger || null,
102+
origin: origin || null, // No fallback - must be explicitly set
103103
statusline: {
104104
personality: statuslineConfig.personality,
105105
customPersonality: statuslineConfig.customPersonality
@@ -393,10 +393,10 @@ class SecureApiClient {
393393
async uploadSessions(
394394
sessions: Session[],
395395
onProgress?: (current: number, total: number, sizeKB?: number) => void,
396-
hookTrigger?: string
396+
origin?: string
397397
): Promise<any> {
398398
// Gather CLI configuration to send with the upload
399-
const cliConfig = await gatherCLIConfiguration(hookTrigger);
399+
const cliConfig = await gatherCLIConfiguration(origin);
400400

401401
// Validate and sanitize sessions
402402
const sanitizedSessions = sessions.map(session => this.sanitizeSession(session));
@@ -476,8 +476,8 @@ class SecureApiClient {
476476
const configHeaders: Record<string, string> = {};
477477
if (cliConfig) {
478478
configHeaders['x-vibe-config-version'] = cliConfig.version;
479-
if (cliConfig.hookTrigger) {
480-
configHeaders['x-vibe-config-hook-trigger'] = cliConfig.hookTrigger;
479+
if (cliConfig.origin) {
480+
configHeaders['x-vibe-config-origin'] = cliConfig.origin;
481481
}
482482
configHeaders['x-vibe-config-statusline'] = JSON.stringify(cliConfig.statusline);
483483
configHeaders['x-vibe-config-hooks'] = JSON.stringify(cliConfig.hooks);

src/lib/hooks/hooks-controller.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,11 +187,14 @@ export function buildHookCommand(
187187
hookTrigger: 'sessionstart' | 'precompact' | 'sessionend',
188188
mode?: 'all' | 'selected'
189189
): string {
190+
// Keep using --hook-trigger for backwards compatibility with existing installed hooks
191+
// The mapping to origin happens in index.ts action handler
192+
190193
// For global mode (track all), use --all flag instead of --claude-project-dir
191194
if (mode === 'all') {
192195
return `${cliPath} send --silent --background --hook-trigger=${hookTrigger} --hook-version=${HOOKS_VERSION} --all`;
193196
}
194-
197+
195198
// For selected mode or backward compatibility, use --claude-project-dir
196199
return `${cliPath} send --silent --background --hook-trigger=${hookTrigger} --hook-version=${HOOKS_VERSION} --claude-project-dir="$CLAUDE_PROJECT_DIR"`;
197200
}

src/lib/orchestrators/send-orchestrator.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ export interface SendOptions {
2424
dry?: boolean;
2525
all?: boolean;
2626
silent?: boolean;
27-
hookTrigger?: string;
27+
origin?: string; // Origin of upload: 'manual-upload', 'hook-sessionstart', 'hook-precompact', 'hook-sessionend'
28+
hookTrigger?: string; // Hook trigger (mapped to origin internally)
2829
hookVersion?: string;
2930
test?: boolean;
3031
background?: boolean;
@@ -358,7 +359,9 @@ export class SendOrchestrator {
358359
console.log('[DEBUG] SendOrchestrator.uploadSessions called with', apiSessions.length, 'sessions');
359360
}
360361
logger.debug(`Uploading ${apiSessions.length} sessions to API`);
361-
const result = await apiClient.uploadSessions(apiSessions, onProgress, options.hookTrigger);
362+
// Map hookTrigger to origin format (hookTrigger is used by installed hooks)
363+
const origin = options.origin || (options.hookTrigger ? `hook-${options.hookTrigger}` : undefined);
364+
const result = await apiClient.uploadSessions(apiSessions, onProgress, origin);
362365
if (process.env.VIBELOG_DEBUG === 'true') {
363366
console.log('[DEBUG] Upload completed successfully');
364367
}

tests/unit/commands/send-directory-filter.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ describe('Send Command - Directory Filtering', () => {
178178
})
179179
]),
180180
expect.any(Function), // Progress callback
181-
undefined // hookTrigger
181+
"manual-upload" // origin - explicitly set for manual uploads
182182
);
183183
});
184184

tests/unit/lib/hooks/hooks-controller.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ describe('Hooks Controller', () => {
6161
it('should maintain backward compatibility when mode is undefined', () => {
6262
const cliPath = 'npx vibe-log-cli';
6363
const command = buildHookCommand(cliPath, 'sessionstart');
64-
64+
6565
// Should default to current behavior (with --claude-project-dir)
6666
expect(command).not.toContain('--all');
6767
expect(command).toContain('--claude-project-dir="$CLAUDE_PROJECT_DIR"');
@@ -70,10 +70,10 @@ describe('Hooks Controller', () => {
7070

7171
it('should handle both sessionstart and precompact triggers with mode="all"', () => {
7272
const cliPath = 'node /path/to/vibe-log.js';
73-
73+
7474
const sessionStartCommand = buildHookCommand(cliPath, 'sessionstart', 'all');
7575
expect(sessionStartCommand).toBe('node /path/to/vibe-log.js send --silent --background --hook-trigger=sessionstart --hook-version=1.0.0 --all');
76-
76+
7777
const preCompactCommand = buildHookCommand(cliPath, 'precompact', 'all');
7878
expect(preCompactCommand).toBe('node /path/to/vibe-log.js send --silent --background --hook-trigger=precompact --hook-version=1.0.0 --all');
7979
});

tests/unit/lib/orchestrators/send-orchestrator.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,7 @@ describe('SendOrchestrator', () => {
401401
).rejects.toThrow('Network failure');
402402

403403
// Error should have been logged
404+
// Third parameter is origin (should be undefined for error test)
404405
expect(mockApiClient.uploadSessions).toHaveBeenCalledWith(apiSessions, undefined, undefined);
405406
});
406407

0 commit comments

Comments
 (0)