codiedev 0.3.6 → 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/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.6",
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",