codiedev 0.8.2 → 0.9.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 +1 -1
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +5 -5
- package/dist/commands/createTicket.js +1 -1
- package/dist/commands/doctor.js +3 -3
- package/dist/commands/library.js +1 -1
- package/dist/commands/shared.d.ts +2 -2
- package/dist/commands/shared.js +2 -2
- package/dist/connect.js +5 -5
- package/dist/hook.js +70 -1
- package/dist/mcp.d.ts +2 -2
- package/dist/mcp.js +11 -11
- package/dist/utils.d.ts +6 -4
- package/dist/utils.js +61 -31
- package/package.json +2 -2
- package/dist/hookTransport.d.ts +0 -13
- package/dist/hookTransport.js +0 -136
package/README.md
CHANGED
package/dist/cli.d.ts
CHANGED
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* Codiedev CLI dispatcher.
|
|
5
5
|
*
|
|
6
6
|
* Subcommands:
|
|
7
7
|
* codiedev connect — link agent CLIs to your org
|
|
@@ -35,7 +35,7 @@ const send_1 = require("./commands/send");
|
|
|
35
35
|
const react_1 = require("./commands/react");
|
|
36
36
|
const version_1 = require("./version");
|
|
37
37
|
const HELP = `
|
|
38
|
-
|
|
38
|
+
Codiedev CLI
|
|
39
39
|
|
|
40
40
|
The team artifact layer for agent-coded work. Share specs and reviews
|
|
41
41
|
with teammates, get pinged when they need your input, pick up where
|
|
@@ -132,7 +132,7 @@ Docs: codiedev docs
|
|
|
132
132
|
`.trim();
|
|
133
133
|
const COMMAND_HELP = {
|
|
134
134
|
connect: `
|
|
135
|
-
codiedev connect — link your agent CLI to
|
|
135
|
+
codiedev connect — link your agent CLI to Codiedev
|
|
136
136
|
|
|
137
137
|
Interactive setup. Prompts for an API token, then installs:
|
|
138
138
|
- ~/.codiedev/config.json (token + org + tracked repos)
|
|
@@ -272,11 +272,11 @@ codiedev create-ticket — forward-write a scoped ticket plan
|
|
|
272
272
|
`.trim(),
|
|
273
273
|
};
|
|
274
274
|
const DOCS = `
|
|
275
|
-
|
|
275
|
+
Codiedev — usage guide
|
|
276
276
|
|
|
277
277
|
## What this is
|
|
278
278
|
|
|
279
|
-
|
|
279
|
+
Codiedev is a team artifact layer on top of your coding agent (Claude
|
|
280
280
|
Code, Codex, etc.). It persists the *durable* outputs of agent work —
|
|
281
281
|
specs, reviews, decisions, proposals, and captured thoughts — so:
|
|
282
282
|
|
|
@@ -65,7 +65,7 @@ function resolveRepoIds(fullNames, repos) {
|
|
|
65
65
|
for (const fn of fullNames) {
|
|
66
66
|
const id = lookup.get(fn.toLowerCase());
|
|
67
67
|
if (!id) {
|
|
68
|
-
throw new Error(`Repo "${fn}" is not connected to your
|
|
68
|
+
throw new Error(`Repo "${fn}" is not connected to your Codiedev account. Run \`codiedev connect\` to refresh, or push the missing repo through Settings.`);
|
|
69
69
|
}
|
|
70
70
|
ids.push(id);
|
|
71
71
|
}
|
package/dist/commands/doctor.js
CHANGED
|
@@ -134,13 +134,13 @@ function hasGlabCli() {
|
|
|
134
134
|
}
|
|
135
135
|
async function runDoctor(_args) {
|
|
136
136
|
const checks = [];
|
|
137
|
-
console.log("\
|
|
137
|
+
console.log("\nCodiedev doctor — checking your setup…\n");
|
|
138
138
|
await (0, upgradeNudge_1.maybePrintUpgradeNudge)();
|
|
139
139
|
// 1. Config file
|
|
140
140
|
const config = (0, utils_1.readConfig)();
|
|
141
141
|
if (!config) {
|
|
142
142
|
checks.push({
|
|
143
|
-
name: "
|
|
143
|
+
name: "Codiedev config (~/.codiedev/config.json)",
|
|
144
144
|
status: "fail",
|
|
145
145
|
detail: "Not found. Run `codiedev connect` first.",
|
|
146
146
|
});
|
|
@@ -148,7 +148,7 @@ async function runDoctor(_args) {
|
|
|
148
148
|
process.exit(1);
|
|
149
149
|
}
|
|
150
150
|
checks.push({
|
|
151
|
-
name: "
|
|
151
|
+
name: "Codiedev config",
|
|
152
152
|
status: "pass",
|
|
153
153
|
detail: `connected to ${config.companyName}`,
|
|
154
154
|
});
|
package/dist/commands/library.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
// `codiedev library [--scope mine|shared|all] [--kind doc|skill] [--tag X]
|
|
3
|
-
// [--type spec|...] [--limit N]` — list artifacts in your
|
|
3
|
+
// [--type spec|...] [--limit N]` — list artifacts in your Codiedev workspace.
|
|
4
4
|
// Server applies companyId scoping; --scope filters within that.
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.runLibrary = runLibrary;
|
|
@@ -8,12 +8,12 @@ import { CodiedevConfig } from "../utils";
|
|
|
8
8
|
*/
|
|
9
9
|
export declare function applyConfigEnvOverrides(config: CodiedevConfig): CodiedevConfig;
|
|
10
10
|
/**
|
|
11
|
-
* Require a connected
|
|
11
|
+
* Require a connected Codiedev config. Exits the process with a helpful
|
|
12
12
|
* message if the user hasn't run `codiedev connect` yet.
|
|
13
13
|
*/
|
|
14
14
|
export declare function requireConfig(): CodiedevConfig;
|
|
15
15
|
/**
|
|
16
|
-
* Perform an authenticated HTTP request against the
|
|
16
|
+
* Perform an authenticated HTTP request against the Codiedev backend.
|
|
17
17
|
* Returns the parsed JSON response body. Throws on non-2xx.
|
|
18
18
|
*/
|
|
19
19
|
export declare function apiRequest<T = unknown>(method: "GET" | "POST", path: string, options: {
|
package/dist/commands/shared.js
CHANGED
|
@@ -61,7 +61,7 @@ function applyConfigEnvOverrides(config) {
|
|
|
61
61
|
return config;
|
|
62
62
|
}
|
|
63
63
|
/**
|
|
64
|
-
* Require a connected
|
|
64
|
+
* Require a connected Codiedev config. Exits the process with a helpful
|
|
65
65
|
* message if the user hasn't run `codiedev connect` yet.
|
|
66
66
|
*/
|
|
67
67
|
function requireConfig() {
|
|
@@ -73,7 +73,7 @@ function requireConfig() {
|
|
|
73
73
|
return applyConfigEnvOverrides(config);
|
|
74
74
|
}
|
|
75
75
|
/**
|
|
76
|
-
* Perform an authenticated HTTP request against the
|
|
76
|
+
* Perform an authenticated HTTP request against the Codiedev backend.
|
|
77
77
|
* Returns the parsed JSON response body. Throws on non-2xx.
|
|
78
78
|
*/
|
|
79
79
|
function apiRequest(method, path, options) {
|
package/dist/connect.js
CHANGED
|
@@ -161,9 +161,9 @@ async function runConnect(args = []) {
|
|
|
161
161
|
// mint a fresh token from the portal — which is what bit Sonifi's first
|
|
162
162
|
// engineer when he interpreted "no visible token" as "the old one is gone".
|
|
163
163
|
const force = args.includes("--force") || args.includes("-f");
|
|
164
|
-
console.log("\nWelcome to
|
|
164
|
+
console.log("\nWelcome to Codiedev CLI\n");
|
|
165
165
|
await (0, upgradeNudge_1.maybePrintUpgradeNudge)();
|
|
166
|
-
console.log("This will connect your coding agent to your
|
|
166
|
+
console.log("This will connect your coding agent to your Codiedev workspace and install");
|
|
167
167
|
console.log("the session capture hook for org-wide session sharing.\n");
|
|
168
168
|
const saved = (0, utils_1.readConfig)();
|
|
169
169
|
const decision = (0, connectFlow_1.resolveConnectToken)({ savedConfig: saved, force });
|
|
@@ -175,7 +175,7 @@ async function runConnect(args = []) {
|
|
|
175
175
|
else {
|
|
176
176
|
// Hidden prompt — masks the token with `*` as it's typed/pasted so it
|
|
177
177
|
// doesn't end up in terminal scrollback or screenshots.
|
|
178
|
-
token = await promptHidden("Enter your
|
|
178
|
+
token = await promptHidden("Enter your Codiedev API token: ");
|
|
179
179
|
if (!token) {
|
|
180
180
|
console.error("Error: API token is required.");
|
|
181
181
|
process.exit(1);
|
|
@@ -278,7 +278,7 @@ async function runConnect(args = []) {
|
|
|
278
278
|
}
|
|
279
279
|
};
|
|
280
280
|
if (hasClaude) {
|
|
281
|
-
runInstall("claude-code-hook", utils_1.installHook, "Claude Code SessionEnd
|
|
281
|
+
runInstall("claude-code-hook", utils_1.installHook, "Claude Code SessionEnd (capture) + UserPromptSubmit (skill discovery) hooks (~/.claude/settings.json)", "claudeCode-hook");
|
|
282
282
|
runInstall("claude-code-instructions", utils_1.installClaudeCodeInstructions, "Claude Code agent instructions (~/.claude/CLAUDE.md)", "claudeCode-instructions");
|
|
283
283
|
}
|
|
284
284
|
if (hasCodex) {
|
|
@@ -319,7 +319,7 @@ async function runConnect(args = []) {
|
|
|
319
319
|
console.log(`\nConnected to ${companyName}`);
|
|
320
320
|
console.log(`Tracking ${repos.length} repo${repos.length === 1 ? "" : "s"}`);
|
|
321
321
|
if (installed.length > 0) {
|
|
322
|
-
console.log("Installed
|
|
322
|
+
console.log("Installed hook(s):");
|
|
323
323
|
for (const target of installed) {
|
|
324
324
|
console.log(` - ${target}`);
|
|
325
325
|
}
|
package/dist/hook.js
CHANGED
|
@@ -39,6 +39,67 @@ const https = __importStar(require("https"));
|
|
|
39
39
|
const http = __importStar(require("http"));
|
|
40
40
|
const utils_1 = require("./utils");
|
|
41
41
|
const version_1 = require("./version");
|
|
42
|
+
function getJson(url, bearerToken) {
|
|
43
|
+
return new Promise((resolve, reject) => {
|
|
44
|
+
const parsed = new URL(url);
|
|
45
|
+
const options = {
|
|
46
|
+
hostname: parsed.hostname,
|
|
47
|
+
port: parsed.port || (parsed.protocol === "https:" ? 443 : 80),
|
|
48
|
+
path: parsed.pathname + parsed.search,
|
|
49
|
+
method: "GET",
|
|
50
|
+
headers: {
|
|
51
|
+
Authorization: `Bearer ${bearerToken}`,
|
|
52
|
+
"X-Codiedev-Cli-Version": version_1.CLI_VERSION,
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
const lib = parsed.protocol === "https:" ? https : http;
|
|
56
|
+
const req = lib.request(options, (res) => {
|
|
57
|
+
let body = "";
|
|
58
|
+
res.on("data", (c) => (body += c));
|
|
59
|
+
res.on("end", () => {
|
|
60
|
+
try {
|
|
61
|
+
resolve(JSON.parse(body));
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
reject(new Error("bad json"));
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
req.on("error", reject);
|
|
69
|
+
req.end();
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
// UserPromptSubmit: query skill discovery for the prompt and, if a team skill
|
|
73
|
+
// clears the confidence threshold, inject it as additionalContext so the agent
|
|
74
|
+
// can offer to pull+run it. Fully silent on miss/error — never blocks input.
|
|
75
|
+
async function runDiscover(prompt) {
|
|
76
|
+
if (!prompt || !prompt.trim())
|
|
77
|
+
process.exit(0);
|
|
78
|
+
const config = (0, utils_1.readConfig)();
|
|
79
|
+
if (!config)
|
|
80
|
+
process.exit(0);
|
|
81
|
+
try {
|
|
82
|
+
const res = await getJson(`${config.backendUrl}/api/cli/skill-discovery?q=${encodeURIComponent(prompt)}`, config.token);
|
|
83
|
+
const skills = res?.skills ?? [];
|
|
84
|
+
if (skills.length > 0) {
|
|
85
|
+
const lines = skills
|
|
86
|
+
.slice(0, 3)
|
|
87
|
+
.map((s) => `- ${s.key} — ${(s.score * 100).toFixed(0)}% match`)
|
|
88
|
+
.join("\n");
|
|
89
|
+
const additionalContext = "Your team has a CodieDev skill that fits this task — prefer it over improvising:\n" +
|
|
90
|
+
lines +
|
|
91
|
+
"\nPull and follow it now: `codiedev pull <key>`. It encodes the team's vetted " +
|
|
92
|
+
"approach. Skip it only if it clearly doesn't apply.";
|
|
93
|
+
process.stdout.write(JSON.stringify({
|
|
94
|
+
hookSpecificOutput: { hookEventName: "UserPromptSubmit", additionalContext },
|
|
95
|
+
}));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
// silent — discovery is best-effort
|
|
100
|
+
}
|
|
101
|
+
process.exit(0);
|
|
102
|
+
}
|
|
42
103
|
function postJson(url, body, bearerToken) {
|
|
43
104
|
return new Promise((resolve, reject) => {
|
|
44
105
|
const parsed = new URL(url);
|
|
@@ -94,7 +155,6 @@ function resolveMode(arg) {
|
|
|
94
155
|
return "claude-code";
|
|
95
156
|
}
|
|
96
157
|
async function main() {
|
|
97
|
-
const mode = resolveMode(process.argv[2]);
|
|
98
158
|
let hookInput = {};
|
|
99
159
|
try {
|
|
100
160
|
const raw = await readStdin();
|
|
@@ -105,6 +165,15 @@ async function main() {
|
|
|
105
165
|
catch {
|
|
106
166
|
process.exit(0);
|
|
107
167
|
}
|
|
168
|
+
// UserPromptSubmit discovery path — separate from session capture.
|
|
169
|
+
if (process.argv[2] === "discover") {
|
|
170
|
+
await runDiscover(hookInput.prompt);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
const mode = resolveMode(process.argv[2]);
|
|
174
|
+
// Self-heal: a user who upgraded the CLI but hasn't re-run `connect` gets the
|
|
175
|
+
// discovery hook wired on their next SessionEnd, without re-connecting.
|
|
176
|
+
(0, utils_1.ensureDiscoveryHookInstalled)();
|
|
108
177
|
const { session_id, transcript_path, cwd, workspace_roots } = hookInput;
|
|
109
178
|
const workspaceRoot = workspace_roots && workspace_roots.length > 0
|
|
110
179
|
? workspace_roots[0]
|
package/dist/mcp.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* Codiedev MCP server.
|
|
4
4
|
*
|
|
5
|
-
* Exposes the
|
|
5
|
+
* Exposes the Codiedev artifact-collaboration surface as Model Context
|
|
6
6
|
* Protocol tools so Claude Code / Codex / any MCP-capable agent can invoke
|
|
7
7
|
* them natively from natural language (e.g. "push the spec we just worked on").
|
|
8
8
|
*
|
package/dist/mcp.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* Codiedev MCP server.
|
|
5
5
|
*
|
|
6
|
-
* Exposes the
|
|
6
|
+
* Exposes the Codiedev artifact-collaboration surface as Model Context
|
|
7
7
|
* Protocol tools so Claude Code / Codex / any MCP-capable agent can invoke
|
|
8
8
|
* them natively from natural language (e.g. "push the spec we just worked on").
|
|
9
9
|
*
|
|
@@ -70,7 +70,7 @@ const PKG_VERSION = (() => {
|
|
|
70
70
|
const TOOLS = [
|
|
71
71
|
{
|
|
72
72
|
name: "codiedev_push",
|
|
73
|
-
description: "Push any markdown artifact (doc or skill) to the team's
|
|
73
|
+
description: "Push any markdown artifact (doc or skill) to the team's Codiedev " +
|
|
74
74
|
"artifact layer so teammates can see, discuss, and run it. Works for " +
|
|
75
75
|
"specs, decisions, proposals, bugfixes, reviews, notes, AND " +
|
|
76
76
|
"Claude Code / Codex skills (files with SKILL.md frontmatter — " +
|
|
@@ -127,7 +127,7 @@ const TOOLS = [
|
|
|
127
127
|
},
|
|
128
128
|
{
|
|
129
129
|
name: "codiedev_pull",
|
|
130
|
-
description: "Fetch any artifact (doc or skill) from the team's
|
|
130
|
+
description: "Fetch any artifact (doc or skill) from the team's Codiedev artifact " +
|
|
131
131
|
"layer. Works for specs, decisions, proposals, bugfixes, reviews, " +
|
|
132
132
|
"notes, AND skills (SKILL.md frontmatter files surfaced as slash " +
|
|
133
133
|
"commands). Use when the user asks to pull, grab, fetch, or resume " +
|
|
@@ -171,7 +171,7 @@ const TOOLS = [
|
|
|
171
171
|
},
|
|
172
172
|
{
|
|
173
173
|
name: "codiedev_ping",
|
|
174
|
-
description: "Send a message to a teammate on
|
|
174
|
+
description: "Send a message to a teammate on Codiedev, optionally attached to a " +
|
|
175
175
|
"specific artifact. Use when the user asks to ping, ask, share with, " +
|
|
176
176
|
"or get feedback from a teammate by name (e.g. 'ping a teammate about " +
|
|
177
177
|
"this spec'). Recipients are resolved by first name, full name, or email " +
|
|
@@ -200,7 +200,7 @@ const TOOLS = [
|
|
|
200
200
|
},
|
|
201
201
|
{
|
|
202
202
|
name: "codiedev_inbox",
|
|
203
|
-
description: "Check the user's
|
|
203
|
+
description: "Check the user's Codiedev inbox for messages from teammates. Use " +
|
|
204
204
|
"when the user asks 'any messages?', 'what did [name] say?', 'check " +
|
|
205
205
|
"my inbox', or when they open a session and want to catch up on " +
|
|
206
206
|
"what's waiting for them. Returns a list with sender, subject " +
|
|
@@ -224,7 +224,7 @@ const TOOLS = [
|
|
|
224
224
|
description: "Capture a passing thought and link it to the current agent session. " +
|
|
225
225
|
"Use when the user says 'note that', 'remember', 'worth capturing', " +
|
|
226
226
|
"'jot this down', or drops a short observation that isn't a full " +
|
|
227
|
-
"spec. Notes show up in the user's
|
|
227
|
+
"spec. Notes show up in the user's Codiedev memory feed.",
|
|
228
228
|
inputSchema: {
|
|
229
229
|
type: "object",
|
|
230
230
|
properties: {
|
|
@@ -238,7 +238,7 @@ const TOOLS = [
|
|
|
238
238
|
},
|
|
239
239
|
{
|
|
240
240
|
name: "codiedev_promote",
|
|
241
|
-
description: "Promote an auto-extracted
|
|
241
|
+
description: "Promote an auto-extracted Codiedev artifact (spec/bugfix/decision " +
|
|
242
242
|
"produced by the session-capture pipeline) to an authored artifact " +
|
|
243
243
|
"so teammates can collaborate on it. Use when the user says 'publish " +
|
|
244
244
|
"the extracted spec', 'promote this', or 'make the auto-extracted X " +
|
|
@@ -248,7 +248,7 @@ const TOOLS = [
|
|
|
248
248
|
properties: {
|
|
249
249
|
artifactId: {
|
|
250
250
|
type: "string",
|
|
251
|
-
description: "The extracted artifact's id from
|
|
251
|
+
description: "The extracted artifact's id from Codiedev.",
|
|
252
252
|
},
|
|
253
253
|
keyOverride: {
|
|
254
254
|
type: "string",
|
|
@@ -545,7 +545,7 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
|
545
545
|
content: [
|
|
546
546
|
{
|
|
547
547
|
type: "text",
|
|
548
|
-
text: "
|
|
548
|
+
text: "Codiedev is not connected on this machine. Ask the user to run " +
|
|
549
549
|
"`npx codiedev connect` with an API token from " +
|
|
550
550
|
"https://codiedev.com/portal/integrations/claude-code.",
|
|
551
551
|
},
|
|
@@ -579,7 +579,7 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
|
579
579
|
content: [
|
|
580
580
|
{
|
|
581
581
|
type: "text",
|
|
582
|
-
text: `
|
|
582
|
+
text: `Codiedev error: ${err.message}`,
|
|
583
583
|
},
|
|
584
584
|
],
|
|
585
585
|
};
|
package/dist/utils.d.ts
CHANGED
|
@@ -33,24 +33,26 @@ export declare function cursorInstalled(): boolean;
|
|
|
33
33
|
* the presence of any `github.copilot*` directory as the Copilot fingerprint.
|
|
34
34
|
*/
|
|
35
35
|
export declare function vscodeCopilotInstalled(): boolean;
|
|
36
|
+
export declare function upsertCodiedevHook(hooks: Record<string, unknown>, eventName: string, subcommand: string): void;
|
|
36
37
|
export declare function installHook(): void;
|
|
38
|
+
export declare function ensureDiscoveryHookInstalled(): void;
|
|
37
39
|
export declare function installClaudeCodeInstructions(): void;
|
|
38
40
|
export declare function installCodexInstructions(): void;
|
|
39
41
|
export declare function installCursorInstructions(): void;
|
|
40
42
|
/**
|
|
41
|
-
* Install the
|
|
43
|
+
* Install the Codiedev MCP server into Claude Code's user-scope config.
|
|
42
44
|
* Safe to call multiple times — updates the existing entry if present.
|
|
43
45
|
*/
|
|
44
46
|
export declare function installClaudeCodeMcp(): void;
|
|
45
47
|
/**
|
|
46
|
-
* Install the
|
|
48
|
+
* Install the Codiedev MCP server into Cursor's user-scope config.
|
|
47
49
|
* Cursor uses the same `mcpServers` schema as Claude Code; just a different
|
|
48
50
|
* file path (~/.cursor/mcp.json). Project-scoped servers can also live at
|
|
49
51
|
* <project>/.cursor/mcp.json — we only manage the user-scope one.
|
|
50
52
|
*/
|
|
51
53
|
export declare function installCursorMcp(): void;
|
|
52
54
|
/**
|
|
53
|
-
* Install the
|
|
55
|
+
* Install the Codiedev MCP server into VS Code's user-scope MCP config so
|
|
54
56
|
* GitHub Copilot Chat picks it up. VS Code uses a different schema than
|
|
55
57
|
* Claude / Cursor: top-level key is `servers` (not `mcpServers`). On first
|
|
56
58
|
* Copilot Chat run after install, VS Code prompts the user to trust the
|
|
@@ -84,7 +86,7 @@ export declare function cleanupVSCodeMcp(): void;
|
|
|
84
86
|
*/
|
|
85
87
|
export declare function cleanupCodexMcp(): void;
|
|
86
88
|
/**
|
|
87
|
-
* Best-effort append of the
|
|
89
|
+
* Best-effort append of the Codiedev MCP server block to ~/.codex/config.toml.
|
|
88
90
|
*
|
|
89
91
|
* Codex uses TOML and we don't want a full parser for such a small addition,
|
|
90
92
|
* so we append a well-marked block if one isn't already present. Idempotent.
|
package/dist/utils.js
CHANGED
|
@@ -43,7 +43,9 @@ exports.claudeCodeInstalled = claudeCodeInstalled;
|
|
|
43
43
|
exports.codexInstalled = codexInstalled;
|
|
44
44
|
exports.cursorInstalled = cursorInstalled;
|
|
45
45
|
exports.vscodeCopilotInstalled = vscodeCopilotInstalled;
|
|
46
|
+
exports.upsertCodiedevHook = upsertCodiedevHook;
|
|
46
47
|
exports.installHook = installHook;
|
|
48
|
+
exports.ensureDiscoveryHookInstalled = ensureDiscoveryHookInstalled;
|
|
47
49
|
exports.installClaudeCodeInstructions = installClaudeCodeInstructions;
|
|
48
50
|
exports.installCodexInstructions = installCodexInstructions;
|
|
49
51
|
exports.installCursorInstructions = installCursorInstructions;
|
|
@@ -230,6 +232,27 @@ function vscodeCopilotInstalled() {
|
|
|
230
232
|
return false;
|
|
231
233
|
}
|
|
232
234
|
}
|
|
235
|
+
// Idempotently put a single codiedev hook (matcher ".*", running our hook.js
|
|
236
|
+
// with `subcommand`) into the given hook event, stripping any prior codiedev
|
|
237
|
+
// entries first so connect re-runs / upgrades refresh the resolved binary path.
|
|
238
|
+
// Mutates `hooks` in place. Exported for unit testing.
|
|
239
|
+
function upsertCodiedevHook(hooks, eventName, subcommand) {
|
|
240
|
+
if (!hooks[eventName])
|
|
241
|
+
hooks[eventName] = [];
|
|
242
|
+
const existing = hooks[eventName];
|
|
243
|
+
const filtered = existing.filter((hook) => {
|
|
244
|
+
const inner = hook.hooks;
|
|
245
|
+
if (Array.isArray(inner)) {
|
|
246
|
+
return !inner.some((h) => (0, detection_1.isCodiedevHookCommand)(h.command));
|
|
247
|
+
}
|
|
248
|
+
return !(0, detection_1.isCodiedevHookCommand)(hook.matcher);
|
|
249
|
+
});
|
|
250
|
+
filtered.push({
|
|
251
|
+
matcher: ".*",
|
|
252
|
+
hooks: [{ type: "command", command: resolveHookCommand(subcommand) }],
|
|
253
|
+
});
|
|
254
|
+
hooks[eventName] = filtered;
|
|
255
|
+
}
|
|
233
256
|
function installHook() {
|
|
234
257
|
let settings = {};
|
|
235
258
|
try {
|
|
@@ -245,35 +268,42 @@ function installHook() {
|
|
|
245
268
|
settings.hooks = {};
|
|
246
269
|
}
|
|
247
270
|
const hooks = settings.hooks;
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
// paths) before re-adding so connect re-runs upgrade the resolved binary
|
|
253
|
-
// path instead of leaving stale entries behind.
|
|
254
|
-
const existing = hooks.SessionEnd;
|
|
255
|
-
const filtered = existing.filter((hook) => {
|
|
256
|
-
const inner = hook.hooks;
|
|
257
|
-
if (Array.isArray(inner)) {
|
|
258
|
-
return !inner.some((h) => (0, detection_1.isCodiedevHookCommand)(h.command));
|
|
259
|
-
}
|
|
260
|
-
return !(0, detection_1.isCodiedevHookCommand)(hook.matcher);
|
|
261
|
-
});
|
|
262
|
-
filtered.push({
|
|
263
|
-
matcher: ".*",
|
|
264
|
-
hooks: [
|
|
265
|
-
{
|
|
266
|
-
type: "command",
|
|
267
|
-
command: resolveHookCommand("capture"),
|
|
268
|
-
},
|
|
269
|
-
],
|
|
270
|
-
});
|
|
271
|
-
hooks.SessionEnd = filtered;
|
|
271
|
+
// SessionEnd → capture (upload the finished session for the team library).
|
|
272
|
+
upsertCodiedevHook(hooks, "SessionEnd", "capture");
|
|
273
|
+
// UserPromptSubmit → discover (surface a matching team skill for the task).
|
|
274
|
+
upsertCodiedevHook(hooks, "UserPromptSubmit", "discover");
|
|
272
275
|
if (!fs.existsSync(CLAUDE_DIR)) {
|
|
273
276
|
fs.mkdirSync(CLAUDE_DIR, { recursive: true });
|
|
274
277
|
}
|
|
275
278
|
fs.writeFileSync(CLAUDE_SETTINGS_PATH, JSON.stringify(settings, null, 2), "utf8");
|
|
276
279
|
}
|
|
280
|
+
// Self-heal: ensure the discovery (UserPromptSubmit) hook is present without a
|
|
281
|
+
// full `connect` re-run. Called from the capture hook on SessionEnd, so a user
|
|
282
|
+
// who upgrades the CLI gets the new hook wired on their next session. Only
|
|
283
|
+
// writes when missing, to avoid churning settings.json every run.
|
|
284
|
+
function ensureDiscoveryHookInstalled() {
|
|
285
|
+
try {
|
|
286
|
+
if (!fs.existsSync(CLAUDE_SETTINGS_PATH))
|
|
287
|
+
return; // not a Claude Code user
|
|
288
|
+
const raw = fs.readFileSync(CLAUDE_SETTINGS_PATH, "utf8");
|
|
289
|
+
const settings = JSON.parse(raw);
|
|
290
|
+
const hooks = (settings.hooks ?? {});
|
|
291
|
+
const ups = (hooks.UserPromptSubmit ?? []);
|
|
292
|
+
const present = ups.some((hook) => {
|
|
293
|
+
const inner = hook.hooks;
|
|
294
|
+
return (Array.isArray(inner) &&
|
|
295
|
+
inner.some((h) => (0, detection_1.isCodiedevHookCommand)(h.command)));
|
|
296
|
+
});
|
|
297
|
+
if (present)
|
|
298
|
+
return;
|
|
299
|
+
settings.hooks = hooks;
|
|
300
|
+
upsertCodiedevHook(hooks, "UserPromptSubmit", "discover");
|
|
301
|
+
fs.writeFileSync(CLAUDE_SETTINGS_PATH, JSON.stringify(settings, null, 2), "utf8");
|
|
302
|
+
}
|
|
303
|
+
catch {
|
|
304
|
+
// Best-effort — never block a session on self-heal.
|
|
305
|
+
}
|
|
306
|
+
}
|
|
277
307
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
278
308
|
// MCP server installation — writes the codiedev MCP server config into each
|
|
279
309
|
// agent CLI's expected location so "npx codiedev connect" wires it up once.
|
|
@@ -284,9 +314,9 @@ const CODEX_USER_INSTRUCTIONS_PATH = path.join(os.homedir(), ".codex", "AGENTS.m
|
|
|
284
314
|
const CODEX_CONFIG_PATH = path.join(os.homedir(), ".codex", "config.toml");
|
|
285
315
|
const CODIEDEV_INSTRUCTIONS_BEGIN = "<!-- codiedev-cli:begin -->";
|
|
286
316
|
const CODIEDEV_INSTRUCTIONS_END = "<!-- codiedev-cli:end -->";
|
|
287
|
-
const CODIEDEV_INSTRUCTIONS_BODY = `##
|
|
317
|
+
const CODIEDEV_INSTRUCTIONS_BODY = `## Codiedev — team artifact layer
|
|
288
318
|
|
|
289
|
-
This machine is connected to
|
|
319
|
+
This machine is connected to Codiedev. Teammates share specs, reviews,
|
|
290
320
|
decisions, and messages via a CLI on every dev's machine.
|
|
291
321
|
|
|
292
322
|
**When the user asks to share, push, ping, check messages, or capture a
|
|
@@ -426,7 +456,7 @@ function installCodexInstructions() {
|
|
|
426
456
|
// Cursor uses .mdc rule files with YAML frontmatter (description, alwaysApply,
|
|
427
457
|
// globs). `.cursorrules` is deprecated and ignored by Agent mode as of 2026.
|
|
428
458
|
const CURSOR_INSTRUCTIONS_FRONTMATTER = `---
|
|
429
|
-
description:
|
|
459
|
+
description: Codiedev team artifact layer — push, pull, search, ping, and share artifacts with teammates
|
|
430
460
|
alwaysApply: true
|
|
431
461
|
---
|
|
432
462
|
|
|
@@ -483,7 +513,7 @@ function readMcpConfigSafely(path) {
|
|
|
483
513
|
}
|
|
484
514
|
}
|
|
485
515
|
/**
|
|
486
|
-
* Install the
|
|
516
|
+
* Install the Codiedev MCP server into Claude Code's user-scope config.
|
|
487
517
|
* Safe to call multiple times — updates the existing entry if present.
|
|
488
518
|
*/
|
|
489
519
|
function installClaudeCodeMcp() {
|
|
@@ -499,7 +529,7 @@ function installClaudeCodeMcp() {
|
|
|
499
529
|
fs.writeFileSync(CLAUDE_USER_CONFIG_PATH, JSON.stringify(config, null, 2), "utf8");
|
|
500
530
|
}
|
|
501
531
|
/**
|
|
502
|
-
* Install the
|
|
532
|
+
* Install the Codiedev MCP server into Cursor's user-scope config.
|
|
503
533
|
* Cursor uses the same `mcpServers` schema as Claude Code; just a different
|
|
504
534
|
* file path (~/.cursor/mcp.json). Project-scoped servers can also live at
|
|
505
535
|
* <project>/.cursor/mcp.json — we only manage the user-scope one.
|
|
@@ -520,7 +550,7 @@ function installCursorMcp() {
|
|
|
520
550
|
fs.writeFileSync(CURSOR_MCP_PATH, JSON.stringify(config, null, 2), "utf8");
|
|
521
551
|
}
|
|
522
552
|
/**
|
|
523
|
-
* Install the
|
|
553
|
+
* Install the Codiedev MCP server into VS Code's user-scope MCP config so
|
|
524
554
|
* GitHub Copilot Chat picks it up. VS Code uses a different schema than
|
|
525
555
|
* Claude / Cursor: top-level key is `servers` (not `mcpServers`). On first
|
|
526
556
|
* Copilot Chat run after install, VS Code prompts the user to trust the
|
|
@@ -635,7 +665,7 @@ function cleanupCodexMcp() {
|
|
|
635
665
|
fs.writeFileSync(CODEX_CONFIG_PATH, out.join("\n"), "utf8");
|
|
636
666
|
}
|
|
637
667
|
/**
|
|
638
|
-
* Best-effort append of the
|
|
668
|
+
* Best-effort append of the Codiedev MCP server block to ~/.codex/config.toml.
|
|
639
669
|
*
|
|
640
670
|
* Codex uses TOML and we don't want a full parser for such a small addition,
|
|
641
671
|
* so we append a well-marked block if one isn't already present. Idempotent.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codiedev",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Connect Claude Code, Codex, Cursor, or VS Code Copilot to
|
|
3
|
+
"version": "0.9.0",
|
|
4
|
+
"description": "Connect Claude Code, Codex, Cursor, or VS Code Copilot to Codiedev for org-wide session capture and artifact collaboration",
|
|
5
5
|
"bin": {
|
|
6
6
|
"codiedev": "dist/cli.js",
|
|
7
7
|
"codiedev-hook": "dist/hook.js"
|
package/dist/hookTransport.d.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import * as http from "http";
|
|
2
|
-
export declare const DEFAULT_HOOK_HTTP_TIMEOUT_MS = 8000;
|
|
3
|
-
/** Hard cap for GET requests used in sync-context (e.g. auto-pull skill list).
|
|
4
|
-
* If the backend accepts the connection but never writes a response the hook
|
|
5
|
-
* would stall every Claude Code session start — we cap it well below any
|
|
6
|
-
* reasonable UX threshold. */
|
|
7
|
-
export declare const GET_JSON_TIMEOUT_MS = 5000;
|
|
8
|
-
export declare function getHookHttpTimeoutMs(): number;
|
|
9
|
-
type RequestImpl = typeof http.request;
|
|
10
|
-
export declare function getJson<T>(url: string, bearerToken: string): Promise<T>;
|
|
11
|
-
export declare function postJson(url: string, body: Record<string, unknown>, bearerToken: string, requestImpl?: RequestImpl): Promise<void>;
|
|
12
|
-
export declare function postJsonWithHookTimeout(url: string, body: Record<string, unknown>, bearerToken: string, requestImpl?: RequestImpl): Promise<void>;
|
|
13
|
-
export {};
|
package/dist/hookTransport.js
DELETED
|
@@ -1,136 +0,0 @@
|
|
|
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.GET_JSON_TIMEOUT_MS = exports.DEFAULT_HOOK_HTTP_TIMEOUT_MS = void 0;
|
|
37
|
-
exports.getHookHttpTimeoutMs = getHookHttpTimeoutMs;
|
|
38
|
-
exports.getJson = getJson;
|
|
39
|
-
exports.postJson = postJson;
|
|
40
|
-
exports.postJsonWithHookTimeout = postJsonWithHookTimeout;
|
|
41
|
-
const http = __importStar(require("http"));
|
|
42
|
-
const https = __importStar(require("https"));
|
|
43
|
-
const version_1 = require("./version");
|
|
44
|
-
exports.DEFAULT_HOOK_HTTP_TIMEOUT_MS = 8_000;
|
|
45
|
-
/** Hard cap for GET requests used in sync-context (e.g. auto-pull skill list).
|
|
46
|
-
* If the backend accepts the connection but never writes a response the hook
|
|
47
|
-
* would stall every Claude Code session start — we cap it well below any
|
|
48
|
-
* reasonable UX threshold. */
|
|
49
|
-
exports.GET_JSON_TIMEOUT_MS = 5_000;
|
|
50
|
-
function getHookHttpTimeoutMs() {
|
|
51
|
-
const raw = process.env.CODIEDEV_HOOK_HTTP_TIMEOUT_MS;
|
|
52
|
-
if (!raw)
|
|
53
|
-
return exports.DEFAULT_HOOK_HTTP_TIMEOUT_MS;
|
|
54
|
-
const parsed = Number(raw);
|
|
55
|
-
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
56
|
-
return exports.DEFAULT_HOOK_HTTP_TIMEOUT_MS;
|
|
57
|
-
}
|
|
58
|
-
return parsed;
|
|
59
|
-
}
|
|
60
|
-
function requestJson(method, url, bearerToken, body, requestImpl, useTimeout = false,
|
|
61
|
-
/** When set, overrides getHookHttpTimeoutMs() for this request. */
|
|
62
|
-
explicitTimeoutMs) {
|
|
63
|
-
return new Promise((resolve, reject) => {
|
|
64
|
-
const parsed = new URL(url);
|
|
65
|
-
const data = body === undefined ? undefined : JSON.stringify(body);
|
|
66
|
-
const headers = {
|
|
67
|
-
Authorization: `Bearer ${bearerToken}`,
|
|
68
|
-
"X-Codiedev-Cli-Version": version_1.CLI_VERSION,
|
|
69
|
-
};
|
|
70
|
-
if (data !== undefined) {
|
|
71
|
-
headers["Content-Type"] = "application/json";
|
|
72
|
-
headers["Content-Length"] = Buffer.byteLength(data);
|
|
73
|
-
}
|
|
74
|
-
const options = {
|
|
75
|
-
hostname: parsed.hostname,
|
|
76
|
-
port: parsed.port || (parsed.protocol === "https:" ? 443 : 80),
|
|
77
|
-
path: parsed.pathname + parsed.search,
|
|
78
|
-
method,
|
|
79
|
-
headers,
|
|
80
|
-
};
|
|
81
|
-
const lib = parsed.protocol === "https:" ? https : http;
|
|
82
|
-
const request = requestImpl ?? lib.request;
|
|
83
|
-
let settled = false;
|
|
84
|
-
const timeoutMs = explicitTimeoutMs ?? getHookHttpTimeoutMs();
|
|
85
|
-
let timer;
|
|
86
|
-
const finish = (fn) => {
|
|
87
|
-
if (settled)
|
|
88
|
-
return;
|
|
89
|
-
settled = true;
|
|
90
|
-
if (timer)
|
|
91
|
-
clearTimeout(timer);
|
|
92
|
-
fn();
|
|
93
|
-
};
|
|
94
|
-
const req = request(options, (res) => {
|
|
95
|
-
let responseData = "";
|
|
96
|
-
res.on("data", (chunk) => {
|
|
97
|
-
responseData += chunk;
|
|
98
|
-
});
|
|
99
|
-
res.on("end", () => {
|
|
100
|
-
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
|
|
101
|
-
try {
|
|
102
|
-
const parsedBody = responseData.length === 0 ? undefined : JSON.parse(responseData);
|
|
103
|
-
finish(() => resolve(parsedBody));
|
|
104
|
-
}
|
|
105
|
-
catch (e) {
|
|
106
|
-
finish(() => reject(e));
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
else {
|
|
110
|
-
finish(() => reject(new Error(`HTTP ${res.statusCode}: ${responseData}`)));
|
|
111
|
-
}
|
|
112
|
-
});
|
|
113
|
-
});
|
|
114
|
-
if (useTimeout) {
|
|
115
|
-
timer = setTimeout(() => {
|
|
116
|
-
req.destroy(new Error(`request timed out after ${timeoutMs}ms`));
|
|
117
|
-
}, timeoutMs);
|
|
118
|
-
}
|
|
119
|
-
req.on("error", (err) => finish(() => reject(err)));
|
|
120
|
-
if (data !== undefined) {
|
|
121
|
-
req.write(data);
|
|
122
|
-
}
|
|
123
|
-
req.end();
|
|
124
|
-
});
|
|
125
|
-
}
|
|
126
|
-
function getJson(url, bearerToken) {
|
|
127
|
-
// Always apply a 5-second cap so a hanging backend cannot stall a
|
|
128
|
-
// SessionStart hook (which would freeze every Claude Code session start).
|
|
129
|
-
return requestJson("GET", url, bearerToken, undefined, undefined, true, exports.GET_JSON_TIMEOUT_MS);
|
|
130
|
-
}
|
|
131
|
-
function postJson(url, body, bearerToken, requestImpl) {
|
|
132
|
-
return requestJson("POST", url, bearerToken, body, requestImpl);
|
|
133
|
-
}
|
|
134
|
-
function postJsonWithHookTimeout(url, body, bearerToken, requestImpl) {
|
|
135
|
-
return requestJson("POST", url, bearerToken, body, requestImpl, true);
|
|
136
|
-
}
|