next-ai-editor 0.1.3 → 0.2.1

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.
Files changed (63) hide show
  1. package/dist/AIEditorProvider-CKA2K_g2.js +1428 -0
  2. package/dist/AIEditorProvider-CKA2K_g2.js.map +1 -0
  3. package/dist/AIEditorProvider-C_zRSAuV.cjs +1427 -0
  4. package/dist/AIEditorProvider-C_zRSAuV.cjs.map +1 -0
  5. package/dist/client/AIEditorProvider.d.ts +1 -33
  6. package/dist/client/AIEditorProvider.d.ts.map +1 -1
  7. package/dist/client/components/ChatPanel.d.ts +18 -0
  8. package/dist/client/components/ChatPanel.d.ts.map +1 -0
  9. package/dist/client/components/ControlPill.d.ts +8 -0
  10. package/dist/client/components/ControlPill.d.ts.map +1 -0
  11. package/dist/client/components/MessageItem.d.ts +7 -0
  12. package/dist/client/components/MessageItem.d.ts.map +1 -0
  13. package/dist/client/components/MessageList.d.ts +7 -0
  14. package/dist/client/components/MessageList.d.ts.map +1 -0
  15. package/dist/client/components/TaskHistoryPanel.d.ts +10 -0
  16. package/dist/client/components/TaskHistoryPanel.d.ts.map +1 -0
  17. package/dist/client/components/index.d.ts +11 -0
  18. package/dist/client/components/index.d.ts.map +1 -0
  19. package/dist/client/hooks/index.d.ts +3 -0
  20. package/dist/client/hooks/index.d.ts.map +1 -0
  21. package/dist/client/hooks/useChatStream.d.ts +66 -0
  22. package/dist/client/hooks/useChatStream.d.ts.map +1 -0
  23. package/dist/client/hooks/useHotReload.d.ts +10 -0
  24. package/dist/client/hooks/useHotReload.d.ts.map +1 -0
  25. package/dist/client/index.d.ts +3 -0
  26. package/dist/client/index.d.ts.map +1 -1
  27. package/dist/client.cjs +7 -1
  28. package/dist/client.cjs.map +1 -1
  29. package/dist/client.js +8 -2
  30. package/dist/client.js.map +1 -1
  31. package/dist/{index-DrmEf13c.js → comments-D3m0RsOO.js} +440 -15
  32. package/dist/comments-D3m0RsOO.js.map +1 -0
  33. package/dist/{index-CNJqd4EQ.cjs → comments-Daur80r4.cjs} +426 -1
  34. package/dist/comments-Daur80r4.cjs.map +1 -0
  35. package/dist/index.cjs +34 -27
  36. package/dist/index.cjs.map +1 -1
  37. package/dist/index.js +22 -15
  38. package/dist/index.js.map +1 -1
  39. package/dist/next-ai-editor.css +880 -0
  40. package/dist/server/agent/sdk-client.d.ts +54 -0
  41. package/dist/server/agent/sdk-client.d.ts.map +1 -0
  42. package/dist/server/agent/session-store.d.ts +101 -0
  43. package/dist/server/agent/session-store.d.ts.map +1 -0
  44. package/dist/server/handlers/chat.d.ts +6 -0
  45. package/dist/server/handlers/chat.d.ts.map +1 -0
  46. package/dist/server/handlers/comments.d.ts +10 -0
  47. package/dist/server/handlers/comments.d.ts.map +1 -0
  48. package/dist/server/handlers/index.d.ts +2 -1
  49. package/dist/server/handlers/index.d.ts.map +1 -1
  50. package/dist/server/index.d.ts +1 -0
  51. package/dist/server/index.d.ts.map +1 -1
  52. package/dist/server.cjs +27 -26
  53. package/dist/server.cjs.map +1 -1
  54. package/dist/server.js +14 -13
  55. package/dist/shared/comment-types.d.ts +140 -0
  56. package/dist/shared/comment-types.d.ts.map +1 -0
  57. package/package.json +13 -4
  58. package/dist/AIEditorProvider-BGHm2xyU.cjs +0 -2167
  59. package/dist/AIEditorProvider-BGHm2xyU.cjs.map +0 -1
  60. package/dist/AIEditorProvider-CxdGjdLL.js +0 -2168
  61. package/dist/AIEditorProvider-CxdGjdLL.js.map +0 -1
  62. package/dist/index-CNJqd4EQ.cjs.map +0 -1
  63. package/dist/index-DrmEf13c.js.map +0 -1
