@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
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { SetupBaseCommand } from '../../lib/setup-base-command.js';
|
|
2
|
+
export default class SetupTracing extends SetupBaseCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
agent: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
|
+
'no-instrument': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
8
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
9
|
+
csv: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
10
|
+
'api-key': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
'base-url': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
14
|
+
};
|
|
15
|
+
run(): Promise<void>;
|
|
16
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Flags } from '@oclif/core';
|
|
2
|
+
import { SetupBaseCommand } from '../../lib/setup-base-command.js';
|
|
3
|
+
class SetupTracing extends SetupBaseCommand {
|
|
4
|
+
async run() {
|
|
5
|
+
const { flags } = await this.parse(SetupTracing);
|
|
6
|
+
this.globalFlags = flags;
|
|
7
|
+
await this.runSetup('tracing', {
|
|
8
|
+
agent: flags.agent,
|
|
9
|
+
noInstrument: flags['no-instrument'],
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
SetupTracing.description = `Set up Respan SDK tracing in this project.
|
|
14
|
+
|
|
15
|
+
Runs the tracing setup flow directly (skips the "Tracing or Gateway?"
|
|
16
|
+
question). Sets up your API key, installs the Respan skill, and optionally
|
|
17
|
+
opens your coding agent to instrument the project.`;
|
|
18
|
+
SetupTracing.examples = [
|
|
19
|
+
'respan setup tracing',
|
|
20
|
+
'respan setup tracing --agent claude-code',
|
|
21
|
+
'respan setup tracing --no-instrument',
|
|
22
|
+
];
|
|
23
|
+
SetupTracing.flags = {
|
|
24
|
+
...SetupBaseCommand.baseFlags,
|
|
25
|
+
agent: Flags.string({
|
|
26
|
+
description: 'Agent to configure (claude-code, cursor, codex-cli, gemini-cli, opencode)',
|
|
27
|
+
}),
|
|
28
|
+
'no-instrument': Flags.boolean({
|
|
29
|
+
description: 'Skip opening the agent after setup',
|
|
30
|
+
default: false,
|
|
31
|
+
}),
|
|
32
|
+
};
|
|
33
|
+
export default SetupTracing;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type CliTool = 'claude-code' | 'cursor' | 'codex-cli' | 'gemini-cli' | 'opencode';
|
|
2
|
+
export interface ToolMeta {
|
|
3
|
+
name: string;
|
|
4
|
+
binary: string;
|
|
5
|
+
description: string;
|
|
6
|
+
configDirs: string[];
|
|
7
|
+
}
|
|
8
|
+
export declare const CLI_TOOLS: Record<CliTool, ToolMeta>;
|
|
9
|
+
export interface DetectionSignal {
|
|
10
|
+
tool: CliTool;
|
|
11
|
+
onPath: boolean;
|
|
12
|
+
hasConfigDir: boolean;
|
|
13
|
+
reason: string;
|
|
14
|
+
}
|
|
15
|
+
export declare function isBinaryInstalled(binary: string): boolean;
|
|
16
|
+
export declare function detectAgents(projectRoot: string, home: string): DetectionSignal[];
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// Coding-agent registry and detection primitives.
|
|
2
|
+
//
|
|
3
|
+
// This is the single source of truth for the agents Respan can configure.
|
|
4
|
+
// commands/setup/index.ts and commands/setup/doctor.ts previously maintained
|
|
5
|
+
// parallel copies of this metadata.
|
|
6
|
+
import * as fs from 'node:fs';
|
|
7
|
+
import * as path from 'node:path';
|
|
8
|
+
import { execSync } from 'node:child_process';
|
|
9
|
+
export const CLI_TOOLS = {
|
|
10
|
+
'claude-code': {
|
|
11
|
+
name: 'Claude Code',
|
|
12
|
+
binary: 'claude',
|
|
13
|
+
description: 'Anthropic\'s coding agent',
|
|
14
|
+
configDirs: ['~/.claude', '.claude'],
|
|
15
|
+
},
|
|
16
|
+
'cursor': {
|
|
17
|
+
name: 'Cursor',
|
|
18
|
+
binary: 'cursor',
|
|
19
|
+
description: 'AI-powered code editor',
|
|
20
|
+
configDirs: ['.cursor', '.cursorrc'],
|
|
21
|
+
},
|
|
22
|
+
'codex-cli': {
|
|
23
|
+
name: 'Codex CLI',
|
|
24
|
+
binary: 'codex',
|
|
25
|
+
description: 'OpenAI\'s coding agent',
|
|
26
|
+
configDirs: ['~/.codex', '.codex'],
|
|
27
|
+
},
|
|
28
|
+
'gemini-cli': {
|
|
29
|
+
name: 'Gemini CLI',
|
|
30
|
+
binary: 'gemini',
|
|
31
|
+
description: 'Google\'s coding agent',
|
|
32
|
+
configDirs: ['~/.gemini', '.gemini'],
|
|
33
|
+
},
|
|
34
|
+
'opencode': {
|
|
35
|
+
name: 'OpenCode',
|
|
36
|
+
binary: 'opencode',
|
|
37
|
+
description: 'Open-source coding agent',
|
|
38
|
+
configDirs: ['.opencode'],
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
export function isBinaryInstalled(binary) {
|
|
42
|
+
try {
|
|
43
|
+
execSync(`command -v ${binary}`, { stdio: 'pipe' });
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
export function detectAgents(projectRoot, home) {
|
|
51
|
+
const signals = [];
|
|
52
|
+
for (const [id, meta] of Object.entries(CLI_TOOLS)) {
|
|
53
|
+
const onPath = isBinaryInstalled(meta.binary);
|
|
54
|
+
const hasConfigDir = meta.configDirs.some((dir) => {
|
|
55
|
+
const resolved = dir.startsWith('~')
|
|
56
|
+
? path.join(home, dir.slice(1))
|
|
57
|
+
: dir.startsWith('.')
|
|
58
|
+
? path.join(projectRoot, dir)
|
|
59
|
+
: dir;
|
|
60
|
+
return fs.existsSync(resolved);
|
|
61
|
+
});
|
|
62
|
+
const reasons = [];
|
|
63
|
+
if (onPath)
|
|
64
|
+
reasons.push('binary on PATH');
|
|
65
|
+
if (hasConfigDir)
|
|
66
|
+
reasons.push('config directory found');
|
|
67
|
+
signals.push({
|
|
68
|
+
tool: id,
|
|
69
|
+
onPath,
|
|
70
|
+
hasConfigDir,
|
|
71
|
+
reason: reasons.length > 0 ? reasons.join(', ') : 'not detected',
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
return signals;
|
|
75
|
+
}
|
package/dist/lib/auth.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import * as path from 'node:path';
|
|
1
2
|
import { getCredential } from './config.js';
|
|
3
|
+
import { findProjectRoot, readTextFile, extractEnvVar } from './integrate.js';
|
|
2
4
|
export const DEFAULT_BASE_URL = 'https://api.respan.ai';
|
|
3
5
|
export const ENTERPRISE_BASE_URL = 'https://endpoint.respan.ai';
|
|
4
6
|
function normalizeBaseUrl(baseUrl) {
|
|
@@ -9,6 +11,19 @@ function resolveConfiguredBaseUrl(credential, flagBaseUrl) {
|
|
|
9
11
|
// the main persistent configuration path for CLI users.
|
|
10
12
|
return normalizeBaseUrl(flagBaseUrl || process.env.RESPAN_API_BASE_URL || credential?.baseUrl || DEFAULT_BASE_URL);
|
|
11
13
|
}
|
|
14
|
+
/**
|
|
15
|
+
* Read RESPAN_API_KEY from the project's `.env`. setup writes it there as the
|
|
16
|
+
* project source of truth, so in-project commands (e.g. `respan logs list`)
|
|
17
|
+
* work right after setup without a separate `respan auth login`.
|
|
18
|
+
*/
|
|
19
|
+
function readProjectEnvKey() {
|
|
20
|
+
try {
|
|
21
|
+
return extractEnvVar(readTextFile(path.join(findProjectRoot(), '.env')), 'RESPAN_API_KEY');
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
12
27
|
export function resolveAuth(flags) {
|
|
13
28
|
const credential = getCredential(flags.profile);
|
|
14
29
|
const baseUrl = resolveConfiguredBaseUrl(credential, flags['base-url']);
|
|
@@ -21,6 +36,15 @@ export function resolveAuth(flags) {
|
|
|
21
36
|
baseUrl,
|
|
22
37
|
};
|
|
23
38
|
}
|
|
39
|
+
// The project .env key wins over a saved credential — but an explicit
|
|
40
|
+
// --profile selects a specific credential (and its base URL), so don't let
|
|
41
|
+
// an ambient .env key override that choice and break enterprise setups.
|
|
42
|
+
if (!flags.profile) {
|
|
43
|
+
const projectEnvKey = readProjectEnvKey();
|
|
44
|
+
if (projectEnvKey) {
|
|
45
|
+
return { apiKey: projectEnvKey, baseUrl };
|
|
46
|
+
}
|
|
47
|
+
}
|
|
24
48
|
if (credential) {
|
|
25
49
|
return credentialToAuth(credential, baseUrl);
|
|
26
50
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare const PC = "\u001B[38;2;100;131;240m";
|
|
2
|
+
export declare const RESET = "\u001B[0m";
|
|
3
|
+
export declare const DIM = "\u001B[2m";
|
|
4
|
+
export declare const GREEN = "\u001B[32m";
|
|
5
|
+
export declare const BOLD = "\u001B[1m";
|
|
6
|
+
export declare const RED = "\u001B[31m";
|
|
7
|
+
export declare const YELLOW = "\u001B[33m";
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Shared ANSI color/style constants for setup-related commands.
|
|
2
|
+
// Consolidated from the copies previously inlined in commands/setup/index.ts
|
|
3
|
+
// and commands/setup/doctor.ts.
|
|
4
|
+
export const PC = '\x1b[38;2;100;131;240m';
|
|
5
|
+
export const RESET = '\x1b[0m';
|
|
6
|
+
export const DIM = '\x1b[2m';
|
|
7
|
+
export const GREEN = '\x1b[32m';
|
|
8
|
+
export const BOLD = '\x1b[1m';
|
|
9
|
+
export const RED = '\x1b[31m';
|
|
10
|
+
export const YELLOW = '\x1b[33m';
|
package/dist/lib/integrate.d.ts
CHANGED
|
@@ -51,6 +51,12 @@ export declare function readJsonFile(filePath: string): Record<string, unknown>;
|
|
|
51
51
|
export declare function writeJsonFile(filePath: string, data: Record<string, unknown>): void;
|
|
52
52
|
export declare function readTextFile(filePath: string): string;
|
|
53
53
|
export declare function writeTextFile(filePath: string, content: string): void;
|
|
54
|
+
/**
|
|
55
|
+
* Read a `KEY=value` line from .env-style content. Trims before stripping a
|
|
56
|
+
* single layer of surrounding quotes — the closing quote can sit inside
|
|
57
|
+
* trailing whitespace, so order matters. Returns undefined if absent or empty.
|
|
58
|
+
*/
|
|
59
|
+
export declare function extractEnvVar(content: string, key: string): string | undefined;
|
|
54
60
|
export declare function expandHome(p: string): string;
|
|
55
61
|
export declare function parseAttrs(raw: string): Record<string, string>;
|
|
56
62
|
/**
|
package/dist/lib/integrate.js
CHANGED
|
@@ -148,6 +148,17 @@ export function writeTextFile(filePath, content) {
|
|
|
148
148
|
ensureDir(path.dirname(filePath));
|
|
149
149
|
fs.writeFileSync(filePath, content);
|
|
150
150
|
}
|
|
151
|
+
/**
|
|
152
|
+
* Read a `KEY=value` line from .env-style content. Trims before stripping a
|
|
153
|
+
* single layer of surrounding quotes — the closing quote can sit inside
|
|
154
|
+
* trailing whitespace, so order matters. Returns undefined if absent or empty.
|
|
155
|
+
*/
|
|
156
|
+
export function extractEnvVar(content, key) {
|
|
157
|
+
const match = content.match(new RegExp(`^${key}=(.+)$`, 'm'));
|
|
158
|
+
if (!match)
|
|
159
|
+
return undefined;
|
|
160
|
+
return match[1].trim().replace(/^["']|["']$/g, '').trim() || undefined;
|
|
161
|
+
}
|
|
151
162
|
export function expandHome(p) {
|
|
152
163
|
if (p.startsWith('~')) {
|
|
153
164
|
return path.join(os.homedir(), p.slice(1));
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { BaseCommand } from './base-command.js';
|
|
2
|
+
import { CliTool, DetectionSignal } from './agents.js';
|
|
3
|
+
export type SetupMode = 'tracing' | 'gateway';
|
|
4
|
+
interface RunSetupOptions {
|
|
5
|
+
agent?: string;
|
|
6
|
+
noInstrument?: boolean;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Intermediate base class for the `respan setup` family of commands
|
|
10
|
+
* (`setup`, `setup tracing`, `setup gateway`).
|
|
11
|
+
*
|
|
12
|
+
* It deliberately sits between the concrete commands and {@link BaseCommand}
|
|
13
|
+
* so that setup-only concerns (interactive prompts, the agent registry, and
|
|
14
|
+
* multi-kilobyte skill content) never leak onto the root command class that
|
|
15
|
+
* every other command extends.
|
|
16
|
+
*/
|
|
17
|
+
export declare abstract class SetupBaseCommand extends BaseCommand {
|
|
18
|
+
static baseFlags: {
|
|
19
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
20
|
+
csv: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
21
|
+
'api-key': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
22
|
+
'base-url': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
23
|
+
profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
24
|
+
verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Shared setup flow for all three entry points.
|
|
28
|
+
*
|
|
29
|
+
* 1. askApiKey() // .env → global credential → prompt
|
|
30
|
+
* 2. if mode is undefined → ask "Tracing or Gateway?" (interactive only)
|
|
31
|
+
* 3. verifyApiKey(mode) // both re-prompt on a rejected key; can pause setup
|
|
32
|
+
* 4. selectAgent() // drives only the launch
|
|
33
|
+
* 5. installSkill() // full bundle, for all agents, regardless of selection
|
|
34
|
+
* 6. launchAgent(mode) // only if an agent was selected
|
|
35
|
+
*/
|
|
36
|
+
protected runSetup(mode?: SetupMode, opts?: RunSetupOptions): Promise<void>;
|
|
37
|
+
protected askMode(step: number): Promise<SetupMode>;
|
|
38
|
+
protected askApiKey(projectRoot: string): Promise<string>;
|
|
39
|
+
/**
|
|
40
|
+
* Verify the key for the chosen mode. Both paths re-prompt on a rejected key
|
|
41
|
+
* and return false if the user abandons the check, so the caller can pause
|
|
42
|
+
* setup instead of reporting success with a key that doesn't work.
|
|
43
|
+
*/
|
|
44
|
+
protected verifyApiKey(apiKey: string, mode: SetupMode, projectRoot: string): Promise<boolean>;
|
|
45
|
+
/** Prompt for a replacement key and persist it to the project .env. */
|
|
46
|
+
private reenterApiKey;
|
|
47
|
+
private verifyTracingKey;
|
|
48
|
+
/**
|
|
49
|
+
* Gateway verification gates on a real request succeeding: there is no
|
|
50
|
+
* credit-balance endpoint, so a 200 is the only proof of a valid key with a
|
|
51
|
+
* positive balance. A rejected key (401/403) re-prompts; any other failure
|
|
52
|
+
* loops on the user's say-so. Returns true on success, false if abandoned.
|
|
53
|
+
*/
|
|
54
|
+
private verifyGatewayKey;
|
|
55
|
+
/**
|
|
56
|
+
* Build a rich demo OTLP trace that mimics a customer support agent workflow:
|
|
57
|
+
* workflow: customer_support_pipeline
|
|
58
|
+
* └── agent: support_agent
|
|
59
|
+
* ├── tool: lookup_order
|
|
60
|
+
* ├── chat: openai.chat (classify intent)
|
|
61
|
+
* ├── tool: process_refund
|
|
62
|
+
* ├── chat: openai.chat (generate response)
|
|
63
|
+
* └── task: log_resolution
|
|
64
|
+
*/
|
|
65
|
+
protected buildDemoTrace(email?: string): Record<string, unknown>;
|
|
66
|
+
/**
|
|
67
|
+
* Minimal chat completion sent through the gateway to prove auth + routing
|
|
68
|
+
* and drop a real logged request into the user's account.
|
|
69
|
+
*/
|
|
70
|
+
protected buildDemoCompletion(): Record<string, unknown>;
|
|
71
|
+
protected selectAgent(flagAgent: CliTool | undefined, detected: DetectionSignal[]): Promise<CliTool | null>;
|
|
72
|
+
protected installSkill(): Promise<void>;
|
|
73
|
+
protected launchAgent(tool: CliTool, projectRoot: string, mode: SetupMode): Promise<void>;
|
|
74
|
+
protected logStep(num: number, label: string): void;
|
|
75
|
+
protected saveToEnv(envPath: string, existingContent: string, key: string, value: string): void;
|
|
76
|
+
protected getGitEmail(): string | undefined;
|
|
77
|
+
/**
|
|
78
|
+
* Send a lightweight setup notification (email only).
|
|
79
|
+
* This helps the team know when new users set up Respan so we can
|
|
80
|
+
* reach out and help with onboarding.
|
|
81
|
+
*/
|
|
82
|
+
protected notifySetup(email?: string): Promise<void>;
|
|
83
|
+
}
|
|
84
|
+
export {};
|