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 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.
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -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
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
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
+ });
@@ -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
+ }