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 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.\n");
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.3.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" / "share this with the team" | \`codiedev push <file.md>\` |
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codiedev",
3
- "version": "0.3.5",
3
+ "version": "0.4.0",
4
4
  "description": "Connect Claude Code or Codex to CodieDev for org-wide session capture and artifact collaboration",
5
5
  "bin": {
6
6
  "codiedev": "./dist/cli.js",