codiedev 0.2.0 → 0.3.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/dist/cli.d.ts +16 -0
- package/dist/cli.js +111 -0
- package/dist/commands/inbox.d.ts +2 -0
- package/dist/commands/inbox.js +106 -0
- package/dist/commands/note.d.ts +1 -0
- package/dist/commands/note.js +28 -0
- package/dist/commands/ping.d.ts +1 -0
- package/dist/commands/ping.js +55 -0
- package/dist/commands/promote.d.ts +1 -0
- package/dist/commands/promote.js +41 -0
- package/dist/commands/pull.d.ts +1 -0
- package/dist/commands/pull.js +106 -0
- package/dist/commands/push.d.ts +1 -0
- package/dist/commands/push.js +104 -0
- package/dist/commands/shared.d.ts +24 -0
- package/dist/commands/shared.js +143 -0
- package/dist/connect.d.ts +1 -2
- package/dist/connect.js +21 -9
- package/dist/mcp.d.ts +12 -0
- package/dist/mcp.js +425 -0
- package/dist/utils.d.ts +12 -0
- package/dist/utils.js +61 -0
- package/package.json +14 -7
|
@@ -0,0 +1,143 @@
|
|
|
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.requireConfig = requireConfig;
|
|
37
|
+
exports.apiRequest = apiRequest;
|
|
38
|
+
exports.timeAgo = timeAgo;
|
|
39
|
+
exports.stripQuotes = stripQuotes;
|
|
40
|
+
const https = __importStar(require("https"));
|
|
41
|
+
const http = __importStar(require("http"));
|
|
42
|
+
const utils_1 = require("../utils");
|
|
43
|
+
/**
|
|
44
|
+
* Require a connected CodieDev config. Exits the process with a helpful
|
|
45
|
+
* message if the user hasn't run `codiedev connect` yet.
|
|
46
|
+
*/
|
|
47
|
+
function requireConfig() {
|
|
48
|
+
const config = (0, utils_1.readConfig)();
|
|
49
|
+
if (!config) {
|
|
50
|
+
console.error("Not connected. Run `npx codiedev connect` first and enter your API token.");
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
return config;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Perform an authenticated HTTP request against the CodieDev backend.
|
|
57
|
+
* Returns the parsed JSON response body. Throws on non-2xx.
|
|
58
|
+
*/
|
|
59
|
+
function apiRequest(method, path, options) {
|
|
60
|
+
return new Promise((resolve, reject) => {
|
|
61
|
+
const base = options.config.backendUrl;
|
|
62
|
+
const url = new URL(path, base);
|
|
63
|
+
if (options.query) {
|
|
64
|
+
for (const [k, v] of Object.entries(options.query)) {
|
|
65
|
+
if (v === undefined)
|
|
66
|
+
continue;
|
|
67
|
+
url.searchParams.set(k, String(v));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const data = options.body ? JSON.stringify(options.body) : undefined;
|
|
71
|
+
const requestOptions = {
|
|
72
|
+
hostname: url.hostname,
|
|
73
|
+
port: url.port || (url.protocol === "https:" ? 443 : 80),
|
|
74
|
+
path: url.pathname + url.search,
|
|
75
|
+
method,
|
|
76
|
+
headers: {
|
|
77
|
+
Authorization: `Bearer ${options.config.token}`,
|
|
78
|
+
...(data
|
|
79
|
+
? {
|
|
80
|
+
"Content-Type": "application/json",
|
|
81
|
+
"Content-Length": Buffer.byteLength(data),
|
|
82
|
+
}
|
|
83
|
+
: {}),
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
const lib = url.protocol === "https:" ? https : http;
|
|
87
|
+
const req = lib.request(requestOptions, (res) => {
|
|
88
|
+
let chunks = "";
|
|
89
|
+
res.on("data", (c) => (chunks += c));
|
|
90
|
+
res.on("end", () => {
|
|
91
|
+
try {
|
|
92
|
+
const parsed = chunks ? JSON.parse(chunks) : {};
|
|
93
|
+
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
|
|
94
|
+
resolve(parsed);
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
const errMsg = (parsed && typeof parsed === "object" && "error" in parsed
|
|
98
|
+
? parsed.error
|
|
99
|
+
: undefined) || `HTTP ${res.statusCode}`;
|
|
100
|
+
reject(Object.assign(new Error(errMsg), {
|
|
101
|
+
status: res.statusCode,
|
|
102
|
+
body: parsed,
|
|
103
|
+
}));
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
reject(new Error(`Failed to parse response: ${chunks}`));
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
req.on("error", reject);
|
|
112
|
+
if (data)
|
|
113
|
+
req.write(data);
|
|
114
|
+
req.end();
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Relative-time formatter for inbox / listing output.
|
|
119
|
+
*/
|
|
120
|
+
function timeAgo(ts) {
|
|
121
|
+
const diff = Date.now() - ts;
|
|
122
|
+
const mins = Math.floor(diff / 60000);
|
|
123
|
+
if (mins < 1)
|
|
124
|
+
return "just now";
|
|
125
|
+
if (mins < 60)
|
|
126
|
+
return `${mins}m ago`;
|
|
127
|
+
const hours = Math.floor(mins / 60);
|
|
128
|
+
if (hours < 24)
|
|
129
|
+
return `${hours}h ago`;
|
|
130
|
+
const days = Math.floor(hours / 24);
|
|
131
|
+
return `${days}d ago`;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Strip surrounding quotes from a CLI argument if the user wrapped it.
|
|
135
|
+
* `codiedev ping maya "hey"` on some shells passes the quotes through.
|
|
136
|
+
*/
|
|
137
|
+
function stripQuotes(s) {
|
|
138
|
+
if ((s.startsWith('"') && s.endsWith('"')) ||
|
|
139
|
+
(s.startsWith("'") && s.endsWith("'"))) {
|
|
140
|
+
return s.slice(1, -1);
|
|
141
|
+
}
|
|
142
|
+
return s;
|
|
143
|
+
}
|
package/dist/connect.d.ts
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export {};
|
|
1
|
+
export declare function runConnect(): Promise<void>;
|
package/dist/connect.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
1
|
"use strict";
|
|
3
2
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
3
|
if (k2 === undefined) k2 = k;
|
|
@@ -34,6 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
33
|
};
|
|
35
34
|
})();
|
|
36
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.runConnect = runConnect;
|
|
37
37
|
const readline = __importStar(require("readline"));
|
|
38
38
|
const https = __importStar(require("https"));
|
|
39
39
|
const http = __importStar(require("http"));
|
|
@@ -86,13 +86,13 @@ function postJson(url, body) {
|
|
|
86
86
|
req.end();
|
|
87
87
|
});
|
|
88
88
|
}
|
|
89
|
-
async function
|
|
89
|
+
async function runConnect() {
|
|
90
90
|
const rl = readline.createInterface({
|
|
91
91
|
input: process.stdin,
|
|
92
92
|
output: process.stdout,
|
|
93
93
|
});
|
|
94
94
|
console.log("\nWelcome to CodieDev CLI\n");
|
|
95
|
-
console.log("This will connect
|
|
95
|
+
console.log("This will connect your coding agent to your CodieDev workspace and install");
|
|
96
96
|
console.log("the session capture hook for org-wide session sharing.\n");
|
|
97
97
|
let token;
|
|
98
98
|
try {
|
|
@@ -145,20 +145,36 @@ async function main() {
|
|
|
145
145
|
if (hasClaude) {
|
|
146
146
|
try {
|
|
147
147
|
(0, utils_1.installHook)();
|
|
148
|
-
installed.push("Claude Code
|
|
148
|
+
installed.push("Claude Code SessionEnd hook (~/.claude/settings.json)");
|
|
149
149
|
}
|
|
150
150
|
catch (err) {
|
|
151
151
|
console.error(`\nWarning: Failed to install Claude Code hook — ${err.message}`);
|
|
152
152
|
}
|
|
153
|
+
try {
|
|
154
|
+
(0, utils_1.installClaudeCodeMcp)();
|
|
155
|
+
installed.push("Claude Code MCP server (~/.claude.json)");
|
|
156
|
+
}
|
|
157
|
+
catch (err) {
|
|
158
|
+
console.error(`\nWarning: Failed to install Claude Code MCP server — ${err.message}`);
|
|
159
|
+
}
|
|
153
160
|
}
|
|
154
161
|
if (hasCodex) {
|
|
155
162
|
try {
|
|
156
163
|
(0, utils_1.installCodexHook)();
|
|
157
|
-
installed.push("Codex
|
|
164
|
+
installed.push("Codex Stop hook (~/.codex/hooks.json)");
|
|
158
165
|
}
|
|
159
166
|
catch (err) {
|
|
160
167
|
console.error(`\nWarning: Failed to install Codex hook — ${err.message}`);
|
|
161
168
|
}
|
|
169
|
+
try {
|
|
170
|
+
const added = (0, utils_1.installCodexMcp)();
|
|
171
|
+
if (added) {
|
|
172
|
+
installed.push("Codex MCP server (~/.codex/config.toml)");
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
catch (err) {
|
|
176
|
+
console.error(`\nWarning: Failed to install Codex MCP server — ${err.message}`);
|
|
177
|
+
}
|
|
162
178
|
}
|
|
163
179
|
if (!hasClaude && !hasCodex) {
|
|
164
180
|
console.warn("\nNo Claude Code (~/.claude) or Codex (~/.codex) install detected.");
|
|
@@ -177,7 +193,3 @@ async function main() {
|
|
|
177
193
|
console.log();
|
|
178
194
|
}
|
|
179
195
|
}
|
|
180
|
-
main().catch((err) => {
|
|
181
|
-
console.error("Unexpected error:", err);
|
|
182
|
-
process.exit(1);
|
|
183
|
-
});
|
package/dist/mcp.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* CodieDev MCP server.
|
|
4
|
+
*
|
|
5
|
+
* Exposes the CodieDev artifact-collaboration surface as Model Context
|
|
6
|
+
* Protocol tools so Claude Code / Codex / any MCP-capable agent can invoke
|
|
7
|
+
* them natively from natural language (e.g. "push the spec we just worked on").
|
|
8
|
+
*
|
|
9
|
+
* Reads the same ~/.codiedev/config.json the CLI uses. Install + wire up by
|
|
10
|
+
* running `npx codiedev connect`.
|
|
11
|
+
*/
|
|
12
|
+
export {};
|
package/dist/mcp.js
ADDED
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
/**
|
|
4
|
+
* CodieDev MCP server.
|
|
5
|
+
*
|
|
6
|
+
* Exposes the CodieDev artifact-collaboration surface as Model Context
|
|
7
|
+
* Protocol tools so Claude Code / Codex / any MCP-capable agent can invoke
|
|
8
|
+
* them natively from natural language (e.g. "push the spec we just worked on").
|
|
9
|
+
*
|
|
10
|
+
* Reads the same ~/.codiedev/config.json the CLI uses. Install + wire up by
|
|
11
|
+
* running `npx codiedev connect`.
|
|
12
|
+
*/
|
|
13
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
16
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
17
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
18
|
+
}
|
|
19
|
+
Object.defineProperty(o, k2, desc);
|
|
20
|
+
}) : (function(o, m, k, k2) {
|
|
21
|
+
if (k2 === undefined) k2 = k;
|
|
22
|
+
o[k2] = m[k];
|
|
23
|
+
}));
|
|
24
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
25
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
26
|
+
}) : function(o, v) {
|
|
27
|
+
o["default"] = v;
|
|
28
|
+
});
|
|
29
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
30
|
+
var ownKeys = function(o) {
|
|
31
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
32
|
+
var ar = [];
|
|
33
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
34
|
+
return ar;
|
|
35
|
+
};
|
|
36
|
+
return ownKeys(o);
|
|
37
|
+
};
|
|
38
|
+
return function (mod) {
|
|
39
|
+
if (mod && mod.__esModule) return mod;
|
|
40
|
+
var result = {};
|
|
41
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
42
|
+
__setModuleDefault(result, mod);
|
|
43
|
+
return result;
|
|
44
|
+
};
|
|
45
|
+
})();
|
|
46
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
47
|
+
const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
|
|
48
|
+
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
49
|
+
const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
|
|
50
|
+
const fs = __importStar(require("fs"));
|
|
51
|
+
const path = __importStar(require("path"));
|
|
52
|
+
const utils_1 = require("./utils");
|
|
53
|
+
const shared_1 = require("./commands/shared");
|
|
54
|
+
const PKG_NAME = "codiedev";
|
|
55
|
+
const PKG_VERSION = "0.3.0";
|
|
56
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
57
|
+
// Tool definitions — descriptions tuned so Claude/Codex resolve natural-language
|
|
58
|
+
// requests into the right tool without manual steering.
|
|
59
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
60
|
+
const TOOLS = [
|
|
61
|
+
{
|
|
62
|
+
name: "codiedev_push",
|
|
63
|
+
description: "Push a spec, review, decision, proposal, bugfix, or note to the " +
|
|
64
|
+
"team's CodieDev artifact layer so teammates can see and discuss it. " +
|
|
65
|
+
"Use when the user asks to share, push, save, or publish something " +
|
|
66
|
+
"(e.g., 'push this spec', 'share the review with the team', 'save as " +
|
|
67
|
+
"a decision'). Prefer passing the filename of a recently-edited local " +
|
|
68
|
+
"file — provide its current contents as `markdown`. Filename should " +
|
|
69
|
+
"end in .md and start with the artifact type (spec-, review-, " +
|
|
70
|
+
"decision-, proposal-, bugfix-, or note-).",
|
|
71
|
+
inputSchema: {
|
|
72
|
+
type: "object",
|
|
73
|
+
properties: {
|
|
74
|
+
filename: {
|
|
75
|
+
type: "string",
|
|
76
|
+
description: "Basename of the artifact file, e.g. 'spec-214.md'. Becomes the " +
|
|
77
|
+
"artifact's key for pulling and pinging.",
|
|
78
|
+
},
|
|
79
|
+
markdown: {
|
|
80
|
+
type: "string",
|
|
81
|
+
description: "Full markdown content of the artifact.",
|
|
82
|
+
},
|
|
83
|
+
type: {
|
|
84
|
+
type: "string",
|
|
85
|
+
enum: ["spec", "bugfix", "decision", "proposal", "review", "note"],
|
|
86
|
+
description: "Override the inferred type. Usually leave unset — type is " +
|
|
87
|
+
"inferred from filename prefix.",
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
required: ["filename", "markdown"],
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
name: "codiedev_pull",
|
|
95
|
+
description: "Fetch an artifact (spec, review, decision, proposal, bugfix, or " +
|
|
96
|
+
"note) from the team's CodieDev artifact layer. Use when the user " +
|
|
97
|
+
"asks to pull, grab, fetch, or resume work on a named artifact, or " +
|
|
98
|
+
"when they want to see what a teammate pushed. Returns the full " +
|
|
99
|
+
"markdown content.",
|
|
100
|
+
inputSchema: {
|
|
101
|
+
type: "object",
|
|
102
|
+
properties: {
|
|
103
|
+
key: {
|
|
104
|
+
type: "string",
|
|
105
|
+
description: "Filename key of the artifact to pull, e.g. 'spec-214.md'.",
|
|
106
|
+
},
|
|
107
|
+
version: {
|
|
108
|
+
type: "integer",
|
|
109
|
+
description: "Specific version to pull. Omit for the latest version.",
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
required: ["key"],
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
name: "codiedev_ping",
|
|
117
|
+
description: "Send a message to a teammate on CodieDev, optionally attached to a " +
|
|
118
|
+
"specific artifact. Use when the user asks to ping, ask, share with, " +
|
|
119
|
+
"or get feedback from a teammate by name (e.g. 'ping Maya about this " +
|
|
120
|
+
"spec'). Recipients are resolved by first name, full name, or email " +
|
|
121
|
+
"within the current org. They're notified by email and can reply " +
|
|
122
|
+
"from their own agent.",
|
|
123
|
+
inputSchema: {
|
|
124
|
+
type: "object",
|
|
125
|
+
properties: {
|
|
126
|
+
to: {
|
|
127
|
+
type: "string",
|
|
128
|
+
description: "Teammate's first name, full name, or email. Ambiguous matches " +
|
|
129
|
+
"return a list of candidates — reprompt the user if so.",
|
|
130
|
+
},
|
|
131
|
+
body: {
|
|
132
|
+
type: "string",
|
|
133
|
+
description: "Message body — the actual ask/comment.",
|
|
134
|
+
},
|
|
135
|
+
subjectKey: {
|
|
136
|
+
type: "string",
|
|
137
|
+
description: "Optional filename key of an artifact the message is about " +
|
|
138
|
+
"(e.g. 'spec-214.md'). Leaving blank sends a standalone ping.",
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
required: ["to", "body"],
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
name: "codiedev_inbox",
|
|
146
|
+
description: "Check the user's CodieDev inbox for messages from teammates. Use " +
|
|
147
|
+
"when the user asks 'any messages?', 'what did [name] say?', 'check " +
|
|
148
|
+
"my inbox', or when they open a session and want to catch up on " +
|
|
149
|
+
"what's waiting for them. Returns a list with sender, subject " +
|
|
150
|
+
"artifact, time, and preview.",
|
|
151
|
+
inputSchema: {
|
|
152
|
+
type: "object",
|
|
153
|
+
properties: {
|
|
154
|
+
unreadOnly: {
|
|
155
|
+
type: "boolean",
|
|
156
|
+
description: "Only return unread pings. Default false.",
|
|
157
|
+
},
|
|
158
|
+
limit: {
|
|
159
|
+
type: "integer",
|
|
160
|
+
description: "Max pings to return. Default 50.",
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
name: "codiedev_note",
|
|
167
|
+
description: "Capture a passing thought and link it to the current agent session. " +
|
|
168
|
+
"Use when the user says 'note that', 'remember', 'worth capturing', " +
|
|
169
|
+
"'jot this down', or drops a short observation that isn't a full " +
|
|
170
|
+
"spec. Notes show up in the user's CodieDev memory feed.",
|
|
171
|
+
inputSchema: {
|
|
172
|
+
type: "object",
|
|
173
|
+
properties: {
|
|
174
|
+
body: {
|
|
175
|
+
type: "string",
|
|
176
|
+
description: "The thought to capture.",
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
required: ["body"],
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
name: "codiedev_promote",
|
|
184
|
+
description: "Promote an auto-extracted CodieDev artifact (spec/bugfix/decision " +
|
|
185
|
+
"produced by the session-capture pipeline) to an authored artifact " +
|
|
186
|
+
"so teammates can collaborate on it. Use when the user says 'publish " +
|
|
187
|
+
"the extracted spec', 'promote this', or 'make the auto-extracted X " +
|
|
188
|
+
"shareable'.",
|
|
189
|
+
inputSchema: {
|
|
190
|
+
type: "object",
|
|
191
|
+
properties: {
|
|
192
|
+
artifactId: {
|
|
193
|
+
type: "string",
|
|
194
|
+
description: "The extracted artifact's id from CodieDev.",
|
|
195
|
+
},
|
|
196
|
+
keyOverride: {
|
|
197
|
+
type: "string",
|
|
198
|
+
description: "Optional filename key for the promoted artifact. If omitted, " +
|
|
199
|
+
"one is generated from the artifact's type + session id.",
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
required: ["artifactId"],
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
];
|
|
206
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
207
|
+
// Server
|
|
208
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
209
|
+
const server = new index_js_1.Server({ name: PKG_NAME, version: PKG_VERSION }, { capabilities: { tools: {} } });
|
|
210
|
+
server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({
|
|
211
|
+
tools: TOOLS,
|
|
212
|
+
}));
|
|
213
|
+
server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
214
|
+
const config = (0, utils_1.readConfig)();
|
|
215
|
+
if (!config) {
|
|
216
|
+
return {
|
|
217
|
+
isError: true,
|
|
218
|
+
content: [
|
|
219
|
+
{
|
|
220
|
+
type: "text",
|
|
221
|
+
text: "CodieDev is not connected on this machine. Ask the user to run " +
|
|
222
|
+
"`npx codiedev connect` with an API token from " +
|
|
223
|
+
"https://codiedev.com/portal/integrations/claude-code.",
|
|
224
|
+
},
|
|
225
|
+
],
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
const { name, arguments: argsRaw } = request.params;
|
|
229
|
+
const args = (argsRaw ?? {});
|
|
230
|
+
try {
|
|
231
|
+
switch (name) {
|
|
232
|
+
case "codiedev_push":
|
|
233
|
+
return await handlePush(args, config);
|
|
234
|
+
case "codiedev_pull":
|
|
235
|
+
return await handlePull(args, config);
|
|
236
|
+
case "codiedev_ping":
|
|
237
|
+
return await handlePing(args, config);
|
|
238
|
+
case "codiedev_inbox":
|
|
239
|
+
return await handleInbox(args, config);
|
|
240
|
+
case "codiedev_note":
|
|
241
|
+
return await handleNote(args, config);
|
|
242
|
+
case "codiedev_promote":
|
|
243
|
+
return await handlePromote(args, config);
|
|
244
|
+
default:
|
|
245
|
+
return {
|
|
246
|
+
isError: true,
|
|
247
|
+
content: [{ type: "text", text: `Unknown tool: ${name}` }],
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
catch (err) {
|
|
252
|
+
return {
|
|
253
|
+
isError: true,
|
|
254
|
+
content: [
|
|
255
|
+
{
|
|
256
|
+
type: "text",
|
|
257
|
+
text: `CodieDev error: ${err.message}`,
|
|
258
|
+
},
|
|
259
|
+
],
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
async function handlePush(args, config) {
|
|
264
|
+
const filename = asString(args.filename);
|
|
265
|
+
const markdown = asString(args.markdown);
|
|
266
|
+
const type = asStringOrUndefined(args.type);
|
|
267
|
+
if (!filename)
|
|
268
|
+
throw new Error("filename required");
|
|
269
|
+
if (!markdown)
|
|
270
|
+
throw new Error("markdown required");
|
|
271
|
+
const res = await (0, shared_1.apiRequest)("POST", "/api/cli/push", {
|
|
272
|
+
config,
|
|
273
|
+
body: { filename, markdown, type },
|
|
274
|
+
});
|
|
275
|
+
return {
|
|
276
|
+
content: [
|
|
277
|
+
{
|
|
278
|
+
type: "text",
|
|
279
|
+
text: `✓ Pushed ${filename} (type=${res.type}, version=v${res.version}, id=${res.artifactId}).\n` +
|
|
280
|
+
`Teammates can now pull it with \`codiedev pull ${filename}\`.`,
|
|
281
|
+
},
|
|
282
|
+
],
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
async function handlePull(args, config) {
|
|
286
|
+
const key = asString(args.key);
|
|
287
|
+
const version = asIntOrUndefined(args.version);
|
|
288
|
+
if (!key)
|
|
289
|
+
throw new Error("key required");
|
|
290
|
+
const res = await (0, shared_1.apiRequest)("GET", "/api/cli/pull", { config, query: { key, version } });
|
|
291
|
+
const a = res.artifact;
|
|
292
|
+
const header = `# ${a.title}\n\n` +
|
|
293
|
+
`**${a.type}** · v${a.version ?? 1} · ${a.lifecycle ?? "draft"} · ` +
|
|
294
|
+
`updated ${(0, shared_1.timeAgo)(a.updatedAt ?? a.createdAt)}\n\n---\n\n`;
|
|
295
|
+
return {
|
|
296
|
+
content: [{ type: "text", text: header + a.markdown }],
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
async function handlePing(args, config) {
|
|
300
|
+
const to = asString(args.to);
|
|
301
|
+
const body = asString(args.body);
|
|
302
|
+
const subjectKey = asStringOrUndefined(args.subjectKey);
|
|
303
|
+
if (!to)
|
|
304
|
+
throw new Error("to required");
|
|
305
|
+
if (!body)
|
|
306
|
+
throw new Error("body required");
|
|
307
|
+
try {
|
|
308
|
+
const res = await (0, shared_1.apiRequest)("POST", "/api/cli/ping", { config, body: { to, body, subjectKey } });
|
|
309
|
+
return {
|
|
310
|
+
content: [
|
|
311
|
+
{
|
|
312
|
+
type: "text",
|
|
313
|
+
text: `✓ Pinged ${res.recipient.name} <${res.recipient.email}>` +
|
|
314
|
+
(subjectKey ? ` about ${subjectKey}.` : "."),
|
|
315
|
+
},
|
|
316
|
+
],
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
catch (err) {
|
|
320
|
+
const e = err;
|
|
321
|
+
if (e.status === 409 && e.body?.candidates?.length) {
|
|
322
|
+
const list = e.body.candidates
|
|
323
|
+
.map((c) => `- ${c.name} <${c.email}>`)
|
|
324
|
+
.join("\n");
|
|
325
|
+
return {
|
|
326
|
+
isError: true,
|
|
327
|
+
content: [
|
|
328
|
+
{
|
|
329
|
+
type: "text",
|
|
330
|
+
text: `Multiple recipients matched "${to}". Ask the user which:\n${list}`,
|
|
331
|
+
},
|
|
332
|
+
],
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
throw err;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
async function handleInbox(args, config) {
|
|
339
|
+
const unreadOnly = args.unreadOnly === true;
|
|
340
|
+
const limit = asIntOrUndefined(args.limit);
|
|
341
|
+
const res = await (0, shared_1.apiRequest)("GET", "/api/cli/inbox", {
|
|
342
|
+
config,
|
|
343
|
+
query: { unreadOnly: unreadOnly ? "1" : undefined, limit },
|
|
344
|
+
});
|
|
345
|
+
if (res.pings.length === 0) {
|
|
346
|
+
return {
|
|
347
|
+
content: [
|
|
348
|
+
{
|
|
349
|
+
type: "text",
|
|
350
|
+
text: unreadOnly ? "No unread messages." : "Inbox is empty.",
|
|
351
|
+
},
|
|
352
|
+
],
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
const unreadCount = res.pings.filter((p) => !p.readAt).length;
|
|
356
|
+
const lines = [
|
|
357
|
+
`Inbox · ${res.pings.length} ${unreadOnly ? "unread" : "recent"}${unreadOnly ? "" : ` · ${unreadCount} unread`}`,
|
|
358
|
+
"",
|
|
359
|
+
];
|
|
360
|
+
for (const p of res.pings) {
|
|
361
|
+
const status = p.readAt ? "·" : "●";
|
|
362
|
+
const from = p.from?.name ?? "unknown";
|
|
363
|
+
const where = p.subjectKey ? ` · on ${p.subjectKey}` : "";
|
|
364
|
+
lines.push(`${status} ${from}${where} · ${(0, shared_1.timeAgo)(p.createdAt)}`);
|
|
365
|
+
lines.push(` ${p.preview || "(no body)"}`);
|
|
366
|
+
lines.push(` id: ${p.pingId}`);
|
|
367
|
+
lines.push("");
|
|
368
|
+
}
|
|
369
|
+
return {
|
|
370
|
+
content: [{ type: "text", text: lines.join("\n") }],
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
async function handleNote(args, config) {
|
|
374
|
+
const body = asString(args.body);
|
|
375
|
+
if (!body)
|
|
376
|
+
throw new Error("body required");
|
|
377
|
+
const res = await (0, shared_1.apiRequest)("POST", "/api/cli/note", { config, body: { body } });
|
|
378
|
+
return {
|
|
379
|
+
content: [{ type: "text", text: `✓ Noted (${res.key})` }],
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
async function handlePromote(args, config) {
|
|
383
|
+
const artifactId = asString(args.artifactId);
|
|
384
|
+
const keyOverride = asStringOrUndefined(args.keyOverride);
|
|
385
|
+
if (!artifactId)
|
|
386
|
+
throw new Error("artifactId required");
|
|
387
|
+
const res = await (0, shared_1.apiRequest)("POST", "/api/cli/promote", { config, body: { artifactId, keyOverride } });
|
|
388
|
+
return {
|
|
389
|
+
content: [
|
|
390
|
+
{
|
|
391
|
+
type: "text",
|
|
392
|
+
text: `✓ Promoted to authored artifact ${res.key} (id=${res.artifactId}).`,
|
|
393
|
+
},
|
|
394
|
+
],
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
398
|
+
// Helpers
|
|
399
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
400
|
+
function asString(v) {
|
|
401
|
+
return typeof v === "string" ? v : "";
|
|
402
|
+
}
|
|
403
|
+
function asStringOrUndefined(v) {
|
|
404
|
+
return typeof v === "string" ? v : undefined;
|
|
405
|
+
}
|
|
406
|
+
function asIntOrUndefined(v) {
|
|
407
|
+
return typeof v === "number" && Number.isInteger(v) ? v : undefined;
|
|
408
|
+
}
|
|
409
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
410
|
+
// Main
|
|
411
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
412
|
+
async function main() {
|
|
413
|
+
const transport = new stdio_js_1.StdioServerTransport();
|
|
414
|
+
await server.connect(transport);
|
|
415
|
+
// stdio transport handles I/O until process exits.
|
|
416
|
+
// Log to stderr only — stdout is reserved for MCP protocol.
|
|
417
|
+
process.stderr.write(`[codiedev-mcp] ready (v${PKG_VERSION}, config ${(0, utils_1.readConfig)() ? "present" : "missing"})\n`);
|
|
418
|
+
}
|
|
419
|
+
main().catch((err) => {
|
|
420
|
+
process.stderr.write(`[codiedev-mcp] fatal: ${err.message}\n`);
|
|
421
|
+
process.exit(1);
|
|
422
|
+
});
|
|
423
|
+
// Avoid "Cannot find name 'path', 'fs'" since we imported them but may not use.
|
|
424
|
+
void path;
|
|
425
|
+
void fs;
|
package/dist/utils.d.ts
CHANGED
|
@@ -27,6 +27,18 @@ export declare function hashToken(token: string): string;
|
|
|
27
27
|
export declare function claudeCodeInstalled(): boolean;
|
|
28
28
|
export declare function codexInstalled(): boolean;
|
|
29
29
|
export declare function installHook(): void;
|
|
30
|
+
/**
|
|
31
|
+
* Install the CodieDev MCP server into Claude Code's user-scope config.
|
|
32
|
+
* Safe to call multiple times — updates the existing entry if present.
|
|
33
|
+
*/
|
|
34
|
+
export declare function installClaudeCodeMcp(): void;
|
|
35
|
+
/**
|
|
36
|
+
* Best-effort append of the CodieDev MCP server block to ~/.codex/config.toml.
|
|
37
|
+
*
|
|
38
|
+
* Codex uses TOML and we don't want a full parser for such a small addition,
|
|
39
|
+
* so we append a well-marked block if one isn't already present. Idempotent.
|
|
40
|
+
*/
|
|
41
|
+
export declare function installCodexMcp(): boolean;
|
|
30
42
|
export declare function installCodexHook(): void;
|
|
31
43
|
export interface ParsedStats {
|
|
32
44
|
messageCount: number;
|