codiedev 0.3.5 → 0.4.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.js +6 -0
- package/dist/commands/doctor.d.ts +1 -0
- package/dist/commands/doctor.js +263 -0
- package/dist/connect.js +4 -4
- package/dist/mcp.js +370 -1
- package/dist/utils.js +23 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -23,6 +23,7 @@ const inbox_1 = require("./commands/inbox");
|
|
|
23
23
|
const note_1 = require("./commands/note");
|
|
24
24
|
const promote_1 = require("./commands/promote");
|
|
25
25
|
const reverseTicket_1 = require("./commands/reverseTicket");
|
|
26
|
+
const doctor_1 = require("./commands/doctor");
|
|
26
27
|
const HELP = `
|
|
27
28
|
CodieDev CLI
|
|
28
29
|
|
|
@@ -32,6 +33,7 @@ CodieDev CLI
|
|
|
32
33
|
|
|
33
34
|
Connect:
|
|
34
35
|
codiedev connect Link Claude Code / Codex to your org
|
|
36
|
+
codiedev doctor Verify your setup after connecting
|
|
35
37
|
|
|
36
38
|
Artifacts:
|
|
37
39
|
codiedev push <file.md> Author or update an artifact
|
|
@@ -314,6 +316,10 @@ async function main() {
|
|
|
314
316
|
case "connect":
|
|
315
317
|
await (0, connect_1.runConnect)();
|
|
316
318
|
return;
|
|
319
|
+
case "doctor":
|
|
320
|
+
case "verify":
|
|
321
|
+
await (0, doctor_1.runDoctor)(rest);
|
|
322
|
+
return;
|
|
317
323
|
case "push":
|
|
318
324
|
await (0, push_1.runPush)(rest);
|
|
319
325
|
return;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runDoctor(_args: string[]): Promise<void>;
|
|
@@ -0,0 +1,263 @@
|
|
|
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.runDoctor = runDoctor;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const os = __importStar(require("os"));
|
|
40
|
+
const child_process_1 = require("child_process");
|
|
41
|
+
const shared_1 = require("./shared");
|
|
42
|
+
const utils_1 = require("../utils");
|
|
43
|
+
const CLAUDE_SETTINGS_PATH = path.join(os.homedir(), ".claude", "settings.json");
|
|
44
|
+
const CLAUDE_INSTRUCTIONS_PATH = path.join(os.homedir(), ".claude", "CLAUDE.md");
|
|
45
|
+
const CODEX_HOOKS_PATH = path.join(os.homedir(), ".codex", "hooks.json");
|
|
46
|
+
const CODEX_INSTRUCTIONS_PATH = path.join(os.homedir(), ".codex", "AGENTS.md");
|
|
47
|
+
function symbol(status) {
|
|
48
|
+
if (status === "pass")
|
|
49
|
+
return "✓";
|
|
50
|
+
if (status === "warn")
|
|
51
|
+
return "!";
|
|
52
|
+
return "✗";
|
|
53
|
+
}
|
|
54
|
+
function claudeCodeInstalled() {
|
|
55
|
+
return fs.existsSync(path.join(os.homedir(), ".claude"));
|
|
56
|
+
}
|
|
57
|
+
function codexInstalled() {
|
|
58
|
+
return fs.existsSync(path.join(os.homedir(), ".codex"));
|
|
59
|
+
}
|
|
60
|
+
function hasCodiedevHook(settingsPath, hookKey) {
|
|
61
|
+
try {
|
|
62
|
+
if (!fs.existsSync(settingsPath))
|
|
63
|
+
return false;
|
|
64
|
+
const raw = fs.readFileSync(settingsPath, "utf8");
|
|
65
|
+
const parsed = JSON.parse(raw);
|
|
66
|
+
const hooks = parsed.hooks?.[hookKey];
|
|
67
|
+
if (!Array.isArray(hooks))
|
|
68
|
+
return false;
|
|
69
|
+
return hooks.some((h) => {
|
|
70
|
+
const inner = h.hooks;
|
|
71
|
+
if (!Array.isArray(inner))
|
|
72
|
+
return false;
|
|
73
|
+
return inner.some((x) => (x.command ?? "").includes("codiedev-hook"));
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function hasCodiedevInstructions(instructionsPath) {
|
|
81
|
+
try {
|
|
82
|
+
if (!fs.existsSync(instructionsPath))
|
|
83
|
+
return false;
|
|
84
|
+
const raw = fs.readFileSync(instructionsPath, "utf8");
|
|
85
|
+
return raw.includes("codiedev-cli:begin");
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
function hasGhCli() {
|
|
92
|
+
try {
|
|
93
|
+
(0, child_process_1.execSync)("gh --version", { stdio: ["pipe", "pipe", "pipe"] });
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
async function runDoctor(_args) {
|
|
101
|
+
const checks = [];
|
|
102
|
+
console.log("\nCodieDev doctor — checking your setup…\n");
|
|
103
|
+
// 1. Config file
|
|
104
|
+
const config = (0, utils_1.readConfig)();
|
|
105
|
+
if (!config) {
|
|
106
|
+
checks.push({
|
|
107
|
+
name: "CodieDev config (~/.codiedev/config.json)",
|
|
108
|
+
status: "fail",
|
|
109
|
+
detail: "Not found. Run `codiedev connect` first.",
|
|
110
|
+
});
|
|
111
|
+
report(checks);
|
|
112
|
+
process.exit(1);
|
|
113
|
+
}
|
|
114
|
+
checks.push({
|
|
115
|
+
name: "CodieDev config",
|
|
116
|
+
status: "pass",
|
|
117
|
+
detail: `connected to ${config.companyName}`,
|
|
118
|
+
});
|
|
119
|
+
// 2. Token still valid + backend reachable (re-validate)
|
|
120
|
+
try {
|
|
121
|
+
const res = await fetch(`${config.backendUrl}/api/cli/validateToken`, {
|
|
122
|
+
method: "POST",
|
|
123
|
+
headers: { "Content-Type": "application/json" },
|
|
124
|
+
body: JSON.stringify({ token: config.token }),
|
|
125
|
+
});
|
|
126
|
+
if (res.ok) {
|
|
127
|
+
checks.push({
|
|
128
|
+
name: "Backend reachable + token valid",
|
|
129
|
+
status: "pass",
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
checks.push({
|
|
134
|
+
name: "Backend reachable + token valid",
|
|
135
|
+
status: "fail",
|
|
136
|
+
detail: `HTTP ${res.status} — run \`codiedev connect\` with a fresh token`,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
catch (err) {
|
|
141
|
+
checks.push({
|
|
142
|
+
name: "Backend reachable + token valid",
|
|
143
|
+
status: "fail",
|
|
144
|
+
detail: err.message,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
// 3. Authenticated endpoint smoke test (inbox query)
|
|
148
|
+
try {
|
|
149
|
+
await (0, shared_1.apiRequest)("GET", "/api/cli/inbox", {
|
|
150
|
+
config,
|
|
151
|
+
query: { limit: 1 },
|
|
152
|
+
});
|
|
153
|
+
checks.push({
|
|
154
|
+
name: "Authenticated endpoint smoke test (`codiedev inbox`)",
|
|
155
|
+
status: "pass",
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
catch (err) {
|
|
159
|
+
checks.push({
|
|
160
|
+
name: "Authenticated endpoint smoke test",
|
|
161
|
+
status: "fail",
|
|
162
|
+
detail: err.message,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
// 4. Tracked repos
|
|
166
|
+
const repoCount = config.repos?.length ?? 0;
|
|
167
|
+
checks.push({
|
|
168
|
+
name: "Tracked repos",
|
|
169
|
+
status: repoCount > 0 ? "pass" : "warn",
|
|
170
|
+
detail: repoCount > 0
|
|
171
|
+
? `${repoCount} repo${repoCount === 1 ? "" : "s"}`
|
|
172
|
+
: "0 — link repos in the portal so sessions get captured",
|
|
173
|
+
});
|
|
174
|
+
// 5. Claude Code setup (if present)
|
|
175
|
+
if (claudeCodeInstalled()) {
|
|
176
|
+
checks.push({
|
|
177
|
+
name: "Claude Code detected",
|
|
178
|
+
status: "pass",
|
|
179
|
+
detail: "~/.claude",
|
|
180
|
+
});
|
|
181
|
+
checks.push({
|
|
182
|
+
name: "Claude Code SessionEnd hook",
|
|
183
|
+
status: hasCodiedevHook(CLAUDE_SETTINGS_PATH, "SessionEnd") ? "pass" : "fail",
|
|
184
|
+
detail: hasCodiedevHook(CLAUDE_SETTINGS_PATH, "SessionEnd")
|
|
185
|
+
? "~/.claude/settings.json"
|
|
186
|
+
: "missing — re-run `codiedev connect`",
|
|
187
|
+
});
|
|
188
|
+
checks.push({
|
|
189
|
+
name: "Claude Code agent instructions",
|
|
190
|
+
status: hasCodiedevInstructions(CLAUDE_INSTRUCTIONS_PATH) ? "pass" : "fail",
|
|
191
|
+
detail: hasCodiedevInstructions(CLAUDE_INSTRUCTIONS_PATH)
|
|
192
|
+
? "~/.claude/CLAUDE.md"
|
|
193
|
+
: "missing — re-run `codiedev connect`",
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
checks.push({
|
|
198
|
+
name: "Claude Code",
|
|
199
|
+
status: "warn",
|
|
200
|
+
detail: "not installed on this machine",
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
// 6. Codex setup (if present)
|
|
204
|
+
if (codexInstalled()) {
|
|
205
|
+
checks.push({
|
|
206
|
+
name: "Codex detected",
|
|
207
|
+
status: "pass",
|
|
208
|
+
detail: "~/.codex",
|
|
209
|
+
});
|
|
210
|
+
checks.push({
|
|
211
|
+
name: "Codex Stop hook",
|
|
212
|
+
status: hasCodiedevHook(CODEX_HOOKS_PATH, "Stop") ? "pass" : "fail",
|
|
213
|
+
detail: hasCodiedevHook(CODEX_HOOKS_PATH, "Stop")
|
|
214
|
+
? "~/.codex/hooks.json"
|
|
215
|
+
: "missing — re-run `codiedev connect`",
|
|
216
|
+
});
|
|
217
|
+
checks.push({
|
|
218
|
+
name: "Codex agent instructions",
|
|
219
|
+
status: hasCodiedevInstructions(CODEX_INSTRUCTIONS_PATH) ? "pass" : "fail",
|
|
220
|
+
detail: hasCodiedevInstructions(CODEX_INSTRUCTIONS_PATH)
|
|
221
|
+
? "~/.codex/AGENTS.md"
|
|
222
|
+
: "missing — re-run `codiedev connect`",
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
else {
|
|
226
|
+
checks.push({
|
|
227
|
+
name: "Codex",
|
|
228
|
+
status: "warn",
|
|
229
|
+
detail: "not installed on this machine",
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
// 7. gh CLI (optional — only needed for reverse-ticket)
|
|
233
|
+
checks.push({
|
|
234
|
+
name: "GitHub CLI (`gh`) for reverse-ticket command",
|
|
235
|
+
status: hasGhCli() ? "pass" : "warn",
|
|
236
|
+
detail: hasGhCli()
|
|
237
|
+
? "installed"
|
|
238
|
+
: "not installed — only needed if you use `codiedev reverse-ticket <pr-url>`",
|
|
239
|
+
});
|
|
240
|
+
report(checks);
|
|
241
|
+
const failures = checks.filter((c) => c.status === "fail");
|
|
242
|
+
if (failures.length > 0) {
|
|
243
|
+
process.exit(1);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
function report(checks) {
|
|
247
|
+
for (const c of checks) {
|
|
248
|
+
const sym = symbol(c.status);
|
|
249
|
+
const line = c.detail ? ` ${sym} ${c.name} — ${c.detail}` : ` ${sym} ${c.name}`;
|
|
250
|
+
console.log(line);
|
|
251
|
+
}
|
|
252
|
+
const fails = checks.filter((c) => c.status === "fail").length;
|
|
253
|
+
const warns = checks.filter((c) => c.status === "warn").length;
|
|
254
|
+
const passes = checks.filter((c) => c.status === "pass").length;
|
|
255
|
+
console.log();
|
|
256
|
+
if (fails === 0) {
|
|
257
|
+
console.log(`All core checks passed (${passes} ok${warns > 0 ? `, ${warns} warning${warns === 1 ? "" : "s"}` : ""}). You're set.`);
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
console.log(`${fails} check${fails === 1 ? "" : "s"} failed. Re-run \`codiedev connect\` or check your token at https://codiedev.com/portal/integrations/claude-code`);
|
|
261
|
+
}
|
|
262
|
+
console.log();
|
|
263
|
+
}
|
package/dist/connect.js
CHANGED
|
@@ -185,9 +185,9 @@ async function runConnect() {
|
|
|
185
185
|
for (const target of installed) {
|
|
186
186
|
console.log(` - ${target}`);
|
|
187
187
|
}
|
|
188
|
-
console.log("Sessions will be captured automatically
|
|
189
|
-
}
|
|
190
|
-
else {
|
|
191
|
-
console.log();
|
|
188
|
+
console.log("Sessions will be captured automatically.");
|
|
192
189
|
}
|
|
190
|
+
console.log();
|
|
191
|
+
console.log("Run `codiedev doctor` to verify everything's wired up.");
|
|
192
|
+
console.log();
|
|
193
193
|
}
|
package/dist/mcp.js
CHANGED
|
@@ -52,7 +52,7 @@ const path = __importStar(require("path"));
|
|
|
52
52
|
const utils_1 = require("./utils");
|
|
53
53
|
const shared_1 = require("./commands/shared");
|
|
54
54
|
const PKG_NAME = "codiedev";
|
|
55
|
-
const PKG_VERSION = "0.
|
|
55
|
+
const PKG_VERSION = "0.4.0";
|
|
56
56
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
57
57
|
// Tool definitions — descriptions tuned so Claude/Codex resolve natural-language
|
|
58
58
|
// requests into the right tool without manual steering.
|
|
@@ -202,6 +202,176 @@ const TOOLS = [
|
|
|
202
202
|
required: ["artifactId"],
|
|
203
203
|
},
|
|
204
204
|
},
|
|
205
|
+
{
|
|
206
|
+
name: "codiedev_post_to_feed",
|
|
207
|
+
description: "Publish an artifact to the team's break-room feed with an intent " +
|
|
208
|
+
"and optional @mentions. Use when the user asks to 'post this to " +
|
|
209
|
+
"the team', 'publish to the feed', 'share with the team', 'ask the " +
|
|
210
|
+
"team', or when they want broader visibility than a single ping. " +
|
|
211
|
+
"Pass the artifact's filename (e.g., 'spec-214.md') and pick an " +
|
|
212
|
+
"intent that matches the request: share = here's a skill/spec; " +
|
|
213
|
+
"request_review = please review; request_expertise = I'm stuck, " +
|
|
214
|
+
"who knows this; link_share = fyi; rfc = proposing a change. " +
|
|
215
|
+
"Optionally @mention teammates (by first name) so they get a " +
|
|
216
|
+
"notification. Returns the feed post id.",
|
|
217
|
+
inputSchema: {
|
|
218
|
+
type: "object",
|
|
219
|
+
properties: {
|
|
220
|
+
filename: {
|
|
221
|
+
type: "string",
|
|
222
|
+
description: "Artifact filename to attach (e.g., 'spec-214.md'). The post " +
|
|
223
|
+
"will render a preview card linking to it.",
|
|
224
|
+
},
|
|
225
|
+
title: {
|
|
226
|
+
type: "string",
|
|
227
|
+
description: "Headline of the post. Short, imperative — e.g., 'Check my " +
|
|
228
|
+
"OAuth middleware spec' or 'Anyone know how we handle X?'.",
|
|
229
|
+
},
|
|
230
|
+
body: {
|
|
231
|
+
type: "string",
|
|
232
|
+
description: "Body of the post in markdown. A short explanation of why the " +
|
|
233
|
+
"user is posting (the artifact carries the detail).",
|
|
234
|
+
},
|
|
235
|
+
intent: {
|
|
236
|
+
type: "string",
|
|
237
|
+
enum: [
|
|
238
|
+
"share",
|
|
239
|
+
"request_review",
|
|
240
|
+
"request_expertise",
|
|
241
|
+
"link_share",
|
|
242
|
+
"rfc",
|
|
243
|
+
],
|
|
244
|
+
description: "Why the user is posting. See tool description.",
|
|
245
|
+
},
|
|
246
|
+
mentions: {
|
|
247
|
+
type: "array",
|
|
248
|
+
items: { type: "string" },
|
|
249
|
+
description: "Teammate first names (or handles) to @mention. Each mention " +
|
|
250
|
+
"creates a notification for that teammate.",
|
|
251
|
+
},
|
|
252
|
+
format: {
|
|
253
|
+
type: "string",
|
|
254
|
+
enum: ["article", "quick", "workflow", "review"],
|
|
255
|
+
description: "Post format. Default 'article' if omitted. Use 'quick' for " +
|
|
256
|
+
"short one-liners.",
|
|
257
|
+
},
|
|
258
|
+
tags: {
|
|
259
|
+
type: "array",
|
|
260
|
+
items: { type: "string" },
|
|
261
|
+
description: "Optional tags, lowercase, hyphen-safe.",
|
|
262
|
+
},
|
|
263
|
+
},
|
|
264
|
+
required: ["title", "body"],
|
|
265
|
+
},
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
name: "codiedev_share_with",
|
|
269
|
+
description: "Grant a teammate persistent read (or edit) access to an artifact " +
|
|
270
|
+
"without sending a notification. Use when the user says 'share this " +
|
|
271
|
+
"with Greg' or 'give Jason access', and doesn't want to surface it " +
|
|
272
|
+
"as a message. If they want the teammate actively notified, use " +
|
|
273
|
+
"codiedev_send_to instead.",
|
|
274
|
+
inputSchema: {
|
|
275
|
+
type: "object",
|
|
276
|
+
properties: {
|
|
277
|
+
filename: { type: "string", description: "Artifact filename, e.g. 'spec-214.md'." },
|
|
278
|
+
to: {
|
|
279
|
+
type: "string",
|
|
280
|
+
description: "Teammate first name, full name, or email. Ambiguous matches " +
|
|
281
|
+
"return a list — reprompt the user if so.",
|
|
282
|
+
},
|
|
283
|
+
role: {
|
|
284
|
+
type: "string",
|
|
285
|
+
enum: ["read", "edit"],
|
|
286
|
+
description: "Access level. Default 'read' if omitted.",
|
|
287
|
+
},
|
|
288
|
+
},
|
|
289
|
+
required: ["filename", "to"],
|
|
290
|
+
},
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
name: "codiedev_send_to",
|
|
294
|
+
description: "Grant access to an artifact AND send a short message to a " +
|
|
295
|
+
"teammate. Use when the user asks to 'send this to Greg', 'ask " +
|
|
296
|
+
"Greg to look at this', or 'share this with Jason and tell him X'. " +
|
|
297
|
+
"This is the active handoff version of codiedev_share_with — the " +
|
|
298
|
+
"recipient gets a ping in their inbox.",
|
|
299
|
+
inputSchema: {
|
|
300
|
+
type: "object",
|
|
301
|
+
properties: {
|
|
302
|
+
filename: { type: "string", description: "Artifact filename." },
|
|
303
|
+
to: { type: "string", description: "Teammate first name, full name, or email." },
|
|
304
|
+
message: {
|
|
305
|
+
type: "string",
|
|
306
|
+
description: "Optional short note to send with the share. If omitted, only " +
|
|
307
|
+
"the grant is created (no ping).",
|
|
308
|
+
},
|
|
309
|
+
},
|
|
310
|
+
required: ["filename", "to"],
|
|
311
|
+
},
|
|
312
|
+
},
|
|
313
|
+
{
|
|
314
|
+
name: "codiedev_react",
|
|
315
|
+
description: "React to a feed post with an emoji. Use when the user says 'react " +
|
|
316
|
+
"to that post with X', 'mark as used', or 'upvote'. The 🛠 " +
|
|
317
|
+
"reaction is the signal for 'I used this' and bumps the linked " +
|
|
318
|
+
"artifact's reuse count. Pass the post id (get it from " +
|
|
319
|
+
"codiedev_get_library or a previous post-to-feed response).",
|
|
320
|
+
inputSchema: {
|
|
321
|
+
type: "object",
|
|
322
|
+
properties: {
|
|
323
|
+
postId: { type: "string", description: "Feed post id." },
|
|
324
|
+
emoji: {
|
|
325
|
+
type: "string",
|
|
326
|
+
enum: ["👍", "🔥", "💡", "🛠", "👑"],
|
|
327
|
+
description: "Reaction emoji. 🛠 = 'I used this' (counts heaviest toward " +
|
|
328
|
+
"the author's score and the artifact's reuse count).",
|
|
329
|
+
},
|
|
330
|
+
},
|
|
331
|
+
required: ["postId", "emoji"],
|
|
332
|
+
},
|
|
333
|
+
},
|
|
334
|
+
{
|
|
335
|
+
name: "codiedev_search",
|
|
336
|
+
description: "Search the team's artifact library by natural-language query. Use " +
|
|
337
|
+
"when the user asks 'has anyone solved X?', 'find me the spec on Y', " +
|
|
338
|
+
"or 'look for prior work on Z'. Always search before pushing a new " +
|
|
339
|
+
"artifact so the user doesn't duplicate work. Returns ranked hits " +
|
|
340
|
+
"with title, type, filename key, and a snippet.",
|
|
341
|
+
inputSchema: {
|
|
342
|
+
type: "object",
|
|
343
|
+
properties: {
|
|
344
|
+
query: { type: "string", description: "Natural-language search query." },
|
|
345
|
+
limit: { type: "integer", description: "Max hits. Default 10." },
|
|
346
|
+
},
|
|
347
|
+
required: ["query"],
|
|
348
|
+
},
|
|
349
|
+
},
|
|
350
|
+
{
|
|
351
|
+
name: "codiedev_get_library",
|
|
352
|
+
description: "List artifacts in the team library, scoped to what the current " +
|
|
353
|
+
"user authored, what's been shared with them, or everything in the " +
|
|
354
|
+
"company. Use when the user asks 'show me my artifacts', 'what did " +
|
|
355
|
+
"I push?', 'what has Greg shared with me?', or 'browse the library'.",
|
|
356
|
+
inputSchema: {
|
|
357
|
+
type: "object",
|
|
358
|
+
properties: {
|
|
359
|
+
scope: {
|
|
360
|
+
type: "string",
|
|
361
|
+
enum: ["mine", "shared", "all"],
|
|
362
|
+
description: "'mine' = artifacts I authored (default). 'shared' = artifacts " +
|
|
363
|
+
"explicitly shared with me. 'all' = everything in the company.",
|
|
364
|
+
},
|
|
365
|
+
type: {
|
|
366
|
+
type: "string",
|
|
367
|
+
enum: ["spec", "bugfix", "decision", "proposal", "review", "note"],
|
|
368
|
+
description: "Filter to one artifact type.",
|
|
369
|
+
},
|
|
370
|
+
folderPath: { type: "string", description: "Filter to one folder path." },
|
|
371
|
+
limit: { type: "integer", description: "Max rows. Default 50." },
|
|
372
|
+
},
|
|
373
|
+
},
|
|
374
|
+
},
|
|
205
375
|
];
|
|
206
376
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
207
377
|
// Server
|
|
@@ -241,6 +411,18 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
|
241
411
|
return await handleNote(args, config);
|
|
242
412
|
case "codiedev_promote":
|
|
243
413
|
return await handlePromote(args, config);
|
|
414
|
+
case "codiedev_post_to_feed":
|
|
415
|
+
return await handlePostToFeed(args, config);
|
|
416
|
+
case "codiedev_share_with":
|
|
417
|
+
return await handleShareWith(args, config);
|
|
418
|
+
case "codiedev_send_to":
|
|
419
|
+
return await handleSendTo(args, config);
|
|
420
|
+
case "codiedev_react":
|
|
421
|
+
return await handleReact(args, config);
|
|
422
|
+
case "codiedev_search":
|
|
423
|
+
return await handleSearch(args, config);
|
|
424
|
+
case "codiedev_get_library":
|
|
425
|
+
return await handleGetLibrary(args, config);
|
|
244
426
|
default:
|
|
245
427
|
return {
|
|
246
428
|
isError: true,
|
|
@@ -394,6 +576,193 @@ async function handlePromote(args, config) {
|
|
|
394
576
|
],
|
|
395
577
|
};
|
|
396
578
|
}
|
|
579
|
+
async function handlePostToFeed(args, config) {
|
|
580
|
+
const filename = asStringOrUndefined(args.filename);
|
|
581
|
+
const title = asString(args.title);
|
|
582
|
+
const body = asString(args.body);
|
|
583
|
+
const intent = asStringOrUndefined(args.intent);
|
|
584
|
+
const format = asStringOrUndefined(args.format);
|
|
585
|
+
const mentionsRaw = Array.isArray(args.mentions) ? args.mentions : [];
|
|
586
|
+
const mentions = mentionsRaw.filter((m) => typeof m === "string");
|
|
587
|
+
const tagsRaw = Array.isArray(args.tags) ? args.tags : [];
|
|
588
|
+
const tags = tagsRaw.filter((t) => typeof t === "string");
|
|
589
|
+
if (!title)
|
|
590
|
+
throw new Error("title required");
|
|
591
|
+
if (!body)
|
|
592
|
+
throw new Error("body required");
|
|
593
|
+
const res = await (0, shared_1.apiRequest)("POST", "/api/cli/post-to-feed", {
|
|
594
|
+
config,
|
|
595
|
+
body: { filename, title, body, intent, format, mentions, tags },
|
|
596
|
+
});
|
|
597
|
+
const pieces = [`✓ Posted to feed (id=${res.postId}).`];
|
|
598
|
+
if (res.linkedArtifact?.key)
|
|
599
|
+
pieces.push(` attached: ${res.linkedArtifact.key}`);
|
|
600
|
+
if (res.intent)
|
|
601
|
+
pieces.push(` intent: ${res.intent}`);
|
|
602
|
+
if (res.mentions?.length) {
|
|
603
|
+
pieces.push(` mentioned: ${res.mentions.map((m) => m.name).join(", ")}`);
|
|
604
|
+
}
|
|
605
|
+
return { content: [{ type: "text", text: pieces.join("\n") }] };
|
|
606
|
+
}
|
|
607
|
+
async function handleShareWith(args, config) {
|
|
608
|
+
const filename = asString(args.filename);
|
|
609
|
+
const to = asString(args.to);
|
|
610
|
+
const role = asStringOrUndefined(args.role);
|
|
611
|
+
if (!filename)
|
|
612
|
+
throw new Error("filename required");
|
|
613
|
+
if (!to)
|
|
614
|
+
throw new Error("to required");
|
|
615
|
+
try {
|
|
616
|
+
const res = await (0, shared_1.apiRequest)("POST", "/api/cli/share-with", {
|
|
617
|
+
config,
|
|
618
|
+
body: { filename, to, role },
|
|
619
|
+
});
|
|
620
|
+
const verb = res.action === "added"
|
|
621
|
+
? "Shared"
|
|
622
|
+
: res.action === "updated"
|
|
623
|
+
? "Updated share for"
|
|
624
|
+
: "Already shared with";
|
|
625
|
+
return {
|
|
626
|
+
content: [
|
|
627
|
+
{
|
|
628
|
+
type: "text",
|
|
629
|
+
text: `✓ ${verb} ${res.recipient.name} <${res.recipient.email}> on ${filename} (role=${role ?? "read"}).`,
|
|
630
|
+
},
|
|
631
|
+
],
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
catch (err) {
|
|
635
|
+
return formatAmbiguousRecipient(err, to);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
async function handleSendTo(args, config) {
|
|
639
|
+
const filename = asString(args.filename);
|
|
640
|
+
const to = asString(args.to);
|
|
641
|
+
const message = asStringOrUndefined(args.message);
|
|
642
|
+
if (!filename)
|
|
643
|
+
throw new Error("filename required");
|
|
644
|
+
if (!to)
|
|
645
|
+
throw new Error("to required");
|
|
646
|
+
try {
|
|
647
|
+
const res = await (0, shared_1.apiRequest)("POST", "/api/cli/send-to", {
|
|
648
|
+
config,
|
|
649
|
+
body: { filename, to, message },
|
|
650
|
+
});
|
|
651
|
+
const suffix = res.pingId ? ` with a message.` : ` (no message).`;
|
|
652
|
+
return {
|
|
653
|
+
content: [
|
|
654
|
+
{
|
|
655
|
+
type: "text",
|
|
656
|
+
text: `✓ Sent ${filename} to ${res.recipient.name} <${res.recipient.email}>${suffix}`,
|
|
657
|
+
},
|
|
658
|
+
],
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
catch (err) {
|
|
662
|
+
return formatAmbiguousRecipient(err, to);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
async function handleReact(args, config) {
|
|
666
|
+
const postId = asString(args.postId);
|
|
667
|
+
const emoji = asString(args.emoji);
|
|
668
|
+
if (!postId)
|
|
669
|
+
throw new Error("postId required");
|
|
670
|
+
if (!emoji)
|
|
671
|
+
throw new Error("emoji required");
|
|
672
|
+
const res = await (0, shared_1.apiRequest)("POST", "/api/cli/react", {
|
|
673
|
+
config,
|
|
674
|
+
body: { postId, emoji },
|
|
675
|
+
});
|
|
676
|
+
const verb = res.action === "added"
|
|
677
|
+
? "Reacted"
|
|
678
|
+
: res.action === "removed"
|
|
679
|
+
? "Removed reaction"
|
|
680
|
+
: "Swapped reaction to";
|
|
681
|
+
return {
|
|
682
|
+
content: [{ type: "text", text: `✓ ${verb} ${res.emoji}` }],
|
|
683
|
+
};
|
|
684
|
+
}
|
|
685
|
+
async function handleSearch(args, config) {
|
|
686
|
+
const query = asString(args.query);
|
|
687
|
+
const limit = asIntOrUndefined(args.limit);
|
|
688
|
+
if (!query)
|
|
689
|
+
throw new Error("query required");
|
|
690
|
+
const res = await (0, shared_1.apiRequest)("GET", "/api/cli/search", {
|
|
691
|
+
config,
|
|
692
|
+
query: { q: query, limit: limit?.toString() },
|
|
693
|
+
});
|
|
694
|
+
if (res.hits.length === 0) {
|
|
695
|
+
return {
|
|
696
|
+
content: [{ type: "text", text: `No artifacts matched "${query}".` }],
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
const lines = [`Found ${res.hits.length} match${res.hits.length === 1 ? "" : "es"}:`, ""];
|
|
700
|
+
for (const h of res.hits) {
|
|
701
|
+
lines.push(`[${h.type}] ${h.title}${h.key ? ` (${h.key})` : ""}`);
|
|
702
|
+
if (h.snippet) {
|
|
703
|
+
lines.push(` ${h.snippet.slice(0, 160)}${h.snippet.length > 160 ? "…" : ""}`);
|
|
704
|
+
}
|
|
705
|
+
lines.push("");
|
|
706
|
+
}
|
|
707
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
708
|
+
}
|
|
709
|
+
async function handleGetLibrary(args, config) {
|
|
710
|
+
const scope = asStringOrUndefined(args.scope);
|
|
711
|
+
const type = asStringOrUndefined(args.type);
|
|
712
|
+
const folderPath = asStringOrUndefined(args.folderPath);
|
|
713
|
+
const limit = asIntOrUndefined(args.limit);
|
|
714
|
+
const res = await (0, shared_1.apiRequest)("GET", "/api/cli/library", {
|
|
715
|
+
config,
|
|
716
|
+
query: {
|
|
717
|
+
scope,
|
|
718
|
+
type,
|
|
719
|
+
folderPath,
|
|
720
|
+
limit: limit?.toString(),
|
|
721
|
+
},
|
|
722
|
+
});
|
|
723
|
+
if (res.artifacts.length === 0) {
|
|
724
|
+
return {
|
|
725
|
+
content: [
|
|
726
|
+
{
|
|
727
|
+
type: "text",
|
|
728
|
+
text: `Library is empty${scope && scope !== "mine" ? ` for scope=${scope}` : ""}.`,
|
|
729
|
+
},
|
|
730
|
+
],
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
const lines = [
|
|
734
|
+
`Library · scope=${scope ?? "mine"} · ${res.artifacts.length} item${res.artifacts.length === 1 ? "" : "s"}`,
|
|
735
|
+
"",
|
|
736
|
+
];
|
|
737
|
+
for (const a of res.artifacts) {
|
|
738
|
+
const loc = a.folderPath ? `${a.folderPath}/` : "";
|
|
739
|
+
const v = a.version ? ` v${a.version}` : "";
|
|
740
|
+
const life = a.lifecycle ? ` · ${a.lifecycle}` : "";
|
|
741
|
+
const when = a.updatedAt ? ` · ${(0, shared_1.timeAgo)(a.updatedAt)}` : "";
|
|
742
|
+
lines.push(`[${a.type}] ${loc}${a.key ?? a.title}${v}${life}${when}`);
|
|
743
|
+
lines.push(` ${a.title}`);
|
|
744
|
+
lines.push("");
|
|
745
|
+
}
|
|
746
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
747
|
+
}
|
|
748
|
+
function formatAmbiguousRecipient(err, to) {
|
|
749
|
+
const e = err;
|
|
750
|
+
if (e.status === 409 && e.body?.candidates?.length) {
|
|
751
|
+
const list = e.body.candidates
|
|
752
|
+
.map((c) => `- ${c.name} <${c.email}>`)
|
|
753
|
+
.join("\n");
|
|
754
|
+
return {
|
|
755
|
+
isError: true,
|
|
756
|
+
content: [
|
|
757
|
+
{
|
|
758
|
+
type: "text",
|
|
759
|
+
text: `Multiple recipients matched "${to}". Ask the user which:\n${list}`,
|
|
760
|
+
},
|
|
761
|
+
],
|
|
762
|
+
};
|
|
763
|
+
}
|
|
764
|
+
throw err;
|
|
765
|
+
}
|
|
397
766
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
398
767
|
// Helpers
|
|
399
768
|
// ─────────────────────────────────────────────────────────────────────────────
|
package/dist/utils.js
CHANGED
|
@@ -206,13 +206,19 @@ thought, use the \`codiedev\` CLI via Bash:**
|
|
|
206
206
|
|
|
207
207
|
| Intent | Command |
|
|
208
208
|
|---|---|
|
|
209
|
-
| "push this spec" / "
|
|
209
|
+
| "push this spec" / "save this as a skill" | \`codiedev push <file.md>\` |
|
|
210
210
|
| "pull X" / "grab the latest spec-X" / "what did Maya push?" | \`codiedev pull <key>\` |
|
|
211
211
|
| "ping Nic about this" / "ask Maya for her take" | \`codiedev ping <name> "<msg>" [--with <key>]\` |
|
|
212
212
|
| "any messages?" / "check my inbox" / "what did Nic say?" | \`codiedev inbox\` |
|
|
213
213
|
| "read Nic's reply" / "mark that ping read" | \`codiedev read <ping-id>\` |
|
|
214
214
|
| "note that X is a follow-up" / "remember X" | \`codiedev note "<text>"\` |
|
|
215
215
|
| "promote the extracted spec" | \`codiedev promote <artifact-id>\` |
|
|
216
|
+
| "post this to the team" / "publish to the feed" / "share with the team" | MCP tool \`codiedev_post_to_feed\` |
|
|
217
|
+
| "share with Greg" / "give Jason access" (no notification) | MCP tool \`codiedev_share_with\` |
|
|
218
|
+
| "send this to Greg" / "ask Greg to look at this" | MCP tool \`codiedev_send_to\` |
|
|
219
|
+
| "find something about X" / "has anyone solved Y?" | MCP tool \`codiedev_search\` |
|
|
220
|
+
| "show my library" / "what artifacts exist?" | MCP tool \`codiedev_get_library\` |
|
|
221
|
+
| "react 🛠 to that post" / "mark as used" | MCP tool \`codiedev_react\` |
|
|
216
222
|
|
|
217
223
|
**Filename conventions (set artifact type automatically):**
|
|
218
224
|
- \`spec-*.md\` → spec
|
|
@@ -231,6 +237,22 @@ thought, use the \`codiedev\` CLI via Bash:**
|
|
|
231
237
|
**Teammate names:** first name usually works (\`codiedev ping nic ...\`).
|
|
232
238
|
If ambiguous, the CLI returns candidates — retry with the full email.
|
|
233
239
|
|
|
240
|
+
**Feed posts (broad team reach):** use \`codiedev_post_to_feed\` when the
|
|
241
|
+
user wants visibility beyond one teammate — e.g., announcing a new skill,
|
|
242
|
+
asking the team who knows about X, proposing a change. Always pass an
|
|
243
|
+
\`intent\` that matches the request: \`share\` (here's a skill),
|
|
244
|
+
\`request_review\`, \`request_expertise\` (stuck, who knows this),
|
|
245
|
+
\`link_share\` (fyi), or \`rfc\` (proposing a change). Attach a \`filename\`
|
|
246
|
+
to link a specific artifact and \`mentions\` to tag teammates.
|
|
247
|
+
|
|
248
|
+
**Search before pushing.** For any "save this as …" request, call
|
|
249
|
+
\`codiedev_search\` first with a relevant query. If prior art exists, pull
|
|
250
|
+
it and iterate rather than duplicating.
|
|
251
|
+
|
|
252
|
+
**Sharing vs sending:** \`codiedev_share_with\` is silent (access only, no
|
|
253
|
+
notification). \`codiedev_send_to\` both grants access AND pings the
|
|
254
|
+
recipient. Use send when the user is actively looping someone in.
|
|
255
|
+
|
|
234
256
|
**Errors:**
|
|
235
257
|
- "not connected" → user needs to run \`npx codiedev connect\` with their
|
|
236
258
|
API token from https://codiedev.com/portal/integrations/claude-code
|