@respan/cli 0.7.1 → 0.7.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.
@@ -5,6 +5,8 @@ export default class IntegrateClaudeCode extends BaseCommand {
5
5
  static flags: {
6
6
  local: import("@oclif/core/interfaces").BooleanFlag<boolean>;
7
7
  global: import("@oclif/core/interfaces").BooleanFlag<boolean>;
8
+ enable: import("@oclif/core/interfaces").BooleanFlag<boolean>;
9
+ disable: import("@oclif/core/interfaces").BooleanFlag<boolean>;
8
10
  'project-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
9
11
  'base-url': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
10
12
  attrs: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
@@ -7,6 +7,37 @@ class IntegrateClaudeCode extends BaseCommand {
7
7
  const { flags } = await this.parse(IntegrateClaudeCode);
8
8
  this.globalFlags = flags;
9
9
  try {
10
+ const dryRun = flags['dry-run'];
11
+ const scope = resolveScope(flags, 'both');
12
+ const doLocal = scope === 'local' || scope === 'both';
13
+ // ── Disable mode ─────────────────────────────────────────────
14
+ if (flags.disable) {
15
+ // Remove the Stop hook entry from global settings
16
+ const globalSettingsPath = expandHome('~/.claude/settings.json');
17
+ const globalSettings = readJsonFile(globalSettingsPath);
18
+ const hooksSection = (globalSettings.hooks || {});
19
+ if (Array.isArray(hooksSection.Stop)) {
20
+ hooksSection.Stop = hooksSection.Stop.filter((entry) => {
21
+ const inner = Array.isArray(entry.hooks)
22
+ ? entry.hooks
23
+ : [];
24
+ return !inner.some((h) => typeof h.command === 'string' &&
25
+ (h.command.includes('respan') || h.command.includes('hook.py') || h.command.includes('claude-code')));
26
+ });
27
+ }
28
+ const merged = deepMerge(globalSettings, { hooks: hooksSection });
29
+ if (dryRun) {
30
+ this.log(`[dry-run] Would update: ${globalSettingsPath}`);
31
+ this.log(JSON.stringify(merged, null, 2));
32
+ }
33
+ else {
34
+ writeJsonFile(globalSettingsPath, merged);
35
+ this.log(`Removed hook entry: ${globalSettingsPath}`);
36
+ }
37
+ this.log('Claude Code tracing disabled. Run "respan integrate claude-code" to re-enable.');
38
+ return;
39
+ }
40
+ // ── Enable mode (default) ────────────────────────────────────
10
41
  // Verify the user is authenticated (key is read by hook from ~/.respan/)
11
42
  this.resolveApiKey();
12
43
  const baseUrl = flags['base-url'];
@@ -15,11 +46,8 @@ class IntegrateClaudeCode extends BaseCommand {
15
46
  const spanName = flags['span-name'];
16
47
  const workflowName = flags['workflow-name'];
17
48
  const attrs = parseAttrs(flags.attrs);
18
- const dryRun = flags['dry-run'];
19
- // Claude Code default: both global + local
20
- const scope = resolveScope(flags, 'both');
49
+ // Claude Code default: both global + local.
21
50
  const doGlobal = scope === 'global' || scope === 'both';
22
- const doLocal = scope === 'local' || scope === 'both';
23
51
  // ── Global: hook script + registration ────────────────────────
24
52
  if (doGlobal) {
25
53
  // 1. Write JS hook script (no Python dependency needed)
@@ -165,9 +193,11 @@ them to Respan as structured spans (chat, tool, thinking).
165
193
  Scope:
166
194
  --global Install hook script + register in ~/.claude/settings.json
167
195
  --local Write credentials + enable flag to .claude/settings.local.json
168
- (default) Both: install hook globally + enable for current project`;
196
+
197
+ Default behavior installs the global hook and writes local project config.`;
169
198
  IntegrateClaudeCode.examples = [
170
199
  'respan integrate claude-code',
200
+ 'respan integrate claude-code --disable',
171
201
  'respan integrate claude-code --global',
172
202
  'respan integrate claude-code --local --project-id my-project',
173
203
  'respan integrate claude-code --attrs \'{"env":"prod"}\'',
@@ -5,6 +5,8 @@ export default class IntegrateCodexCli extends BaseCommand {
5
5
  static flags: {
6
6
  local: import("@oclif/core/interfaces").BooleanFlag<boolean>;
7
7
  global: import("@oclif/core/interfaces").BooleanFlag<boolean>;
8
+ enable: import("@oclif/core/interfaces").BooleanFlag<boolean>;
9
+ disable: import("@oclif/core/interfaces").BooleanFlag<boolean>;
8
10
  'project-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
9
11
  'base-url': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
10
12
  attrs: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
@@ -6,6 +6,26 @@ class IntegrateCodexCli extends BaseCommand {
6
6
  const { flags } = await this.parse(IntegrateCodexCli);
7
7
  this.globalFlags = flags;
8
8
  try {
9
+ const dryRun = flags['dry-run'];
10
+ // ── Disable mode ─────────────────────────────────────────────
11
+ if (flags.disable) {
12
+ // Remove the notify line from ~/.codex/config.toml
13
+ const configPath = expandHome('~/.codex/config.toml');
14
+ const existing = readTextFile(configPath);
15
+ const lines = existing.split('\n');
16
+ const filtered = lines.filter((line) => !/^\s*notify\s*=/.test(line) && !line.includes('respan integrate codex-cli'));
17
+ if (dryRun) {
18
+ this.log(`[dry-run] Would update: ${configPath}`);
19
+ this.log(filtered.join('\n'));
20
+ }
21
+ else {
22
+ writeTextFile(configPath, filtered.join('\n'));
23
+ this.log(`Removed notify hook: ${configPath}`);
24
+ }
25
+ this.log('Codex CLI tracing disabled. Run "respan integrate codex-cli" to re-enable.');
26
+ return;
27
+ }
28
+ // ── Enable mode (default) ────────────────────────────────────
9
29
  // Verify the user is authenticated (key is read by hook from ~/.respan/)
10
30
  this.resolveApiKey();
11
31
  const projectId = flags['project-id'];
@@ -13,7 +33,6 @@ class IntegrateCodexCli extends BaseCommand {
13
33
  const spanName = flags['span-name'];
14
34
  const workflowName = flags['workflow-name'];
15
35
  const attrs = parseAttrs(flags.attrs);
16
- const dryRun = flags['dry-run'];
17
36
  // Codex CLI default: both global + local
18
37
  const scope = resolveScope(flags, 'both');
19
38
  const doGlobal = scope === 'global' || scope === 'both';
@@ -155,9 +174,11 @@ them to Respan as structured spans (chat, tool, reasoning).
155
174
  Scope:
156
175
  --global Install hook script + register notify in ~/.codex/config.toml
157
176
  --local Write .codex/respan.json with customer_id, span_name, etc.
158
- (default) Both: install hook globally + config for current project`;
177
+
178
+ Default behavior installs the global notify hook and writes local project config.`;
159
179
  IntegrateCodexCli.examples = [
160
180
  'respan integrate codex-cli',
181
+ 'respan integrate codex-cli --disable',
161
182
  'respan integrate codex-cli --global',
162
183
  'respan integrate codex-cli --local --customer-id frank',
163
184
  'respan integrate codex-cli --attrs \'{"env":"prod"}\'',
@@ -5,6 +5,8 @@ export default class IntegrateGeminiCli extends BaseCommand {
5
5
  static flags: {
6
6
  local: import("@oclif/core/interfaces").BooleanFlag<boolean>;
7
7
  global: import("@oclif/core/interfaces").BooleanFlag<boolean>;
8
+ enable: import("@oclif/core/interfaces").BooleanFlag<boolean>;
9
+ disable: import("@oclif/core/interfaces").BooleanFlag<boolean>;
8
10
  'project-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
9
11
  'base-url': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
10
12
  attrs: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
@@ -7,6 +7,40 @@ class IntegrateGeminiCli extends BaseCommand {
7
7
  const { flags } = await this.parse(IntegrateGeminiCli);
8
8
  this.globalFlags = flags;
9
9
  try {
10
+ const dryRun = flags['dry-run'];
11
+ const scope = resolveScope(flags, 'global');
12
+ // ── Disable mode ─────────────────────────────────────────────
13
+ if (flags.disable) {
14
+ // Remove respan hook entries from settings.json
15
+ const settingsPath = scope === 'global'
16
+ ? expandHome('~/.gemini/settings.json')
17
+ : path.join(findProjectRoot(), '.gemini', 'settings.json');
18
+ const existing = readJsonFile(settingsPath);
19
+ const hooksSection = (existing.hooks || {});
20
+ for (const eventName of ['AfterModel', 'BeforeTool', 'AfterTool']) {
21
+ if (!Array.isArray(hooksSection[eventName]))
22
+ continue;
23
+ hooksSection[eventName] = hooksSection[eventName].filter((entry) => {
24
+ const inner = Array.isArray(entry.hooks)
25
+ ? entry.hooks
26
+ : [];
27
+ return !inner.some((h) => typeof h.command === 'string' &&
28
+ (h.command.includes('respan') || h.command.includes('gemini-cli')));
29
+ });
30
+ }
31
+ const merged = { ...existing, hooks: hooksSection };
32
+ if (dryRun) {
33
+ this.log(`[dry-run] Would update: ${settingsPath}`);
34
+ this.log(JSON.stringify(merged, null, 2));
35
+ }
36
+ else {
37
+ writeJsonFile(settingsPath, merged);
38
+ this.log(`Removed hook entries: ${settingsPath}`);
39
+ }
40
+ this.log('Gemini CLI tracing disabled. Run "respan integrate gemini-cli" to re-enable.');
41
+ return;
42
+ }
43
+ // ── Enable mode (default) ────────────────────────────────────
10
44
  // Verify the user is authenticated (key is read by hook from ~/.respan/)
11
45
  this.resolveApiKey();
12
46
  const projectId = flags['project-id'];
@@ -14,8 +48,6 @@ class IntegrateGeminiCli extends BaseCommand {
14
48
  const spanName = flags['span-name'];
15
49
  const workflowName = flags['workflow-name'];
16
50
  const attrs = parseAttrs(flags.attrs);
17
- const dryRun = flags['dry-run'];
18
- const scope = resolveScope(flags, 'global');
19
51
  // ── 1. Install hook script ──────────────────────────────────
20
52
  const hookDir = expandHome('~/.respan/hooks');
21
53
  const hookPath = `${hookDir}/gemini-cli.cjs`;
@@ -142,10 +174,10 @@ Scope:
142
174
  --global Write to ~/.gemini/settings.json (default)
143
175
  --local Write to .gemini/settings.json in project root
144
176
 
145
- Note: Gemini CLI ignores workspace-level telemetry settings, so
146
- --global is the default.`;
177
+ Gemini CLI ignores workspace-level telemetry settings, so --global is the default.`;
147
178
  IntegrateGeminiCli.examples = [
148
179
  'respan integrate gemini-cli',
180
+ 'respan integrate gemini-cli --disable',
149
181
  'respan integrate gemini-cli --local',
150
182
  'respan integrate gemini-cli --project-id my-project --attrs \'{"env":"prod"}\'',
151
183
  'respan integrate gemini-cli --dry-run',
@@ -5,6 +5,8 @@ export default class IntegrateOpencode extends BaseCommand {
5
5
  static flags: {
6
6
  local: import("@oclif/core/interfaces").BooleanFlag<boolean>;
7
7
  global: import("@oclif/core/interfaces").BooleanFlag<boolean>;
8
+ enable: import("@oclif/core/interfaces").BooleanFlag<boolean>;
9
+ disable: import("@oclif/core/interfaces").BooleanFlag<boolean>;
8
10
  'project-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
9
11
  'base-url': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
10
12
  attrs: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
@@ -1,3 +1,4 @@
1
+ import * as fs from 'node:fs';
1
2
  import * as path from 'node:path';
2
3
  import { execSync } from 'node:child_process';
3
4
  import { BaseCommand } from '../../lib/base-command.js';
@@ -7,12 +8,33 @@ class IntegrateOpencode extends BaseCommand {
7
8
  const { flags } = await this.parse(IntegrateOpencode);
8
9
  this.globalFlags = flags;
9
10
  try {
11
+ const dryRun = flags['dry-run'];
12
+ const scope = resolveScope(flags, 'local');
13
+ // ── Disable mode ─────────────────────────────────────────────
14
+ if (flags.disable) {
15
+ const pluginPath = scope === 'global'
16
+ ? expandHome('~/.config/opencode/plugins/otel.json')
17
+ : path.join(findProjectRoot(), '.opencode', 'plugins', 'otel.json');
18
+ if (dryRun) {
19
+ this.log(`[dry-run] Would remove: ${pluginPath}`);
20
+ }
21
+ else {
22
+ try {
23
+ fs.unlinkSync(pluginPath);
24
+ this.log(`Removed plugin config: ${pluginPath}`);
25
+ }
26
+ catch {
27
+ this.log(`No plugin config found at: ${pluginPath}`);
28
+ }
29
+ }
30
+ this.log('OpenCode tracing disabled. Run with --enable to re-enable.');
31
+ return;
32
+ }
33
+ // ── Enable mode (default) ────────────────────────────────────
10
34
  const apiKey = this.resolveApiKey();
11
35
  const baseUrl = (flags['base-url']).replace(/\/+$/, '');
12
36
  const projectId = flags['project-id'];
13
37
  const attrs = parseAttrs(flags.attrs);
14
- const dryRun = flags['dry-run'];
15
- const scope = resolveScope(flags, 'local');
16
38
  // ── 1. Install opencode-otel (always global) ─────────────────
17
39
  if (dryRun) {
18
40
  this.log('[dry-run] Would run: npm install -g opencode-otel');
@@ -89,6 +111,7 @@ Scope:
89
111
  The opencode-otel package is always installed globally.`;
90
112
  IntegrateOpencode.examples = [
91
113
  'respan integrate opencode',
114
+ 'respan integrate opencode --disable',
92
115
  'respan integrate opencode --global',
93
116
  'respan integrate opencode --project-id my-project --attrs \'{"env":"prod"}\'',
94
117
  'respan integrate opencode --dry-run',
@@ -280,6 +280,7 @@ function stringToSpanId(s) {
280
280
  function toOtlpPayload(spans) {
281
281
  const otlpSpans = spans.map((span) => {
282
282
  const attrs = {};
283
+ if (span.session_identifier) attrs["respan.sessions.session_identifier"] = span.session_identifier;
283
284
  if (span.thread_identifier) attrs["respan.threads.thread_identifier"] = span.thread_identifier;
284
285
  if (span.customer_identifier) attrs["respan.customer_params.customer_identifier"] = span.customer_identifier;
285
286
  if (span.span_workflow_name) attrs["traceloop.workflow.name"] = span.span_workflow_name;
@@ -340,7 +341,7 @@ function toOtlpPayload(spans) {
340
341
  })
341
342
  },
342
343
  scopeSpans: [{
343
- scope: { name: "respan-cli-hooks", version: "0.7.0" },
344
+ scope: { name: "respan-cli-hooks", version: "0.7.1" },
344
345
  spans: otlpSpans
345
346
  }]
346
347
  }]
@@ -599,6 +600,7 @@ function createSpans(sessionId, turnNum, userMsg, assistantMsgs, toolResults, co
599
600
  const rootSpanId = `claudecode_${traceUniqueId}_root`;
600
601
  spans.push({
601
602
  trace_unique_id: traceUniqueId,
603
+ session_identifier: threadId,
602
604
  thread_identifier: threadId,
603
605
  customer_identifier: customerId,
604
606
  span_unique_id: rootSpanId,
@@ -248,6 +248,7 @@ function createSpans(sessionId, turnNum, userMsg, assistantMsgs, toolResults, co
248
248
  const rootSpanId = `claudecode_${traceUniqueId}_root`;
249
249
  spans.push({
250
250
  trace_unique_id: traceUniqueId,
251
+ session_identifier: threadId,
251
252
  thread_identifier: threadId,
252
253
  customer_identifier: customerId,
253
254
  span_unique_id: rootSpanId,
@@ -280,6 +280,7 @@ function stringToSpanId(s) {
280
280
  function toOtlpPayload(spans) {
281
281
  const otlpSpans = spans.map((span) => {
282
282
  const attrs = {};
283
+ if (span.session_identifier) attrs["respan.sessions.session_identifier"] = span.session_identifier;
283
284
  if (span.thread_identifier) attrs["respan.threads.thread_identifier"] = span.thread_identifier;
284
285
  if (span.customer_identifier) attrs["respan.customer_params.customer_identifier"] = span.customer_identifier;
285
286
  if (span.span_workflow_name) attrs["traceloop.workflow.name"] = span.span_workflow_name;
@@ -340,7 +341,7 @@ function toOtlpPayload(spans) {
340
341
  })
341
342
  },
342
343
  scopeSpans: [{
343
- scope: { name: "respan-cli-hooks", version: "0.7.0" },
344
+ scope: { name: "respan-cli-hooks", version: "0.7.1" },
344
345
  spans: otlpSpans
345
346
  }]
346
347
  }]
@@ -573,6 +574,7 @@ function createSpans(sessionId, turnNum, turn, config) {
573
574
  const rootSpanId = `codexcli_${traceUniqueId}_root`;
574
575
  spans.push({
575
576
  trace_unique_id: traceUniqueId,
577
+ session_identifier: threadId,
576
578
  thread_identifier: threadId,
577
579
  customer_identifier: customerId,
578
580
  span_unique_id: rootSpanId,
@@ -222,6 +222,7 @@ function createSpans(sessionId, turnNum, turn, config) {
222
222
  const rootSpanId = `codexcli_${traceUniqueId}_root`;
223
223
  spans.push({
224
224
  trace_unique_id: traceUniqueId,
225
+ session_identifier: threadId,
225
226
  thread_identifier: threadId,
226
227
  customer_identifier: customerId,
227
228
  span_unique_id: rootSpanId,
@@ -258,6 +258,7 @@ function stringToSpanId(s) {
258
258
  function toOtlpPayload(spans) {
259
259
  const otlpSpans = spans.map((span) => {
260
260
  const attrs = {};
261
+ if (span.session_identifier) attrs["respan.sessions.session_identifier"] = span.session_identifier;
261
262
  if (span.thread_identifier) attrs["respan.threads.thread_identifier"] = span.thread_identifier;
262
263
  if (span.customer_identifier) attrs["respan.customer_params.customer_identifier"] = span.customer_identifier;
263
264
  if (span.span_workflow_name) attrs["traceloop.workflow.name"] = span.span_workflow_name;
@@ -318,7 +319,7 @@ function toOtlpPayload(spans) {
318
319
  })
319
320
  },
320
321
  scopeSpans: [{
321
- scope: { name: "respan-cli-hooks", version: "0.7.0" },
322
+ scope: { name: "respan-cli-hooks", version: "0.7.1" },
322
323
  spans: otlpSpans
323
324
  }]
324
325
  }]
@@ -492,6 +493,7 @@ function buildSpans(hookData, outputText, tokens, config, startTimeIso, toolTurn
492
493
  const metadata = buildMetadata(config, baseMeta);
493
494
  spans.push({
494
495
  trace_unique_id: traceUniqueId,
496
+ session_identifier: threadId,
495
497
  thread_identifier: threadId,
496
498
  customer_identifier: customerId,
497
499
  span_unique_id: rootSpanId,
@@ -201,6 +201,7 @@ function buildSpans(hookData, outputText, tokens, config, startTimeIso, toolTurn
201
201
  // Root span
202
202
  spans.push({
203
203
  trace_unique_id: traceUniqueId,
204
+ session_identifier: threadId,
204
205
  thread_identifier: threadId,
205
206
  customer_identifier: customerId,
206
207
  span_unique_id: rootSpanId,
@@ -15,6 +15,7 @@ export interface SpanData {
15
15
  span_name: string;
16
16
  span_workflow_name: string;
17
17
  span_path?: string;
18
+ session_identifier?: string;
18
19
  thread_identifier?: string;
19
20
  customer_identifier?: string;
20
21
  model?: string;
@@ -315,6 +315,8 @@ export function toOtlpPayload(spans) {
315
315
  // Build OTEL-compatible attributes from SpanData fields
316
316
  const attrs = {};
317
317
  // Respan-specific attributes
318
+ if (span.session_identifier)
319
+ attrs['respan.sessions.session_identifier'] = span.session_identifier;
318
320
  if (span.thread_identifier)
319
321
  attrs['respan.threads.thread_identifier'] = span.thread_identifier;
320
322
  if (span.customer_identifier)
@@ -395,7 +397,7 @@ export function toOtlpPayload(spans) {
395
397
  }),
396
398
  },
397
399
  scopeSpans: [{
398
- scope: { name: 'respan-cli-hooks', version: '0.7.0' },
400
+ scope: { name: 'respan-cli-hooks', version: '0.7.1' },
399
401
  spans: otlpSpans,
400
402
  }],
401
403
  }],
@@ -3,6 +3,8 @@ export declare const DEFAULT_BASE_URL = "https://api.respan.ai/api";
3
3
  export declare const integrateFlags: {
4
4
  local: import("@oclif/core/interfaces").BooleanFlag<boolean>;
5
5
  global: import("@oclif/core/interfaces").BooleanFlag<boolean>;
6
+ enable: import("@oclif/core/interfaces").BooleanFlag<boolean>;
7
+ disable: import("@oclif/core/interfaces").BooleanFlag<boolean>;
6
8
  'project-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
7
9
  'base-url': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
8
10
  attrs: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
@@ -19,6 +19,16 @@ export const integrateFlags = {
19
19
  default: false,
20
20
  exclusive: ['local'],
21
21
  }),
22
+ enable: Flags.boolean({
23
+ description: 'Enable tracing (default)',
24
+ default: false,
25
+ exclusive: ['disable'],
26
+ }),
27
+ disable: Flags.boolean({
28
+ description: 'Disable tracing',
29
+ default: false,
30
+ exclusive: ['enable'],
31
+ }),
22
32
  'project-id': Flags.string({
23
33
  description: 'Respan project ID (added to metadata / resource attributes)',
24
34
  env: 'RESPAN_PROJECT_ID',