@@ -1,14 +1,16 @@
1
1
  import { NextResponse } from "next/server";
2
- import fs from "fs/promises";
2
+ import fs, { readFile, writeFile, mkdir } from "fs/promises";
3
3
  import { ChatAnthropic } from "@langchain/anthropic";
4
4
  import { SystemMessage, HumanMessage } from "@langchain/core/messages";
5
- import path from "path";
5
+ import path, { join } from "path";
6
6
  import * as parser from "@babel/parser";
7
7
  import traverse from "@babel/traverse";
8
8
  import * as t from "@babel/types";
9
9
  import { SourceMapConsumer } from "@jridgewell/source-map";
10
10
  import { c as cleanPath, s as shouldSkipPath, n as normalizeSourcePath } from "./path-utils-Bai2xKx9.js";
11
11
  import crypto from "crypto";
12
+ import { unstable_v2_resumeSession, unstable_v2_createSession } from "@anthropic-ai/claude-agent-sdk";
13
+ import { existsSync } from "fs";
12
14
  function validateDevMode() {
13
15
  if (process.env.NODE_ENV !== "development") {
14
16
  return NextResponse.json(
@@ -1514,6 +1516,361 @@ Generate 6-8 initial suggestions:`;
1514
1516
  return null;
1515
1517
  }
1516
1518
  }
1519
+ class AIEditorAgent {
1520
+ constructor(session, projectRoot) {
1521
+ this.session = session;
1522
+ this.projectRoot = projectRoot;
1523
+ }
1524
+ /**
1525
+ * Create a new agent instance
1526
+ */
1527
+ static async create(config) {
1528
+ const sessionOptions = {
1529
+ model: "claude-sonnet-4-5-20250929",
1530
+ // Enable auto-apply for edits without permission prompts
1531
+ permissionMode: "acceptEdits",
1532
+ env: {
1533
+ ...process.env,
1534
+ ANTHROPIC_API_KEY: config.apiKey,
1535
+ // Set working directory
1536
+ PWD: config.projectRoot
1537
+ },
1538
+ // Explicitly allow core development tools
1539
+ allowedTools: config.allowedTools || ["Read", "Edit", "Glob", "Grep"],
1540
+ disallowedTools: config.disallowedTools || []
1541
+ };
1542
+ let session;
1543
+ if (config.sessionId) {
1544
+ session = unstable_v2_resumeSession(config.sessionId, sessionOptions);
1545
+ } else {
1546
+ session = unstable_v2_createSession(sessionOptions);
1547
+ }
1548
+ return new AIEditorAgent(session, config.projectRoot);
1549
+ }
1550
+ /**
1551
+ * Send a message to the agent and stream responses
1552
+ */
1553
+ async *sendMessage(message, componentContext) {
1554
+ let fullMessage = `<response_style>
1555
+ KEEP RESPONSES EXTREMELY BRIEF AND STATUS-LIKE.
1556
+ - Use terse status messages: "Reading file.tsx", "Changing color", "Done"
1557
+ - NO conversational language (avoid: "I'll", "Let me", "Sure", "Great", "I can see")
1558
+ - NO explanations unless explicitly asked
1559
+ - State ONLY what you're doing, nothing more
1560
+ - Format: Action + target (e.g., "Updating StatsCard.tsx:24")
1561
+ </response_style>
1562
+
1563
+ `;
1564
+ if (componentContext) {
1565
+ fullMessage += `[Component: ${componentContext.componentName} at ${componentContext.filePath}:${componentContext.lineNumber}]
1566
+
1567
+ `;
1568
+ }
1569
+ fullMessage += message;
1570
+ await this.session.send(fullMessage);
1571
+ for await (const event of this.session.stream()) {
1572
+ yield event;
1573
+ }
1574
+ }
1575
+ /**
1576
+ * Get the session ID
1577
+ */
1578
+ getSessionId() {
1579
+ return this.session.sessionId;
1580
+ }
1581
+ /**
1582
+ * Close the session
1583
+ */
1584
+ close() {
1585
+ this.session.close();
1586
+ }
1587
+ /**
1588
+ * Async disposal support
1589
+ */
1590
+ async [Symbol.asyncDispose]() {
1591
+ await this.session[Symbol.asyncDispose]();
1592
+ }
1593
+ }
1594
+ class AgentSessionStore {
1595
+ // 5 minutes
1596
+ constructor() {
1597
+ this.sessions = /* @__PURE__ */ new Map();
1598
+ this.SESSION_TIMEOUT = 1e3 * 60 * 30;
1599
+ this.CLEANUP_INTERVAL = 1e3 * 60 * 5;
1600
+ this.startCleanupTimer();
1601
+ }
1602
+ /**
1603
+ * Create or resume a session
1604
+ */
1605
+ async createSession(config) {
1606
+ const sessionId = config.sessionId || this.generateSessionId();
1607
+ const existing = this.sessions.get(sessionId);
1608
+ if (existing) {
1609
+ existing.lastActive = Date.now();
1610
+ existing.threadId = config.threadId;
1611
+ return existing;
1612
+ }
1613
+ const agent = await AIEditorAgent.create({
1614
+ apiKey: config.apiKey,
1615
+ projectRoot: config.projectRoot,
1616
+ sessionId: config.sessionId,
1617
+ allowedTools: config.allowedTools,
1618
+ disallowedTools: config.disallowedTools
1619
+ });
1620
+ const session = {
1621
+ sessionId,
1622
+ threadId: config.threadId,
1623
+ agent,
1624
+ createdAt: Date.now(),
1625
+ lastActive: Date.now(),
1626
+ isLocked: false
1627
+ };
1628
+ this.sessions.set(sessionId, session);
1629
+ return session;
1630
+ }
1631
+ /**
1632
+ * Get a session by ID
1633
+ */
1634
+ getSession(sessionId) {
1635
+ const session = this.sessions.get(sessionId);
1636
+ if (session) {
1637
+ session.lastActive = Date.now();
1638
+ }
1639
+ return session;
1640
+ }
1641
+ /**
1642
+ * Acquire a lock on a session to prevent concurrent edits
1643
+ * Returns true if lock was acquired, false if already locked
1644
+ */
1645
+ acquireLock(sessionId) {
1646
+ const session = this.sessions.get(sessionId);
1647
+ if (!session) {
1648
+ throw new Error(`Session ${sessionId} not found`);
1649
+ }
1650
+ if (session.isLocked) {
1651
+ return false;
1652
+ }
1653
+ session.isLocked = true;
1654
+ session.lastActive = Date.now();
1655
+ return true;
1656
+ }
1657
+ /**
1658
+ * Release a lock on a session
1659
+ */
1660
+ releaseLock(sessionId) {
1661
+ const session = this.sessions.get(sessionId);
1662
+ if (session) {
1663
+ session.isLocked = false;
1664
+ session.lastActive = Date.now();
1665
+ }
1666
+ }
1667
+ /**
1668
+ * Check if any session is currently locked (to prevent concurrent edits)
1669
+ */
1670
+ hasActiveLock() {
1671
+ for (const session of this.sessions.values()) {
1672
+ if (session.isLocked) {
1673
+ return true;
1674
+ }
1675
+ }
1676
+ return false;
1677
+ }
1678
+ /**
1679
+ * Get all active sessions for a thread
1680
+ */
1681
+ getSessionsByThread(threadId) {
1682
+ const sessions = [];
1683
+ for (const session of this.sessions.values()) {
1684
+ if (session.threadId === threadId) {
1685
+ sessions.push(session);
1686
+ }
1687
+ }
1688
+ return sessions;
1689
+ }
1690
+ /**
1691
+ * Delete a session
1692
+ */
1693
+ deleteSession(sessionId) {
1694
+ const session = this.sessions.get(sessionId);
1695
+ if (session) {
1696
+ session.agent.close();
1697
+ this.sessions.delete(sessionId);
1698
+ }
1699
+ }
1700
+ /**
1701
+ * Delete all sessions for a thread
1702
+ */
1703
+ deleteSessionsByThread(threadId) {
1704
+ for (const [sessionId, session] of this.sessions.entries()) {
1705
+ if (session.threadId === threadId) {
1706
+ session.agent.close();
1707
+ this.sessions.delete(sessionId);
1708
+ }
1709
+ }
1710
+ }
1711
+ /**
1712
+ * Get all active sessions
1713
+ */
1714
+ getAllSessions() {
1715
+ return Array.from(this.sessions.values());
1716
+ }
1717
+ /**
1718
+ * Clean up expired sessions
1719
+ */
1720
+ cleanupExpiredSessions() {
1721
+ const now = Date.now();
1722
+ const expiredSessions = [];
1723
+ for (const [sessionId, session] of this.sessions.entries()) {
1724
+ const inactiveTime = now - session.lastActive;
1725
+ if (inactiveTime > this.SESSION_TIMEOUT && !session.isLocked) {
1726
+ expiredSessions.push(sessionId);
1727
+ }
1728
+ }
1729
+ for (const sessionId of expiredSessions) {
1730
+ console.log(
1731
+ `[AgentSessionStore] Cleaning up expired session: ${sessionId}`
1732
+ );
1733
+ this.deleteSession(sessionId);
1734
+ }
1735
+ if (expiredSessions.length > 0) {
1736
+ console.log(
1737
+ `[AgentSessionStore] Cleaned up ${expiredSessions.length} expired sessions`
1738
+ );
1739
+ }
1740
+ }
1741
+ /**
1742
+ * Start periodic cleanup timer
1743
+ */
1744
+ startCleanupTimer() {
1745
+ setInterval(() => {
1746
+ this.cleanupExpiredSessions();
1747
+ }, this.CLEANUP_INTERVAL);
1748
+ }
1749
+ /**
1750
+ * Generate a unique session ID
1751
+ */
1752
+ generateSessionId() {
1753
+ return `session-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
1754
+ }
1755
+ /**
1756
+ * Get statistics about the session store
1757
+ */
1758
+ getStats() {
1759
+ return {
1760
+ totalSessions: this.sessions.size,
1761
+ lockedSessions: Array.from(this.sessions.values()).filter(
1762
+ (s) => s.isLocked
1763
+ ).length,
1764
+ sessions: Array.from(this.sessions.values()).map((s) => ({
1765
+ sessionId: s.sessionId,
1766
+ threadId: s.threadId,
1767
+ isLocked: s.isLocked,
1768
+ ageMinutes: Math.floor((Date.now() - s.createdAt) / 1e3 / 60),
1769
+ inactiveMinutes: Math.floor((Date.now() - s.lastActive) / 1e3 / 60)
1770
+ }))
1771
+ };
1772
+ }
1773
+ }
1774
+ let sessionStore = null;
1775
+ function getSessionStore() {
1776
+ if (!sessionStore) {
1777
+ sessionStore = new AgentSessionStore();
1778
+ }
1779
+ return sessionStore;
1780
+ }
1781
+ async function handleChat(req) {
1782
+ const devModeError = validateDevMode();
1783
+ if (devModeError) return devModeError;
1784
+ try {
1785
+ const body = await req.json();
1786
+ const { sessionId, threadId, message, componentContext } = body;
1787
+ if (!threadId || !message) {
1788
+ return NextResponse.json(
1789
+ { error: "threadId and message are required" },
1790
+ { status: 400 }
1791
+ );
1792
+ }
1793
+ const apiKey = process.env.ANTHROPIC_API_KEY;
1794
+ if (!apiKey) {
1795
+ return NextResponse.json(
1796
+ { error: "ANTHROPIC_API_KEY not configured" },
1797
+ { status: 500 }
1798
+ );
1799
+ }
1800
+ const sessionStore2 = getSessionStore();
1801
+ const projectRoot = process.cwd();
1802
+ const session = await sessionStore2.createSession({
1803
+ sessionId,
1804
+ threadId,
1805
+ apiKey,
1806
+ projectRoot
1807
+ });
1808
+ if (!sessionStore2.acquireLock(session.sessionId)) {
1809
+ return NextResponse.json(
1810
+ {
1811
+ error: "Another operation is in progress. Please wait for it to complete."
1812
+ },
1813
+ { status: 409 }
1814
+ );
1815
+ }
1816
+ const encoder = new TextEncoder();
1817
+ const stream = new ReadableStream({
1818
+ async start(controller) {
1819
+ try {
1820
+ controller.enqueue(
1821
+ encoder.encode(
1822
+ `data: ${JSON.stringify({ type: "connected", sessionId: session.sessionId })}
1823
+
1824
+ `
1825
+ )
1826
+ );
1827
+ for await (const event of session.agent.sendMessage(
1828
+ message,
1829
+ componentContext
1830
+ )) {
1831
+ console.log("[AGENT EVENT]", event.type, JSON.stringify(event, null, 2));
1832
+ controller.enqueue(
1833
+ encoder.encode(`data: ${JSON.stringify(event)}
1834
+
1835
+ `)
1836
+ );
1837
+ }
1838
+ controller.enqueue(
1839
+ encoder.encode(`data: ${JSON.stringify({ type: "done" })}
1840
+
1841
+ `)
1842
+ );
1843
+ controller.close();
1844
+ } catch (error) {
1845
+ console.error("[handleChat] Error:", error);
1846
+ controller.enqueue(
1847
+ encoder.encode(
1848
+ `data: ${JSON.stringify({ type: "error", error: error.message || String(error) })}
1849
+
1850
+ `
1851
+ )
1852
+ );
1853
+ controller.close();
1854
+ } finally {
1855
+ sessionStore2.releaseLock(session.sessionId);
1856
+ }
1857
+ }
1858
+ });
1859
+ return new Response(stream, {
1860
+ headers: {
1861
+ "Content-Type": "text/event-stream",
1862
+ "Cache-Control": "no-cache",
1863
+ Connection: "keep-alive"
1864
+ }
1865
+ });
1866
+ } catch (error) {
1867
+ console.error("[handleChat] Request error:", error);
1868
+ return NextResponse.json(
1869
+ { error: String(error) },
1870
+ { status: 500 }
1871
+ );
1872
+ }
1873
+ }
1517
1874
  async function handleAIEditorRequest(req, context) {
1518
1875
  const { path: path2 } = await context.params;
1519
1876
  const endpoint = path2[0];
@@ -1540,12 +1897,79 @@ async function handleAIEditorRequest(req, context) {
1540
1897
  case "suggestions":
1541
1898
  if (method === "GET") return handleSuggestions(req);
1542
1899
  break;
1900
+ case "chat":
1901
+ if (method === "POST") return handleChat(req);
1902
+ break;
1543
1903
  }
1544
1904
  return NextResponse.json(
1545
1905
  { error: `Unknown endpoint: ${endpoint}` },
1546
1906
  { status: 404 }
1547
1907
  );
1548
1908
  }
1909
+ const STORAGE_DIR = ".ai-editor";
1910
+ const STORAGE_FILE = "comments.json";
1911
+ function getProjectRoot() {
1912
+ return process.cwd();
1913
+ }
1914
+ function getStoragePath() {
1915
+ return join(getProjectRoot(), STORAGE_DIR, STORAGE_FILE);
1916
+ }
1917
+ async function ensureStorageDir() {
1918
+ const dir = join(getProjectRoot(), STORAGE_DIR);
1919
+ if (!existsSync(dir)) {
1920
+ await mkdir(dir, { recursive: true });
1921
+ }
1922
+ }
1923
+ async function handleCommentsRequest(request) {
1924
+ if (process.env.NODE_ENV !== "development") {
1925
+ return NextResponse.json(
1926
+ { error: "Comments API only available in development" },
1927
+ { status: 403 }
1928
+ );
1929
+ }
1930
+ try {
1931
+ if (request.method === "GET") {
1932
+ const storagePath = getStoragePath();
1933
+ if (!existsSync(storagePath)) {
1934
+ return NextResponse.json({ comments: [] });
1935
+ }
1936
+ const data = await readFile(storagePath, "utf-8");
1937
+ const comments = JSON.parse(data);
1938
+ return NextResponse.json({ comments });
1939
+ }
1940
+ if (request.method === "POST") {
1941
+ const body = await request.json();
1942
+ const { action } = body;
1943
+ if (action === "save") {
1944
+ const { comments } = body;
1945
+ await ensureStorageDir();
1946
+ const storagePath = getStoragePath();
1947
+ await writeFile(storagePath, JSON.stringify(comments, null, 2), "utf-8");
1948
+ return NextResponse.json({ success: true });
1949
+ }
1950
+ if (action === "clear") {
1951
+ await ensureStorageDir();
1952
+ const storagePath = getStoragePath();
1953
+ await writeFile(storagePath, JSON.stringify([]), "utf-8");
1954
+ return NextResponse.json({ success: true });
1955
+ }
1956
+ return NextResponse.json(
1957
+ { error: "Invalid action" },
1958
+ { status: 400 }
1959
+ );
1960
+ }
1961
+ return NextResponse.json(
1962
+ { error: "Method not allowed" },
1963
+ { status: 405 }
1964
+ );
1965
+ } catch (error) {
1966
+ console.error("Comment storage error:", error);
1967
+ return NextResponse.json(
1968
+ { error: "Internal server error" },
1969
+ { status: 500 }
1970
+ );
1971
+ }
1972
+ }
1549
1973
  export {
1550
1974
  handleEdit as a,
1551
1975
  handleRead as b,
@@ -1555,22 +1979,23 @@ export {
1555
1979
  handleValidateSession as f,
1556
1980
  handleSuggestions as g,
1557
1981
  handleAIEditorRequest as h,
1558
- isPathSecure as i,
1559
- fileExists$1 as j,
1560
- extractComponentName as k,
1561
- validateGeneratedCode as l,
1562
- findTargetElement as m,
1982
+ handleCommentsRequest as i,
1983
+ isPathSecure as j,
1984
+ fileExists$1 as k,
1985
+ extractComponentName as l,
1986
+ validateGeneratedCode as m,
1563
1987
  normalizePath as n,
1564
- getJSXMemberName as o,
1988
+ findTargetElement as o,
1565
1989
  parseFile as p,
1566
- getAttributeValue as q,
1990
+ getJSXMemberName as q,
1567
1991
  resolveFilePath as r,
1568
1992
  scoreElementMatch as s,
1569
- parseDebugStack as t,
1570
- extractComponentNameFromStack as u,
1993
+ getAttributeValue as t,
1994
+ parseDebugStack as u,
1571
1995
  validateDevMode as v,
1572
- parseDebugStackFrames as w,
1573
- resolveOriginalPosition as x,
1574
- getOriginalPositionFromDebugStack as y
1996
+ extractComponentNameFromStack as w,
1997
+ parseDebugStackFrames as x,
1998
+ resolveOriginalPosition as y,
1999
+ getOriginalPositionFromDebugStack as z
1575
2000
  };
1576
- //# sourceMappingURL=index-DrmEf13c.js.map
2001
+ //# sourceMappingURL=comments-D3m0RsOO.js.map