codiedev 0.1.0 → 0.2.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/README.md +14 -5
- package/dist/connect.js +33 -7
- package/dist/hook.js +27 -63
- package/dist/utils.d.ts +10 -0
- package/dist/utils.js +146 -8
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# codiedev
|
|
2
2
|
|
|
3
|
-
Connect Claude Code to CodieDev for org-wide session capture.
|
|
3
|
+
Connect Claude Code or Codex to CodieDev for org-wide session capture.
|
|
4
4
|
|
|
5
5
|
## Usage
|
|
6
6
|
|
|
@@ -8,7 +8,12 @@ Connect Claude Code to CodieDev for org-wide session capture.
|
|
|
8
8
|
npx codiedev connect
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
The CLI will prompt you for an API token (get one at https://codiedev.com/portal/integrations/claude-code), then install a
|
|
11
|
+
The CLI will prompt you for an API token (get one at https://codiedev.com/portal/integrations/claude-code), then install a capture hook into whichever agent CLIs are present on your machine:
|
|
12
|
+
|
|
13
|
+
- **Claude Code** — `SessionEnd` hook in `~/.claude/settings.json`
|
|
14
|
+
- **Codex** — `Stop` hook in `~/.codex/hooks.json`
|
|
15
|
+
|
|
16
|
+
If both are installed, hooks are wired up for both. If neither is installed, the config is saved and you can re-run `connect` after installing one.
|
|
12
17
|
|
|
13
18
|
## What gets captured
|
|
14
19
|
|
|
@@ -16,8 +21,12 @@ The CLI will prompt you for an API token (get one at https://codiedev.com/portal
|
|
|
16
21
|
- Transcript + basic stats (message count, tool calls)
|
|
17
22
|
- Processed into summaries and embeddings server-side for search in `/portal/memories`
|
|
18
23
|
|
|
19
|
-
|
|
24
|
+
Each captured memory records its `source` (`claude-code` or `codex`) so you can filter in the portal.
|
|
20
25
|
|
|
21
|
-
|
|
26
|
+
## Codex note
|
|
27
|
+
|
|
28
|
+
Codex doesn't currently expose a `SessionEnd` event, so the hook runs on `Stop` (fires after each assistant turn). The backend dedupes by `(company, session_id)` and replaces the stored transcript on re-upload so the latest turn's snapshot wins. Summarization happens once per session; subsequent turn uploads keep the raw transcript fresh but don't re-summarize (to avoid running up LLM cost).
|
|
29
|
+
|
|
30
|
+
## Config
|
|
22
31
|
|
|
23
|
-
Override the backend with `CODIEDEV_URL` if needed.
|
|
32
|
+
Written to `~/.codiedev/config.json`. Override the backend with `CODIEDEV_URL` if needed.
|
package/dist/connect.js
CHANGED
|
@@ -139,17 +139,43 @@ async function main() {
|
|
|
139
139
|
console.error(`\nError: Failed to save config — ${err.message}`);
|
|
140
140
|
process.exit(1);
|
|
141
141
|
}
|
|
142
|
-
|
|
143
|
-
|
|
142
|
+
const hasClaude = (0, utils_1.claudeCodeInstalled)();
|
|
143
|
+
const hasCodex = (0, utils_1.codexInstalled)();
|
|
144
|
+
const installed = [];
|
|
145
|
+
if (hasClaude) {
|
|
146
|
+
try {
|
|
147
|
+
(0, utils_1.installHook)();
|
|
148
|
+
installed.push("Claude Code (SessionEnd → ~/.claude/settings.json)");
|
|
149
|
+
}
|
|
150
|
+
catch (err) {
|
|
151
|
+
console.error(`\nWarning: Failed to install Claude Code hook — ${err.message}`);
|
|
152
|
+
}
|
|
144
153
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
154
|
+
if (hasCodex) {
|
|
155
|
+
try {
|
|
156
|
+
(0, utils_1.installCodexHook)();
|
|
157
|
+
installed.push("Codex (Stop → ~/.codex/hooks.json)");
|
|
158
|
+
}
|
|
159
|
+
catch (err) {
|
|
160
|
+
console.error(`\nWarning: Failed to install Codex hook — ${err.message}`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (!hasClaude && !hasCodex) {
|
|
164
|
+
console.warn("\nNo Claude Code (~/.claude) or Codex (~/.codex) install detected.");
|
|
165
|
+
console.warn("Config saved. Install one, then re-run `npx codiedev connect` to wire up capture hooks.");
|
|
149
166
|
}
|
|
150
167
|
console.log(`\nConnected to ${companyName}`);
|
|
151
168
|
console.log(`Tracking ${repos.length} repo${repos.length === 1 ? "" : "s"}`);
|
|
152
|
-
|
|
169
|
+
if (installed.length > 0) {
|
|
170
|
+
console.log("Installed capture hook(s):");
|
|
171
|
+
for (const target of installed) {
|
|
172
|
+
console.log(` - ${target}`);
|
|
173
|
+
}
|
|
174
|
+
console.log("Sessions will be captured automatically.\n");
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
console.log();
|
|
178
|
+
}
|
|
153
179
|
}
|
|
154
180
|
main().catch((err) => {
|
|
155
181
|
console.error("Unexpected error:", err);
|
package/dist/hook.js
CHANGED
|
@@ -73,41 +73,6 @@ function postJson(url, body, bearerToken) {
|
|
|
73
73
|
req.end();
|
|
74
74
|
});
|
|
75
75
|
}
|
|
76
|
-
function parseTranscriptStats(transcriptPath) {
|
|
77
|
-
let messageCount = 0;
|
|
78
|
-
let toolCallCount = 0;
|
|
79
|
-
try {
|
|
80
|
-
const content = fs.readFileSync(transcriptPath, "utf8");
|
|
81
|
-
const lines = content.split("\n").filter((l) => l.trim());
|
|
82
|
-
for (const line of lines) {
|
|
83
|
-
try {
|
|
84
|
-
const entry = JSON.parse(line);
|
|
85
|
-
if (!entry || typeof entry !== "object")
|
|
86
|
-
continue;
|
|
87
|
-
// Claude Code transcript entries have type: "user" or "assistant" for messages
|
|
88
|
-
if (entry.type === "user" || entry.type === "assistant") {
|
|
89
|
-
messageCount++;
|
|
90
|
-
}
|
|
91
|
-
// Tool calls live in the message.content array as blocks with type "tool_use"
|
|
92
|
-
const content = entry.message?.content;
|
|
93
|
-
if (Array.isArray(content)) {
|
|
94
|
-
for (const block of content) {
|
|
95
|
-
if (block && block.type === "tool_use") {
|
|
96
|
-
toolCallCount++;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
catch {
|
|
102
|
-
// Skip malformed lines
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
catch {
|
|
107
|
-
// If we can't read/parse, return zeros
|
|
108
|
-
}
|
|
109
|
-
return { messageCount, toolCallCount };
|
|
110
|
-
}
|
|
111
76
|
async function readStdin() {
|
|
112
77
|
return new Promise((resolve, reject) => {
|
|
113
78
|
let data = "";
|
|
@@ -119,8 +84,13 @@ async function readStdin() {
|
|
|
119
84
|
process.stdin.on("error", reject);
|
|
120
85
|
});
|
|
121
86
|
}
|
|
87
|
+
function resolveMode(arg) {
|
|
88
|
+
if (arg === "capture-codex")
|
|
89
|
+
return "codex";
|
|
90
|
+
return "claude-code";
|
|
91
|
+
}
|
|
122
92
|
async function main() {
|
|
123
|
-
|
|
93
|
+
const mode = resolveMode(process.argv[2]);
|
|
124
94
|
let hookInput = {};
|
|
125
95
|
try {
|
|
126
96
|
const raw = await readStdin();
|
|
@@ -129,67 +99,61 @@ async function main() {
|
|
|
129
99
|
}
|
|
130
100
|
}
|
|
131
101
|
catch {
|
|
132
|
-
// If stdin is malformed, exit silently
|
|
133
102
|
process.exit(0);
|
|
134
103
|
}
|
|
135
104
|
const { session_id, transcript_path, cwd } = hookInput;
|
|
136
|
-
// Read config — if missing, exit silently
|
|
137
105
|
const config = (0, utils_1.readConfig)();
|
|
138
106
|
if (!config) {
|
|
139
107
|
process.exit(0);
|
|
140
108
|
}
|
|
141
|
-
// Determine working directory
|
|
142
|
-
const workingDir = cwd || process.cwd();
|
|
143
|
-
// Get git remote URL for this directory
|
|
144
|
-
const remoteUrl = (0, utils_1.getGitRemoteUrl)(workingDir);
|
|
145
|
-
if (!remoteUrl) {
|
|
146
|
-
// Not a git repo or no remote — exit silently
|
|
147
|
-
process.exit(0);
|
|
148
|
-
}
|
|
149
|
-
// Check if this repo is in our tracked repos
|
|
150
|
-
const matchedRepo = (0, utils_1.matchRepo)(remoteUrl, config.repos);
|
|
151
|
-
if (!matchedRepo) {
|
|
152
|
-
// Repo not tracked — exit silently
|
|
153
|
-
process.exit(0);
|
|
154
|
-
}
|
|
155
|
-
// Validate transcript path
|
|
156
109
|
if (!transcript_path) {
|
|
157
110
|
process.exit(0);
|
|
158
111
|
}
|
|
159
|
-
// Read transcript file
|
|
160
112
|
let transcriptContent;
|
|
161
113
|
try {
|
|
162
114
|
transcriptContent = fs.readFileSync(transcript_path, "utf8");
|
|
163
115
|
}
|
|
164
116
|
catch {
|
|
165
|
-
// Can't read transcript — exit silently
|
|
166
117
|
process.exit(0);
|
|
167
118
|
}
|
|
168
|
-
//
|
|
169
|
-
|
|
170
|
-
|
|
119
|
+
// Resolve repo URL. Codex embeds it in session_meta; fall back to git for CC.
|
|
120
|
+
let remoteUrl = null;
|
|
121
|
+
if (mode === "codex") {
|
|
122
|
+
remoteUrl = (0, utils_1.extractCodexRepoUrl)(transcriptContent);
|
|
123
|
+
}
|
|
124
|
+
if (!remoteUrl) {
|
|
125
|
+
remoteUrl = (0, utils_1.getGitRemoteUrl)(cwd || process.cwd());
|
|
126
|
+
}
|
|
127
|
+
if (!remoteUrl) {
|
|
128
|
+
process.exit(0);
|
|
129
|
+
}
|
|
130
|
+
const matchedRepo = (0, utils_1.matchRepo)(remoteUrl, config.repos);
|
|
131
|
+
if (!matchedRepo) {
|
|
132
|
+
process.exit(0);
|
|
133
|
+
}
|
|
134
|
+
const stats = mode === "codex"
|
|
135
|
+
? (0, utils_1.parseCodexStats)(transcriptContent)
|
|
136
|
+
: (0, utils_1.parseClaudeCodeStats)(transcriptContent);
|
|
171
137
|
const transcriptBase64 = Buffer.from(transcriptContent, "utf8").toString("base64");
|
|
172
|
-
// POST to backend
|
|
173
138
|
const payload = {
|
|
174
139
|
sessionId: session_id,
|
|
175
140
|
repoUrl: remoteUrl,
|
|
176
141
|
repoId: matchedRepo.repoId,
|
|
177
142
|
companyId: config.companyId,
|
|
178
143
|
transcript: transcriptBase64,
|
|
179
|
-
messageCount,
|
|
180
|
-
toolCallCount,
|
|
144
|
+
messageCount: stats.messageCount,
|
|
145
|
+
toolCallCount: stats.toolCallCount,
|
|
181
146
|
capturedAt: Date.now(),
|
|
147
|
+
source: mode,
|
|
182
148
|
};
|
|
183
149
|
try {
|
|
184
150
|
await postJson(`${config.backendUrl}/api/captureSession`, payload, config.token);
|
|
185
151
|
}
|
|
186
152
|
catch (err) {
|
|
187
|
-
// Write warning to stderr, but exit 0 — never block Claude Code
|
|
188
153
|
process.stderr.write(`[codiedev-hook] Warning: Failed to capture session — ${err.message}\n`);
|
|
189
154
|
}
|
|
190
155
|
process.exit(0);
|
|
191
156
|
}
|
|
192
157
|
main().catch(() => {
|
|
193
|
-
// Never block Claude Code on any error
|
|
194
158
|
process.exit(0);
|
|
195
159
|
});
|
package/dist/utils.d.ts
CHANGED
|
@@ -24,4 +24,14 @@ export declare function matchRepo(remoteUrl: string, repos: Array<{
|
|
|
24
24
|
fullName: string;
|
|
25
25
|
} | null;
|
|
26
26
|
export declare function hashToken(token: string): string;
|
|
27
|
+
export declare function claudeCodeInstalled(): boolean;
|
|
28
|
+
export declare function codexInstalled(): boolean;
|
|
27
29
|
export declare function installHook(): void;
|
|
30
|
+
export declare function installCodexHook(): void;
|
|
31
|
+
export interface ParsedStats {
|
|
32
|
+
messageCount: number;
|
|
33
|
+
toolCallCount: number;
|
|
34
|
+
}
|
|
35
|
+
export declare function parseClaudeCodeStats(content: string): ParsedStats;
|
|
36
|
+
export declare function parseCodexStats(content: string): ParsedStats;
|
|
37
|
+
export declare function extractCodexRepoUrl(content: string): string | null;
|
package/dist/utils.js
CHANGED
|
@@ -39,7 +39,13 @@ exports.getGitRemoteUrl = getGitRemoteUrl;
|
|
|
39
39
|
exports.normalizeRepoUrl = normalizeRepoUrl;
|
|
40
40
|
exports.matchRepo = matchRepo;
|
|
41
41
|
exports.hashToken = hashToken;
|
|
42
|
+
exports.claudeCodeInstalled = claudeCodeInstalled;
|
|
43
|
+
exports.codexInstalled = codexInstalled;
|
|
42
44
|
exports.installHook = installHook;
|
|
45
|
+
exports.installCodexHook = installCodexHook;
|
|
46
|
+
exports.parseClaudeCodeStats = parseClaudeCodeStats;
|
|
47
|
+
exports.parseCodexStats = parseCodexStats;
|
|
48
|
+
exports.extractCodexRepoUrl = extractCodexRepoUrl;
|
|
43
49
|
const fs = __importStar(require("fs"));
|
|
44
50
|
const path = __importStar(require("path"));
|
|
45
51
|
const os = __importStar(require("os"));
|
|
@@ -120,6 +126,15 @@ function hashToken(token) {
|
|
|
120
126
|
return hash.toString(36);
|
|
121
127
|
}
|
|
122
128
|
const CLAUDE_SETTINGS_PATH = path.join(os.homedir(), ".claude", "settings.json");
|
|
129
|
+
const CLAUDE_DIR = path.join(os.homedir(), ".claude");
|
|
130
|
+
const CODEX_HOOKS_PATH = path.join(os.homedir(), ".codex", "hooks.json");
|
|
131
|
+
const CODEX_DIR = path.join(os.homedir(), ".codex");
|
|
132
|
+
function claudeCodeInstalled() {
|
|
133
|
+
return fs.existsSync(CLAUDE_DIR);
|
|
134
|
+
}
|
|
135
|
+
function codexInstalled() {
|
|
136
|
+
return fs.existsSync(CODEX_DIR);
|
|
137
|
+
}
|
|
123
138
|
function installHook() {
|
|
124
139
|
let settings = {};
|
|
125
140
|
try {
|
|
@@ -131,17 +146,14 @@ function installHook() {
|
|
|
131
146
|
catch {
|
|
132
147
|
// Start with empty settings if file doesn't exist or is invalid
|
|
133
148
|
}
|
|
134
|
-
// Ensure hooks object exists
|
|
135
149
|
if (!settings.hooks) {
|
|
136
150
|
settings.hooks = {};
|
|
137
151
|
}
|
|
138
152
|
const hooks = settings.hooks;
|
|
139
|
-
// Ensure SessionEnd array exists
|
|
140
153
|
if (!hooks.SessionEnd) {
|
|
141
154
|
hooks.SessionEnd = [];
|
|
142
155
|
}
|
|
143
156
|
const sessionEndHooks = hooks.SessionEnd;
|
|
144
|
-
// Check if codiedev-hook is already installed
|
|
145
157
|
const alreadyInstalled = sessionEndHooks.some((hook) => {
|
|
146
158
|
const matcher = hook.matcher;
|
|
147
159
|
const hooks2 = hook.hooks;
|
|
@@ -156,7 +168,6 @@ function installHook() {
|
|
|
156
168
|
if (alreadyInstalled) {
|
|
157
169
|
return;
|
|
158
170
|
}
|
|
159
|
-
// Add the codiedev-hook capture entry
|
|
160
171
|
sessionEndHooks.push({
|
|
161
172
|
matcher: ".*",
|
|
162
173
|
hooks: [
|
|
@@ -166,10 +177,137 @@ function installHook() {
|
|
|
166
177
|
},
|
|
167
178
|
],
|
|
168
179
|
});
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
if (!fs.existsSync(settingsDir)) {
|
|
172
|
-
fs.mkdirSync(settingsDir, { recursive: true });
|
|
180
|
+
if (!fs.existsSync(CLAUDE_DIR)) {
|
|
181
|
+
fs.mkdirSync(CLAUDE_DIR, { recursive: true });
|
|
173
182
|
}
|
|
174
183
|
fs.writeFileSync(CLAUDE_SETTINGS_PATH, JSON.stringify(settings, null, 2), "utf8");
|
|
175
184
|
}
|
|
185
|
+
// Codex doesn't have a SessionEnd event yet — Stop fires after every turn.
|
|
186
|
+
// The backend dedupes on (companyId, sessionId) and replaces the stored
|
|
187
|
+
// transcript on re-upload, so the latest turn's snapshot wins.
|
|
188
|
+
function installCodexHook() {
|
|
189
|
+
let hooksFile = {};
|
|
190
|
+
try {
|
|
191
|
+
if (fs.existsSync(CODEX_HOOKS_PATH)) {
|
|
192
|
+
const raw = fs.readFileSync(CODEX_HOOKS_PATH, "utf8");
|
|
193
|
+
hooksFile = JSON.parse(raw);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
catch {
|
|
197
|
+
// Start fresh if missing or invalid
|
|
198
|
+
}
|
|
199
|
+
if (!hooksFile.hooks) {
|
|
200
|
+
hooksFile.hooks = {};
|
|
201
|
+
}
|
|
202
|
+
const hooks = hooksFile.hooks;
|
|
203
|
+
if (!hooks.Stop) {
|
|
204
|
+
hooks.Stop = [];
|
|
205
|
+
}
|
|
206
|
+
const stopHooks = hooks.Stop;
|
|
207
|
+
const alreadyInstalled = stopHooks.some((hook) => {
|
|
208
|
+
const inner = hook.hooks;
|
|
209
|
+
if (!inner)
|
|
210
|
+
return false;
|
|
211
|
+
return inner.some((h) => {
|
|
212
|
+
const cmd = h.command;
|
|
213
|
+
return cmd && cmd.includes("codiedev-hook");
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
if (alreadyInstalled) {
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
stopHooks.push({
|
|
220
|
+
hooks: [
|
|
221
|
+
{
|
|
222
|
+
type: "command",
|
|
223
|
+
command: "npx codiedev-hook capture-codex",
|
|
224
|
+
timeout: 30,
|
|
225
|
+
},
|
|
226
|
+
],
|
|
227
|
+
});
|
|
228
|
+
if (!fs.existsSync(CODEX_DIR)) {
|
|
229
|
+
fs.mkdirSync(CODEX_DIR, { recursive: true });
|
|
230
|
+
}
|
|
231
|
+
fs.writeFileSync(CODEX_HOOKS_PATH, JSON.stringify(hooksFile, null, 2), "utf8");
|
|
232
|
+
}
|
|
233
|
+
function parseClaudeCodeStats(content) {
|
|
234
|
+
let messageCount = 0;
|
|
235
|
+
let toolCallCount = 0;
|
|
236
|
+
const lines = content.split("\n").filter((l) => l.trim());
|
|
237
|
+
for (const line of lines) {
|
|
238
|
+
try {
|
|
239
|
+
const entry = JSON.parse(line);
|
|
240
|
+
if (!entry || typeof entry !== "object")
|
|
241
|
+
continue;
|
|
242
|
+
if (entry.type === "user" || entry.type === "assistant") {
|
|
243
|
+
messageCount++;
|
|
244
|
+
}
|
|
245
|
+
const c = entry.message?.content;
|
|
246
|
+
if (Array.isArray(c)) {
|
|
247
|
+
for (const block of c) {
|
|
248
|
+
if (block && block.type === "tool_use")
|
|
249
|
+
toolCallCount++;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
catch {
|
|
254
|
+
// Skip malformed lines
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
return { messageCount, toolCallCount };
|
|
258
|
+
}
|
|
259
|
+
// Codex session JSONL format (rollout-*.jsonl):
|
|
260
|
+
// {type:"session_meta", payload:{id, cwd, git:{repository_url,...}, ...}}
|
|
261
|
+
// {type:"response_item", payload:{type:"message", role, content:[{type:"input_text",text}|{type:"output_text",text}]}}
|
|
262
|
+
// {type:"response_item", payload:{type:"function_call", name, arguments, call_id}}
|
|
263
|
+
// {type:"response_item", payload:{type:"function_call_output", call_id, output}}
|
|
264
|
+
// {type:"event_msg", payload:{type:"token_count"|"agent_reasoning"|...}}
|
|
265
|
+
// {type:"turn_context", payload:{cwd, model, ...}}
|
|
266
|
+
function parseCodexStats(content) {
|
|
267
|
+
let messageCount = 0;
|
|
268
|
+
let toolCallCount = 0;
|
|
269
|
+
const lines = content.split("\n").filter((l) => l.trim());
|
|
270
|
+
for (const line of lines) {
|
|
271
|
+
try {
|
|
272
|
+
const entry = JSON.parse(line);
|
|
273
|
+
if (!entry || typeof entry !== "object")
|
|
274
|
+
continue;
|
|
275
|
+
if (entry.type !== "response_item")
|
|
276
|
+
continue;
|
|
277
|
+
const payloadType = entry.payload?.type;
|
|
278
|
+
if (payloadType === "message") {
|
|
279
|
+
messageCount++;
|
|
280
|
+
}
|
|
281
|
+
else if (payloadType === "function_call") {
|
|
282
|
+
toolCallCount++;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
catch {
|
|
286
|
+
// Skip malformed lines
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return { messageCount, toolCallCount };
|
|
290
|
+
}
|
|
291
|
+
// Pull repository URL from the Codex transcript's session_meta line so we
|
|
292
|
+
// don't depend on the hook's cwd being a git repo.
|
|
293
|
+
function extractCodexRepoUrl(content) {
|
|
294
|
+
const lines = content.split("\n");
|
|
295
|
+
for (const line of lines) {
|
|
296
|
+
const trimmed = line.trim();
|
|
297
|
+
if (!trimmed)
|
|
298
|
+
continue;
|
|
299
|
+
try {
|
|
300
|
+
const entry = JSON.parse(trimmed);
|
|
301
|
+
if (entry?.type === "session_meta") {
|
|
302
|
+
const url = entry.payload?.git?.repository_url;
|
|
303
|
+
if (typeof url === "string" && url.length > 0)
|
|
304
|
+
return url;
|
|
305
|
+
return null; // session_meta is first — no point scanning further
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
catch {
|
|
309
|
+
// Keep scanning
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
return null;
|
|
313
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codiedev",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Connect Claude Code to CodieDev for org-wide session capture",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Connect Claude Code or Codex to CodieDev for org-wide session capture",
|
|
5
5
|
"bin": {
|
|
6
6
|
"codiedev": "./dist/connect.js",
|
|
7
7
|
"codiedev-hook": "./dist/hook.js"
|
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
"codiedev",
|
|
17
17
|
"claude-code",
|
|
18
18
|
"claude",
|
|
19
|
+
"codex",
|
|
20
|
+
"openai",
|
|
19
21
|
"ai",
|
|
20
22
|
"session-capture"
|
|
21
23
|
],
|