@respan/cli 0.10.0 → 0.11.0
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/setup/doctor.d.ts +0 -1
- package/dist/commands/setup/doctor.js +25 -40
- package/dist/commands/setup/gateway.d.ts +16 -0
- package/dist/commands/setup/gateway.js +33 -0
- package/dist/commands/setup/index.d.ts +4 -33
- package/dist/commands/setup/index.js +11 -762
- package/dist/commands/setup/tracing.d.ts +16 -0
- package/dist/commands/setup/tracing.js +33 -0
- package/dist/lib/agents.d.ts +16 -0
- package/dist/lib/agents.js +75 -0
- package/dist/lib/auth.js +24 -0
- package/dist/lib/colors.d.ts +7 -0
- package/dist/lib/colors.js +10 -0
- package/dist/lib/integrate.d.ts +6 -0
- package/dist/lib/integrate.js +11 -0
- package/dist/lib/setup-base-command.d.ts +84 -0
- package/dist/lib/setup-base-command.js +649 -0
- package/dist/lib/skill-content.d.ts +1 -0
- package/dist/lib/skill-content.js +16 -0
- package/dist/lib/skill-refs.generated.d.ts +3 -2
- package/dist/lib/skill-refs.generated.js +423 -102
- package/oclif.manifest.json +238 -75
- package/package.json +2 -1
|
@@ -1,784 +1,33 @@
|
|
|
1
|
-
import * as fs from 'node:fs';
|
|
2
|
-
import * as path from 'node:path';
|
|
3
|
-
import * as os from 'node:os';
|
|
4
|
-
import { execSync, spawnSync } from 'node:child_process';
|
|
5
1
|
import { Flags } from '@oclif/core';
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
import { printBanner } from '../../lib/banner.js';
|
|
9
|
-
import { findProjectRoot, writeTextFile, readTextFile, ensureDir, } from '../../lib/integrate.js';
|
|
10
|
-
import { createSpinner } from '../../lib/spinner.js';
|
|
11
|
-
import { TRACING_MD, GATEWAY_MD, PROMPTS_MD, EVALS_MD, MONITORS_MD, } from '#lib/skill-refs.generated.js';
|
|
12
|
-
// ── Shared skill content (same for all agents) ────────────────────────
|
|
13
|
-
// ── Skill files ────────────────────────────────────────────────────────
|
|
14
|
-
const SKILL_MD = `# Respan
|
|
15
|
-
|
|
16
|
-
Use the Respan CLI and SDK for LLM observability — tracing, evals, prompts, datasets, and gateway routing.
|
|
17
|
-
|
|
18
|
-
## When To Use
|
|
19
|
-
|
|
20
|
-
- **Set up Respan** or **add tracing** → read [references/setup.md](references/setup.md)
|
|
21
|
-
- **Advanced tracing** (decorators, propagation, processors) → read [references/tracing.md](references/tracing.md)
|
|
22
|
-
- **Gateway routing** or **proxying LLM calls** → read [references/gateway.md](references/gateway.md)
|
|
23
|
-
- **Prompt management** (create, version, deploy) → read [references/prompts.md](references/prompts.md)
|
|
24
|
-
- **Evals** (datasets, evaluators, experiments) → read [references/evals.md](references/evals.md)
|
|
25
|
-
- **Monitors & automation** (alerts, online evals, webhooks) → read [references/monitors.md](references/monitors.md)
|
|
26
|
-
|
|
27
|
-
## Core Principles
|
|
28
|
-
|
|
29
|
-
1. **Read the reference first.** Each reference file has the exact API patterns, MCP tools, and CLI commands.
|
|
30
|
-
2. **Use MCP tools** for platform operations (prompts, datasets, evaluators, experiments, traces, logs).
|
|
31
|
-
3. **Use CLI** when MCP is not available: \`respan traces list\`, \`respan prompts list\`, etc.
|
|
32
|
-
4. **Fetch docs** for integration-specific details not covered in references.
|
|
33
|
-
|
|
34
|
-
## Quick Reference
|
|
35
|
-
|
|
36
|
-
| Task | Reference / Command |
|
|
37
|
-
|------|--------------------|
|
|
38
|
-
| Set up SDK tracing | [references/setup.md](references/setup.md) |
|
|
39
|
-
| Decorators & propagation | [references/tracing.md](references/tracing.md) |
|
|
40
|
-
| Gateway routing | [references/gateway.md](references/gateway.md) |
|
|
41
|
-
| Prompt management | [references/prompts.md](references/prompts.md) |
|
|
42
|
-
| Evals & experiments | [references/evals.md](references/evals.md) |
|
|
43
|
-
| Monitors & automation | [references/monitors.md](references/monitors.md) |
|
|
44
|
-
| List traces | \`respan traces list --limit 10\` |
|
|
45
|
-
| View a trace | \`respan traces get <id>\` |
|
|
46
|
-
| Check auth | \`respan auth status\` |
|
|
47
|
-
|
|
48
|
-
## Documentation Access
|
|
49
|
-
|
|
50
|
-
Any doc page can be fetched as markdown:
|
|
51
|
-
\`https://respan.ai/docs/integrations/openai-sdk.md\`
|
|
52
|
-
\`https://respan.ai/docs/sdks/typescript-sdk/overview.md\`
|
|
53
|
-
|
|
54
|
-
Full docs index: \`https://www.respan.ai/docs/llms.txt\`
|
|
55
|
-
|
|
56
|
-
Platform: \`https://platform.respan.ai\`
|
|
57
|
-
`;
|
|
58
|
-
const SETUP_MD = `# Respan Setup
|
|
59
|
-
|
|
60
|
-
Use this skill when the user asks to set up Respan tracing in their project.
|
|
61
|
-
|
|
62
|
-
## Hard Rules
|
|
63
|
-
|
|
64
|
-
- **Interactive mode:** Ask the user questions when you need input. Do not assume.
|
|
65
|
-
- **Only add Respan code.** Do not refactor or modify unrelated code.
|
|
66
|
-
- **Pin exact versions.** Never use \`latest\` or unpinned ranges.
|
|
67
|
-
- **Do not guess APIs.** Use only the patterns from the integration docs linked below.
|
|
68
|
-
- **If Respan is already installed/configured, do not duplicate work.** Check for existing \`respan\` imports first.
|
|
69
|
-
- **Read the code before proposing changes.** Understand the actual workflow, not just the dependencies.
|
|
70
|
-
|
|
71
|
-
## Context
|
|
72
|
-
|
|
73
|
-
The API key is stored in \`.env\` as \`RESPAN_API_KEY\`.
|
|
74
|
-
Full docs index: \`https://www.respan.ai/docs/llms.txt\`
|
|
75
|
-
|
|
76
|
-
## Steps
|
|
77
|
-
|
|
78
|
-
### 1. Analyze the Project
|
|
79
|
-
|
|
80
|
-
**1a. Detect language and package manager:**
|
|
81
|
-
- Check \`package.json\` (JS/TS) or \`pyproject.toml\` / \`requirements.txt\` (Python)
|
|
82
|
-
- Detect package manager from lock files
|
|
83
|
-
|
|
84
|
-
**1b. Detect libraries in priority order:**
|
|
85
|
-
|
|
86
|
-
Check higher-priority categories first. If a match is found, use that instrumentation — do NOT also add lower-level SDK instrumentation.
|
|
87
|
-
|
|
88
|
-
**Priority 1 — Agent Frameworks & High-Level SDKs:**
|
|
89
|
-
|
|
90
|
-
| Library | Python package | JS/TS package | Respan instrumentation (Python) | Respan instrumentation (JS/TS) | Docs |
|
|
91
|
-
|---------|---------------|---------------|--------------------------------|-------------------------------|------|
|
|
92
|
-
| Vercel AI SDK | — | \`ai\` | — | \`@respan/instrumentation-vercel\` | [docs](https://respan.ai/docs/integrations/vercel-ai-sdk.md) |
|
|
93
|
-
| OpenAI Agents SDK | \`openai-agents\` | \`@openai/agents\` | \`respan-instrumentation-openai-agents\` | \`@respan/instrumentation-openai-agents\` | [docs](https://respan.ai/docs/integrations/openai-agents-sdk.md) |
|
|
94
|
-
| Claude Agent SDK | \`claude-agent-sdk\` | — | \`respan-instrumentation-claude-agent-sdk\` | — | [docs](https://respan.ai/docs/integrations/claude-agents-sdk.md) |
|
|
95
|
-
| Pydantic AI | \`pydantic-ai\` | — | \`respan-instrumentation-pydantic-ai\` | — | [docs](https://respan.ai/docs/integrations/pydantic-ai.md) |
|
|
96
|
-
| LangChain | \`langchain\` | \`langchain\` | via OpenInference | — | [docs](https://respan.ai/docs/integrations/langchain.md) |
|
|
97
|
-
| LangGraph | \`langgraph\` | — | via OpenInference | — | [docs](https://respan.ai/docs/integrations/langgraph.md) |
|
|
98
|
-
| CrewAI | \`crewai\` | — | \`respan-instrumentation-crewai\` | — | [docs](https://respan.ai/docs/integrations/crewai.md) |
|
|
99
|
-
| LlamaIndex | \`llama-index\` | — | via OpenInference | — | [docs](https://respan.ai/docs/integrations/llama-index.md) |
|
|
100
|
-
| Haystack | \`haystack-ai\` | — | \`respan-instrumentation-haystack\` | — | [docs](https://respan.ai/docs/integrations/haystack.md) |
|
|
101
|
-
| Mastra | — | \`mastra\` | — | via OTEL | [docs](https://respan.ai/docs/integrations/mastra.md) |
|
|
102
|
-
| Google ADK | \`google-adk\` | — | via OpenInference | — | [docs](https://respan.ai/docs/integrations/google-adk.md) |
|
|
103
|
-
|
|
104
|
-
If a Priority 1 framework is found, use its instrumentation. Do NOT also add Priority 2 instrumentation for the same provider.
|
|
105
|
-
|
|
106
|
-
**Priority 2 — Direct LLM SDKs** (only if no P1 framework covers this provider):
|
|
107
|
-
|
|
108
|
-
These are **auto-instrumented** — just \`Respan()\` / \`new Respan()\`, no extra packages needed:
|
|
109
|
-
|
|
110
|
-
| Library | Python package | JS/TS package | Docs |
|
|
111
|
-
|---------|---------------|---------------|------|
|
|
112
|
-
| OpenAI SDK | \`openai\` | \`openai\` | [docs](https://respan.ai/docs/integrations/openai-sdk.md) |
|
|
113
|
-
| Anthropic SDK | \`anthropic\` | \`@anthropic-ai/sdk\` | [docs](https://respan.ai/docs/integrations/anthropic.md) |
|
|
114
|
-
| Azure OpenAI | \`openai\` (azure config) | \`openai\` | [docs](https://respan.ai/docs/integrations/providers/azure.md) |
|
|
115
|
-
| Google Vertex AI | \`google-cloud-aiplatform\` | — | [docs](https://respan.ai/docs/integrations/vertex-ai.md) |
|
|
116
|
-
| AWS Bedrock | \`boto3\` | — | [docs](https://respan.ai/docs/integrations/aws-bedrock.md) |
|
|
117
|
-
| Cohere | \`cohere\` | — | [docs](https://respan.ai/docs/integrations/providers/cohere.md) |
|
|
118
|
-
| Together AI | \`together\` | — | [docs](https://respan.ai/docs/integrations/together-ai.md) |
|
|
119
|
-
|
|
120
|
-
**Note:** LiteLLM in JS uses the OpenAI-compatible API, so the OpenAI auto-instrument covers it. For Python LiteLLM, see [LiteLLM guide](https://respan.ai/docs/integrations/litellm.md). For Google GenAI (\`@google/genai\`), see [Google GenAI guide](https://respan.ai/docs/integrations/google-genai.md).
|
|
121
|
-
|
|
122
|
-
**1c. Read the actual code and understand the workflow:**
|
|
123
|
-
|
|
124
|
-
This is the most important step. Read the entrypoint and all files that make LLM calls. Map out:
|
|
125
|
-
|
|
126
|
-
- What is the **overall workflow**? (e.g. "user sends question → retrieve context → generate answer → format response")
|
|
127
|
-
- What are the **individual steps/tasks**? (e.g. "embed query", "search DB", "call GPT", "parse output")
|
|
128
|
-
- Are there **agent loops**? (e.g. a loop that calls tools until done)
|
|
129
|
-
- Are there **tool calls**? (e.g. functions the LLM invokes)
|
|
130
|
-
|
|
131
|
-
### 2. Propose an Implementation Plan
|
|
132
|
-
|
|
133
|
-
Present the user with a concrete plan before making any changes. The plan should include:
|
|
134
|
-
|
|
135
|
-
**a) Packages to install** — core SDK + instrumentation package (with exact versions)
|
|
136
|
-
|
|
137
|
-
**b) Initialization code** — where to add it (which file, which line)
|
|
138
|
-
|
|
139
|
-
**c) Workflow structure** — how to wrap the existing code:
|
|
140
|
-
|
|
141
|
-
For **agent frameworks** (Priority 1): The framework instrumentation auto-captures the workflow structure. Usually just need init code, no manual wrapping needed. Fetch and follow the integration doc.
|
|
142
|
-
|
|
143
|
-
For **direct LLM SDKs** (Priority 2): Individual LLM calls will be auto-traced as flat spans. Propose wrapping the logical workflow with Respan decorators/wrappers to get structured nested traces:
|
|
144
|
-
|
|
145
|
-
TypeScript example:
|
|
146
|
-
\`\`\`typescript
|
|
147
|
-
// Before: flat traces — each LLM call is an isolated span
|
|
148
|
-
const outline = await openai.chat.completions.create({...});
|
|
149
|
-
const draft = await openai.chat.completions.create({...});
|
|
150
|
-
|
|
151
|
-
// After: structured traces — nested spans showing the workflow
|
|
152
|
-
const result = await withWorkflow({ name: "write_article" }, async () => {
|
|
153
|
-
const outline = await withTask({ name: "generate_outline" }, async () => {
|
|
154
|
-
return await openai.chat.completions.create({...});
|
|
155
|
-
});
|
|
156
|
-
const draft = await withTask({ name: "write_draft" }, async () => {
|
|
157
|
-
return await openai.chat.completions.create({...});
|
|
158
|
-
});
|
|
159
|
-
return draft;
|
|
160
|
-
});
|
|
161
|
-
\`\`\`
|
|
162
|
-
|
|
163
|
-
Python example:
|
|
164
|
-
\`\`\`python
|
|
165
|
-
# Before: flat traces
|
|
166
|
-
outline = client.chat.completions.create(...)
|
|
167
|
-
draft = client.chat.completions.create(...)
|
|
168
|
-
|
|
169
|
-
# After: structured traces
|
|
170
|
-
@workflow(name="write_article")
|
|
171
|
-
def write_article(topic):
|
|
172
|
-
outline = generate_outline(topic)
|
|
173
|
-
return write_draft(outline)
|
|
174
|
-
|
|
175
|
-
@task(name="generate_outline")
|
|
176
|
-
def generate_outline(topic):
|
|
177
|
-
return client.chat.completions.create(...)
|
|
178
|
-
|
|
179
|
-
@task(name="write_draft")
|
|
180
|
-
def write_draft(outline):
|
|
181
|
-
return client.chat.completions.create(...)
|
|
182
|
-
\`\`\`
|
|
183
|
-
|
|
184
|
-
**Ask the user which approach they prefer:**
|
|
185
|
-
1. **Auto-trace only** — just add init code, every LLM call is automatically captured as a flat span. Zero code changes beyond initialization. Good for quick setup or simple projects.
|
|
186
|
-
2. **Structured traces** — wrap existing code with workflow/task decorators for nested spans showing how the app flows. Better for complex projects with multiple LLM calls.
|
|
187
|
-
|
|
188
|
-
If the user picks option 1, skip the wrappers entirely — just install + init code.
|
|
189
|
-
|
|
190
|
-
If the user picks option 2:
|
|
191
|
-
- **If multiple independent workflows are detected** (e.g. \`writeArticle()\`, \`summarizeDoc()\`, \`classifyEmail()\`), list them and ask which ones to instrument. Don't assume all of them.
|
|
192
|
-
- **Show the user what the trace will look like** — describe the span hierarchy:
|
|
193
|
-
\`\`\`
|
|
194
|
-
workflow: write_article
|
|
195
|
-
├── task: generate_outline
|
|
196
|
-
│ └── llm: openai.chat (auto-captured)
|
|
197
|
-
└── task: write_draft
|
|
198
|
-
└── llm: openai.chat (auto-captured)
|
|
199
|
-
\`\`\`
|
|
200
|
-
|
|
201
|
-
Wait for user confirmation before proceeding.
|
|
202
|
-
|
|
203
|
-
### 3. Implement
|
|
204
|
-
|
|
205
|
-
**a) Install packages:**
|
|
206
|
-
|
|
207
|
-
For direct LLM SDKs (Priority 2) — just the core SDK:
|
|
208
|
-
\`\`\`bash
|
|
209
|
-
# Python
|
|
210
|
-
pip install respan-ai
|
|
211
|
-
|
|
212
|
-
# TypeScript
|
|
213
|
-
npm install @respan/respan
|
|
214
|
-
\`\`\`
|
|
215
|
-
|
|
216
|
-
For agent frameworks (Priority 1) — also install the instrumentor. Check the docs link in the table above for the exact packages.
|
|
217
|
-
|
|
218
|
-
**b) Add initialization code** — at the top of the entrypoint, before any LLM client is created:
|
|
219
|
-
|
|
220
|
-
For **direct LLM SDKs** (auto-instrumented):
|
|
221
|
-
\`\`\`python
|
|
222
|
-
# Python
|
|
223
|
-
from respan import Respan
|
|
224
|
-
Respan()
|
|
225
|
-
\`\`\`
|
|
226
|
-
\`\`\`typescript
|
|
227
|
-
// TypeScript
|
|
228
|
-
import { Respan } from "@respan/respan";
|
|
229
|
-
const respan = new Respan();
|
|
230
|
-
await respan.initialize();
|
|
231
|
-
\`\`\`
|
|
232
|
-
|
|
233
|
-
For **agent frameworks** (explicit instrumentor — fetch the docs URL from the table for the exact pattern):
|
|
234
|
-
\`\`\`python
|
|
235
|
-
# Python example (OpenAI Agents)
|
|
236
|
-
from respan import Respan
|
|
237
|
-
from respan_instrumentation_openai_agents import OpenAIAgentsInstrumentor
|
|
238
|
-
Respan(instrumentations=[OpenAIAgentsInstrumentor()])
|
|
239
|
-
\`\`\`
|
|
240
|
-
\`\`\`typescript
|
|
241
|
-
// TypeScript example (OpenAI Agents)
|
|
242
|
-
import { Respan } from "@respan/respan";
|
|
243
|
-
import { OpenAIAgentsInstrumentor } from "@respan/instrumentation-openai-agents";
|
|
244
|
-
const respan = new Respan({ instrumentations: [new OpenAIAgentsInstrumentor()] });
|
|
245
|
-
await respan.initialize();
|
|
246
|
-
\`\`\`
|
|
247
|
-
|
|
248
|
-
**c) Add workflow wrappers** — if the user chose structured traces in the plan.
|
|
249
|
-
|
|
250
|
-
### 4. Verify
|
|
251
|
-
|
|
252
|
-
Run the application and confirm:
|
|
253
|
-
- The app runs without errors
|
|
254
|
-
- Traces appear at https://platform.respan.ai or via \`respan traces list --limit 5\`
|
|
255
|
-
- If wrappers were added, verify the trace shows the expected nested span hierarchy
|
|
256
|
-
`;
|
|
257
|
-
// ── Constants ──────────────────────────────────────────────────────────
|
|
258
|
-
const PC = '\x1b[38;2;100;131;240m';
|
|
259
|
-
const RESET = '\x1b[0m';
|
|
260
|
-
const DIM = '\x1b[2m';
|
|
261
|
-
const GREEN = '\x1b[32m';
|
|
262
|
-
const BOLD = '\x1b[1m';
|
|
263
|
-
const CLI_TOOLS = {
|
|
264
|
-
'claude-code': {
|
|
265
|
-
name: 'Claude Code',
|
|
266
|
-
binary: 'claude',
|
|
267
|
-
description: 'Anthropic\'s coding agent',
|
|
268
|
-
configDirs: ['~/.claude', '.claude'],
|
|
269
|
-
agentSkillsDir: '.claude',
|
|
270
|
-
},
|
|
271
|
-
'cursor': {
|
|
272
|
-
name: 'Cursor',
|
|
273
|
-
binary: 'cursor',
|
|
274
|
-
description: 'AI-powered code editor',
|
|
275
|
-
configDirs: ['.cursor', '.cursorrc'],
|
|
276
|
-
agentSkillsDir: '.cursor',
|
|
277
|
-
},
|
|
278
|
-
'codex-cli': {
|
|
279
|
-
name: 'Codex CLI',
|
|
280
|
-
binary: 'codex',
|
|
281
|
-
description: 'OpenAI\'s coding agent',
|
|
282
|
-
configDirs: ['~/.codex', '.codex'],
|
|
283
|
-
agentSkillsDir: '.codex',
|
|
284
|
-
},
|
|
285
|
-
'gemini-cli': {
|
|
286
|
-
name: 'Gemini CLI',
|
|
287
|
-
binary: 'gemini',
|
|
288
|
-
description: 'Google\'s coding agent',
|
|
289
|
-
configDirs: ['~/.gemini', '.gemini'],
|
|
290
|
-
agentSkillsDir: '.gemini',
|
|
291
|
-
},
|
|
292
|
-
'opencode': {
|
|
293
|
-
name: 'OpenCode',
|
|
294
|
-
binary: 'opencode',
|
|
295
|
-
description: 'Open-source coding agent',
|
|
296
|
-
configDirs: ['.opencode'],
|
|
297
|
-
agentSkillsDir: '.opencode',
|
|
298
|
-
},
|
|
299
|
-
};
|
|
300
|
-
class Setup extends BaseCommand {
|
|
2
|
+
import { SetupBaseCommand } from '../../lib/setup-base-command.js';
|
|
3
|
+
class Setup extends SetupBaseCommand {
|
|
301
4
|
async run() {
|
|
302
5
|
const { flags } = await this.parse(Setup);
|
|
303
6
|
this.globalFlags = flags;
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
this.logStep(1, 'API Key');
|
|
309
|
-
const apiKey = await this.askApiKey(projectRoot);
|
|
310
|
-
await this.verifyApiKey(apiKey);
|
|
311
|
-
// ── Step 2: Choose agent ─────────────────────────────────────────
|
|
312
|
-
this.logStep(2, 'Choose your coding agent');
|
|
313
|
-
const detected = this.detectAgents(projectRoot, home);
|
|
314
|
-
const selectedTool = await this.selectAgent(flags.agent, detected);
|
|
315
|
-
if (!selectedTool) {
|
|
316
|
-
this.log(` ${DIM}No agent selected. You can always run ${RESET}respan setup${DIM} again.${RESET}`);
|
|
317
|
-
return;
|
|
318
|
-
}
|
|
319
|
-
// ── Step 3: Install skill ────────────────────────────────────────
|
|
320
|
-
this.logStep(3, 'Install skill');
|
|
321
|
-
await this.installSkill(selectedTool, projectRoot);
|
|
322
|
-
// ── Done ─────────────────────────────────────────────────────────
|
|
323
|
-
this.log('');
|
|
324
|
-
this.log(` ${GREEN}${BOLD}Setup complete!${RESET}`);
|
|
325
|
-
this.log('');
|
|
326
|
-
this.log(` ${DIM}Your API key is saved in ${RESET}.env`);
|
|
327
|
-
this.log(` ${DIM}Respan skill installed for all agents${RESET}`);
|
|
328
|
-
this.log(` ${DIM}View traces at ${RESET}https://platform.respan.ai`);
|
|
329
|
-
this.log('');
|
|
330
|
-
this.notifySetup(this.getGitEmail()).catch(() => { });
|
|
331
|
-
// ── Step 4: Open agent ───────────────────────────────────────────
|
|
332
|
-
const shouldInstrument = !flags['no-instrument'];
|
|
333
|
-
if (shouldInstrument) {
|
|
334
|
-
await this.runInstrumentAgent(selectedTool, projectRoot);
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
// ── Step 1: API Key ──────────────────────────────────────────────────
|
|
338
|
-
async askApiKey(projectRoot) {
|
|
339
|
-
const envPath = path.join(projectRoot, '.env');
|
|
340
|
-
const existingEnv = readTextFile(envPath);
|
|
341
|
-
const existingKey = this.extractEnvVar(existingEnv, 'RESPAN_API_KEY');
|
|
342
|
-
if (existingKey) {
|
|
343
|
-
const masked = existingKey.slice(0, 8) + '...' + existingKey.slice(-4);
|
|
344
|
-
this.log(` ${PC}Found existing API key:${RESET} ${masked}`);
|
|
345
|
-
const keep = await confirm({
|
|
346
|
-
message: 'Keep this API key?',
|
|
347
|
-
default: true,
|
|
348
|
-
});
|
|
349
|
-
if (keep)
|
|
350
|
-
return existingKey;
|
|
351
|
-
}
|
|
352
|
-
this.log('');
|
|
353
|
-
this.log(` ${DIM}Get your API key at ${RESET}https://platform.respan.ai/settings/api-keys`);
|
|
354
|
-
this.log('');
|
|
355
|
-
const apiKey = await input({
|
|
356
|
-
message: 'Enter your Respan API key:',
|
|
357
|
-
validate: (val) => val.trim().length > 0 || 'API key is required',
|
|
358
|
-
});
|
|
359
|
-
this.saveToEnv(envPath, existingEnv, 'RESPAN_API_KEY', apiKey.trim());
|
|
360
|
-
this.log(` ${GREEN}\u2713${RESET} Saved API key to ${DIM}${envPath}${RESET}`);
|
|
361
|
-
return apiKey.trim();
|
|
362
|
-
}
|
|
363
|
-
async verifyApiKey(apiKey) {
|
|
364
|
-
const spinner = createSpinner('Verifying API key');
|
|
365
|
-
spinner.start();
|
|
366
|
-
try {
|
|
367
|
-
const email = this.getGitEmail();
|
|
368
|
-
const payload = this.buildDemoTrace(email);
|
|
369
|
-
const response = await fetch('https://api.respan.ai/api/v2/traces', {
|
|
370
|
-
method: 'POST',
|
|
371
|
-
headers: {
|
|
372
|
-
'Authorization': `Bearer ${apiKey}`,
|
|
373
|
-
'Content-Type': 'application/json',
|
|
374
|
-
},
|
|
375
|
-
body: JSON.stringify(payload),
|
|
376
|
-
});
|
|
377
|
-
if (response.ok) {
|
|
378
|
-
spinner.succeed('API key verified');
|
|
379
|
-
this.log('');
|
|
380
|
-
this.log(` ${PC}A demo trace has been sent to your account.${RESET}`);
|
|
381
|
-
this.log(` ${DIM}View it at ${RESET}https://platform.respan.ai${DIM} to see what Respan traces look like.${RESET}`);
|
|
382
|
-
this.log('');
|
|
383
|
-
await confirm({ message: 'Ready to continue?', default: true });
|
|
384
|
-
}
|
|
385
|
-
else if (response.status === 401 || response.status === 403) {
|
|
386
|
-
spinner.fail('Invalid API key');
|
|
387
|
-
this.warn(' Check your key at https://platform.respan.ai/settings/api-keys');
|
|
388
|
-
}
|
|
389
|
-
else {
|
|
390
|
-
spinner.fail(`Verification failed (status: ${response.status})`);
|
|
391
|
-
this.warn(' Setup will continue — verify manually at https://platform.respan.ai');
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
catch {
|
|
395
|
-
spinner.fail('Could not verify API key (network error)');
|
|
396
|
-
this.warn(' Setup will continue — verify manually at https://platform.respan.ai');
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
/**
|
|
400
|
-
* Build a rich demo OTLP trace that mimics a customer support agent workflow:
|
|
401
|
-
* workflow: customer_support_pipeline
|
|
402
|
-
* └── agent: support_agent
|
|
403
|
-
* ├── tool: lookup_order
|
|
404
|
-
* ├── chat: openai.chat (classify intent)
|
|
405
|
-
* ├── tool: process_refund
|
|
406
|
-
* ├── chat: openai.chat (generate response)
|
|
407
|
-
* └── task: log_resolution
|
|
408
|
-
*/
|
|
409
|
-
buildDemoTrace(email) {
|
|
410
|
-
const randHex = (len) => Array.from({ length: len }, () => Math.floor(Math.random() * 16).toString(16)).join('');
|
|
411
|
-
const traceId = randHex(32);
|
|
412
|
-
const ns = (ms) => `${ms}000000`;
|
|
413
|
-
const now = Date.now();
|
|
414
|
-
const attr = (key, val) => ({ key, value: { stringValue: val } });
|
|
415
|
-
const intAttr = (key, val) => ({ key, value: { intValue: String(val) } });
|
|
416
|
-
const spans = [
|
|
417
|
-
// 1. Workflow (root)
|
|
418
|
-
{
|
|
419
|
-
traceId, spanId: randHex(16),
|
|
420
|
-
name: 'respan-setup (demo)', kind: 1,
|
|
421
|
-
startTimeUnixNano: ns(now), endTimeUnixNano: ns(now + 8000),
|
|
422
|
-
attributes: [
|
|
423
|
-
attr('respan.entity.log_type', 'workflow'),
|
|
424
|
-
attr('traceloop.entity.name', 'respan-setup (demo)'),
|
|
425
|
-
attr('traceloop.entity.input', '{"query": "My order #12345 hasn\'t arrived yet", "customer_id": "cust_789"}'),
|
|
426
|
-
attr('traceloop.entity.output', '{"status": "resolved", "action": "refund_initiated", "ticket_id": "TKT-001"}'),
|
|
427
|
-
],
|
|
428
|
-
status: { code: 1 },
|
|
429
|
-
_spanId: 'workflow',
|
|
430
|
-
},
|
|
431
|
-
// 2. Agent (child of workflow)
|
|
432
|
-
{
|
|
433
|
-
traceId, spanId: randHex(16), parentSpanId: '', // filled below
|
|
434
|
-
name: 'customer_support_agent', kind: 1,
|
|
435
|
-
startTimeUnixNano: ns(now + 100), endTimeUnixNano: ns(now + 7500),
|
|
436
|
-
attributes: [
|
|
437
|
-
attr('respan.entity.log_type', 'agent'),
|
|
438
|
-
attr('traceloop.entity.name', 'customer_support_agent'),
|
|
439
|
-
attr('traceloop.entity.input', '[{"role": "user", "content": "My order #12345 hasn\'t arrived yet"}]'),
|
|
440
|
-
attr('traceloop.entity.output', '{"role": "assistant", "content": "I\'ve looked into your order #12345. It appears there was a shipping delay. I\'ve initiated a refund for you."}'),
|
|
441
|
-
],
|
|
442
|
-
status: { code: 1 },
|
|
443
|
-
_spanId: 'agent', _parentRef: 'workflow',
|
|
444
|
-
},
|
|
445
|
-
// 3. Tool: lookup_order (child of agent)
|
|
446
|
-
{
|
|
447
|
-
traceId, spanId: randHex(16), parentSpanId: '',
|
|
448
|
-
name: 'lookup_order', kind: 1,
|
|
449
|
-
startTimeUnixNano: ns(now + 200), endTimeUnixNano: ns(now + 1200),
|
|
450
|
-
attributes: [
|
|
451
|
-
attr('respan.entity.log_type', 'tool'),
|
|
452
|
-
attr('traceloop.entity.name', 'lookup_order'),
|
|
453
|
-
attr('traceloop.entity.input', '{"order_id": "12345"}'),
|
|
454
|
-
attr('traceloop.entity.output', '{"order_id": "12345", "status": "delayed", "items": ["Widget A", "Widget B"]}'),
|
|
455
|
-
],
|
|
456
|
-
status: { code: 1 },
|
|
457
|
-
_spanId: 'tool1', _parentRef: 'agent',
|
|
458
|
-
},
|
|
459
|
-
// 4. Chat: classify intent (child of agent)
|
|
460
|
-
{
|
|
461
|
-
traceId, spanId: randHex(16), parentSpanId: '',
|
|
462
|
-
name: 'openai.chat', kind: 1,
|
|
463
|
-
startTimeUnixNano: ns(now + 1300), endTimeUnixNano: ns(now + 3000),
|
|
464
|
-
attributes: [
|
|
465
|
-
attr('respan.entity.log_type', 'chat'),
|
|
466
|
-
attr('llm.request.type', 'chat'),
|
|
467
|
-
attr('gen_ai.system', 'openai'),
|
|
468
|
-
attr('gen_ai.request.model', 'gpt-4o-mini'),
|
|
469
|
-
attr('gen_ai.response.model', 'gpt-4o-mini'),
|
|
470
|
-
intAttr('gen_ai.usage.input_tokens', 145),
|
|
471
|
-
intAttr('gen_ai.usage.output_tokens', 38),
|
|
472
|
-
intAttr('gen_ai.usage.prompt_tokens', 145),
|
|
473
|
-
intAttr('gen_ai.usage.completion_tokens', 38),
|
|
474
|
-
attr('traceloop.entity.input', '[{"role": "system", "content": "Classify the customer intent."}, {"role": "user", "content": "My order #12345 hasn\'t arrived yet"}]'),
|
|
475
|
-
attr('traceloop.entity.output', '{"role": "assistant", "content": "intent: order_status_inquiry, sentiment: frustrated"}'),
|
|
476
|
-
],
|
|
477
|
-
status: { code: 1 },
|
|
478
|
-
_spanId: 'chat1', _parentRef: 'agent',
|
|
479
|
-
},
|
|
480
|
-
// 5. Tool: process_refund (child of agent)
|
|
481
|
-
{
|
|
482
|
-
traceId, spanId: randHex(16), parentSpanId: '',
|
|
483
|
-
name: 'process_refund', kind: 1,
|
|
484
|
-
startTimeUnixNano: ns(now + 3100), endTimeUnixNano: ns(now + 4500),
|
|
485
|
-
attributes: [
|
|
486
|
-
attr('respan.entity.log_type', 'tool'),
|
|
487
|
-
attr('traceloop.entity.name', 'process_refund'),
|
|
488
|
-
attr('traceloop.entity.input', '{"order_id": "12345", "reason": "shipping_delay"}'),
|
|
489
|
-
attr('traceloop.entity.output', '{"refund_id": "REF-789", "amount": 49.99, "status": "initiated"}'),
|
|
490
|
-
],
|
|
491
|
-
status: { code: 1 },
|
|
492
|
-
_spanId: 'tool2', _parentRef: 'agent',
|
|
493
|
-
},
|
|
494
|
-
// 6. Chat: generate response (child of agent)
|
|
495
|
-
{
|
|
496
|
-
traceId, spanId: randHex(16), parentSpanId: '',
|
|
497
|
-
name: 'openai.chat', kind: 1,
|
|
498
|
-
startTimeUnixNano: ns(now + 4600), endTimeUnixNano: ns(now + 6800),
|
|
499
|
-
attributes: [
|
|
500
|
-
attr('respan.entity.log_type', 'chat'),
|
|
501
|
-
attr('llm.request.type', 'chat'),
|
|
502
|
-
attr('gen_ai.system', 'openai'),
|
|
503
|
-
attr('gen_ai.request.model', 'gpt-4o-mini'),
|
|
504
|
-
attr('gen_ai.response.model', 'gpt-4o-mini'),
|
|
505
|
-
intAttr('gen_ai.usage.input_tokens', 210),
|
|
506
|
-
intAttr('gen_ai.usage.output_tokens', 85),
|
|
507
|
-
intAttr('gen_ai.usage.prompt_tokens', 210),
|
|
508
|
-
intAttr('gen_ai.usage.completion_tokens', 85),
|
|
509
|
-
attr('traceloop.entity.input', '[{"role": "system", "content": "Generate a helpful response to the customer."}, {"role": "user", "content": "Order delayed, refund initiated for #12345"}]'),
|
|
510
|
-
attr('traceloop.entity.output', '{"role": "assistant", "content": "I\'ve looked into your order #12345. It appears there was a shipping delay. I\'ve initiated a refund of $49.99 for you. You should see it within 3-5 business days."}'),
|
|
511
|
-
],
|
|
512
|
-
status: { code: 1 },
|
|
513
|
-
_spanId: 'chat2', _parentRef: 'agent',
|
|
514
|
-
},
|
|
515
|
-
// 7. Task: log_resolution (child of agent)
|
|
516
|
-
{
|
|
517
|
-
traceId, spanId: randHex(16), parentSpanId: '',
|
|
518
|
-
name: 'log_resolution', kind: 1,
|
|
519
|
-
startTimeUnixNano: ns(now + 6900), endTimeUnixNano: ns(now + 7400),
|
|
520
|
-
attributes: [
|
|
521
|
-
attr('respan.entity.log_type', 'task'),
|
|
522
|
-
attr('traceloop.entity.name', 'log_resolution'),
|
|
523
|
-
attr('traceloop.entity.input', '{"ticket_id": "TKT-001", "resolution": "refund_initiated"}'),
|
|
524
|
-
attr('traceloop.entity.output', '{"logged": true}'),
|
|
525
|
-
],
|
|
526
|
-
status: { code: 1 },
|
|
527
|
-
_spanId: 'task1', _parentRef: 'agent',
|
|
528
|
-
},
|
|
529
|
-
];
|
|
530
|
-
// Wire up parent references
|
|
531
|
-
const spanIdMap = {};
|
|
532
|
-
for (const span of spans) {
|
|
533
|
-
const ref = span._spanId;
|
|
534
|
-
if (ref)
|
|
535
|
-
spanIdMap[ref] = span.spanId;
|
|
536
|
-
}
|
|
537
|
-
for (const span of spans) {
|
|
538
|
-
const parentRef = span._parentRef;
|
|
539
|
-
if (parentRef && spanIdMap[parentRef]) {
|
|
540
|
-
span.parentSpanId = spanIdMap[parentRef];
|
|
541
|
-
}
|
|
542
|
-
delete span._spanId;
|
|
543
|
-
delete span._parentRef;
|
|
544
|
-
}
|
|
545
|
-
return {
|
|
546
|
-
resourceSpans: [{
|
|
547
|
-
resource: {
|
|
548
|
-
attributes: [
|
|
549
|
-
attr('service.name', 'respan-setup (demo)'),
|
|
550
|
-
...(email ? [attr('respan.setup.email', email)] : []),
|
|
551
|
-
],
|
|
552
|
-
},
|
|
553
|
-
scopeSpans: [{
|
|
554
|
-
scope: { name: 'respan.setup' },
|
|
555
|
-
spans,
|
|
556
|
-
}],
|
|
557
|
-
}],
|
|
558
|
-
};
|
|
559
|
-
}
|
|
560
|
-
// ── Step 2: Agent detection & selection ───────────────────────────────
|
|
561
|
-
detectAgents(projectRoot, home) {
|
|
562
|
-
const signals = [];
|
|
563
|
-
for (const [id, meta] of Object.entries(CLI_TOOLS)) {
|
|
564
|
-
const onPath = this.isBinaryInstalled(meta.binary);
|
|
565
|
-
const hasConfigDir = meta.configDirs.some((dir) => {
|
|
566
|
-
const resolved = dir.startsWith('~')
|
|
567
|
-
? path.join(home, dir.slice(1))
|
|
568
|
-
: dir.startsWith('.')
|
|
569
|
-
? path.join(projectRoot, dir)
|
|
570
|
-
: dir;
|
|
571
|
-
return fs.existsSync(resolved);
|
|
572
|
-
});
|
|
573
|
-
const reasons = [];
|
|
574
|
-
if (onPath)
|
|
575
|
-
reasons.push('binary on PATH');
|
|
576
|
-
if (hasConfigDir)
|
|
577
|
-
reasons.push('config directory found');
|
|
578
|
-
signals.push({
|
|
579
|
-
tool: id,
|
|
580
|
-
onPath,
|
|
581
|
-
hasConfigDir,
|
|
582
|
-
reason: reasons.length > 0 ? reasons.join(', ') : 'not detected',
|
|
583
|
-
});
|
|
584
|
-
}
|
|
585
|
-
return signals;
|
|
586
|
-
}
|
|
587
|
-
async selectAgent(flagAgent, detected) {
|
|
588
|
-
// If --agent flag provided, use it
|
|
589
|
-
if (flagAgent && CLI_TOOLS[flagAgent]) {
|
|
590
|
-
const signal = detected.find((d) => d.tool === flagAgent);
|
|
591
|
-
this.log(` ${GREEN}\u2713${RESET} Using ${CLI_TOOLS[flagAgent].name}${signal?.onPath ? '' : ` ${DIM}(not found on PATH)${RESET}`}`);
|
|
592
|
-
return flagAgent;
|
|
593
|
-
}
|
|
594
|
-
// Auto-detect: if only one agent detected, use it directly
|
|
595
|
-
const detectedAgents = detected.filter((d) => d.onPath || d.hasConfigDir);
|
|
596
|
-
if (detectedAgents.length === 1) {
|
|
597
|
-
const tool = detectedAgents[0].tool;
|
|
598
|
-
const useIt = await confirm({
|
|
599
|
-
message: `Detected ${CLI_TOOLS[tool].name}. Use it?`,
|
|
600
|
-
default: true,
|
|
601
|
-
});
|
|
602
|
-
if (useIt)
|
|
603
|
-
return tool;
|
|
604
|
-
}
|
|
605
|
-
// Prompt for selection — only show detected agents first, then the rest
|
|
606
|
-
const detectedIds = new Set(detectedAgents.map((d) => d.tool));
|
|
607
|
-
const choices = [
|
|
608
|
-
...detectedAgents.map((d) => ({
|
|
609
|
-
name: `${CLI_TOOLS[d.tool].name} ${DIM}— ${CLI_TOOLS[d.tool].description}${RESET}`,
|
|
610
|
-
value: d.tool,
|
|
611
|
-
})),
|
|
612
|
-
...Object.entries(CLI_TOOLS)
|
|
613
|
-
.filter(([id]) => !detectedIds.has(id))
|
|
614
|
-
.map(([id, meta]) => ({
|
|
615
|
-
name: `${meta.name} ${DIM}— ${meta.description} (not detected)${RESET}`,
|
|
616
|
-
value: id,
|
|
617
|
-
})),
|
|
618
|
-
];
|
|
619
|
-
const selected = await select({
|
|
620
|
-
message: 'Select your coding agent:',
|
|
621
|
-
choices: [
|
|
622
|
-
...choices,
|
|
623
|
-
{ name: `None — I'll set up later`, value: 'none' },
|
|
624
|
-
],
|
|
7
|
+
// No mode → runSetup asks "Tracing or Gateway?"
|
|
8
|
+
await this.runSetup(undefined, {
|
|
9
|
+
agent: flags.agent,
|
|
10
|
+
noInstrument: flags['no-instrument'],
|
|
625
11
|
});
|
|
626
|
-
return selected === 'none' ? null : selected;
|
|
627
|
-
}
|
|
628
|
-
// ── Step 3: Install skill ──────────────────────────────────────────────
|
|
629
|
-
async installSkill(tool, projectRoot) {
|
|
630
|
-
const home = os.homedir();
|
|
631
|
-
const writeSkillTo = (baseDir) => {
|
|
632
|
-
const skillDir = path.join(baseDir, 'respan');
|
|
633
|
-
const refsDir = path.join(skillDir, 'references');
|
|
634
|
-
ensureDir(refsDir);
|
|
635
|
-
writeTextFile(path.join(skillDir, 'SKILL.md'), this.getSkillMd());
|
|
636
|
-
writeTextFile(path.join(refsDir, 'setup.md'), SETUP_MD);
|
|
637
|
-
writeTextFile(path.join(refsDir, 'tracing.md'), TRACING_MD);
|
|
638
|
-
writeTextFile(path.join(refsDir, 'gateway.md'), GATEWAY_MD);
|
|
639
|
-
writeTextFile(path.join(refsDir, 'prompts.md'), PROMPTS_MD);
|
|
640
|
-
writeTextFile(path.join(refsDir, 'evals.md'), EVALS_MD);
|
|
641
|
-
writeTextFile(path.join(refsDir, 'monitors.md'), MONITORS_MD);
|
|
642
|
-
};
|
|
643
|
-
// Write to ~/.agents/skills/ (Cursor, Codex, Gemini CLI, OpenCode)
|
|
644
|
-
writeSkillTo(path.join(home, '.agents', 'skills'));
|
|
645
|
-
// Also write to ~/.claude/skills/ (Claude Code doesn't read ~/.agents/)
|
|
646
|
-
writeSkillTo(path.join(home, '.claude', 'skills'));
|
|
647
|
-
this.log(` ${GREEN}\u2713${RESET} Installed respan skill for all agents`);
|
|
648
|
-
}
|
|
649
|
-
// ── Step 4: Instrument ───────────────────────────────────────────────
|
|
650
|
-
async runInstrumentAgent(tool, projectRoot) {
|
|
651
|
-
const meta = CLI_TOOLS[tool];
|
|
652
|
-
if (!this.isBinaryInstalled(meta.binary)) {
|
|
653
|
-
this.log(` ${DIM}${meta.binary} is not installed. Install it first, then run it — it will pick up the setup skill.${RESET}`);
|
|
654
|
-
return;
|
|
655
|
-
}
|
|
656
|
-
const launch = await confirm({
|
|
657
|
-
message: `Open ${meta.name} now? It will pick up the setup skill to configure SDK tracing.`,
|
|
658
|
-
default: true,
|
|
659
|
-
});
|
|
660
|
-
if (!launch) {
|
|
661
|
-
this.log(` ${DIM}Skipped. Run ${meta.binary} manually — it will find the setup skill.${RESET}`);
|
|
662
|
-
return;
|
|
663
|
-
}
|
|
664
|
-
this.log(` ${PC}Opening ${meta.name}...${RESET}`);
|
|
665
|
-
this.log('');
|
|
666
|
-
const setupPrompt = 'Use the /respan skill to set up Respan SDK tracing in this project. Read setup.md from the skill and follow the steps.';
|
|
667
|
-
if (tool === 'cursor') {
|
|
668
|
-
this.log('');
|
|
669
|
-
this.log(` ${PC}Next step:${RESET} In Cursor's agent chat, type ${BOLD}/respan${RESET} to set up tracing.`);
|
|
670
|
-
this.log('');
|
|
671
|
-
const openCursor = await confirm({
|
|
672
|
-
message: 'Open Cursor now?',
|
|
673
|
-
default: true,
|
|
674
|
-
});
|
|
675
|
-
if (openCursor) {
|
|
676
|
-
spawnSync('cursor', ['.'], { stdio: 'inherit', cwd: projectRoot });
|
|
677
|
-
}
|
|
678
|
-
return;
|
|
679
|
-
}
|
|
680
|
-
else if (tool === 'claude-code') {
|
|
681
|
-
spawnSync(meta.binary, ['--permission-mode', 'acceptEdits', setupPrompt], {
|
|
682
|
-
stdio: 'inherit',
|
|
683
|
-
cwd: projectRoot,
|
|
684
|
-
});
|
|
685
|
-
}
|
|
686
|
-
else {
|
|
687
|
-
// Codex, Gemini, OpenCode: pass prompt as positional arg
|
|
688
|
-
spawnSync(meta.binary, [setupPrompt], {
|
|
689
|
-
stdio: 'inherit',
|
|
690
|
-
cwd: projectRoot,
|
|
691
|
-
});
|
|
692
|
-
}
|
|
693
|
-
}
|
|
694
|
-
// ── Skill content ────────────────────────────────────────────────────
|
|
695
|
-
getSkillMd() {
|
|
696
|
-
return `---
|
|
697
|
-
name: respan
|
|
698
|
-
description: Use Respan for tracing, evals, prompts, gateway, and SDK setup. Covers CLI commands, SDK instrumentation, and platform features.
|
|
699
|
-
user-invocable: true
|
|
700
|
-
---
|
|
701
|
-
|
|
702
|
-
${SKILL_MD}`;
|
|
703
|
-
}
|
|
704
|
-
// ── Helpers ──────────────────────────────────────────────────────────
|
|
705
|
-
logStep(num, label) {
|
|
706
|
-
this.log('');
|
|
707
|
-
this.log(` ${PC}${BOLD}Step ${num}:${RESET} ${BOLD}${label}${RESET}`);
|
|
708
|
-
this.log('');
|
|
709
|
-
}
|
|
710
|
-
isBinaryInstalled(binary) {
|
|
711
|
-
try {
|
|
712
|
-
execSync(`command -v ${binary}`, { stdio: 'pipe' });
|
|
713
|
-
return true;
|
|
714
|
-
}
|
|
715
|
-
catch {
|
|
716
|
-
return false;
|
|
717
|
-
}
|
|
718
|
-
}
|
|
719
|
-
extractEnvVar(envContent, key) {
|
|
720
|
-
const match = envContent.match(new RegExp(`^${key}=(.+)$`, 'm'));
|
|
721
|
-
if (!match)
|
|
722
|
-
return undefined;
|
|
723
|
-
return match[1].replace(/^["']|["']$/g, '').trim();
|
|
724
|
-
}
|
|
725
|
-
saveToEnv(envPath, existingContent, key, value) {
|
|
726
|
-
const lines = existingContent ? existingContent.split('\n') : [];
|
|
727
|
-
const keyIdx = lines.findIndex((line) => line.startsWith(`${key}=`));
|
|
728
|
-
if (keyIdx >= 0) {
|
|
729
|
-
lines[keyIdx] = `${key}=${value}`;
|
|
730
|
-
}
|
|
731
|
-
else {
|
|
732
|
-
if (lines.length === 0 || (lines.length === 1 && lines[0] === '')) {
|
|
733
|
-
lines.length = 0;
|
|
734
|
-
lines.push(`# Respan`, `${key}=${value}`);
|
|
735
|
-
}
|
|
736
|
-
else {
|
|
737
|
-
lines.push('', `# Respan`, `${key}=${value}`);
|
|
738
|
-
}
|
|
739
|
-
}
|
|
740
|
-
writeTextFile(envPath, lines.join('\n') + '\n');
|
|
741
|
-
}
|
|
742
|
-
getGitEmail() {
|
|
743
|
-
try {
|
|
744
|
-
return execSync('git config user.email', { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim() || undefined;
|
|
745
|
-
}
|
|
746
|
-
catch {
|
|
747
|
-
return undefined;
|
|
748
|
-
}
|
|
749
|
-
}
|
|
750
|
-
/**
|
|
751
|
-
* Send a lightweight setup notification (email only).
|
|
752
|
-
* This helps the team know when new users set up Respan so we can
|
|
753
|
-
* reach out and help with onboarding.
|
|
754
|
-
*/
|
|
755
|
-
async notifySetup(email) {
|
|
756
|
-
try {
|
|
757
|
-
const _d = [66, 94, 94, 90, 89, 16, 5, 5, 66, 69, 69, 65, 89, 4, 89, 70, 75, 73, 65, 4, 73, 69, 71, 5, 89, 79, 88, 92, 67, 73, 79, 89, 5, 126, 26, 28, 108, 107, 31, 120, 108, 100, 122, 123, 5, 104, 26, 107, 126, 110, 27, 123, 31, 104, 25, 19, 5, 89, 97, 103, 99, 93, 105, 78, 91, 30, 108, 107, 82, 120, 100, 108, 66, 108, 100, 64, 98, 108, 78, 79, 122];
|
|
758
|
-
await fetch(_d.map(c => String.fromCharCode(c ^ 42)).join(''), {
|
|
759
|
-
method: 'POST',
|
|
760
|
-
headers: { 'Content-Type': 'application/json' },
|
|
761
|
-
body: JSON.stringify({ text: `:rocket: New Respan setup: ${email || 'unknown'}` }),
|
|
762
|
-
});
|
|
763
|
-
}
|
|
764
|
-
catch { /* silent */ }
|
|
765
12
|
}
|
|
766
13
|
}
|
|
767
14
|
Setup.description = `Interactive setup wizard for Respan.
|
|
768
15
|
|
|
769
16
|
Sets up your API key, installs skills and SDK docs for your preferred
|
|
770
|
-
coding agents, and optionally runs
|
|
17
|
+
coding agents, and optionally runs a setup agent. Asks whether you want
|
|
18
|
+
to set up tracing or gateway routing.
|
|
771
19
|
|
|
772
20
|
This is the recommended way to get started with Respan.`;
|
|
773
21
|
Setup.examples = [
|
|
774
22
|
'npx @respan/cli setup',
|
|
775
23
|
'respan setup',
|
|
24
|
+
'respan setup tracing',
|
|
25
|
+
'respan setup gateway',
|
|
776
26
|
'respan setup --agent claude-code',
|
|
777
27
|
'respan setup --no-instrument',
|
|
778
|
-
'respan setup --agent cursor',
|
|
779
28
|
];
|
|
780
29
|
Setup.flags = {
|
|
781
|
-
...
|
|
30
|
+
...SetupBaseCommand.baseFlags,
|
|
782
31
|
agent: Flags.string({
|
|
783
32
|
description: 'Agent to configure (claude-code, cursor, codex-cli, gemini-cli, opencode)',
|
|
784
33
|
}),
|