codiedev 0.1.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 +23 -0
- package/dist/connect.d.ts +2 -0
- package/dist/connect.js +157 -0
- package/dist/hook.d.ts +2 -0
- package/dist/hook.js +195 -0
- package/dist/utils.d.ts +27 -0
- package/dist/utils.js +175 -0
- package/package.json +34 -0
package/README.md
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# codiedev
|
|
2
|
+
|
|
3
|
+
Connect Claude Code to CodieDev for org-wide session capture.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx codiedev connect
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
The CLI will prompt you for an API token (get one at https://codiedev.com/portal/integrations/claude-code), then install a Claude Code `SessionEnd` hook that uploads transcripts from your tracked repos to your CodieDev workspace.
|
|
12
|
+
|
|
13
|
+
## What gets captured
|
|
14
|
+
|
|
15
|
+
- Only sessions inside repos your workspace already tracks
|
|
16
|
+
- Transcript + basic stats (message count, tool calls)
|
|
17
|
+
- Processed into summaries and embeddings server-side for search in `/portal/memories`
|
|
18
|
+
|
|
19
|
+
## Config
|
|
20
|
+
|
|
21
|
+
Written to `~/.codiedev/config.json`. The `SessionEnd` hook is added to `~/.claude/settings.json`.
|
|
22
|
+
|
|
23
|
+
Override the backend with `CODIEDEV_URL` if needed.
|
package/dist/connect.js
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
+
var ownKeys = function(o) {
|
|
21
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
+
var ar = [];
|
|
23
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
+
return ar;
|
|
25
|
+
};
|
|
26
|
+
return ownKeys(o);
|
|
27
|
+
};
|
|
28
|
+
return function (mod) {
|
|
29
|
+
if (mod && mod.__esModule) return mod;
|
|
30
|
+
var result = {};
|
|
31
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
+
__setModuleDefault(result, mod);
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
})();
|
|
36
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
+
const readline = __importStar(require("readline"));
|
|
38
|
+
const https = __importStar(require("https"));
|
|
39
|
+
const http = __importStar(require("http"));
|
|
40
|
+
const utils_1 = require("./utils");
|
|
41
|
+
const BACKEND_URL = process.env.CODIEDEV_URL || "https://judicious-falcon-861.convex.site";
|
|
42
|
+
function prompt(rl, question) {
|
|
43
|
+
return new Promise((resolve) => {
|
|
44
|
+
rl.question(question, (answer) => {
|
|
45
|
+
resolve(answer.trim());
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
function postJson(url, body) {
|
|
50
|
+
return new Promise((resolve, reject) => {
|
|
51
|
+
const parsed = new URL(url);
|
|
52
|
+
const data = JSON.stringify(body);
|
|
53
|
+
const options = {
|
|
54
|
+
hostname: parsed.hostname,
|
|
55
|
+
port: parsed.port || (parsed.protocol === "https:" ? 443 : 80),
|
|
56
|
+
path: parsed.pathname + parsed.search,
|
|
57
|
+
method: "POST",
|
|
58
|
+
headers: {
|
|
59
|
+
"Content-Type": "application/json",
|
|
60
|
+
"Content-Length": Buffer.byteLength(data),
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
const lib = parsed.protocol === "https:" ? https : http;
|
|
64
|
+
const req = lib.request(options, (res) => {
|
|
65
|
+
let responseData = "";
|
|
66
|
+
res.on("data", (chunk) => {
|
|
67
|
+
responseData += chunk;
|
|
68
|
+
});
|
|
69
|
+
res.on("end", () => {
|
|
70
|
+
try {
|
|
71
|
+
const parsed = JSON.parse(responseData);
|
|
72
|
+
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
|
|
73
|
+
resolve(parsed);
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
reject(new Error(`HTTP ${res.statusCode}: ${parsed.error || responseData}`));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
reject(new Error(`Failed to parse response: ${responseData}`));
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
req.on("error", reject);
|
|
85
|
+
req.write(data);
|
|
86
|
+
req.end();
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
async function main() {
|
|
90
|
+
const rl = readline.createInterface({
|
|
91
|
+
input: process.stdin,
|
|
92
|
+
output: process.stdout,
|
|
93
|
+
});
|
|
94
|
+
console.log("\nWelcome to CodieDev CLI\n");
|
|
95
|
+
console.log("This will connect Claude Code to your CodieDev workspace and install");
|
|
96
|
+
console.log("the session capture hook for org-wide session sharing.\n");
|
|
97
|
+
let token;
|
|
98
|
+
try {
|
|
99
|
+
token = await prompt(rl, "Enter your CodieDev API token: ");
|
|
100
|
+
}
|
|
101
|
+
finally {
|
|
102
|
+
rl.close();
|
|
103
|
+
}
|
|
104
|
+
if (!token) {
|
|
105
|
+
console.error("Error: API token is required.");
|
|
106
|
+
process.exit(1);
|
|
107
|
+
}
|
|
108
|
+
console.log("\nValidating token...");
|
|
109
|
+
let responseData;
|
|
110
|
+
try {
|
|
111
|
+
responseData = await postJson(`${BACKEND_URL}/api/cli/validateToken`, {
|
|
112
|
+
token,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
console.error(`\nError: Failed to validate token — ${err.message}`);
|
|
117
|
+
process.exit(1);
|
|
118
|
+
}
|
|
119
|
+
// Extract fields from response
|
|
120
|
+
const companyId = responseData.companyId;
|
|
121
|
+
const companyName = responseData.companyName;
|
|
122
|
+
const repos = responseData.repos ?? [];
|
|
123
|
+
if (!companyId || !companyName) {
|
|
124
|
+
console.error("\nError: Invalid response from server. Token may be invalid.");
|
|
125
|
+
process.exit(1);
|
|
126
|
+
}
|
|
127
|
+
const config = {
|
|
128
|
+
token,
|
|
129
|
+
companyId,
|
|
130
|
+
companyName,
|
|
131
|
+
repos,
|
|
132
|
+
lastSynced: new Date().toISOString(),
|
|
133
|
+
backendUrl: BACKEND_URL,
|
|
134
|
+
};
|
|
135
|
+
try {
|
|
136
|
+
(0, utils_1.writeConfig)(config);
|
|
137
|
+
}
|
|
138
|
+
catch (err) {
|
|
139
|
+
console.error(`\nError: Failed to save config — ${err.message}`);
|
|
140
|
+
process.exit(1);
|
|
141
|
+
}
|
|
142
|
+
try {
|
|
143
|
+
(0, utils_1.installHook)();
|
|
144
|
+
}
|
|
145
|
+
catch (err) {
|
|
146
|
+
console.error(`\nWarning: Failed to install SessionEnd hook — ${err.message}`);
|
|
147
|
+
console.error("You can install it manually by adding npx codiedev-hook capture to your ~/.claude/settings.json hooks.");
|
|
148
|
+
// Don't exit — config was saved successfully
|
|
149
|
+
}
|
|
150
|
+
console.log(`\nConnected to ${companyName}`);
|
|
151
|
+
console.log(`Tracking ${repos.length} repo${repos.length === 1 ? "" : "s"}`);
|
|
152
|
+
console.log("SessionEnd hook installed — sessions will be captured automatically.\n");
|
|
153
|
+
}
|
|
154
|
+
main().catch((err) => {
|
|
155
|
+
console.error("Unexpected error:", err);
|
|
156
|
+
process.exit(1);
|
|
157
|
+
});
|
package/dist/hook.d.ts
ADDED
package/dist/hook.js
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
+
var ownKeys = function(o) {
|
|
21
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
+
var ar = [];
|
|
23
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
+
return ar;
|
|
25
|
+
};
|
|
26
|
+
return ownKeys(o);
|
|
27
|
+
};
|
|
28
|
+
return function (mod) {
|
|
29
|
+
if (mod && mod.__esModule) return mod;
|
|
30
|
+
var result = {};
|
|
31
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
+
__setModuleDefault(result, mod);
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
})();
|
|
36
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const https = __importStar(require("https"));
|
|
39
|
+
const http = __importStar(require("http"));
|
|
40
|
+
const utils_1 = require("./utils");
|
|
41
|
+
function postJson(url, body, bearerToken) {
|
|
42
|
+
return new Promise((resolve, reject) => {
|
|
43
|
+
const parsed = new URL(url);
|
|
44
|
+
const data = JSON.stringify(body);
|
|
45
|
+
const options = {
|
|
46
|
+
hostname: parsed.hostname,
|
|
47
|
+
port: parsed.port || (parsed.protocol === "https:" ? 443 : 80),
|
|
48
|
+
path: parsed.pathname + parsed.search,
|
|
49
|
+
method: "POST",
|
|
50
|
+
headers: {
|
|
51
|
+
"Content-Type": "application/json",
|
|
52
|
+
"Content-Length": Buffer.byteLength(data),
|
|
53
|
+
Authorization: `Bearer ${bearerToken}`,
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
const lib = parsed.protocol === "https:" ? https : http;
|
|
57
|
+
const req = lib.request(options, (res) => {
|
|
58
|
+
let responseData = "";
|
|
59
|
+
res.on("data", (chunk) => {
|
|
60
|
+
responseData += chunk;
|
|
61
|
+
});
|
|
62
|
+
res.on("end", () => {
|
|
63
|
+
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
|
|
64
|
+
resolve();
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
reject(new Error(`HTTP ${res.statusCode}: ${responseData}`));
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
req.on("error", reject);
|
|
72
|
+
req.write(data);
|
|
73
|
+
req.end();
|
|
74
|
+
});
|
|
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
|
+
async function readStdin() {
|
|
112
|
+
return new Promise((resolve, reject) => {
|
|
113
|
+
let data = "";
|
|
114
|
+
process.stdin.setEncoding("utf8");
|
|
115
|
+
process.stdin.on("data", (chunk) => {
|
|
116
|
+
data += chunk;
|
|
117
|
+
});
|
|
118
|
+
process.stdin.on("end", () => resolve(data));
|
|
119
|
+
process.stdin.on("error", reject);
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
async function main() {
|
|
123
|
+
// Read hook input from stdin
|
|
124
|
+
let hookInput = {};
|
|
125
|
+
try {
|
|
126
|
+
const raw = await readStdin();
|
|
127
|
+
if (raw.trim()) {
|
|
128
|
+
hookInput = JSON.parse(raw);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
// If stdin is malformed, exit silently
|
|
133
|
+
process.exit(0);
|
|
134
|
+
}
|
|
135
|
+
const { session_id, transcript_path, cwd } = hookInput;
|
|
136
|
+
// Read config — if missing, exit silently
|
|
137
|
+
const config = (0, utils_1.readConfig)();
|
|
138
|
+
if (!config) {
|
|
139
|
+
process.exit(0);
|
|
140
|
+
}
|
|
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
|
+
if (!transcript_path) {
|
|
157
|
+
process.exit(0);
|
|
158
|
+
}
|
|
159
|
+
// Read transcript file
|
|
160
|
+
let transcriptContent;
|
|
161
|
+
try {
|
|
162
|
+
transcriptContent = fs.readFileSync(transcript_path, "utf8");
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
// Can't read transcript — exit silently
|
|
166
|
+
process.exit(0);
|
|
167
|
+
}
|
|
168
|
+
// Parse transcript stats
|
|
169
|
+
const { messageCount, toolCallCount } = parseTranscriptStats(transcript_path);
|
|
170
|
+
// Base64 encode transcript
|
|
171
|
+
const transcriptBase64 = Buffer.from(transcriptContent, "utf8").toString("base64");
|
|
172
|
+
// POST to backend
|
|
173
|
+
const payload = {
|
|
174
|
+
sessionId: session_id,
|
|
175
|
+
repoUrl: remoteUrl,
|
|
176
|
+
repoId: matchedRepo.repoId,
|
|
177
|
+
companyId: config.companyId,
|
|
178
|
+
transcript: transcriptBase64,
|
|
179
|
+
messageCount,
|
|
180
|
+
toolCallCount,
|
|
181
|
+
capturedAt: Date.now(),
|
|
182
|
+
};
|
|
183
|
+
try {
|
|
184
|
+
await postJson(`${config.backendUrl}/api/captureSession`, payload, config.token);
|
|
185
|
+
}
|
|
186
|
+
catch (err) {
|
|
187
|
+
// Write warning to stderr, but exit 0 — never block Claude Code
|
|
188
|
+
process.stderr.write(`[codiedev-hook] Warning: Failed to capture session — ${err.message}\n`);
|
|
189
|
+
}
|
|
190
|
+
process.exit(0);
|
|
191
|
+
}
|
|
192
|
+
main().catch(() => {
|
|
193
|
+
// Never block Claude Code on any error
|
|
194
|
+
process.exit(0);
|
|
195
|
+
});
|
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export interface CodiedevConfig {
|
|
2
|
+
token: string;
|
|
3
|
+
companyId: string;
|
|
4
|
+
companyName: string;
|
|
5
|
+
repos: Array<{
|
|
6
|
+
repoId: string;
|
|
7
|
+
url: string;
|
|
8
|
+
fullName: string;
|
|
9
|
+
}>;
|
|
10
|
+
lastSynced: string;
|
|
11
|
+
backendUrl: string;
|
|
12
|
+
}
|
|
13
|
+
export declare function readConfig(): CodiedevConfig | null;
|
|
14
|
+
export declare function writeConfig(config: CodiedevConfig): void;
|
|
15
|
+
export declare function getGitRemoteUrl(cwd: string): string | null;
|
|
16
|
+
export declare function normalizeRepoUrl(url: string): string;
|
|
17
|
+
export declare function matchRepo(remoteUrl: string, repos: Array<{
|
|
18
|
+
repoId: string;
|
|
19
|
+
url: string;
|
|
20
|
+
fullName: string;
|
|
21
|
+
}>): {
|
|
22
|
+
repoId: string;
|
|
23
|
+
url: string;
|
|
24
|
+
fullName: string;
|
|
25
|
+
} | null;
|
|
26
|
+
export declare function hashToken(token: string): string;
|
|
27
|
+
export declare function installHook(): void;
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.readConfig = readConfig;
|
|
37
|
+
exports.writeConfig = writeConfig;
|
|
38
|
+
exports.getGitRemoteUrl = getGitRemoteUrl;
|
|
39
|
+
exports.normalizeRepoUrl = normalizeRepoUrl;
|
|
40
|
+
exports.matchRepo = matchRepo;
|
|
41
|
+
exports.hashToken = hashToken;
|
|
42
|
+
exports.installHook = installHook;
|
|
43
|
+
const fs = __importStar(require("fs"));
|
|
44
|
+
const path = __importStar(require("path"));
|
|
45
|
+
const os = __importStar(require("os"));
|
|
46
|
+
const child_process_1 = require("child_process");
|
|
47
|
+
const CONFIG_DIR = path.join(os.homedir(), ".codiedev");
|
|
48
|
+
const CONFIG_PATH = path.join(CONFIG_DIR, "config.json");
|
|
49
|
+
function readConfig() {
|
|
50
|
+
try {
|
|
51
|
+
if (!fs.existsSync(CONFIG_PATH))
|
|
52
|
+
return null;
|
|
53
|
+
const raw = fs.readFileSync(CONFIG_PATH, "utf8");
|
|
54
|
+
return JSON.parse(raw);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function writeConfig(config) {
|
|
61
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
62
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
63
|
+
}
|
|
64
|
+
fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), "utf8");
|
|
65
|
+
}
|
|
66
|
+
function getGitRemoteUrl(cwd) {
|
|
67
|
+
try {
|
|
68
|
+
const url = (0, child_process_1.execSync)("git remote get-url origin", {
|
|
69
|
+
cwd,
|
|
70
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
71
|
+
})
|
|
72
|
+
.toString()
|
|
73
|
+
.trim();
|
|
74
|
+
return url || null;
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function normalizeRepoUrl(url) {
|
|
81
|
+
// Normalize SSH (git@github.com:org/repo.git) and HTTPS (https://github.com/org/repo.git)
|
|
82
|
+
// to a comparable form: github.com/org/repo
|
|
83
|
+
let normalized = url.trim();
|
|
84
|
+
// Strip trailing .git
|
|
85
|
+
if (normalized.endsWith(".git")) {
|
|
86
|
+
normalized = normalized.slice(0, -4);
|
|
87
|
+
}
|
|
88
|
+
// Handle SSH format: git@github.com:org/repo → github.com/org/repo
|
|
89
|
+
const sshMatch = normalized.match(/^git@([^:]+):(.+)$/);
|
|
90
|
+
if (sshMatch) {
|
|
91
|
+
return `${sshMatch[1]}/${sshMatch[2]}`;
|
|
92
|
+
}
|
|
93
|
+
// Handle HTTPS format: https://github.com/org/repo → github.com/org/repo
|
|
94
|
+
const httpsMatch = normalized.match(/^https?:\/\/(.+)$/);
|
|
95
|
+
if (httpsMatch) {
|
|
96
|
+
return httpsMatch[1];
|
|
97
|
+
}
|
|
98
|
+
return normalized;
|
|
99
|
+
}
|
|
100
|
+
function matchRepo(remoteUrl, repos) {
|
|
101
|
+
const normalizedRemote = normalizeRepoUrl(remoteUrl);
|
|
102
|
+
for (const repo of repos) {
|
|
103
|
+
const normalizedUrl = normalizeRepoUrl(repo.url);
|
|
104
|
+
const normalizedName = repo.fullName.toLowerCase();
|
|
105
|
+
if (normalizedRemote === normalizedUrl ||
|
|
106
|
+
normalizedRemote.endsWith(`/${normalizedName}`) ||
|
|
107
|
+
normalizedUrl.endsWith(`/${normalizedName}`)) {
|
|
108
|
+
return repo;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
function hashToken(token) {
|
|
114
|
+
let hash = 0;
|
|
115
|
+
for (let i = 0; i < token.length; i++) {
|
|
116
|
+
const char = token.charCodeAt(i);
|
|
117
|
+
hash = ((hash << 5) - hash) + char;
|
|
118
|
+
hash = hash & hash;
|
|
119
|
+
}
|
|
120
|
+
return hash.toString(36);
|
|
121
|
+
}
|
|
122
|
+
const CLAUDE_SETTINGS_PATH = path.join(os.homedir(), ".claude", "settings.json");
|
|
123
|
+
function installHook() {
|
|
124
|
+
let settings = {};
|
|
125
|
+
try {
|
|
126
|
+
if (fs.existsSync(CLAUDE_SETTINGS_PATH)) {
|
|
127
|
+
const raw = fs.readFileSync(CLAUDE_SETTINGS_PATH, "utf8");
|
|
128
|
+
settings = JSON.parse(raw);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
// Start with empty settings if file doesn't exist or is invalid
|
|
133
|
+
}
|
|
134
|
+
// Ensure hooks object exists
|
|
135
|
+
if (!settings.hooks) {
|
|
136
|
+
settings.hooks = {};
|
|
137
|
+
}
|
|
138
|
+
const hooks = settings.hooks;
|
|
139
|
+
// Ensure SessionEnd array exists
|
|
140
|
+
if (!hooks.SessionEnd) {
|
|
141
|
+
hooks.SessionEnd = [];
|
|
142
|
+
}
|
|
143
|
+
const sessionEndHooks = hooks.SessionEnd;
|
|
144
|
+
// Check if codiedev-hook is already installed
|
|
145
|
+
const alreadyInstalled = sessionEndHooks.some((hook) => {
|
|
146
|
+
const matcher = hook.matcher;
|
|
147
|
+
const hooks2 = hook.hooks;
|
|
148
|
+
if (hooks2) {
|
|
149
|
+
return hooks2.some((h) => {
|
|
150
|
+
const cmd = h.command;
|
|
151
|
+
return cmd && cmd.includes("codiedev-hook");
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
return matcher && matcher.includes("codiedev-hook");
|
|
155
|
+
});
|
|
156
|
+
if (alreadyInstalled) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
// Add the codiedev-hook capture entry
|
|
160
|
+
sessionEndHooks.push({
|
|
161
|
+
matcher: ".*",
|
|
162
|
+
hooks: [
|
|
163
|
+
{
|
|
164
|
+
type: "command",
|
|
165
|
+
command: "npx codiedev-hook capture",
|
|
166
|
+
},
|
|
167
|
+
],
|
|
168
|
+
});
|
|
169
|
+
// Ensure directory exists
|
|
170
|
+
const settingsDir = path.dirname(CLAUDE_SETTINGS_PATH);
|
|
171
|
+
if (!fs.existsSync(settingsDir)) {
|
|
172
|
+
fs.mkdirSync(settingsDir, { recursive: true });
|
|
173
|
+
}
|
|
174
|
+
fs.writeFileSync(CLAUDE_SETTINGS_PATH, JSON.stringify(settings, null, 2), "utf8");
|
|
175
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "codiedev",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Connect Claude Code to CodieDev for org-wide session capture",
|
|
5
|
+
"bin": {
|
|
6
|
+
"codiedev": "./dist/connect.js",
|
|
7
|
+
"codiedev-hook": "./dist/hook.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc",
|
|
11
|
+
"dev": "tsc --watch",
|
|
12
|
+
"prepublishOnly": "tsc"
|
|
13
|
+
},
|
|
14
|
+
"files": ["dist", "README.md"],
|
|
15
|
+
"keywords": [
|
|
16
|
+
"codiedev",
|
|
17
|
+
"claude-code",
|
|
18
|
+
"claude",
|
|
19
|
+
"ai",
|
|
20
|
+
"session-capture"
|
|
21
|
+
],
|
|
22
|
+
"homepage": "https://codiedev.com",
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": ">=18"
|
|
26
|
+
},
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"access": "public"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"typescript": "^5.4.0",
|
|
32
|
+
"@types/node": "^20.0.0"
|
|
33
|
+
}
|
|
34
|
+
}
|