@respan/cli 0.17.1 → 0.17.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.
- package/dist/hooks/claude-code.cjs +69 -46
- package/dist/hooks/codex-cli.cjs +68 -45
- package/dist/hooks/gemini-cli.cjs +67 -44
- package/dist/hooks/shared.js +3 -0
- package/dist/lib/config.d.ts +1 -0
- package/dist/lib/config.js +35 -7
- package/oclif.manifest.json +130 -130
- package/package.json +1 -1
|
@@ -23,15 +23,37 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
23
23
|
));
|
|
24
24
|
|
|
25
25
|
// src/hooks/claude-code.ts
|
|
26
|
+
var fs3 = __toESM(require("node:fs"), 1);
|
|
27
|
+
var os3 = __toESM(require("node:os"), 1);
|
|
28
|
+
var path3 = __toESM(require("node:path"), 1);
|
|
29
|
+
var import_node_child_process = require("node:child_process");
|
|
30
|
+
|
|
31
|
+
// src/hooks/shared.ts
|
|
26
32
|
var fs2 = __toESM(require("node:fs"), 1);
|
|
27
33
|
var os2 = __toESM(require("node:os"), 1);
|
|
28
34
|
var path2 = __toESM(require("node:path"), 1);
|
|
29
|
-
var import_node_child_process = require("node:child_process");
|
|
30
35
|
|
|
31
|
-
// src/
|
|
36
|
+
// src/lib/config.ts
|
|
32
37
|
var fs = __toESM(require("node:fs"), 1);
|
|
33
|
-
var os = __toESM(require("node:os"), 1);
|
|
34
38
|
var path = __toESM(require("node:path"), 1);
|
|
39
|
+
var os = __toESM(require("node:os"), 1);
|
|
40
|
+
var CONFIG_DIR = path.join(os.homedir(), ".respan");
|
|
41
|
+
var CREDENTIALS_FILE = path.join(CONFIG_DIR, "credentials.json");
|
|
42
|
+
var CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
|
|
43
|
+
function restrictToOwner(target) {
|
|
44
|
+
if (process.platform === "win32") return;
|
|
45
|
+
try {
|
|
46
|
+
const mode = fs.statSync(target).mode & 511;
|
|
47
|
+
if (mode & 63) fs.chmodSync(target, mode & 448);
|
|
48
|
+
} catch {
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function tightenCredentialPermissions() {
|
|
52
|
+
restrictToOwner(CONFIG_DIR);
|
|
53
|
+
restrictToOwner(CREDENTIALS_FILE);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// src/hooks/shared.ts
|
|
35
57
|
function resolveTracesEndpoint(baseUrl) {
|
|
36
58
|
const DEFAULT = "https://api.respan.ai/api/v2/traces";
|
|
37
59
|
if (!baseUrl) return DEFAULT;
|
|
@@ -44,13 +66,13 @@ var _debug = false;
|
|
|
44
66
|
function initLogging(logFile, debug2) {
|
|
45
67
|
_logFile = logFile;
|
|
46
68
|
_debug = debug2;
|
|
47
|
-
const dir =
|
|
48
|
-
|
|
69
|
+
const dir = path2.dirname(logFile);
|
|
70
|
+
fs2.mkdirSync(dir, { recursive: true });
|
|
49
71
|
}
|
|
50
72
|
function log(level, message) {
|
|
51
73
|
if (!_logFile) return;
|
|
52
74
|
const ts = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19);
|
|
53
|
-
|
|
75
|
+
fs2.appendFileSync(_logFile, `${ts} [${level}] ${message}
|
|
54
76
|
`);
|
|
55
77
|
}
|
|
56
78
|
function debug(message) {
|
|
@@ -61,14 +83,15 @@ function resolveCredentials() {
|
|
|
61
83
|
let apiKey = process.env.RESPAN_API_KEY ?? "";
|
|
62
84
|
let baseUrl = process.env.RESPAN_BASE_URL ?? DEFAULT_BASE_URL;
|
|
63
85
|
if (!apiKey) {
|
|
64
|
-
const credsFile =
|
|
65
|
-
if (
|
|
86
|
+
const credsFile = path2.join(os2.homedir(), ".respan", "credentials.json");
|
|
87
|
+
if (fs2.existsSync(credsFile)) {
|
|
88
|
+
tightenCredentialPermissions();
|
|
66
89
|
try {
|
|
67
|
-
const creds = JSON.parse(
|
|
68
|
-
const configFile =
|
|
90
|
+
const creds = JSON.parse(fs2.readFileSync(credsFile, "utf-8"));
|
|
91
|
+
const configFile = path2.join(os2.homedir(), ".respan", "config.json");
|
|
69
92
|
let profile = "default";
|
|
70
|
-
if (
|
|
71
|
-
const cfg = JSON.parse(
|
|
93
|
+
if (fs2.existsSync(configFile)) {
|
|
94
|
+
const cfg = JSON.parse(fs2.readFileSync(configFile, "utf-8"));
|
|
72
95
|
profile = cfg.activeProfile ?? "default";
|
|
73
96
|
}
|
|
74
97
|
const cred = creds[profile] ?? {};
|
|
@@ -98,11 +121,11 @@ var KNOWN_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
|
98
121
|
"project_id"
|
|
99
122
|
]);
|
|
100
123
|
function loadRespanConfig(configPath) {
|
|
101
|
-
if (!
|
|
124
|
+
if (!fs2.existsSync(configPath)) {
|
|
102
125
|
return { fields: {}, properties: {} };
|
|
103
126
|
}
|
|
104
127
|
try {
|
|
105
|
-
const raw = JSON.parse(
|
|
128
|
+
const raw = JSON.parse(fs2.readFileSync(configPath, "utf-8"));
|
|
106
129
|
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
107
130
|
return { fields: {}, properties: {} };
|
|
108
131
|
}
|
|
@@ -122,36 +145,36 @@ function loadRespanConfig(configPath) {
|
|
|
122
145
|
}
|
|
123
146
|
}
|
|
124
147
|
function loadState(statePath) {
|
|
125
|
-
if (!
|
|
148
|
+
if (!fs2.existsSync(statePath)) return {};
|
|
126
149
|
try {
|
|
127
|
-
return JSON.parse(
|
|
150
|
+
return JSON.parse(fs2.readFileSync(statePath, "utf-8"));
|
|
128
151
|
} catch {
|
|
129
152
|
return {};
|
|
130
153
|
}
|
|
131
154
|
}
|
|
132
155
|
function saveState(statePath, state) {
|
|
133
|
-
const dir =
|
|
134
|
-
|
|
156
|
+
const dir = path2.dirname(statePath);
|
|
157
|
+
fs2.mkdirSync(dir, { recursive: true });
|
|
135
158
|
const tmpPath = statePath + ".tmp." + process.pid;
|
|
136
159
|
try {
|
|
137
|
-
|
|
138
|
-
|
|
160
|
+
fs2.writeFileSync(tmpPath, JSON.stringify(state, null, 2));
|
|
161
|
+
fs2.renameSync(tmpPath, statePath);
|
|
139
162
|
} catch (e) {
|
|
140
163
|
try {
|
|
141
|
-
|
|
164
|
+
fs2.unlinkSync(tmpPath);
|
|
142
165
|
} catch {
|
|
143
166
|
}
|
|
144
|
-
|
|
167
|
+
fs2.writeFileSync(statePath, JSON.stringify(state, null, 2));
|
|
145
168
|
}
|
|
146
169
|
}
|
|
147
170
|
function acquireLock(lockPath, timeoutMs = 5e3) {
|
|
148
171
|
const deadline = Date.now() + timeoutMs;
|
|
149
172
|
while (Date.now() < deadline) {
|
|
150
173
|
try {
|
|
151
|
-
|
|
174
|
+
fs2.mkdirSync(lockPath);
|
|
152
175
|
return () => {
|
|
153
176
|
try {
|
|
154
|
-
|
|
177
|
+
fs2.rmdirSync(lockPath);
|
|
155
178
|
} catch {
|
|
156
179
|
}
|
|
157
180
|
};
|
|
@@ -360,9 +383,9 @@ async function sendSpans(spans, apiKey, baseUrl, context) {
|
|
|
360
383
|
const spanNames = spans.map((s) => s.span_name);
|
|
361
384
|
debug(`Sending ${spans.length} spans (${body.length} bytes) to ${url} for ${context}: ${spanNames.join(", ")}`);
|
|
362
385
|
if (_debug) {
|
|
363
|
-
const debugDir = _logFile ?
|
|
364
|
-
const debugFile =
|
|
365
|
-
|
|
386
|
+
const debugDir = _logFile ? path2.dirname(_logFile) : os2.tmpdir();
|
|
387
|
+
const debugFile = path2.join(debugDir, `respan_spans_${context.replace(/\s+/g, "_")}.json`);
|
|
388
|
+
fs2.writeFileSync(debugFile, body);
|
|
366
389
|
debug(`Dumped OTLP payload to ${debugFile}`);
|
|
367
390
|
}
|
|
368
391
|
for (let attempt = 0; attempt < 3; attempt++) {
|
|
@@ -403,10 +426,10 @@ function sleep(ms) {
|
|
|
403
426
|
}
|
|
404
427
|
|
|
405
428
|
// src/hooks/claude-code.ts
|
|
406
|
-
var STATE_DIR =
|
|
407
|
-
var LOG_FILE =
|
|
408
|
-
var STATE_FILE =
|
|
409
|
-
var LOCK_PATH =
|
|
429
|
+
var STATE_DIR = path3.join(os3.homedir(), ".claude", "state");
|
|
430
|
+
var LOG_FILE = path3.join(STATE_DIR, "respan_hook.log");
|
|
431
|
+
var STATE_FILE = path3.join(STATE_DIR, "respan_state.json");
|
|
432
|
+
var LOCK_PATH = path3.join(STATE_DIR, "respan_hook.lock");
|
|
410
433
|
var DEBUG_MODE = (process.env.CC_RESPAN_DEBUG ?? "").toLowerCase() === "true";
|
|
411
434
|
var MAX_CHARS = parseInt(process.env.CC_RESPAN_MAX_CHARS ?? "4000", 10) || 4e3;
|
|
412
435
|
initLogging(LOG_FILE, DEBUG_MODE);
|
|
@@ -730,7 +753,7 @@ function processTranscript(sessionId, transcriptFile, state, apiKey, baseUrl, co
|
|
|
730
753
|
const sessionState = state[sessionId] ?? {};
|
|
731
754
|
const lastLine = Number(sessionState.last_line ?? 0);
|
|
732
755
|
const turnCount = Number(sessionState.turn_count ?? 0);
|
|
733
|
-
const content =
|
|
756
|
+
const content = fs3.readFileSync(transcriptFile, "utf-8");
|
|
734
757
|
const lines = content.trim().split("\n");
|
|
735
758
|
const totalLines = lines.length;
|
|
736
759
|
if (lastLine >= totalLines) {
|
|
@@ -831,13 +854,13 @@ function processTranscript(sessionId, transcriptFile, state, apiKey, baseUrl, co
|
|
|
831
854
|
function readStdinPayload() {
|
|
832
855
|
if (process.stdin.isTTY) return null;
|
|
833
856
|
try {
|
|
834
|
-
const raw =
|
|
857
|
+
const raw = fs3.readFileSync(0, "utf-8");
|
|
835
858
|
if (!raw.trim()) return null;
|
|
836
859
|
const payload = JSON.parse(raw);
|
|
837
860
|
const sessionId = String(payload.session_id ?? "");
|
|
838
861
|
const transcriptPath = String(payload.transcript_path ?? "");
|
|
839
862
|
if (!sessionId || !transcriptPath) return null;
|
|
840
|
-
if (!
|
|
863
|
+
if (!fs3.existsSync(transcriptPath)) return null;
|
|
841
864
|
debug(`Got transcript from stdin: session=${sessionId}, path=${transcriptPath}`);
|
|
842
865
|
return { sessionId, transcriptPath };
|
|
843
866
|
} catch {
|
|
@@ -845,17 +868,17 @@ function readStdinPayload() {
|
|
|
845
868
|
}
|
|
846
869
|
}
|
|
847
870
|
function findLatestTranscript() {
|
|
848
|
-
const projectsDir =
|
|
849
|
-
if (!
|
|
871
|
+
const projectsDir = path3.join(os3.homedir(), ".claude", "projects");
|
|
872
|
+
if (!fs3.existsSync(projectsDir)) return null;
|
|
850
873
|
let latestFile = null;
|
|
851
874
|
let latestMtime = 0;
|
|
852
|
-
for (const projEntry of
|
|
853
|
-
const projDir =
|
|
854
|
-
if (!
|
|
855
|
-
for (const file of
|
|
875
|
+
for (const projEntry of fs3.readdirSync(projectsDir)) {
|
|
876
|
+
const projDir = path3.join(projectsDir, projEntry);
|
|
877
|
+
if (!fs3.statSync(projDir).isDirectory()) continue;
|
|
878
|
+
for (const file of fs3.readdirSync(projDir)) {
|
|
856
879
|
if (!file.endsWith(".jsonl")) continue;
|
|
857
|
-
const full =
|
|
858
|
-
const mtime =
|
|
880
|
+
const full = path3.join(projDir, file);
|
|
881
|
+
const mtime = fs3.statSync(full).mtimeMs;
|
|
859
882
|
if (mtime > latestMtime) {
|
|
860
883
|
latestMtime = mtime;
|
|
861
884
|
latestFile = full;
|
|
@@ -864,10 +887,10 @@ function findLatestTranscript() {
|
|
|
864
887
|
}
|
|
865
888
|
if (!latestFile) return null;
|
|
866
889
|
try {
|
|
867
|
-
const firstLine =
|
|
890
|
+
const firstLine = fs3.readFileSync(latestFile, "utf-8").split("\n")[0];
|
|
868
891
|
if (!firstLine) return null;
|
|
869
892
|
const firstMsg = JSON.parse(firstLine);
|
|
870
|
-
const sessionId = String(firstMsg.sessionId ??
|
|
893
|
+
const sessionId = String(firstMsg.sessionId ?? path3.basename(latestFile, ".jsonl"));
|
|
871
894
|
return { sessionId, transcriptPath: latestFile };
|
|
872
895
|
} catch {
|
|
873
896
|
return null;
|
|
@@ -886,7 +909,7 @@ async function mainWorker() {
|
|
|
886
909
|
debug(`Processing session: ${sessionId}`);
|
|
887
910
|
let config = null;
|
|
888
911
|
try {
|
|
889
|
-
const content =
|
|
912
|
+
const content = fs3.readFileSync(transcriptPath, "utf-8");
|
|
890
913
|
const lines = content.split("\n");
|
|
891
914
|
let cwd = "";
|
|
892
915
|
for (const line of lines.slice(0, 5)) {
|
|
@@ -901,7 +924,7 @@ async function mainWorker() {
|
|
|
901
924
|
}
|
|
902
925
|
}
|
|
903
926
|
if (cwd) {
|
|
904
|
-
config = loadRespanConfig(
|
|
927
|
+
config = loadRespanConfig(path3.join(cwd, ".claude", "respan.json"));
|
|
905
928
|
}
|
|
906
929
|
} catch {
|
|
907
930
|
}
|
package/dist/hooks/codex-cli.cjs
CHANGED
|
@@ -23,15 +23,37 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
23
23
|
));
|
|
24
24
|
|
|
25
25
|
// src/hooks/codex-cli.ts
|
|
26
|
+
var fs3 = __toESM(require("node:fs"), 1);
|
|
27
|
+
var os3 = __toESM(require("node:os"), 1);
|
|
28
|
+
var path3 = __toESM(require("node:path"), 1);
|
|
29
|
+
var import_node_child_process = require("node:child_process");
|
|
30
|
+
|
|
31
|
+
// src/hooks/shared.ts
|
|
26
32
|
var fs2 = __toESM(require("node:fs"), 1);
|
|
27
33
|
var os2 = __toESM(require("node:os"), 1);
|
|
28
34
|
var path2 = __toESM(require("node:path"), 1);
|
|
29
|
-
var import_node_child_process = require("node:child_process");
|
|
30
35
|
|
|
31
|
-
// src/
|
|
36
|
+
// src/lib/config.ts
|
|
32
37
|
var fs = __toESM(require("node:fs"), 1);
|
|
33
|
-
var os = __toESM(require("node:os"), 1);
|
|
34
38
|
var path = __toESM(require("node:path"), 1);
|
|
39
|
+
var os = __toESM(require("node:os"), 1);
|
|
40
|
+
var CONFIG_DIR = path.join(os.homedir(), ".respan");
|
|
41
|
+
var CREDENTIALS_FILE = path.join(CONFIG_DIR, "credentials.json");
|
|
42
|
+
var CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
|
|
43
|
+
function restrictToOwner(target) {
|
|
44
|
+
if (process.platform === "win32") return;
|
|
45
|
+
try {
|
|
46
|
+
const mode = fs.statSync(target).mode & 511;
|
|
47
|
+
if (mode & 63) fs.chmodSync(target, mode & 448);
|
|
48
|
+
} catch {
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function tightenCredentialPermissions() {
|
|
52
|
+
restrictToOwner(CONFIG_DIR);
|
|
53
|
+
restrictToOwner(CREDENTIALS_FILE);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// src/hooks/shared.ts
|
|
35
57
|
function resolveTracesEndpoint(baseUrl) {
|
|
36
58
|
const DEFAULT = "https://api.respan.ai/api/v2/traces";
|
|
37
59
|
if (!baseUrl) return DEFAULT;
|
|
@@ -44,13 +66,13 @@ var _debug = false;
|
|
|
44
66
|
function initLogging(logFile, debug2) {
|
|
45
67
|
_logFile = logFile;
|
|
46
68
|
_debug = debug2;
|
|
47
|
-
const dir =
|
|
48
|
-
|
|
69
|
+
const dir = path2.dirname(logFile);
|
|
70
|
+
fs2.mkdirSync(dir, { recursive: true });
|
|
49
71
|
}
|
|
50
72
|
function log(level, message) {
|
|
51
73
|
if (!_logFile) return;
|
|
52
74
|
const ts = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19);
|
|
53
|
-
|
|
75
|
+
fs2.appendFileSync(_logFile, `${ts} [${level}] ${message}
|
|
54
76
|
`);
|
|
55
77
|
}
|
|
56
78
|
function debug(message) {
|
|
@@ -61,14 +83,15 @@ function resolveCredentials() {
|
|
|
61
83
|
let apiKey = process.env.RESPAN_API_KEY ?? "";
|
|
62
84
|
let baseUrl = process.env.RESPAN_BASE_URL ?? DEFAULT_BASE_URL;
|
|
63
85
|
if (!apiKey) {
|
|
64
|
-
const credsFile =
|
|
65
|
-
if (
|
|
86
|
+
const credsFile = path2.join(os2.homedir(), ".respan", "credentials.json");
|
|
87
|
+
if (fs2.existsSync(credsFile)) {
|
|
88
|
+
tightenCredentialPermissions();
|
|
66
89
|
try {
|
|
67
|
-
const creds = JSON.parse(
|
|
68
|
-
const configFile =
|
|
90
|
+
const creds = JSON.parse(fs2.readFileSync(credsFile, "utf-8"));
|
|
91
|
+
const configFile = path2.join(os2.homedir(), ".respan", "config.json");
|
|
69
92
|
let profile = "default";
|
|
70
|
-
if (
|
|
71
|
-
const cfg = JSON.parse(
|
|
93
|
+
if (fs2.existsSync(configFile)) {
|
|
94
|
+
const cfg = JSON.parse(fs2.readFileSync(configFile, "utf-8"));
|
|
72
95
|
profile = cfg.activeProfile ?? "default";
|
|
73
96
|
}
|
|
74
97
|
const cred = creds[profile] ?? {};
|
|
@@ -98,11 +121,11 @@ var KNOWN_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
|
98
121
|
"project_id"
|
|
99
122
|
]);
|
|
100
123
|
function loadRespanConfig(configPath) {
|
|
101
|
-
if (!
|
|
124
|
+
if (!fs2.existsSync(configPath)) {
|
|
102
125
|
return { fields: {}, properties: {} };
|
|
103
126
|
}
|
|
104
127
|
try {
|
|
105
|
-
const raw = JSON.parse(
|
|
128
|
+
const raw = JSON.parse(fs2.readFileSync(configPath, "utf-8"));
|
|
106
129
|
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
107
130
|
return { fields: {}, properties: {} };
|
|
108
131
|
}
|
|
@@ -122,36 +145,36 @@ function loadRespanConfig(configPath) {
|
|
|
122
145
|
}
|
|
123
146
|
}
|
|
124
147
|
function loadState(statePath) {
|
|
125
|
-
if (!
|
|
148
|
+
if (!fs2.existsSync(statePath)) return {};
|
|
126
149
|
try {
|
|
127
|
-
return JSON.parse(
|
|
150
|
+
return JSON.parse(fs2.readFileSync(statePath, "utf-8"));
|
|
128
151
|
} catch {
|
|
129
152
|
return {};
|
|
130
153
|
}
|
|
131
154
|
}
|
|
132
155
|
function saveState(statePath, state) {
|
|
133
|
-
const dir =
|
|
134
|
-
|
|
156
|
+
const dir = path2.dirname(statePath);
|
|
157
|
+
fs2.mkdirSync(dir, { recursive: true });
|
|
135
158
|
const tmpPath = statePath + ".tmp." + process.pid;
|
|
136
159
|
try {
|
|
137
|
-
|
|
138
|
-
|
|
160
|
+
fs2.writeFileSync(tmpPath, JSON.stringify(state, null, 2));
|
|
161
|
+
fs2.renameSync(tmpPath, statePath);
|
|
139
162
|
} catch (e) {
|
|
140
163
|
try {
|
|
141
|
-
|
|
164
|
+
fs2.unlinkSync(tmpPath);
|
|
142
165
|
} catch {
|
|
143
166
|
}
|
|
144
|
-
|
|
167
|
+
fs2.writeFileSync(statePath, JSON.stringify(state, null, 2));
|
|
145
168
|
}
|
|
146
169
|
}
|
|
147
170
|
function acquireLock(lockPath, timeoutMs = 5e3) {
|
|
148
171
|
const deadline = Date.now() + timeoutMs;
|
|
149
172
|
while (Date.now() < deadline) {
|
|
150
173
|
try {
|
|
151
|
-
|
|
174
|
+
fs2.mkdirSync(lockPath);
|
|
152
175
|
return () => {
|
|
153
176
|
try {
|
|
154
|
-
|
|
177
|
+
fs2.rmdirSync(lockPath);
|
|
155
178
|
} catch {
|
|
156
179
|
}
|
|
157
180
|
};
|
|
@@ -360,9 +383,9 @@ async function sendSpans(spans, apiKey, baseUrl, context) {
|
|
|
360
383
|
const spanNames = spans.map((s) => s.span_name);
|
|
361
384
|
debug(`Sending ${spans.length} spans (${body.length} bytes) to ${url} for ${context}: ${spanNames.join(", ")}`);
|
|
362
385
|
if (_debug) {
|
|
363
|
-
const debugDir = _logFile ?
|
|
364
|
-
const debugFile =
|
|
365
|
-
|
|
386
|
+
const debugDir = _logFile ? path2.dirname(_logFile) : os2.tmpdir();
|
|
387
|
+
const debugFile = path2.join(debugDir, `respan_spans_${context.replace(/\s+/g, "_")}.json`);
|
|
388
|
+
fs2.writeFileSync(debugFile, body);
|
|
366
389
|
debug(`Dumped OTLP payload to ${debugFile}`);
|
|
367
390
|
}
|
|
368
391
|
for (let attempt = 0; attempt < 3; attempt++) {
|
|
@@ -403,10 +426,10 @@ function sleep(ms) {
|
|
|
403
426
|
}
|
|
404
427
|
|
|
405
428
|
// src/hooks/codex-cli.ts
|
|
406
|
-
var STATE_DIR =
|
|
407
|
-
var LOG_FILE =
|
|
408
|
-
var STATE_FILE =
|
|
409
|
-
var LOCK_PATH =
|
|
429
|
+
var STATE_DIR = path3.join(os3.homedir(), ".codex", "state");
|
|
430
|
+
var LOG_FILE = path3.join(STATE_DIR, "respan_hook.log");
|
|
431
|
+
var STATE_FILE = path3.join(STATE_DIR, "respan_state.json");
|
|
432
|
+
var LOCK_PATH = path3.join(STATE_DIR, "respan_hook.lock");
|
|
410
433
|
var DEBUG_MODE = (process.env.CODEX_RESPAN_DEBUG ?? "").toLowerCase() === "true";
|
|
411
434
|
var MAX_CHARS = parseInt(process.env.CODEX_RESPAN_MAX_CHARS ?? "4000", 10) || 4e3;
|
|
412
435
|
initLogging(LOG_FILE, DEBUG_MODE);
|
|
@@ -656,13 +679,13 @@ function createSpans(sessionId, turnNum, turn, config) {
|
|
|
656
679
|
return addDefaultsToAll(spans);
|
|
657
680
|
}
|
|
658
681
|
function findSessionFile(sessionId) {
|
|
659
|
-
const sessionsDir =
|
|
660
|
-
if (!
|
|
682
|
+
const sessionsDir = path3.join(os3.homedir(), ".codex", "sessions");
|
|
683
|
+
if (!fs3.existsSync(sessionsDir)) return null;
|
|
661
684
|
const walk = (dir) => {
|
|
662
|
-
const entries =
|
|
685
|
+
const entries = fs3.readdirSync(dir).sort().reverse();
|
|
663
686
|
for (const entry of entries) {
|
|
664
|
-
const full =
|
|
665
|
-
if (
|
|
687
|
+
const full = path3.join(dir, entry);
|
|
688
|
+
if (fs3.statSync(full).isDirectory()) {
|
|
666
689
|
const result = walk(full);
|
|
667
690
|
if (result) return result;
|
|
668
691
|
} else if (entry.endsWith(".jsonl") && entry.includes(sessionId)) {
|
|
@@ -674,14 +697,14 @@ function findSessionFile(sessionId) {
|
|
|
674
697
|
return walk(sessionsDir);
|
|
675
698
|
}
|
|
676
699
|
function findLatestSessionFile() {
|
|
677
|
-
const sessionsDir =
|
|
678
|
-
if (!
|
|
700
|
+
const sessionsDir = path3.join(os3.homedir(), ".codex", "sessions");
|
|
701
|
+
if (!fs3.existsSync(sessionsDir)) return null;
|
|
679
702
|
let latestFile = null;
|
|
680
703
|
let latestMtime = 0;
|
|
681
704
|
const walk = (dir) => {
|
|
682
|
-
for (const entry of
|
|
683
|
-
const full =
|
|
684
|
-
const stat =
|
|
705
|
+
for (const entry of fs3.readdirSync(dir)) {
|
|
706
|
+
const full = path3.join(dir, entry);
|
|
707
|
+
const stat = fs3.statSync(full);
|
|
685
708
|
if (stat.isDirectory()) walk(full);
|
|
686
709
|
else if (entry.endsWith(".jsonl") && stat.mtimeMs > latestMtime) {
|
|
687
710
|
latestMtime = stat.mtimeMs;
|
|
@@ -692,11 +715,11 @@ function findLatestSessionFile() {
|
|
|
692
715
|
walk(sessionsDir);
|
|
693
716
|
if (!latestFile) return null;
|
|
694
717
|
try {
|
|
695
|
-
const firstLine =
|
|
718
|
+
const firstLine = fs3.readFileSync(latestFile, "utf-8").split("\n")[0];
|
|
696
719
|
if (!firstLine) return null;
|
|
697
720
|
const firstMsg = JSON.parse(firstLine);
|
|
698
721
|
const payload = firstMsg.payload ?? {};
|
|
699
|
-
const sessionId = String(payload.id ??
|
|
722
|
+
const sessionId = String(payload.id ?? path3.basename(latestFile, ".jsonl"));
|
|
700
723
|
return { sessionId, sessionFile: latestFile };
|
|
701
724
|
} catch {
|
|
702
725
|
return null;
|
|
@@ -713,7 +736,7 @@ async function mainWorker() {
|
|
|
713
736
|
log("ERROR", "No API key");
|
|
714
737
|
return;
|
|
715
738
|
}
|
|
716
|
-
const config = cwd ? loadRespanConfig(
|
|
739
|
+
const config = cwd ? loadRespanConfig(path3.join(cwd, ".codex", "respan.json")) : null;
|
|
717
740
|
const maxAttempts = 3;
|
|
718
741
|
let turns = 0;
|
|
719
742
|
try {
|
|
@@ -723,7 +746,7 @@ async function mainWorker() {
|
|
|
723
746
|
const state = loadState(STATE_FILE);
|
|
724
747
|
const sessionState = state[sessionId] ?? {};
|
|
725
748
|
const lastTurnCount = Number(sessionState.turn_count ?? 0);
|
|
726
|
-
const lines =
|
|
749
|
+
const lines = fs3.readFileSync(sessionFile, "utf-8").trim().split("\n");
|
|
727
750
|
const events = parseSession(lines);
|
|
728
751
|
const allTurns = extractTurns(events);
|
|
729
752
|
if (allTurns.length > lastTurnCount) {
|
|
@@ -23,15 +23,37 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
23
23
|
));
|
|
24
24
|
|
|
25
25
|
// src/hooks/gemini-cli.ts
|
|
26
|
+
var fs3 = __toESM(require("node:fs"), 1);
|
|
27
|
+
var os3 = __toESM(require("node:os"), 1);
|
|
28
|
+
var path3 = __toESM(require("node:path"), 1);
|
|
29
|
+
var import_node_child_process = require("node:child_process");
|
|
30
|
+
|
|
31
|
+
// src/hooks/shared.ts
|
|
26
32
|
var fs2 = __toESM(require("node:fs"), 1);
|
|
27
33
|
var os2 = __toESM(require("node:os"), 1);
|
|
28
34
|
var path2 = __toESM(require("node:path"), 1);
|
|
29
|
-
var import_node_child_process = require("node:child_process");
|
|
30
35
|
|
|
31
|
-
// src/
|
|
36
|
+
// src/lib/config.ts
|
|
32
37
|
var fs = __toESM(require("node:fs"), 1);
|
|
33
|
-
var os = __toESM(require("node:os"), 1);
|
|
34
38
|
var path = __toESM(require("node:path"), 1);
|
|
39
|
+
var os = __toESM(require("node:os"), 1);
|
|
40
|
+
var CONFIG_DIR = path.join(os.homedir(), ".respan");
|
|
41
|
+
var CREDENTIALS_FILE = path.join(CONFIG_DIR, "credentials.json");
|
|
42
|
+
var CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
|
|
43
|
+
function restrictToOwner(target) {
|
|
44
|
+
if (process.platform === "win32") return;
|
|
45
|
+
try {
|
|
46
|
+
const mode = fs.statSync(target).mode & 511;
|
|
47
|
+
if (mode & 63) fs.chmodSync(target, mode & 448);
|
|
48
|
+
} catch {
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function tightenCredentialPermissions() {
|
|
52
|
+
restrictToOwner(CONFIG_DIR);
|
|
53
|
+
restrictToOwner(CREDENTIALS_FILE);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// src/hooks/shared.ts
|
|
35
57
|
function resolveTracesEndpoint(baseUrl) {
|
|
36
58
|
const DEFAULT = "https://api.respan.ai/api/v2/traces";
|
|
37
59
|
if (!baseUrl) return DEFAULT;
|
|
@@ -45,13 +67,13 @@ var _debug = false;
|
|
|
45
67
|
function initLogging(logFile, debug2) {
|
|
46
68
|
_logFile = logFile;
|
|
47
69
|
_debug = debug2;
|
|
48
|
-
const dir =
|
|
49
|
-
|
|
70
|
+
const dir = path2.dirname(logFile);
|
|
71
|
+
fs2.mkdirSync(dir, { recursive: true });
|
|
50
72
|
}
|
|
51
73
|
function log(level, message) {
|
|
52
74
|
if (!_logFile) return;
|
|
53
75
|
const ts = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19);
|
|
54
|
-
|
|
76
|
+
fs2.appendFileSync(_logFile, `${ts} [${level}] ${message}
|
|
55
77
|
`);
|
|
56
78
|
}
|
|
57
79
|
function debug(message) {
|
|
@@ -62,14 +84,15 @@ function resolveCredentials() {
|
|
|
62
84
|
let apiKey = process.env.RESPAN_API_KEY ?? "";
|
|
63
85
|
let baseUrl = process.env.RESPAN_BASE_URL ?? DEFAULT_BASE_URL;
|
|
64
86
|
if (!apiKey) {
|
|
65
|
-
const credsFile =
|
|
66
|
-
if (
|
|
87
|
+
const credsFile = path2.join(os2.homedir(), ".respan", "credentials.json");
|
|
88
|
+
if (fs2.existsSync(credsFile)) {
|
|
89
|
+
tightenCredentialPermissions();
|
|
67
90
|
try {
|
|
68
|
-
const creds = JSON.parse(
|
|
69
|
-
const configFile =
|
|
91
|
+
const creds = JSON.parse(fs2.readFileSync(credsFile, "utf-8"));
|
|
92
|
+
const configFile = path2.join(os2.homedir(), ".respan", "config.json");
|
|
70
93
|
let profile = "default";
|
|
71
|
-
if (
|
|
72
|
-
const cfg = JSON.parse(
|
|
94
|
+
if (fs2.existsSync(configFile)) {
|
|
95
|
+
const cfg = JSON.parse(fs2.readFileSync(configFile, "utf-8"));
|
|
73
96
|
profile = cfg.activeProfile ?? "default";
|
|
74
97
|
}
|
|
75
98
|
const cred = creds[profile] ?? {};
|
|
@@ -99,11 +122,11 @@ var KNOWN_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
|
99
122
|
"project_id"
|
|
100
123
|
]);
|
|
101
124
|
function loadRespanConfig(configPath) {
|
|
102
|
-
if (!
|
|
125
|
+
if (!fs2.existsSync(configPath)) {
|
|
103
126
|
return { fields: {}, properties: {} };
|
|
104
127
|
}
|
|
105
128
|
try {
|
|
106
|
-
const raw = JSON.parse(
|
|
129
|
+
const raw = JSON.parse(fs2.readFileSync(configPath, "utf-8"));
|
|
107
130
|
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
108
131
|
return { fields: {}, properties: {} };
|
|
109
132
|
}
|
|
@@ -126,10 +149,10 @@ function acquireLock(lockPath, timeoutMs = 5e3) {
|
|
|
126
149
|
const deadline = Date.now() + timeoutMs;
|
|
127
150
|
while (Date.now() < deadline) {
|
|
128
151
|
try {
|
|
129
|
-
|
|
152
|
+
fs2.mkdirSync(lockPath);
|
|
130
153
|
return () => {
|
|
131
154
|
try {
|
|
132
|
-
|
|
155
|
+
fs2.rmdirSync(lockPath);
|
|
133
156
|
} catch {
|
|
134
157
|
}
|
|
135
158
|
};
|
|
@@ -327,9 +350,9 @@ function toOtlpPayload(spans) {
|
|
|
327
350
|
}
|
|
328
351
|
|
|
329
352
|
// src/hooks/gemini-cli.ts
|
|
330
|
-
var STATE_DIR =
|
|
331
|
-
var LOG_FILE =
|
|
332
|
-
var LOCK_PATH =
|
|
353
|
+
var STATE_DIR = path3.join(os3.homedir(), ".gemini", "state");
|
|
354
|
+
var LOG_FILE = path3.join(STATE_DIR, "respan_hook.log");
|
|
355
|
+
var LOCK_PATH = path3.join(STATE_DIR, "respan_hook.lock");
|
|
333
356
|
var DEBUG_MODE = (process.env.GEMINI_RESPAN_DEBUG ?? "").toLowerCase() === "true";
|
|
334
357
|
var MAX_CHARS = parseInt(process.env.GEMINI_RESPAN_MAX_CHARS ?? "4000", 10) || 4e3;
|
|
335
358
|
var SEND_DELAY = parseInt(process.env.GEMINI_RESPAN_SEND_DELAY ?? "10", 10) || 10;
|
|
@@ -382,13 +405,13 @@ function formatToolInput(toolName, args) {
|
|
|
382
405
|
}
|
|
383
406
|
function statePath(sessionId) {
|
|
384
407
|
const safeId = sessionId.replace(/[/\\]/g, "_").slice(0, 64);
|
|
385
|
-
return
|
|
408
|
+
return path3.join(STATE_DIR, `respan_stream_${safeId}.json`);
|
|
386
409
|
}
|
|
387
410
|
function loadStreamState(sessionId) {
|
|
388
411
|
const p = statePath(sessionId);
|
|
389
|
-
if (
|
|
412
|
+
if (fs3.existsSync(p)) {
|
|
390
413
|
try {
|
|
391
|
-
return JSON.parse(
|
|
414
|
+
return JSON.parse(fs3.readFileSync(p, "utf-8"));
|
|
392
415
|
} catch {
|
|
393
416
|
}
|
|
394
417
|
}
|
|
@@ -396,22 +419,22 @@ function loadStreamState(sessionId) {
|
|
|
396
419
|
}
|
|
397
420
|
function saveStreamState(sessionId, state) {
|
|
398
421
|
const p = statePath(sessionId);
|
|
399
|
-
|
|
422
|
+
fs3.mkdirSync(path3.dirname(p), { recursive: true });
|
|
400
423
|
const tmp = p + ".tmp." + process.pid;
|
|
401
424
|
try {
|
|
402
|
-
|
|
403
|
-
|
|
425
|
+
fs3.writeFileSync(tmp, JSON.stringify(state));
|
|
426
|
+
fs3.renameSync(tmp, p);
|
|
404
427
|
} catch {
|
|
405
428
|
try {
|
|
406
|
-
|
|
429
|
+
fs3.unlinkSync(tmp);
|
|
407
430
|
} catch {
|
|
408
431
|
}
|
|
409
|
-
|
|
432
|
+
fs3.writeFileSync(p, JSON.stringify(state));
|
|
410
433
|
}
|
|
411
434
|
}
|
|
412
435
|
function clearStreamState(sessionId) {
|
|
413
436
|
try {
|
|
414
|
-
|
|
437
|
+
fs3.unlinkSync(statePath(sessionId));
|
|
415
438
|
} catch {
|
|
416
439
|
}
|
|
417
440
|
}
|
|
@@ -582,11 +605,11 @@ function sendSpansDetached(spans, apiKey, baseUrl) {
|
|
|
582
605
|
const url = resolveTracingIngestEndpoint(baseUrl);
|
|
583
606
|
debug(`Sending ${spans.length} span(s) to ${url}: ${spans.map((s) => s.span_name).join(", ")}`);
|
|
584
607
|
if (DEBUG_MODE) {
|
|
585
|
-
const debugFile =
|
|
586
|
-
|
|
608
|
+
const debugFile = path3.join(STATE_DIR, "respan_last_payload.json");
|
|
609
|
+
fs3.writeFileSync(debugFile, JSON.stringify(spans, null, 2));
|
|
587
610
|
}
|
|
588
|
-
const payloadFile =
|
|
589
|
-
|
|
611
|
+
const payloadFile = path3.join(STATE_DIR, `respan_send_${process.pid}.json`);
|
|
612
|
+
fs3.writeFileSync(payloadFile, JSON.stringify(toOtlpPayload(spans)));
|
|
590
613
|
const senderScript = `
|
|
591
614
|
const fs = require('fs');
|
|
592
615
|
const pf = ${JSON.stringify(payloadFile)};
|
|
@@ -626,14 +649,14 @@ try {
|
|
|
626
649
|
} catch (e) {
|
|
627
650
|
log("ERROR", `Failed to launch sender: ${e}`);
|
|
628
651
|
try {
|
|
629
|
-
|
|
652
|
+
fs3.unlinkSync(payloadFile);
|
|
630
653
|
} catch {
|
|
631
654
|
}
|
|
632
655
|
}
|
|
633
656
|
}
|
|
634
657
|
function launchDelayedSend(sessionId, sendVersion, spans, apiKey, baseUrl) {
|
|
635
|
-
const payloadFile =
|
|
636
|
-
|
|
658
|
+
const payloadFile = path3.join(STATE_DIR, `respan_delayed_${process.pid}.json`);
|
|
659
|
+
fs3.writeFileSync(payloadFile, JSON.stringify(toOtlpPayload(spans)));
|
|
637
660
|
const stateFile = statePath(sessionId);
|
|
638
661
|
const url = resolveTracingIngestEndpoint(baseUrl);
|
|
639
662
|
const script = `
|
|
@@ -679,7 +702,7 @@ setTimeout(async () => {
|
|
|
679
702
|
} catch (e) {
|
|
680
703
|
log("ERROR", `Failed to launch delayed sender: ${e}`);
|
|
681
704
|
try {
|
|
682
|
-
|
|
705
|
+
fs3.unlinkSync(payloadFile);
|
|
683
706
|
} catch {
|
|
684
707
|
}
|
|
685
708
|
}
|
|
@@ -825,7 +848,7 @@ function processChunk(hookData) {
|
|
|
825
848
|
const finalCompletion = completionTokens || state.last_tokens;
|
|
826
849
|
const finalTotal = Number(usage.totalTokenCount ?? 0) || finalPrompt + finalCompletion;
|
|
827
850
|
const tok = { prompt_tokens: finalPrompt, completion_tokens: finalCompletion, total_tokens: finalTotal };
|
|
828
|
-
const config = loadRespanConfig(
|
|
851
|
+
const config = loadRespanConfig(path3.join(os3.homedir(), ".gemini", "respan.json"));
|
|
829
852
|
const spans = buildSpans(
|
|
830
853
|
hookData,
|
|
831
854
|
state.accumulated_text,
|
|
@@ -852,8 +875,8 @@ function processChunk(hookData) {
|
|
|
852
875
|
}
|
|
853
876
|
function processChunkInWorker(dataFile) {
|
|
854
877
|
try {
|
|
855
|
-
const raw =
|
|
856
|
-
|
|
878
|
+
const raw = fs3.readFileSync(dataFile, "utf-8");
|
|
879
|
+
fs3.unlinkSync(dataFile);
|
|
857
880
|
if (!raw.trim()) return;
|
|
858
881
|
const hookData = JSON.parse(raw);
|
|
859
882
|
const unlock = acquireLock(LOCK_PATH);
|
|
@@ -865,7 +888,7 @@ function processChunkInWorker(dataFile) {
|
|
|
865
888
|
} catch (e) {
|
|
866
889
|
log("ERROR", `Worker error: ${e}`);
|
|
867
890
|
try {
|
|
868
|
-
|
|
891
|
+
fs3.unlinkSync(dataFile);
|
|
869
892
|
} catch {
|
|
870
893
|
}
|
|
871
894
|
}
|
|
@@ -878,7 +901,7 @@ function main() {
|
|
|
878
901
|
}
|
|
879
902
|
let raw = "";
|
|
880
903
|
try {
|
|
881
|
-
raw =
|
|
904
|
+
raw = fs3.readFileSync(0, "utf-8");
|
|
882
905
|
} catch {
|
|
883
906
|
}
|
|
884
907
|
process.stdout.write("{}\n");
|
|
@@ -897,9 +920,9 @@ function main() {
|
|
|
897
920
|
unlock?.();
|
|
898
921
|
}
|
|
899
922
|
} else {
|
|
900
|
-
const dataFile =
|
|
901
|
-
|
|
902
|
-
|
|
923
|
+
const dataFile = path3.join(STATE_DIR, `respan_chunk_${process.pid}.json`);
|
|
924
|
+
fs3.mkdirSync(STATE_DIR, { recursive: true });
|
|
925
|
+
fs3.writeFileSync(dataFile, raw);
|
|
903
926
|
try {
|
|
904
927
|
const scriptPath = __filename || process.argv[1];
|
|
905
928
|
const child = (0, import_node_child_process.execFile)("node", [scriptPath], {
|
package/dist/hooks/shared.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import * as fs from 'node:fs';
|
|
8
8
|
import * as os from 'node:os';
|
|
9
9
|
import * as path from 'node:path';
|
|
10
|
+
import { tightenCredentialPermissions } from '../lib/config.js';
|
|
10
11
|
// Resolve the v2/traces endpoint (OTLP JSON format)
|
|
11
12
|
export function resolveTracesEndpoint(baseUrl) {
|
|
12
13
|
const DEFAULT = 'https://api.respan.ai/api/v2/traces';
|
|
@@ -46,6 +47,8 @@ export function resolveCredentials() {
|
|
|
46
47
|
if (!apiKey) {
|
|
47
48
|
const credsFile = path.join(os.homedir(), '.respan', 'credentials.json');
|
|
48
49
|
if (fs.existsSync(credsFile)) {
|
|
50
|
+
// Hook-only users may never run the CLI again after logging in
|
|
51
|
+
tightenCredentialPermissions();
|
|
49
52
|
try {
|
|
50
53
|
const creds = JSON.parse(fs.readFileSync(credsFile, 'utf-8'));
|
|
51
54
|
const configFile = path.join(os.homedir(), '.respan', 'config.json');
|
package/dist/lib/config.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ export interface Config {
|
|
|
15
15
|
activeProfile?: string;
|
|
16
16
|
defaults?: Record<string, string>;
|
|
17
17
|
}
|
|
18
|
+
export declare function tightenCredentialPermissions(): void;
|
|
18
19
|
export declare function getAllCredentials(): Record<string, Credential>;
|
|
19
20
|
export declare function getCredential(profile?: string): Credential | undefined;
|
|
20
21
|
export declare function setCredential(profile: string, credential: Credential): void;
|
package/dist/lib/config.js
CHANGED
|
@@ -4,11 +4,27 @@ import * as os from 'node:os';
|
|
|
4
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
const CONFIG_DIR_MODE = 0o700;
|
|
8
|
+
const CREDENTIALS_FILE_MODE = 0o600;
|
|
9
|
+
// Removes group and other access, keeping the owner's bits. Best-effort: a path
|
|
10
|
+
// we can't chmod (another owner, read-only mount) must stay usable, and Windows
|
|
11
|
+
// doesn't map these modes to ACLs.
|
|
12
|
+
function restrictToOwner(target) {
|
|
13
|
+
if (process.platform === 'win32')
|
|
14
|
+
return;
|
|
15
|
+
try {
|
|
16
|
+
const mode = fs.statSync(target).mode & 0o777;
|
|
17
|
+
if (mode & 0o077)
|
|
18
|
+
fs.chmodSync(target, mode & 0o700);
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
// Missing, or not ours to change
|
|
10
22
|
}
|
|
11
23
|
}
|
|
24
|
+
function ensureConfigDir() {
|
|
25
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true, mode: CONFIG_DIR_MODE });
|
|
26
|
+
restrictToOwner(CONFIG_DIR);
|
|
27
|
+
}
|
|
12
28
|
function readJson(filePath, fallback) {
|
|
13
29
|
try {
|
|
14
30
|
if (!fs.existsSync(filePath))
|
|
@@ -20,12 +36,24 @@ function readJson(filePath, fallback) {
|
|
|
20
36
|
return fallback;
|
|
21
37
|
}
|
|
22
38
|
}
|
|
23
|
-
function writeJson(filePath, data) {
|
|
39
|
+
function writeJson(filePath, data, mode = 0o666) {
|
|
24
40
|
ensureConfigDir();
|
|
25
|
-
fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + '\n', 'utf-8');
|
|
41
|
+
fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + '\n', { encoding: 'utf-8', mode });
|
|
26
42
|
}
|
|
27
43
|
// --- Credentials ---
|
|
44
|
+
// Tightens files written by older CLI versions, which used the default modes.
|
|
45
|
+
export function tightenCredentialPermissions() {
|
|
46
|
+
restrictToOwner(CONFIG_DIR);
|
|
47
|
+
restrictToOwner(CREDENTIALS_FILE);
|
|
48
|
+
}
|
|
49
|
+
function writeCredentials(creds) {
|
|
50
|
+
// writeFileSync's mode only applies when it creates the file, so tighten an
|
|
51
|
+
// existing one first: new secrets must not land in a world-readable file.
|
|
52
|
+
restrictToOwner(CREDENTIALS_FILE);
|
|
53
|
+
writeJson(CREDENTIALS_FILE, creds, CREDENTIALS_FILE_MODE);
|
|
54
|
+
}
|
|
28
55
|
export function getAllCredentials() {
|
|
56
|
+
tightenCredentialPermissions();
|
|
29
57
|
return readJson(CREDENTIALS_FILE, {});
|
|
30
58
|
}
|
|
31
59
|
export function getCredential(profile) {
|
|
@@ -36,7 +64,7 @@ export function getCredential(profile) {
|
|
|
36
64
|
export function setCredential(profile, credential) {
|
|
37
65
|
const creds = getAllCredentials();
|
|
38
66
|
creds[profile] = credential;
|
|
39
|
-
|
|
67
|
+
writeCredentials(creds);
|
|
40
68
|
// Set active profile if this is the first credential
|
|
41
69
|
const config = getConfig();
|
|
42
70
|
if (!config.activeProfile) {
|
|
@@ -46,7 +74,7 @@ export function setCredential(profile, credential) {
|
|
|
46
74
|
export function deleteCredential(profile) {
|
|
47
75
|
const creds = getAllCredentials();
|
|
48
76
|
delete creds[profile];
|
|
49
|
-
|
|
77
|
+
writeCredentials(creds);
|
|
50
78
|
}
|
|
51
79
|
// --- Config ---
|
|
52
80
|
export function getConfig() {
|
package/oclif.manifest.json
CHANGED
|
@@ -914,16 +914,14 @@
|
|
|
914
914
|
"set.js"
|
|
915
915
|
]
|
|
916
916
|
},
|
|
917
|
-
"
|
|
917
|
+
"demo:clear": {
|
|
918
918
|
"aliases": [],
|
|
919
|
-
"args": {
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
},
|
|
926
|
-
"description": "Create a span in a dataset",
|
|
919
|
+
"args": {},
|
|
920
|
+
"description": "Delete the sample prompts, datasets, and evaluators created by `respan demo`. Demo traces are left in place (each run is timestamped history). Idempotent — safe to re-run.",
|
|
921
|
+
"examples": [
|
|
922
|
+
"<%= config.bin %> demo clear",
|
|
923
|
+
"<%= config.bin %> demo clear --yes"
|
|
924
|
+
],
|
|
927
925
|
"flags": {
|
|
928
926
|
"api-key": {
|
|
929
927
|
"description": "API key (env: RESPAN_API_KEY)",
|
|
@@ -966,18 +964,17 @@
|
|
|
966
964
|
"allowNo": false,
|
|
967
965
|
"type": "boolean"
|
|
968
966
|
},
|
|
969
|
-
"
|
|
970
|
-
"
|
|
971
|
-
"
|
|
972
|
-
"
|
|
973
|
-
"
|
|
974
|
-
"
|
|
975
|
-
"type": "option"
|
|
967
|
+
"yes": {
|
|
968
|
+
"char": "y",
|
|
969
|
+
"description": "Skip the confirmation prompt",
|
|
970
|
+
"name": "yes",
|
|
971
|
+
"allowNo": false,
|
|
972
|
+
"type": "boolean"
|
|
976
973
|
}
|
|
977
974
|
},
|
|
978
975
|
"hasDynamicHelp": false,
|
|
979
976
|
"hiddenAliases": [],
|
|
980
|
-
"id": "
|
|
977
|
+
"id": "demo:clear",
|
|
981
978
|
"pluginAlias": "@respan/cli",
|
|
982
979
|
"pluginName": "@respan/cli",
|
|
983
980
|
"pluginType": "core",
|
|
@@ -987,14 +984,17 @@
|
|
|
987
984
|
"relativePath": [
|
|
988
985
|
"dist",
|
|
989
986
|
"commands",
|
|
990
|
-
"
|
|
991
|
-
"
|
|
987
|
+
"demo",
|
|
988
|
+
"clear.js"
|
|
992
989
|
]
|
|
993
990
|
},
|
|
994
|
-
"
|
|
991
|
+
"demo": {
|
|
995
992
|
"aliases": [],
|
|
996
993
|
"args": {},
|
|
997
|
-
"description": "
|
|
994
|
+
"description": "Seed your account with sample data (prompts and traces) so you have something to explore. Idempotent — safe to re-run.",
|
|
995
|
+
"examples": [
|
|
996
|
+
"<%= config.bin %> demo"
|
|
997
|
+
],
|
|
998
998
|
"flags": {
|
|
999
999
|
"api-key": {
|
|
1000
1000
|
"description": "API key (env: RESPAN_API_KEY)",
|
|
@@ -1036,26 +1036,11 @@
|
|
|
1036
1036
|
"name": "verbose",
|
|
1037
1037
|
"allowNo": false,
|
|
1038
1038
|
"type": "boolean"
|
|
1039
|
-
},
|
|
1040
|
-
"name": {
|
|
1041
|
-
"description": "Dataset name",
|
|
1042
|
-
"name": "name",
|
|
1043
|
-
"required": true,
|
|
1044
|
-
"hasDynamicHelp": false,
|
|
1045
|
-
"multiple": false,
|
|
1046
|
-
"type": "option"
|
|
1047
|
-
},
|
|
1048
|
-
"description": {
|
|
1049
|
-
"description": "Dataset description",
|
|
1050
|
-
"name": "description",
|
|
1051
|
-
"hasDynamicHelp": false,
|
|
1052
|
-
"multiple": false,
|
|
1053
|
-
"type": "option"
|
|
1054
1039
|
}
|
|
1055
1040
|
},
|
|
1056
1041
|
"hasDynamicHelp": false,
|
|
1057
1042
|
"hiddenAliases": [],
|
|
1058
|
-
"id": "
|
|
1043
|
+
"id": "demo",
|
|
1059
1044
|
"pluginAlias": "@respan/cli",
|
|
1060
1045
|
"pluginName": "@respan/cli",
|
|
1061
1046
|
"pluginType": "core",
|
|
@@ -1065,25 +1050,20 @@
|
|
|
1065
1050
|
"relativePath": [
|
|
1066
1051
|
"dist",
|
|
1067
1052
|
"commands",
|
|
1068
|
-
"
|
|
1069
|
-
"
|
|
1053
|
+
"demo",
|
|
1054
|
+
"index.js"
|
|
1070
1055
|
]
|
|
1071
1056
|
},
|
|
1072
|
-
"datasets:
|
|
1057
|
+
"datasets:create-span": {
|
|
1073
1058
|
"aliases": [],
|
|
1074
1059
|
"args": {
|
|
1075
1060
|
"dataset-id": {
|
|
1076
1061
|
"description": "Dataset ID",
|
|
1077
1062
|
"name": "dataset-id",
|
|
1078
1063
|
"required": true
|
|
1079
|
-
},
|
|
1080
|
-
"span-id": {
|
|
1081
|
-
"description": "Span ID",
|
|
1082
|
-
"name": "span-id",
|
|
1083
|
-
"required": true
|
|
1084
1064
|
}
|
|
1085
1065
|
},
|
|
1086
|
-
"description": "
|
|
1066
|
+
"description": "Create a span in a dataset",
|
|
1087
1067
|
"flags": {
|
|
1088
1068
|
"api-key": {
|
|
1089
1069
|
"description": "API key (env: RESPAN_API_KEY)",
|
|
@@ -1125,11 +1105,19 @@
|
|
|
1125
1105
|
"name": "verbose",
|
|
1126
1106
|
"allowNo": false,
|
|
1127
1107
|
"type": "boolean"
|
|
1108
|
+
},
|
|
1109
|
+
"body": {
|
|
1110
|
+
"description": "Span body as JSON string",
|
|
1111
|
+
"name": "body",
|
|
1112
|
+
"required": true,
|
|
1113
|
+
"hasDynamicHelp": false,
|
|
1114
|
+
"multiple": false,
|
|
1115
|
+
"type": "option"
|
|
1128
1116
|
}
|
|
1129
1117
|
},
|
|
1130
1118
|
"hasDynamicHelp": false,
|
|
1131
1119
|
"hiddenAliases": [],
|
|
1132
|
-
"id": "datasets:
|
|
1120
|
+
"id": "datasets:create-span",
|
|
1133
1121
|
"pluginAlias": "@respan/cli",
|
|
1134
1122
|
"pluginName": "@respan/cli",
|
|
1135
1123
|
"pluginType": "core",
|
|
@@ -1140,19 +1128,13 @@
|
|
|
1140
1128
|
"dist",
|
|
1141
1129
|
"commands",
|
|
1142
1130
|
"datasets",
|
|
1143
|
-
"
|
|
1131
|
+
"create-span.js"
|
|
1144
1132
|
]
|
|
1145
1133
|
},
|
|
1146
|
-
"datasets:
|
|
1134
|
+
"datasets:create": {
|
|
1147
1135
|
"aliases": [],
|
|
1148
|
-
"args": {
|
|
1149
|
-
|
|
1150
|
-
"description": "Dataset ID",
|
|
1151
|
-
"name": "id",
|
|
1152
|
-
"required": true
|
|
1153
|
-
}
|
|
1154
|
-
},
|
|
1155
|
-
"description": "Get a specific dataset",
|
|
1136
|
+
"args": {},
|
|
1137
|
+
"description": "Create a new dataset",
|
|
1156
1138
|
"flags": {
|
|
1157
1139
|
"api-key": {
|
|
1158
1140
|
"description": "API key (env: RESPAN_API_KEY)",
|
|
@@ -1194,11 +1176,26 @@
|
|
|
1194
1176
|
"name": "verbose",
|
|
1195
1177
|
"allowNo": false,
|
|
1196
1178
|
"type": "boolean"
|
|
1179
|
+
},
|
|
1180
|
+
"name": {
|
|
1181
|
+
"description": "Dataset name",
|
|
1182
|
+
"name": "name",
|
|
1183
|
+
"required": true,
|
|
1184
|
+
"hasDynamicHelp": false,
|
|
1185
|
+
"multiple": false,
|
|
1186
|
+
"type": "option"
|
|
1187
|
+
},
|
|
1188
|
+
"description": {
|
|
1189
|
+
"description": "Dataset description",
|
|
1190
|
+
"name": "description",
|
|
1191
|
+
"hasDynamicHelp": false,
|
|
1192
|
+
"multiple": false,
|
|
1193
|
+
"type": "option"
|
|
1197
1194
|
}
|
|
1198
1195
|
},
|
|
1199
1196
|
"hasDynamicHelp": false,
|
|
1200
1197
|
"hiddenAliases": [],
|
|
1201
|
-
"id": "datasets:
|
|
1198
|
+
"id": "datasets:create",
|
|
1202
1199
|
"pluginAlias": "@respan/cli",
|
|
1203
1200
|
"pluginName": "@respan/cli",
|
|
1204
1201
|
"pluginType": "core",
|
|
@@ -1209,13 +1206,24 @@
|
|
|
1209
1206
|
"dist",
|
|
1210
1207
|
"commands",
|
|
1211
1208
|
"datasets",
|
|
1212
|
-
"
|
|
1209
|
+
"create.js"
|
|
1213
1210
|
]
|
|
1214
1211
|
},
|
|
1215
|
-
"datasets:
|
|
1212
|
+
"datasets:get-span": {
|
|
1216
1213
|
"aliases": [],
|
|
1217
|
-
"args": {
|
|
1218
|
-
|
|
1214
|
+
"args": {
|
|
1215
|
+
"dataset-id": {
|
|
1216
|
+
"description": "Dataset ID",
|
|
1217
|
+
"name": "dataset-id",
|
|
1218
|
+
"required": true
|
|
1219
|
+
},
|
|
1220
|
+
"span-id": {
|
|
1221
|
+
"description": "Span ID",
|
|
1222
|
+
"name": "span-id",
|
|
1223
|
+
"required": true
|
|
1224
|
+
}
|
|
1225
|
+
},
|
|
1226
|
+
"description": "Get a specific span from a dataset",
|
|
1219
1227
|
"flags": {
|
|
1220
1228
|
"api-key": {
|
|
1221
1229
|
"description": "API key (env: RESPAN_API_KEY)",
|
|
@@ -1257,27 +1265,11 @@
|
|
|
1257
1265
|
"name": "verbose",
|
|
1258
1266
|
"allowNo": false,
|
|
1259
1267
|
"type": "boolean"
|
|
1260
|
-
},
|
|
1261
|
-
"limit": {
|
|
1262
|
-
"description": "Number of results per page",
|
|
1263
|
-
"name": "limit",
|
|
1264
|
-
"default": 50,
|
|
1265
|
-
"hasDynamicHelp": false,
|
|
1266
|
-
"multiple": false,
|
|
1267
|
-
"type": "option"
|
|
1268
|
-
},
|
|
1269
|
-
"page": {
|
|
1270
|
-
"description": "Page number",
|
|
1271
|
-
"name": "page",
|
|
1272
|
-
"default": 1,
|
|
1273
|
-
"hasDynamicHelp": false,
|
|
1274
|
-
"multiple": false,
|
|
1275
|
-
"type": "option"
|
|
1276
1268
|
}
|
|
1277
1269
|
},
|
|
1278
1270
|
"hasDynamicHelp": false,
|
|
1279
1271
|
"hiddenAliases": [],
|
|
1280
|
-
"id": "datasets:
|
|
1272
|
+
"id": "datasets:get-span",
|
|
1281
1273
|
"pluginAlias": "@respan/cli",
|
|
1282
1274
|
"pluginName": "@respan/cli",
|
|
1283
1275
|
"pluginType": "core",
|
|
@@ -1288,19 +1280,19 @@
|
|
|
1288
1280
|
"dist",
|
|
1289
1281
|
"commands",
|
|
1290
1282
|
"datasets",
|
|
1291
|
-
"
|
|
1283
|
+
"get-span.js"
|
|
1292
1284
|
]
|
|
1293
1285
|
},
|
|
1294
|
-
"datasets:
|
|
1286
|
+
"datasets:get": {
|
|
1295
1287
|
"aliases": [],
|
|
1296
1288
|
"args": {
|
|
1297
|
-
"
|
|
1289
|
+
"id": {
|
|
1298
1290
|
"description": "Dataset ID",
|
|
1299
|
-
"name": "
|
|
1291
|
+
"name": "id",
|
|
1300
1292
|
"required": true
|
|
1301
1293
|
}
|
|
1302
1294
|
},
|
|
1303
|
-
"description": "
|
|
1295
|
+
"description": "Get a specific dataset",
|
|
1304
1296
|
"flags": {
|
|
1305
1297
|
"api-key": {
|
|
1306
1298
|
"description": "API key (env: RESPAN_API_KEY)",
|
|
@@ -1346,7 +1338,7 @@
|
|
|
1346
1338
|
},
|
|
1347
1339
|
"hasDynamicHelp": false,
|
|
1348
1340
|
"hiddenAliases": [],
|
|
1349
|
-
"id": "datasets:
|
|
1341
|
+
"id": "datasets:get",
|
|
1350
1342
|
"pluginAlias": "@respan/cli",
|
|
1351
1343
|
"pluginName": "@respan/cli",
|
|
1352
1344
|
"pluginType": "core",
|
|
@@ -1357,19 +1349,13 @@
|
|
|
1357
1349
|
"dist",
|
|
1358
1350
|
"commands",
|
|
1359
1351
|
"datasets",
|
|
1360
|
-
"
|
|
1352
|
+
"get.js"
|
|
1361
1353
|
]
|
|
1362
1354
|
},
|
|
1363
|
-
"datasets:
|
|
1355
|
+
"datasets:list": {
|
|
1364
1356
|
"aliases": [],
|
|
1365
|
-
"args": {
|
|
1366
|
-
|
|
1367
|
-
"description": "Dataset ID",
|
|
1368
|
-
"name": "id",
|
|
1369
|
-
"required": true
|
|
1370
|
-
}
|
|
1371
|
-
},
|
|
1372
|
-
"description": "Update a dataset",
|
|
1357
|
+
"args": {},
|
|
1358
|
+
"description": "List datasets",
|
|
1373
1359
|
"flags": {
|
|
1374
1360
|
"api-key": {
|
|
1375
1361
|
"description": "API key (env: RESPAN_API_KEY)",
|
|
@@ -1412,16 +1398,18 @@
|
|
|
1412
1398
|
"allowNo": false,
|
|
1413
1399
|
"type": "boolean"
|
|
1414
1400
|
},
|
|
1415
|
-
"
|
|
1416
|
-
"description": "
|
|
1417
|
-
"name": "
|
|
1401
|
+
"limit": {
|
|
1402
|
+
"description": "Number of results per page",
|
|
1403
|
+
"name": "limit",
|
|
1404
|
+
"default": 50,
|
|
1418
1405
|
"hasDynamicHelp": false,
|
|
1419
1406
|
"multiple": false,
|
|
1420
1407
|
"type": "option"
|
|
1421
1408
|
},
|
|
1422
|
-
"
|
|
1423
|
-
"description": "
|
|
1424
|
-
"name": "
|
|
1409
|
+
"page": {
|
|
1410
|
+
"description": "Page number",
|
|
1411
|
+
"name": "page",
|
|
1412
|
+
"default": 1,
|
|
1425
1413
|
"hasDynamicHelp": false,
|
|
1426
1414
|
"multiple": false,
|
|
1427
1415
|
"type": "option"
|
|
@@ -1429,7 +1417,7 @@
|
|
|
1429
1417
|
},
|
|
1430
1418
|
"hasDynamicHelp": false,
|
|
1431
1419
|
"hiddenAliases": [],
|
|
1432
|
-
"id": "datasets:
|
|
1420
|
+
"id": "datasets:list",
|
|
1433
1421
|
"pluginAlias": "@respan/cli",
|
|
1434
1422
|
"pluginName": "@respan/cli",
|
|
1435
1423
|
"pluginType": "core",
|
|
@@ -1440,17 +1428,19 @@
|
|
|
1440
1428
|
"dist",
|
|
1441
1429
|
"commands",
|
|
1442
1430
|
"datasets",
|
|
1443
|
-
"
|
|
1431
|
+
"list.js"
|
|
1444
1432
|
]
|
|
1445
1433
|
},
|
|
1446
|
-
"
|
|
1434
|
+
"datasets:spans": {
|
|
1447
1435
|
"aliases": [],
|
|
1448
|
-
"args": {
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1436
|
+
"args": {
|
|
1437
|
+
"dataset-id": {
|
|
1438
|
+
"description": "Dataset ID",
|
|
1439
|
+
"name": "dataset-id",
|
|
1440
|
+
"required": true
|
|
1441
|
+
}
|
|
1442
|
+
},
|
|
1443
|
+
"description": "List spans in a dataset",
|
|
1454
1444
|
"flags": {
|
|
1455
1445
|
"api-key": {
|
|
1456
1446
|
"description": "API key (env: RESPAN_API_KEY)",
|
|
@@ -1492,18 +1482,11 @@
|
|
|
1492
1482
|
"name": "verbose",
|
|
1493
1483
|
"allowNo": false,
|
|
1494
1484
|
"type": "boolean"
|
|
1495
|
-
},
|
|
1496
|
-
"yes": {
|
|
1497
|
-
"char": "y",
|
|
1498
|
-
"description": "Skip the confirmation prompt",
|
|
1499
|
-
"name": "yes",
|
|
1500
|
-
"allowNo": false,
|
|
1501
|
-
"type": "boolean"
|
|
1502
1485
|
}
|
|
1503
1486
|
},
|
|
1504
1487
|
"hasDynamicHelp": false,
|
|
1505
1488
|
"hiddenAliases": [],
|
|
1506
|
-
"id": "
|
|
1489
|
+
"id": "datasets:spans",
|
|
1507
1490
|
"pluginAlias": "@respan/cli",
|
|
1508
1491
|
"pluginName": "@respan/cli",
|
|
1509
1492
|
"pluginType": "core",
|
|
@@ -1513,17 +1496,20 @@
|
|
|
1513
1496
|
"relativePath": [
|
|
1514
1497
|
"dist",
|
|
1515
1498
|
"commands",
|
|
1516
|
-
"
|
|
1517
|
-
"
|
|
1499
|
+
"datasets",
|
|
1500
|
+
"spans.js"
|
|
1518
1501
|
]
|
|
1519
1502
|
},
|
|
1520
|
-
"
|
|
1503
|
+
"datasets:update": {
|
|
1521
1504
|
"aliases": [],
|
|
1522
|
-
"args": {
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1505
|
+
"args": {
|
|
1506
|
+
"id": {
|
|
1507
|
+
"description": "Dataset ID",
|
|
1508
|
+
"name": "id",
|
|
1509
|
+
"required": true
|
|
1510
|
+
}
|
|
1511
|
+
},
|
|
1512
|
+
"description": "Update a dataset",
|
|
1527
1513
|
"flags": {
|
|
1528
1514
|
"api-key": {
|
|
1529
1515
|
"description": "API key (env: RESPAN_API_KEY)",
|
|
@@ -1565,11 +1551,25 @@
|
|
|
1565
1551
|
"name": "verbose",
|
|
1566
1552
|
"allowNo": false,
|
|
1567
1553
|
"type": "boolean"
|
|
1554
|
+
},
|
|
1555
|
+
"name": {
|
|
1556
|
+
"description": "Dataset name",
|
|
1557
|
+
"name": "name",
|
|
1558
|
+
"hasDynamicHelp": false,
|
|
1559
|
+
"multiple": false,
|
|
1560
|
+
"type": "option"
|
|
1561
|
+
},
|
|
1562
|
+
"description": {
|
|
1563
|
+
"description": "Dataset description",
|
|
1564
|
+
"name": "description",
|
|
1565
|
+
"hasDynamicHelp": false,
|
|
1566
|
+
"multiple": false,
|
|
1567
|
+
"type": "option"
|
|
1568
1568
|
}
|
|
1569
1569
|
},
|
|
1570
1570
|
"hasDynamicHelp": false,
|
|
1571
1571
|
"hiddenAliases": [],
|
|
1572
|
-
"id": "
|
|
1572
|
+
"id": "datasets:update",
|
|
1573
1573
|
"pluginAlias": "@respan/cli",
|
|
1574
1574
|
"pluginName": "@respan/cli",
|
|
1575
1575
|
"pluginType": "core",
|
|
@@ -1579,8 +1579,8 @@
|
|
|
1579
1579
|
"relativePath": [
|
|
1580
1580
|
"dist",
|
|
1581
1581
|
"commands",
|
|
1582
|
-
"
|
|
1583
|
-
"
|
|
1582
|
+
"datasets",
|
|
1583
|
+
"update.js"
|
|
1584
1584
|
]
|
|
1585
1585
|
},
|
|
1586
1586
|
"eval": {
|
|
@@ -4285,5 +4285,5 @@
|
|
|
4285
4285
|
]
|
|
4286
4286
|
}
|
|
4287
4287
|
},
|
|
4288
|
-
"version": "0.17.
|
|
4288
|
+
"version": "0.17.2"
|
|
4289
4289
|
}
|