@devness/useai 0.4.27 → 0.5.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/index.js +70 -16
- package/package.json +4 -1
package/dist/index.js
CHANGED
|
@@ -112,7 +112,7 @@ var VERSION;
|
|
|
112
112
|
var init_version = __esm({
|
|
113
113
|
"../shared/dist/constants/version.js"() {
|
|
114
114
|
"use strict";
|
|
115
|
-
VERSION = "0.
|
|
115
|
+
VERSION = "0.5.0";
|
|
116
116
|
}
|
|
117
117
|
});
|
|
118
118
|
|
|
@@ -1263,9 +1263,9 @@ var init_tools = __esm({
|
|
|
1263
1263
|
init_dist();
|
|
1264
1264
|
USEAI_INSTRUCTIONS_TEXT = [
|
|
1265
1265
|
"## UseAI Session Tracking",
|
|
1266
|
-
"- At the START of every response, call `useai_start` with the appropriate task_type.",
|
|
1266
|
+
"- At the START of every response, call `useai_start` with the appropriate task_type. Also generate a short session `title` (generic, no project names) and `private_title` (detailed, can include specifics) from the user's prompt.",
|
|
1267
1267
|
"- For LONG sessions (15+ min), call `useai_heartbeat` periodically.",
|
|
1268
|
-
"- At the END of every response, call `useai_end` with languages used, files_touched_count,
|
|
1268
|
+
"- At the END of every response, call `useai_end` with languages used, files_touched_count, milestones (generic descriptions only \u2014 no project names, file paths, or company names), and an `evaluation` object honestly assessing: prompt_quality (1-5), context_provided (1-5), task_outcome, iteration_count, independence_level (1-5), scope_quality (1-5), tools_leveraged count."
|
|
1269
1269
|
].join("\n");
|
|
1270
1270
|
MCP_HTTP_URL = DAEMON_MCP_URL;
|
|
1271
1271
|
MCP_HTTP_ENTRY = { type: "http", url: MCP_HTTP_URL };
|
|
@@ -1584,6 +1584,9 @@ var init_session_state = __esm({
|
|
|
1584
1584
|
sessionRecordCount;
|
|
1585
1585
|
clientName;
|
|
1586
1586
|
sessionTaskType;
|
|
1587
|
+
sessionTitle;
|
|
1588
|
+
sessionPrivateTitle;
|
|
1589
|
+
sessionPromptWordCount;
|
|
1587
1590
|
project;
|
|
1588
1591
|
chainTipHash;
|
|
1589
1592
|
signingKey;
|
|
@@ -1595,6 +1598,9 @@ var init_session_state = __esm({
|
|
|
1595
1598
|
this.sessionRecordCount = 0;
|
|
1596
1599
|
this.clientName = "unknown";
|
|
1597
1600
|
this.sessionTaskType = "coding";
|
|
1601
|
+
this.sessionTitle = null;
|
|
1602
|
+
this.sessionPrivateTitle = null;
|
|
1603
|
+
this.sessionPromptWordCount = null;
|
|
1598
1604
|
this.project = null;
|
|
1599
1605
|
this.chainTipHash = GENESIS_HASH;
|
|
1600
1606
|
this.signingKey = null;
|
|
@@ -1607,6 +1613,9 @@ var init_session_state = __esm({
|
|
|
1607
1613
|
this.sessionRecordCount = 0;
|
|
1608
1614
|
this.chainTipHash = GENESIS_HASH;
|
|
1609
1615
|
this.sessionTaskType = "coding";
|
|
1616
|
+
this.sessionTitle = null;
|
|
1617
|
+
this.sessionPrivateTitle = null;
|
|
1618
|
+
this.sessionPromptWordCount = null;
|
|
1610
1619
|
this.detectProject();
|
|
1611
1620
|
}
|
|
1612
1621
|
detectProject() {
|
|
@@ -1618,6 +1627,15 @@ var init_session_state = __esm({
|
|
|
1618
1627
|
setTaskType(type) {
|
|
1619
1628
|
this.sessionTaskType = type;
|
|
1620
1629
|
}
|
|
1630
|
+
setTitle(title) {
|
|
1631
|
+
this.sessionTitle = title;
|
|
1632
|
+
}
|
|
1633
|
+
setPrivateTitle(title) {
|
|
1634
|
+
this.sessionPrivateTitle = title;
|
|
1635
|
+
}
|
|
1636
|
+
setPromptWordCount(count) {
|
|
1637
|
+
this.sessionPromptWordCount = count;
|
|
1638
|
+
}
|
|
1621
1639
|
incrementHeartbeat() {
|
|
1622
1640
|
this.heartbeatCount++;
|
|
1623
1641
|
}
|
|
@@ -1691,20 +1709,27 @@ function resolveClient(server2, session2) {
|
|
|
1691
1709
|
function registerTools(server2, session2) {
|
|
1692
1710
|
server2.tool(
|
|
1693
1711
|
"useai_start",
|
|
1694
|
-
|
|
1712
|
+
`Start tracking an AI coding session. Call this at the beginning of every response. Generate a session title from the user's prompt: a generic public "title" (no project/file names) and a detailed "private_title" (can include specifics).`,
|
|
1695
1713
|
{
|
|
1696
|
-
task_type: z2.enum(["coding", "debugging", "testing", "planning", "reviewing", "documenting", "learning", "other"]).optional().describe("What kind of task is the developer working on?")
|
|
1714
|
+
task_type: z2.enum(["coding", "debugging", "testing", "planning", "reviewing", "documenting", "learning", "other"]).optional().describe("What kind of task is the developer working on?"),
|
|
1715
|
+
title: z2.string().optional().describe(`Short public session title derived from the user's prompt. No project names, file paths, or identifying details. Example: "Fix authentication bug"`),
|
|
1716
|
+
private_title: z2.string().optional().describe('Detailed session title for private records. Can include project names and specifics. Example: "Fix JWT refresh in UseAI login flow"')
|
|
1697
1717
|
},
|
|
1698
|
-
async ({ task_type }) => {
|
|
1718
|
+
async ({ task_type, title, private_title }) => {
|
|
1699
1719
|
session2.reset();
|
|
1700
1720
|
resolveClient(server2, session2);
|
|
1701
1721
|
session2.setTaskType(task_type ?? "coding");
|
|
1702
|
-
|
|
1722
|
+
session2.setTitle(title ?? null);
|
|
1723
|
+
session2.setPrivateTitle(private_title ?? null);
|
|
1724
|
+
const chainData = {
|
|
1703
1725
|
client: session2.clientName,
|
|
1704
1726
|
task_type: session2.sessionTaskType,
|
|
1705
1727
|
project: session2.project,
|
|
1706
1728
|
version: VERSION
|
|
1707
|
-
}
|
|
1729
|
+
};
|
|
1730
|
+
if (title) chainData.title = title;
|
|
1731
|
+
if (private_title) chainData.private_title = private_title;
|
|
1732
|
+
const record = session2.appendToChain("session_start", chainData);
|
|
1708
1733
|
return {
|
|
1709
1734
|
content: [
|
|
1710
1735
|
{
|
|
@@ -1737,7 +1762,7 @@ function registerTools(server2, session2) {
|
|
|
1737
1762
|
);
|
|
1738
1763
|
server2.tool(
|
|
1739
1764
|
"useai_end",
|
|
1740
|
-
|
|
1765
|
+
'End the current AI coding session and record milestones. Each milestone needs TWO titles: (1) a generic public "title" safe for public display (NEVER include project names, file names, class names, or any identifying details), and (2) an optional detailed "private_title" for the user\'s own records that CAN include project names, file names, and specific details. GOOD title: "Implemented user authentication". GOOD private_title: "Added JWT auth to UseAI API server". BAD title: "Fixed bug in Acme auth service". Also provide an `evaluation` object assessing the session: prompt_quality (1-5), context_provided (1-5), task_outcome (completed/partial/abandoned/blocked), iteration_count, independence_level (1-5), scope_quality (1-5), and tools_leveraged count. Score honestly based on the actual interaction.',
|
|
1741
1766
|
{
|
|
1742
1767
|
task_type: z2.enum(["coding", "debugging", "testing", "planning", "reviewing", "documenting", "learning", "other"]).optional().describe("What kind of task was the developer working on?"),
|
|
1743
1768
|
languages: z2.array(z2.string()).optional().describe("Programming languages used (e.g. ['typescript', 'python'])"),
|
|
@@ -1747,9 +1772,18 @@ function registerTools(server2, session2) {
|
|
|
1747
1772
|
private_title: z2.string().optional().describe("Detailed description for the user's private records. CAN include project names, file names, and specific details. Example: 'Added private/public milestone support to UseAI MCP server'"),
|
|
1748
1773
|
category: z2.enum(["feature", "bugfix", "refactor", "test", "docs", "setup", "deployment", "other"]).describe("Type of work completed"),
|
|
1749
1774
|
complexity: z2.enum(["simple", "medium", "complex"]).optional().describe("How complex was this task?")
|
|
1750
|
-
})).optional().describe("What was accomplished this session? List each distinct piece of work completed. Provide both a generic public title and an optional detailed private_title.")
|
|
1775
|
+
})).optional().describe("What was accomplished this session? List each distinct piece of work completed. Provide both a generic public title and an optional detailed private_title."),
|
|
1776
|
+
evaluation: z2.object({
|
|
1777
|
+
prompt_quality: z2.number().min(1).max(5).describe("How clear, specific, and complete was the initial prompt? 1=vague/ambiguous, 5=crystal clear with acceptance criteria"),
|
|
1778
|
+
context_provided: z2.number().min(1).max(5).describe("Did the user provide relevant context (files, errors, constraints)? 1=no context, 5=comprehensive context"),
|
|
1779
|
+
task_outcome: z2.enum(["completed", "partial", "abandoned", "blocked"]).describe("Was the primary task achieved?"),
|
|
1780
|
+
iteration_count: z2.number().min(1).describe("Number of user-to-AI turns in this session"),
|
|
1781
|
+
independence_level: z2.number().min(1).max(5).describe("How self-directed was the user? 1=needed constant guidance, 5=gave clear spec and let AI execute"),
|
|
1782
|
+
scope_quality: z2.number().min(1).max(5).describe("Was the task well-scoped? 1=vague or impossibly broad, 5=precise and achievable"),
|
|
1783
|
+
tools_leveraged: z2.number().min(0).describe("Count of distinct AI capabilities used (code gen, debugging, refactoring, testing, docs, etc.)")
|
|
1784
|
+
}).optional().describe("AI-assessed evaluation of this session. Score honestly based on the actual interaction.")
|
|
1751
1785
|
},
|
|
1752
|
-
async ({ task_type, languages, files_touched_count, milestones: milestonesInput }) => {
|
|
1786
|
+
async ({ task_type, languages, files_touched_count, milestones: milestonesInput, evaluation }) => {
|
|
1753
1787
|
const duration = session2.getSessionDuration();
|
|
1754
1788
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1755
1789
|
const finalTaskType = task_type ?? session2.sessionTaskType;
|
|
@@ -1759,7 +1793,8 @@ function registerTools(server2, session2) {
|
|
|
1759
1793
|
task_type: finalTaskType,
|
|
1760
1794
|
languages: languages ?? [],
|
|
1761
1795
|
files_touched: files_touched_count ?? 0,
|
|
1762
|
-
heartbeat_count: session2.heartbeatCount
|
|
1796
|
+
heartbeat_count: session2.heartbeatCount,
|
|
1797
|
+
...evaluation ? { evaluation } : {}
|
|
1763
1798
|
});
|
|
1764
1799
|
const sealData = JSON.stringify({
|
|
1765
1800
|
session_id: session2.sessionId,
|
|
@@ -1768,6 +1803,10 @@ function registerTools(server2, session2) {
|
|
|
1768
1803
|
languages: languages ?? [],
|
|
1769
1804
|
files_touched: files_touched_count ?? 0,
|
|
1770
1805
|
project: session2.project,
|
|
1806
|
+
title: session2.sessionTitle ?? void 0,
|
|
1807
|
+
private_title: session2.sessionPrivateTitle ?? void 0,
|
|
1808
|
+
prompt_word_count: session2.sessionPromptWordCount ?? void 0,
|
|
1809
|
+
evaluation: evaluation ?? void 0,
|
|
1771
1810
|
started_at: new Date(session2.sessionStartTime).toISOString(),
|
|
1772
1811
|
ended_at: now,
|
|
1773
1812
|
duration_seconds: duration,
|
|
@@ -1798,6 +1837,10 @@ function registerTools(server2, session2) {
|
|
|
1798
1837
|
languages: languages ?? [],
|
|
1799
1838
|
files_touched: files_touched_count ?? 0,
|
|
1800
1839
|
project: session2.project ?? void 0,
|
|
1840
|
+
title: session2.sessionTitle ?? void 0,
|
|
1841
|
+
private_title: session2.sessionPrivateTitle ?? void 0,
|
|
1842
|
+
prompt_word_count: session2.sessionPromptWordCount ?? void 0,
|
|
1843
|
+
evaluation: evaluation ?? void 0,
|
|
1801
1844
|
started_at: new Date(session2.sessionStartTime).toISOString(),
|
|
1802
1845
|
ended_at: now,
|
|
1803
1846
|
duration_seconds: duration,
|
|
@@ -1850,11 +1893,12 @@ function registerTools(server2, session2) {
|
|
|
1850
1893
|
const durationStr = formatDuration(duration);
|
|
1851
1894
|
const langStr = languages && languages.length > 0 ? ` using ${languages.join(", ")}` : "";
|
|
1852
1895
|
const milestoneStr = milestoneCount > 0 ? ` \xB7 ${milestoneCount} milestone${milestoneCount > 1 ? "s" : ""} recorded` : "";
|
|
1896
|
+
const evalStr = evaluation ? ` \xB7 eval: ${evaluation.task_outcome} (prompt: ${evaluation.prompt_quality}/5)` : "";
|
|
1853
1897
|
return {
|
|
1854
1898
|
content: [
|
|
1855
1899
|
{
|
|
1856
1900
|
type: "text",
|
|
1857
|
-
text: `Session ended: ${durationStr} ${finalTaskType}${langStr}${milestoneStr}`
|
|
1901
|
+
text: `Session ended: ${durationStr} ${finalTaskType}${langStr}${milestoneStr}${evalStr}`
|
|
1858
1902
|
}
|
|
1859
1903
|
]
|
|
1860
1904
|
};
|
|
@@ -1945,6 +1989,16 @@ function readBody(req) {
|
|
|
1945
1989
|
req.on("error", reject);
|
|
1946
1990
|
});
|
|
1947
1991
|
}
|
|
1992
|
+
function deduplicateSessions(sessions2) {
|
|
1993
|
+
const map = /* @__PURE__ */ new Map();
|
|
1994
|
+
for (const s of sessions2) {
|
|
1995
|
+
const existing = map.get(s.session_id);
|
|
1996
|
+
if (!existing || s.duration_seconds > existing.duration_seconds) {
|
|
1997
|
+
map.set(s.session_id, s);
|
|
1998
|
+
}
|
|
1999
|
+
}
|
|
2000
|
+
return [...map.values()];
|
|
2001
|
+
}
|
|
1948
2002
|
function calculateStreak(sessions2) {
|
|
1949
2003
|
if (sessions2.length === 0) return 0;
|
|
1950
2004
|
const days = /* @__PURE__ */ new Set();
|
|
@@ -1971,7 +2025,7 @@ function calculateStreak(sessions2) {
|
|
|
1971
2025
|
}
|
|
1972
2026
|
function handleLocalSessions(_req, res) {
|
|
1973
2027
|
try {
|
|
1974
|
-
const sessions2 = readJson(SESSIONS_FILE, []);
|
|
2028
|
+
const sessions2 = deduplicateSessions(readJson(SESSIONS_FILE, []));
|
|
1975
2029
|
json(res, 200, sessions2);
|
|
1976
2030
|
} catch (err) {
|
|
1977
2031
|
json(res, 500, { error: err.message });
|
|
@@ -1979,7 +2033,7 @@ function handleLocalSessions(_req, res) {
|
|
|
1979
2033
|
}
|
|
1980
2034
|
function handleLocalStats(_req, res) {
|
|
1981
2035
|
try {
|
|
1982
|
-
const sessions2 = readJson(SESSIONS_FILE, []);
|
|
2036
|
+
const sessions2 = deduplicateSessions(readJson(SESSIONS_FILE, []));
|
|
1983
2037
|
let totalSeconds = 0;
|
|
1984
2038
|
let filesTouched = 0;
|
|
1985
2039
|
const byClient = {};
|
|
@@ -2050,7 +2104,7 @@ async function handleLocalSync(req, res) {
|
|
|
2050
2104
|
"Content-Type": "application/json",
|
|
2051
2105
|
"Authorization": `Bearer ${token}`
|
|
2052
2106
|
};
|
|
2053
|
-
const sessions2 = readJson(SESSIONS_FILE, []);
|
|
2107
|
+
const sessions2 = deduplicateSessions(readJson(SESSIONS_FILE, []));
|
|
2054
2108
|
const sessionsRes = await fetch("https://api.useai.dev/api/sync", {
|
|
2055
2109
|
method: "POST",
|
|
2056
2110
|
headers,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@devness/useai",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Track your AI-assisted development workflow. MCP server that records usage metrics across all your AI tools.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mcp",
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
"@types/node": "^22.13.4",
|
|
40
40
|
"tsup": "^8.0.0",
|
|
41
41
|
"typescript": "^5.7.3",
|
|
42
|
+
"vitest": "^4.0.18",
|
|
42
43
|
"@useai/shared": "0.3.0"
|
|
43
44
|
},
|
|
44
45
|
"repository": {
|
|
@@ -54,6 +55,8 @@
|
|
|
54
55
|
"dev": "tsc --watch",
|
|
55
56
|
"daemon": "pnpm run build && node dist/index.js daemon --port 19201",
|
|
56
57
|
"bundle": "tsup src/index.ts --format esm --target node18 --no-splitting",
|
|
58
|
+
"test": "vitest run",
|
|
59
|
+
"test:watch": "vitest",
|
|
57
60
|
"typecheck": "tsc --noEmit",
|
|
58
61
|
"clean": "rm -rf dist"
|
|
59
62
|
}
|