@respan/cli 0.3.3 → 0.4.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/assets/assets/hook.py +919 -0
- package/dist/assets/hook.py +919 -0
- package/dist/commands/integrate/claude-code.d.ts +20 -0
- package/dist/commands/integrate/claude-code.js +167 -0
- package/dist/commands/integrate/codex-cli.d.ts +20 -0
- package/dist/commands/integrate/codex-cli.js +97 -0
- package/dist/commands/integrate/gemini-cli.d.ts +20 -0
- package/dist/commands/integrate/gemini-cli.js +90 -0
- package/dist/commands/integrate/opencode.d.ts +20 -0
- package/dist/commands/integrate/opencode.js +100 -0
- package/dist/lib/integrate.d.ts +51 -0
- package/dist/lib/integrate.js +153 -0
- package/oclif.manifest.json +619 -178
- package/package.json +2 -2
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import * as os from 'node:os';
|
|
4
|
+
import { execSync } from 'node:child_process';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import { Flags } from '@oclif/core';
|
|
7
|
+
// ── Defaults ──────────────────────────────────────────────────────────────
|
|
8
|
+
/** Default Respan API base (SaaS). Enterprise users override this. */
|
|
9
|
+
export const DEFAULT_BASE_URL = 'https://api.respan.ai/api';
|
|
10
|
+
// ── Shared flags ──────────────────────────────────────────────────────────
|
|
11
|
+
export const integrateFlags = {
|
|
12
|
+
local: Flags.boolean({
|
|
13
|
+
description: 'Write per-project config (default)',
|
|
14
|
+
default: false,
|
|
15
|
+
exclusive: ['global'],
|
|
16
|
+
}),
|
|
17
|
+
global: Flags.boolean({
|
|
18
|
+
description: 'Write user-level global config',
|
|
19
|
+
default: false,
|
|
20
|
+
exclusive: ['local'],
|
|
21
|
+
}),
|
|
22
|
+
'project-id': Flags.string({
|
|
23
|
+
description: 'Respan project ID (added to metadata / resource attributes)',
|
|
24
|
+
env: 'RESPAN_PROJECT_ID',
|
|
25
|
+
}),
|
|
26
|
+
'base-url': Flags.string({
|
|
27
|
+
description: 'Respan API base URL (for enterprise deployments)',
|
|
28
|
+
default: DEFAULT_BASE_URL,
|
|
29
|
+
}),
|
|
30
|
+
attrs: Flags.string({
|
|
31
|
+
description: "Custom attributes JSON (e.g. '{\"env\":\"prod\"}')",
|
|
32
|
+
default: '{}',
|
|
33
|
+
}),
|
|
34
|
+
'dry-run': Flags.boolean({
|
|
35
|
+
description: 'Preview changes without writing files',
|
|
36
|
+
default: false,
|
|
37
|
+
}),
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Resolve whether to write local, global, or both configs.
|
|
41
|
+
*
|
|
42
|
+
* - `--global` → 'global'
|
|
43
|
+
* - `--local` → 'local'
|
|
44
|
+
* - neither → tool-specific default (passed by each command)
|
|
45
|
+
*/
|
|
46
|
+
export function resolveScope(flags, defaultScope = 'local') {
|
|
47
|
+
if (flags.global)
|
|
48
|
+
return 'global';
|
|
49
|
+
if (flags.local)
|
|
50
|
+
return 'local';
|
|
51
|
+
return defaultScope;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Find the project root (git root, or cwd if not in a repo).
|
|
55
|
+
*/
|
|
56
|
+
export function findProjectRoot() {
|
|
57
|
+
try {
|
|
58
|
+
return execSync('git rev-parse --show-toplevel', {
|
|
59
|
+
encoding: 'utf-8',
|
|
60
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
61
|
+
}).trim();
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return process.cwd();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// ── Hook script ───────────────────────────────────────────────────────────
|
|
68
|
+
/**
|
|
69
|
+
* Return the bundled Claude Code hook script contents.
|
|
70
|
+
*
|
|
71
|
+
* The .py file lives in src/assets/ and is copied to dist/assets/ during
|
|
72
|
+
* build. Reading at runtime keeps the hook version-locked to the CLI
|
|
73
|
+
* version — upgrading the CLI and re-running integrate updates the hook.
|
|
74
|
+
*/
|
|
75
|
+
export function getHookScript() {
|
|
76
|
+
const dir = path.dirname(fileURLToPath(import.meta.url));
|
|
77
|
+
const hookPath = path.join(dir, '..', 'assets', 'hook.py');
|
|
78
|
+
return fs.readFileSync(hookPath, 'utf-8');
|
|
79
|
+
}
|
|
80
|
+
// ── Utilities ─────────────────────────────────────────────────────────────
|
|
81
|
+
/**
|
|
82
|
+
* Deep merge source into target.
|
|
83
|
+
* Objects are recursively merged; arrays and primitives from source overwrite.
|
|
84
|
+
*/
|
|
85
|
+
export function deepMerge(target, source) {
|
|
86
|
+
const result = { ...target };
|
|
87
|
+
for (const key of Object.keys(source)) {
|
|
88
|
+
const sv = source[key];
|
|
89
|
+
const tv = target[key];
|
|
90
|
+
if (sv && typeof sv === 'object' && !Array.isArray(sv) &&
|
|
91
|
+
tv && typeof tv === 'object' && !Array.isArray(tv)) {
|
|
92
|
+
result[key] = deepMerge(tv, sv);
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
result[key] = sv;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return result;
|
|
99
|
+
}
|
|
100
|
+
export function ensureDir(dirPath) {
|
|
101
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
102
|
+
}
|
|
103
|
+
export function readJsonFile(filePath) {
|
|
104
|
+
try {
|
|
105
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
return {};
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
export function writeJsonFile(filePath, data) {
|
|
112
|
+
ensureDir(path.dirname(filePath));
|
|
113
|
+
fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + '\n');
|
|
114
|
+
}
|
|
115
|
+
export function readTextFile(filePath) {
|
|
116
|
+
try {
|
|
117
|
+
return fs.readFileSync(filePath, 'utf-8');
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
return '';
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
export function writeTextFile(filePath, content) {
|
|
124
|
+
ensureDir(path.dirname(filePath));
|
|
125
|
+
fs.writeFileSync(filePath, content);
|
|
126
|
+
}
|
|
127
|
+
export function expandHome(p) {
|
|
128
|
+
if (p.startsWith('~')) {
|
|
129
|
+
return path.join(os.homedir(), p.slice(1));
|
|
130
|
+
}
|
|
131
|
+
return p;
|
|
132
|
+
}
|
|
133
|
+
export function parseAttrs(raw) {
|
|
134
|
+
try {
|
|
135
|
+
const parsed = JSON.parse(raw);
|
|
136
|
+
if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {
|
|
137
|
+
return parsed;
|
|
138
|
+
}
|
|
139
|
+
return {};
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
throw new Error(`Invalid JSON for --attrs: ${raw}`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Convert a Record to OTel resource attributes string.
|
|
147
|
+
* Format: key1=value1,key2=value2
|
|
148
|
+
*/
|
|
149
|
+
export function toOtelResourceAttrs(attrs) {
|
|
150
|
+
return Object.entries(attrs)
|
|
151
|
+
.map(([k, v]) => `${k}=${v}`)
|
|
152
|
+
.join(',');
|
|
153
|
+
}
|