@respan/cli 0.5.1 → 0.5.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.
@@ -492,6 +492,7 @@ def create_respan_spans(
492
492
  "span_name": root_span_name,
493
493
  "span_workflow_name": workflow_name,
494
494
  "model": model,
495
+ "provider_id": "",
495
496
  "span_path": "",
496
497
  "input": json.dumps(prompt_messages) if prompt_messages else "",
497
498
  "output": json.dumps(completion_message) if completion_message else "",
@@ -524,6 +525,7 @@ def create_respan_spans(
524
525
  "span_path": "claude_chat",
525
526
  "model": model,
526
527
  "provider_id": "anthropic",
528
+ "metadata": {},
527
529
  "input": json.dumps(prompt_messages) if prompt_messages else "",
528
530
  "output": json.dumps(completion_message) if completion_message else "",
529
531
  "prompt_messages": prompt_messages,
@@ -560,6 +562,8 @@ def create_respan_spans(
560
562
  "span_name": f"Thinking {thinking_num}",
561
563
  "span_workflow_name": workflow_name,
562
564
  "span_path": "thinking",
565
+ "provider_id": "",
566
+ "metadata": {},
563
567
  "input": "",
564
568
  "output": thinking_text,
565
569
  "timestamp": thinking_ts,
@@ -610,13 +614,14 @@ def create_respan_spans(
610
614
  "span_name": f"Tool: {td['name']}",
611
615
  "span_workflow_name": workflow_name,
612
616
  "span_path": f"tool_{td['name'].lower()}",
617
+ "provider_id": "",
618
+ "metadata": td.get("result_metadata") or {},
613
619
  "input": format_tool_input(td["name"], td["input"]),
614
620
  "output": format_tool_output(td["name"], td.get("output")),
615
621
  "timestamp": tool_ts,
616
622
  "start_time": tool_start,
617
623
  }
618
624
  if td.get("result_metadata"):
619
- tool_span["metadata"] = td["result_metadata"]
620
625
  duration_ms = td["result_metadata"].get("duration_ms")
621
626
  if duration_ms:
622
627
  tool_span["latency"] = duration_ms / 1000.0
@@ -937,17 +942,17 @@ def main():
937
942
  debug("Tracing disabled (TRACE_TO_RESPAN != true)")
938
943
  sys.exit(0)
939
944
 
940
- # Resolve API key: env var > ~/.config/respan/credentials.json
945
+ # Resolve API key: env var > ~/.respan/credentials.json
941
946
  api_key = os.getenv("RESPAN_API_KEY")
942
947
  base_url = os.getenv("RESPAN_BASE_URL", "https://api.respan.ai/api")
943
948
 
944
949
  if not api_key:
945
- creds_file = Path.home() / ".config" / "respan" / "credentials.json"
950
+ creds_file = Path.home() / ".respan" / "credentials.json"
946
951
  if creds_file.exists():
947
952
  try:
948
953
  creds = json.loads(creds_file.read_text(encoding="utf-8"))
949
954
  # Find the active profile's credential
950
- config_file = Path.home() / ".config" / "respan" / "config.json"
955
+ config_file = Path.home() / ".respan" / "config.json"
951
956
  profile = "default"
952
957
  if config_file.exists():
953
958
  cfg = json.loads(config_file.read_text(encoding="utf-8"))
@@ -956,6 +961,9 @@ def main():
956
961
  api_key = cred.get("apiKey") or cred.get("accessToken")
957
962
  if not base_url or base_url == "https://api.respan.ai/api":
958
963
  base_url = cred.get("baseUrl", base_url)
964
+ # Ensure base_url ends with /api (credentials store the host only)
965
+ if base_url and not base_url.rstrip("/").endswith("/api"):
966
+ base_url = base_url.rstrip("/") + "/api"
959
967
  if api_key:
960
968
  debug(f"Using API key from credentials.json (profile: {profile})")
961
969
  except (json.JSONDecodeError, IOError) as e:
@@ -7,7 +7,7 @@ class IntegrateClaudeCode extends BaseCommand {
7
7
  const { flags } = await this.parse(IntegrateClaudeCode);
8
8
  this.globalFlags = flags;
9
9
  try {
10
- // Verify the user is authenticated (key is read by hook from ~/.config/respan/)
10
+ // Verify the user is authenticated (key is read by hook from ~/.respan/)
11
11
  this.resolveApiKey();
12
12
  const baseUrl = flags['base-url'];
13
13
  const projectId = flags['project-id'];
@@ -93,7 +93,7 @@ class IntegrateClaudeCode extends BaseCommand {
93
93
  const projectRoot = findProjectRoot();
94
94
  const localSettingsPath = `${projectRoot}/.claude/settings.local.json`;
95
95
  const localSettings = readJsonFile(localSettingsPath);
96
- // settings.local.json: enable flag only (API key from ~/.config/respan/)
96
+ // settings.local.json: enable flag only (API key from ~/.respan/)
97
97
  const envBlock = {
98
98
  TRACE_TO_RESPAN: 'true',
99
99
  };
@@ -150,7 +150,7 @@ class IntegrateClaudeCode extends BaseCommand {
150
150
  this.log('Claude Code tracing enabled for this project.');
151
151
  }
152
152
  this.log('');
153
- this.log('Auth: ~/.config/respan/credentials.json (from `respan auth login`)');
153
+ this.log('Auth: ~/.respan/credentials.json (from `respan auth login`)');
154
154
  this.log('Config: .claude/respan.json (shareable, non-secret)');
155
155
  this.log('');
156
156
  this.log('Set properties via integrate flags or edit .claude/respan.json:');
@@ -33,17 +33,31 @@ class IntegrateCodexCli extends BaseCommand {
33
33
  }
34
34
  const otelResStr = toOtelResourceAttrs(resourceAttrs);
35
35
  // Build TOML block
36
+ const endpoint = `${baseUrl}/v2/traces`;
36
37
  const lines = [
37
38
  '',
38
39
  '# Respan observability (added by respan integrate codex-cli)',
39
40
  '[otel]',
40
- `endpoint = "${baseUrl}/v2/traces"`,
41
- `api_key = "${apiKey}"`,
41
+ 'log_user_prompt = true',
42
42
  ];
43
43
  if (otelResStr) {
44
44
  lines.push(`resource_attributes = "${otelResStr}"`);
45
45
  }
46
46
  lines.push('');
47
+ lines.push('[otel.exporter."otlp-http"]');
48
+ lines.push(`endpoint = "${endpoint}"`);
49
+ lines.push('protocol = "binary"');
50
+ lines.push('');
51
+ lines.push('[otel.exporter."otlp-http".headers]');
52
+ lines.push(`"Authorization" = "Bearer ${apiKey}"`);
53
+ lines.push('');
54
+ lines.push('[otel.trace_exporter."otlp-http"]');
55
+ lines.push(`endpoint = "${endpoint}"`);
56
+ lines.push('protocol = "binary"');
57
+ lines.push('');
58
+ lines.push('[otel.trace_exporter."otlp-http".headers]');
59
+ lines.push(`"Authorization" = "Bearer ${apiKey}"`);
60
+ lines.push('');
47
61
  const block = lines.join('\n');
48
62
  const updated = existing.trimEnd() + '\n' + block;
49
63
  if (dryRun) {
@@ -1,6 +1,6 @@
1
1
  import * as path from 'node:path';
2
2
  import { BaseCommand } from '../../lib/base-command.js';
3
- import { integrateFlags, deepMerge, readJsonFile, writeJsonFile, expandHome, parseAttrs, toOtelResourceAttrs, resolveScope, findProjectRoot, } from '../../lib/integrate.js';
3
+ import { integrateFlags, deepMerge, readJsonFile, writeJsonFile, readTextFile, writeTextFile, expandHome, parseAttrs, toOtelResourceAttrs, resolveScope, findProjectRoot, } from '../../lib/integrate.js';
4
4
  class IntegrateGeminiCli extends BaseCommand {
5
5
  async run() {
6
6
  const { flags } = await this.parse(IntegrateGeminiCli);
@@ -25,27 +25,48 @@ class IntegrateGeminiCli extends BaseCommand {
25
25
  if (projectId) {
26
26
  resourceAttrs['respan.project_id'] = projectId;
27
27
  }
28
+ // settings.json — only telemetry fields Gemini CLI supports
28
29
  const patch = {
29
30
  telemetry: {
30
31
  enabled: true,
31
32
  otlpEndpoint: `${baseUrl}/v2/traces`,
32
- headers: {
33
- Authorization: `Bearer ${apiKey}`,
34
- },
33
+ otlpProtocol: 'http',
35
34
  },
36
35
  };
36
+ const merged = deepMerge(existing, patch);
37
+ // .env file — OTel SDK picks up headers & resource attrs from env
38
+ const envDir = scope === 'global'
39
+ ? expandHome('~/.gemini')
40
+ : path.join(findProjectRoot(), '.gemini');
41
+ const envPath = path.join(envDir, '.env');
42
+ const envLines = [];
43
+ envLines.push(`OTEL_EXPORTER_OTLP_HEADERS=Authorization=Bearer ${apiKey}`);
37
44
  const otelResStr = toOtelResourceAttrs(resourceAttrs);
38
45
  if (otelResStr) {
39
- patch.env = { OTEL_RESOURCE_ATTRIBUTES: otelResStr };
46
+ envLines.push(`OTEL_RESOURCE_ATTRIBUTES=${otelResStr}`);
40
47
  }
41
- const merged = deepMerge(existing, patch);
48
+ // Merge with existing .env (replace our keys, keep the rest)
49
+ const existingEnv = readTextFile(envPath);
50
+ const envKeysToSet = new Set(envLines.map(l => l.split('=')[0]));
51
+ const keptLines = existingEnv
52
+ .split('\n')
53
+ .filter(line => {
54
+ const key = line.split('=')[0];
55
+ return !envKeysToSet.has(key);
56
+ });
57
+ const finalEnv = [...keptLines.filter(l => l.trim() !== ''), ...envLines].join('\n') + '\n';
42
58
  if (dryRun) {
43
59
  this.log(`[dry-run] Would update: ${settingsPath}`);
44
60
  this.log(JSON.stringify(merged, null, 2));
61
+ this.log('');
62
+ this.log(`[dry-run] Would update: ${envPath}`);
63
+ this.log(finalEnv);
45
64
  }
46
65
  else {
47
66
  writeJsonFile(settingsPath, merged);
48
67
  this.log(`Updated settings: ${settingsPath}`);
68
+ writeTextFile(envPath, finalEnv);
69
+ this.log(`Updated env: ${envPath}`);
49
70
  }
50
71
  this.log('');
51
72
  this.log(`Gemini CLI integration complete (${scope}).`);
@@ -1,7 +1,7 @@
1
1
  import * as fs from 'node:fs';
2
2
  import * as path from 'node:path';
3
3
  import * as os from 'node:os';
4
- const CONFIG_DIR = path.join(os.homedir(), '.config', 'respan');
4
+ const CONFIG_DIR = path.join(os.homedir(), '.respan');
5
5
  const CREDENTIALS_FILE = path.join(CONFIG_DIR, 'credentials.json');
6
6
  const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
7
7
  function ensureConfigDir() {
@@ -3458,5 +3458,5 @@
3458
3458
  ]
3459
3459
  }
3460
3460
  },
3461
- "version": "0.5.1"
3461
+ "version": "0.5.2"
3462
3462
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@respan/cli",
3
- "version": "0.5.1",
3
+ "version": "0.5.2",
4
4
  "description": "Respan CLI - manage your LLM observability from the command line",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",