youmd 0.6.21 → 0.6.27
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/commands/build.d.ts.map +1 -1
- package/dist/commands/build.js +0 -1
- package/dist/commands/build.js.map +1 -1
- package/dist/commands/chat.d.ts.map +1 -1
- package/dist/commands/chat.js +283 -184
- package/dist/commands/chat.js.map +1 -1
- package/dist/commands/export.d.ts +1 -0
- package/dist/commands/export.d.ts.map +1 -1
- package/dist/commands/export.js +5 -0
- package/dist/commands/export.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +21 -1
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/keys.d.ts.map +1 -1
- package/dist/commands/keys.js +3 -3
- package/dist/commands/keys.js.map +1 -1
- package/dist/commands/link.d.ts.map +1 -1
- package/dist/commands/link.js +3 -3
- package/dist/commands/link.js.map +1 -1
- package/dist/commands/login.d.ts +29 -0
- package/dist/commands/login.d.ts.map +1 -1
- package/dist/commands/login.js +256 -127
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/logs.js +0 -1
- package/dist/commands/logs.js.map +1 -1
- package/dist/commands/mcp.d.ts +14 -0
- package/dist/commands/mcp.d.ts.map +1 -1
- package/dist/commands/mcp.js +284 -28
- package/dist/commands/mcp.js.map +1 -1
- package/dist/commands/okf.d.ts +12 -0
- package/dist/commands/okf.d.ts.map +1 -0
- package/dist/commands/okf.js +411 -0
- package/dist/commands/okf.js.map +1 -0
- package/dist/commands/private.d.ts.map +1 -1
- package/dist/commands/private.js +6 -5
- package/dist/commands/private.js.map +1 -1
- package/dist/commands/project.d.ts.map +1 -1
- package/dist/commands/project.js +9 -5
- package/dist/commands/project.js.map +1 -1
- package/dist/commands/publish.d.ts.map +1 -1
- package/dist/commands/publish.js +0 -1
- package/dist/commands/publish.js.map +1 -1
- package/dist/commands/pull.d.ts +24 -1
- package/dist/commands/pull.d.ts.map +1 -1
- package/dist/commands/pull.js +114 -14
- package/dist/commands/pull.js.map +1 -1
- package/dist/commands/push.d.ts +1 -0
- package/dist/commands/push.d.ts.map +1 -1
- package/dist/commands/push.js +33 -13
- package/dist/commands/push.js.map +1 -1
- package/dist/commands/register.d.ts +6 -0
- package/dist/commands/register.d.ts.map +1 -1
- package/dist/commands/register.js +179 -79
- package/dist/commands/register.js.map +1 -1
- package/dist/commands/skill.d.ts.map +1 -1
- package/dist/commands/skill.js +121 -50
- package/dist/commands/skill.js.map +1 -1
- package/dist/commands/stack.d.ts +16 -0
- package/dist/commands/stack.d.ts.map +1 -0
- package/dist/commands/stack.js +863 -0
- package/dist/commands/stack.js.map +1 -0
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +25 -5
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/sync.d.ts +23 -0
- package/dist/commands/sync.d.ts.map +1 -1
- package/dist/commands/sync.js +193 -12
- package/dist/commands/sync.js.map +1 -1
- package/dist/commands/whoami.d.ts.map +1 -1
- package/dist/commands/whoami.js +21 -16
- package/dist/commands/whoami.js.map +1 -1
- package/dist/create.js +0 -0
- package/dist/index.js +64 -7
- package/dist/index.js.map +1 -1
- package/dist/lib/api.d.ts +127 -5
- package/dist/lib/api.d.ts.map +1 -1
- package/dist/lib/api.js +142 -5
- package/dist/lib/api.js.map +1 -1
- package/dist/lib/ascii.d.ts +1 -0
- package/dist/lib/ascii.d.ts.map +1 -1
- package/dist/lib/ascii.js +9 -26
- package/dist/lib/ascii.js.map +1 -1
- package/dist/lib/canonical-json.d.ts +8 -0
- package/dist/lib/canonical-json.d.ts.map +1 -0
- package/dist/lib/canonical-json.js +29 -0
- package/dist/lib/canonical-json.js.map +1 -0
- package/dist/lib/capability-router.d.ts +58 -0
- package/dist/lib/capability-router.d.ts.map +1 -0
- package/dist/lib/capability-router.js +54 -0
- package/dist/lib/capability-router.js.map +1 -0
- package/dist/lib/compiler.d.ts +32 -0
- package/dist/lib/compiler.d.ts.map +1 -1
- package/dist/lib/compiler.js +150 -0
- package/dist/lib/compiler.js.map +1 -1
- package/dist/lib/config.d.ts +71 -35
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +334 -124
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/decompile.d.ts +6 -1
- package/dist/lib/decompile.d.ts.map +1 -1
- package/dist/lib/decompile.js +120 -11
- package/dist/lib/decompile.js.map +1 -1
- package/dist/lib/hash.d.ts.map +1 -1
- package/dist/lib/hash.js +2 -19
- package/dist/lib/hash.js.map +1 -1
- package/dist/lib/heartbeat.d.ts +38 -0
- package/dist/lib/heartbeat.d.ts.map +1 -0
- package/dist/lib/heartbeat.js +70 -0
- package/dist/lib/heartbeat.js.map +1 -0
- package/dist/lib/host-link.d.ts +123 -0
- package/dist/lib/host-link.d.ts.map +1 -0
- package/dist/lib/host-link.js +350 -0
- package/dist/lib/host-link.js.map +1 -0
- package/dist/lib/merge.d.ts +50 -0
- package/dist/lib/merge.d.ts.map +1 -0
- package/dist/lib/merge.js +167 -0
- package/dist/lib/merge.js.map +1 -0
- package/dist/lib/okf-bundle.d.ts +104 -0
- package/dist/lib/okf-bundle.d.ts.map +1 -0
- package/dist/lib/okf-bundle.js +473 -0
- package/dist/lib/okf-bundle.js.map +1 -0
- package/dist/lib/okf-health.d.ts +40 -0
- package/dist/lib/okf-health.d.ts.map +1 -0
- package/dist/lib/okf-health.js +152 -0
- package/dist/lib/okf-health.js.map +1 -0
- package/dist/lib/okf-stack.d.ts +47 -0
- package/dist/lib/okf-stack.d.ts.map +1 -0
- package/dist/lib/okf-stack.js +331 -0
- package/dist/lib/okf-stack.js.map +1 -0
- package/dist/lib/okf-view.d.ts +49 -0
- package/dist/lib/okf-view.d.ts.map +1 -0
- package/dist/lib/okf-view.js +197 -0
- package/dist/lib/okf-view.js.map +1 -0
- package/dist/lib/okf.d.ts +130 -0
- package/dist/lib/okf.d.ts.map +1 -0
- package/dist/lib/okf.js +263 -0
- package/dist/lib/okf.js.map +1 -0
- package/dist/lib/onboarding.d.ts +17 -0
- package/dist/lib/onboarding.d.ts.map +1 -1
- package/dist/lib/onboarding.js +224 -207
- package/dist/lib/onboarding.js.map +1 -1
- package/dist/lib/project.d.ts +5 -0
- package/dist/lib/project.d.ts.map +1 -1
- package/dist/lib/project.js +8 -0
- package/dist/lib/project.js.map +1 -1
- package/dist/lib/projectContext.d.ts +201 -0
- package/dist/lib/projectContext.d.ts.map +1 -0
- package/dist/lib/projectContext.js +597 -0
- package/dist/lib/projectContext.js.map +1 -0
- package/dist/lib/render.d.ts +38 -1
- package/dist/lib/render.d.ts.map +1 -1
- package/dist/lib/render.js +83 -18
- package/dist/lib/render.js.map +1 -1
- package/dist/lib/skill-catalog.d.ts.map +1 -1
- package/dist/lib/skill-catalog.js +22 -2
- package/dist/lib/skill-catalog.js.map +1 -1
- package/dist/lib/skills.d.ts +20 -5
- package/dist/lib/skills.d.ts.map +1 -1
- package/dist/lib/skills.js +116 -58
- package/dist/lib/skills.js.map +1 -1
- package/dist/lib/stackEval.d.ts +87 -0
- package/dist/lib/stackEval.d.ts.map +1 -0
- package/dist/lib/stackEval.js +233 -0
- package/dist/lib/stackEval.js.map +1 -0
- package/dist/lib/stackImprove.d.ts +49 -0
- package/dist/lib/stackImprove.d.ts.map +1 -0
- package/dist/lib/stackImprove.js +313 -0
- package/dist/lib/stackImprove.js.map +1 -0
- package/dist/lib/stackInstall.d.ts +59 -0
- package/dist/lib/stackInstall.d.ts.map +1 -0
- package/dist/lib/stackInstall.js +162 -0
- package/dist/lib/stackInstall.js.map +1 -0
- package/dist/lib/stackSafety.d.ts +126 -0
- package/dist/lib/stackSafety.d.ts.map +1 -0
- package/dist/lib/stackSafety.js +486 -0
- package/dist/lib/stackSafety.js.map +1 -0
- package/dist/lib/stackUpdate.d.ts +44 -0
- package/dist/lib/stackUpdate.d.ts.map +1 -0
- package/dist/lib/stackUpdate.js +256 -0
- package/dist/lib/stackUpdate.js.map +1 -0
- package/dist/lib/stream.d.ts +116 -0
- package/dist/lib/stream.d.ts.map +1 -0
- package/dist/lib/stream.js +368 -0
- package/dist/lib/stream.js.map +1 -0
- package/dist/lib/youstack.d.ts +219 -0
- package/dist/lib/youstack.d.ts.map +1 -0
- package/dist/lib/youstack.js +1049 -0
- package/dist/lib/youstack.js.map +1 -0
- package/dist/mcp/registry.d.ts +133 -0
- package/dist/mcp/registry.d.ts.map +1 -0
- package/dist/mcp/registry.js +833 -0
- package/dist/mcp/registry.js.map +1 -0
- package/dist/mcp/server.d.ts +106 -5
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +851 -498
- package/dist/mcp/server.js.map +1 -1
- package/dist/postinstall.js +11 -2
- package/dist/postinstall.js.map +1 -1
- package/dist/you.d.ts +1 -0
- package/dist/you.js +40 -4
- package/dist/you.js.map +1 -1
- package/examples/youstack-bamfstack-public/docs/quickstart.md +34 -0
- package/examples/youstack-bamfstack-public/prompts/creator-draft-starter.md +15 -0
- package/examples/youstack-bamfstack-public/skills/bamfstack-start/SKILL.md +36 -0
- package/examples/youstack-bamfstack-public/tests/smoke.md +19 -0
- package/examples/youstack-bamfstack-public/workflows/creator-growth-workflow.md +22 -0
- package/examples/youstack-bamfstack-public/youstack.json +172 -0
- package/examples/youstack-personal/docs/quickstart.md +10 -0
- package/examples/youstack-personal/skills/youstack-start/SKILL.md +9 -0
- package/examples/youstack-personal/tests/smoke.md +9 -0
- package/examples/youstack-personal/workflows/startup.md +7 -0
- package/examples/youstack-personal/youstack.json +123 -0
- package/package.json +3 -3
- package/skills/claude-md-generator.md +1 -1
- package/skills/meta-improve.md +8 -3
- package/skills/proactive-context-fill.md +1 -1
- package/skills/youstack-maintainer.md +128 -0
- package/skills/youstack-start.md +78 -0
package/dist/mcp/server.js
CHANGED
|
@@ -6,11 +6,12 @@
|
|
|
6
6
|
* An MCP where the context is you. Every agent that connects gets structured,
|
|
7
7
|
* portable identity context: who you are, how you work, what you sound like.
|
|
8
8
|
*
|
|
9
|
-
* Resources: identity, profile sections, preferences, voice,
|
|
10
|
-
* memories, projects, skills
|
|
11
|
-
* Tools:
|
|
12
|
-
* compile_bundle, push_bundle, get_project_context,
|
|
13
|
-
* add_source, create_context_link, list_projects, get_remote_status
|
|
9
|
+
* Resources: identity, agent brief, profile sections, preferences, voice,
|
|
10
|
+
* directives, memories, projects, skills
|
|
11
|
+
* Tools: whoami, get_agent_brief, add_memory, update_section,
|
|
12
|
+
* search_memories, use_skill, compile_bundle, push_bundle, get_project_context,
|
|
13
|
+
* add_source, create_context_link, list_projects, get_remote_status,
|
|
14
|
+
* get_stack_manifest, get_stack_capabilities, route_stack_request, smoke_stack
|
|
14
15
|
*
|
|
15
16
|
* Transport: stdio (for Claude Code, Cursor, any MCP client)
|
|
16
17
|
*
|
|
@@ -52,14 +53,32 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
52
53
|
};
|
|
53
54
|
})();
|
|
54
55
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
56
|
+
exports.MEMORY_CATEGORIES = exports.DURABLE_MEMORY_CATEGORIES = exports.AGENT_BRIEF_MEMORY_CAP = void 0;
|
|
57
|
+
exports.activeBundleExists = activeBundleExists;
|
|
58
|
+
exports.getInstalledSkills = getInstalledSkills;
|
|
59
|
+
exports.orderBriefMemories = orderBriefMemories;
|
|
60
|
+
exports.briefOneLine = briefOneLine;
|
|
61
|
+
exports.fetchMemoriesEnvelope = fetchMemoriesEnvelope;
|
|
62
|
+
exports.fetchPrivateContextEnvelope = fetchPrivateContextEnvelope;
|
|
63
|
+
exports.apiRequest = apiRequest;
|
|
64
|
+
exports.setMcpClientName = setMcpClientName;
|
|
65
|
+
exports.resolveAgentName = resolveAgentName;
|
|
66
|
+
exports.buildWhoamiSummaryFromYouJson = buildWhoamiSummaryFromYouJson;
|
|
67
|
+
exports.buildAgentBrief = buildAgentBrief;
|
|
68
|
+
exports.formatAgentBriefMarkdown = formatAgentBriefMarkdown;
|
|
55
69
|
exports.startMcpServer = startMcpServer;
|
|
56
70
|
const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
|
|
57
71
|
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
58
72
|
const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
|
|
73
|
+
// T14: CLI tool registry — single source of truth for future tool additions.
|
|
74
|
+
const registry_js_1 = require("./registry.js");
|
|
59
75
|
const fs = __importStar(require("fs"));
|
|
60
76
|
const path = __importStar(require("path"));
|
|
61
77
|
const config_1 = require("../lib/config");
|
|
62
78
|
const project_1 = require("../lib/project");
|
|
79
|
+
const projectContext_1 = require("../lib/projectContext");
|
|
80
|
+
const youstack_1 = require("../lib/youstack");
|
|
81
|
+
const MCP_SERVER_VERSION = "0.6.23";
|
|
63
82
|
function getCurrentProject() {
|
|
64
83
|
const root = (0, project_1.findProjectsRoot)();
|
|
65
84
|
if (!root)
|
|
@@ -89,13 +108,27 @@ function readJsonOr(filePath, fallback) {
|
|
|
89
108
|
}
|
|
90
109
|
}
|
|
91
110
|
function getBundleDir() {
|
|
92
|
-
|
|
111
|
+
const localDir = (0, config_1.getLocalBundleDir)();
|
|
112
|
+
if ((0, config_1.bundleLooksInitialized)(localDir))
|
|
113
|
+
return localDir;
|
|
114
|
+
const homeDir = (0, config_1.getHomeBundleDir)();
|
|
115
|
+
if ((0, config_1.bundleLooksInitialized)(homeDir))
|
|
116
|
+
return homeDir;
|
|
117
|
+
return localDir;
|
|
118
|
+
}
|
|
119
|
+
function activeBundleExists() {
|
|
120
|
+
return (0, config_1.bundleLooksInitialized)(getBundleDir());
|
|
93
121
|
}
|
|
94
122
|
function getYouJson() {
|
|
95
123
|
const bundleDir = getBundleDir();
|
|
96
124
|
const youJsonPath = path.join(bundleDir, "you.json");
|
|
97
125
|
return readJsonOr(youJsonPath, {});
|
|
98
126
|
}
|
|
127
|
+
function asRecord(value) {
|
|
128
|
+
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
129
|
+
? value
|
|
130
|
+
: {};
|
|
131
|
+
}
|
|
99
132
|
function getYouMd() {
|
|
100
133
|
const bundleDir = getBundleDir();
|
|
101
134
|
return readFileOr(path.join(bundleDir, "you.md"), "");
|
|
@@ -145,9 +178,61 @@ function getInstalledSkills() {
|
|
|
145
178
|
}
|
|
146
179
|
return skills;
|
|
147
180
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
return
|
|
181
|
+
function tryLoadCurrentYouStack(inputPath) {
|
|
182
|
+
try {
|
|
183
|
+
return (0, youstack_1.loadYouStackManifest)(inputPath);
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
// Mirror of the canonical convex/lib/agentContext.ts assembly. The CLI is a
|
|
190
|
+
// separately published npm package and cannot import the convex module at
|
|
191
|
+
// runtime; cli/src/__tests__/agent-context-parity.test.ts asserts the two
|
|
192
|
+
// implementations never drift (cap, durable set, ordering, one-line collapse,
|
|
193
|
+
// identity summary). Exported for that parity test.
|
|
194
|
+
/** Max memories rendered into the agent brief (parity with hosted MCP). */
|
|
195
|
+
exports.AGENT_BRIEF_MEMORY_CAP = 20;
|
|
196
|
+
/** Categories considered durable — surfaced ahead of newer ephemeral notes. */
|
|
197
|
+
exports.DURABLE_MEMORY_CATEGORIES = new Set([
|
|
198
|
+
"preference",
|
|
199
|
+
"decision",
|
|
200
|
+
"goal",
|
|
201
|
+
"fact",
|
|
202
|
+
]);
|
|
203
|
+
function asBriefMemories(memories) {
|
|
204
|
+
return memories.filter((m) => !!m &&
|
|
205
|
+
typeof m === "object" &&
|
|
206
|
+
typeof m.category === "string" &&
|
|
207
|
+
typeof m.content === "string");
|
|
208
|
+
}
|
|
209
|
+
/** Durable categories first, preserving server order (newest-first) within each group. */
|
|
210
|
+
function orderBriefMemories(memories) {
|
|
211
|
+
const durable = [];
|
|
212
|
+
const rest = [];
|
|
213
|
+
for (const memory of memories) {
|
|
214
|
+
(exports.DURABLE_MEMORY_CATEGORIES.has(memory.category) ? durable : rest).push(memory);
|
|
215
|
+
}
|
|
216
|
+
return [...durable, ...rest];
|
|
217
|
+
}
|
|
218
|
+
/** Collapse memory content to a single line capped at `max` chars. */
|
|
219
|
+
function briefOneLine(content, max = 200) {
|
|
220
|
+
const collapsed = content.replace(/\s+/g, " ").trim();
|
|
221
|
+
return collapsed.length > max ? `${collapsed.slice(0, max - 1).trimEnd()}…` : collapsed;
|
|
222
|
+
}
|
|
223
|
+
async function fetchMemoriesEnvelope(category, limit) {
|
|
224
|
+
if (!(0, config_1.isAuthenticated)()) {
|
|
225
|
+
return {
|
|
226
|
+
readiness: {
|
|
227
|
+
status: "auth_required",
|
|
228
|
+
ready: false,
|
|
229
|
+
reason: "Memory search needs an authenticated You.md session or API key.",
|
|
230
|
+
fallback: "Use public identity, project-context files, or run `youmd login` before retrying protected memory search.",
|
|
231
|
+
},
|
|
232
|
+
memories: [],
|
|
233
|
+
count: 0,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
151
236
|
const config = (0, config_1.readGlobalConfig)();
|
|
152
237
|
const siteUrl = (0, config_1.getConvexSiteUrl)();
|
|
153
238
|
const params = new URLSearchParams();
|
|
@@ -160,15 +245,241 @@ async function fetchMemories(category, limit) {
|
|
|
160
245
|
headers: { Authorization: `Bearer ${config.token}` },
|
|
161
246
|
signal: AbortSignal.timeout(10000),
|
|
162
247
|
});
|
|
163
|
-
if (!res.ok)
|
|
164
|
-
return
|
|
248
|
+
if (!res.ok) {
|
|
249
|
+
return {
|
|
250
|
+
readiness: {
|
|
251
|
+
status: "unavailable",
|
|
252
|
+
ready: false,
|
|
253
|
+
reason: `Memory search failed with HTTP ${res.status}.`,
|
|
254
|
+
fallback: "Use project-context files or local stack files first, then retry protected memory search when auth/server access is healthy.",
|
|
255
|
+
},
|
|
256
|
+
memories: [],
|
|
257
|
+
count: 0,
|
|
258
|
+
};
|
|
259
|
+
}
|
|
165
260
|
const data = await res.json();
|
|
166
|
-
|
|
261
|
+
const memories = (data.memories || data) || [];
|
|
262
|
+
return {
|
|
263
|
+
readiness: {
|
|
264
|
+
status: "ready",
|
|
265
|
+
ready: true,
|
|
266
|
+
reason: memories.length > 0 ? "Protected memory search succeeded." : "Protected memory search succeeded but found no matching memories.",
|
|
267
|
+
fallback: memories.length > 0 ? "None needed." : "If nothing matched, fall back to public identity, project-context files, or a narrower keyword query.",
|
|
268
|
+
},
|
|
269
|
+
memories,
|
|
270
|
+
count: memories.length,
|
|
271
|
+
};
|
|
167
272
|
}
|
|
168
273
|
catch {
|
|
169
|
-
return
|
|
274
|
+
return {
|
|
275
|
+
readiness: {
|
|
276
|
+
status: "unavailable",
|
|
277
|
+
ready: false,
|
|
278
|
+
reason: "Memory search could not reach the protected You.md memories endpoint.",
|
|
279
|
+
fallback: "Use public identity, project-context files, or local stack context first, then retry protected memory search later.",
|
|
280
|
+
},
|
|
281
|
+
memories: [],
|
|
282
|
+
count: 0,
|
|
283
|
+
};
|
|
170
284
|
}
|
|
171
285
|
}
|
|
286
|
+
async function fetchPrivateContextEnvelope() {
|
|
287
|
+
if (!(0, config_1.isAuthenticated)()) {
|
|
288
|
+
return {
|
|
289
|
+
readiness: {
|
|
290
|
+
status: "auth_required",
|
|
291
|
+
ready: false,
|
|
292
|
+
reason: "Private context needs an authenticated You.md session or API key.",
|
|
293
|
+
fallback: "Use local stack files, project-context files, public identity, or run `youmd login` before retrying protected private-context access.",
|
|
294
|
+
},
|
|
295
|
+
privateContext: null,
|
|
296
|
+
summary: {
|
|
297
|
+
hasNotes: false,
|
|
298
|
+
linkCount: 0,
|
|
299
|
+
projectCount: 0,
|
|
300
|
+
},
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
const config = (0, config_1.readGlobalConfig)();
|
|
304
|
+
const siteUrl = (0, config_1.getConvexSiteUrl)();
|
|
305
|
+
try {
|
|
306
|
+
const res = await fetch(`${siteUrl}/api/v1/me/private`, {
|
|
307
|
+
headers: { Authorization: `Bearer ${config.token}` },
|
|
308
|
+
signal: AbortSignal.timeout(10000),
|
|
309
|
+
});
|
|
310
|
+
if (!res.ok) {
|
|
311
|
+
return {
|
|
312
|
+
readiness: {
|
|
313
|
+
status: "unavailable",
|
|
314
|
+
ready: false,
|
|
315
|
+
reason: `Private context fetch failed with HTTP ${res.status}.`,
|
|
316
|
+
fallback: "Use local stack files, project-context files, or public identity first, then retry protected private-context access when auth/server access is healthy.",
|
|
317
|
+
},
|
|
318
|
+
privateContext: null,
|
|
319
|
+
summary: {
|
|
320
|
+
hasNotes: false,
|
|
321
|
+
linkCount: 0,
|
|
322
|
+
projectCount: 0,
|
|
323
|
+
},
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
const privateContext = await res.json();
|
|
327
|
+
const links = privateContext && typeof privateContext === "object" && privateContext.internalLinks && typeof privateContext.internalLinks === "object"
|
|
328
|
+
? Object.keys(privateContext.internalLinks).length
|
|
329
|
+
: 0;
|
|
330
|
+
const projects = Array.isArray(privateContext?.privateProjects) ? privateContext.privateProjects.length : 0;
|
|
331
|
+
const hasNotes = typeof privateContext?.privateNotes === "string" && privateContext.privateNotes.trim().length > 0;
|
|
332
|
+
return {
|
|
333
|
+
readiness: {
|
|
334
|
+
status: "ready",
|
|
335
|
+
ready: true,
|
|
336
|
+
reason: privateContext
|
|
337
|
+
? "Protected private context fetch succeeded."
|
|
338
|
+
: "Protected private context fetch succeeded but no private context is stored yet.",
|
|
339
|
+
fallback: privateContext
|
|
340
|
+
? "None needed."
|
|
341
|
+
: "If private context is empty, fall back to local stack files, project-context files, or public identity.",
|
|
342
|
+
},
|
|
343
|
+
privateContext,
|
|
344
|
+
summary: {
|
|
345
|
+
hasNotes,
|
|
346
|
+
linkCount: links,
|
|
347
|
+
projectCount: projects,
|
|
348
|
+
},
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
catch {
|
|
352
|
+
return {
|
|
353
|
+
readiness: {
|
|
354
|
+
status: "unavailable",
|
|
355
|
+
ready: false,
|
|
356
|
+
reason: "Private context fetch could not reach the protected You.md private-context endpoint.",
|
|
357
|
+
fallback: "Use local stack files, project-context files, or public identity first, then retry protected private-context access later.",
|
|
358
|
+
},
|
|
359
|
+
privateContext: null,
|
|
360
|
+
summary: {
|
|
361
|
+
hasNotes: false,
|
|
362
|
+
linkCount: 0,
|
|
363
|
+
projectCount: 0,
|
|
364
|
+
},
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
function fetchProjectContextEnvelope(projectName) {
|
|
369
|
+
// Reads route through the single project-context engine: the repo
|
|
370
|
+
// project-context/ directory is overlaid on the global store, repo wins
|
|
371
|
+
// on file conflicts, and readers see the union (PRODUCT-AUDIT #14).
|
|
372
|
+
if (projectName) {
|
|
373
|
+
const ctx = (0, projectContext_1.readMergedProjectContext)({ projectName });
|
|
374
|
+
if (!ctx) {
|
|
375
|
+
const hasAnyRoot = !!(0, project_1.findProjectsRoot)() || !!(0, projectContext_1.findRepoContextRoot)();
|
|
376
|
+
if (!hasAnyRoot) {
|
|
377
|
+
return {
|
|
378
|
+
readiness: {
|
|
379
|
+
status: "not_found",
|
|
380
|
+
ready: false,
|
|
381
|
+
reason: "No projects directory was found for the requested named project.",
|
|
382
|
+
fallback: "Use local project-context files from the current repo, public identity, or a current project detection flow before retrying a named project lookup.",
|
|
383
|
+
},
|
|
384
|
+
project: {
|
|
385
|
+
name: projectName,
|
|
386
|
+
source: "named",
|
|
387
|
+
},
|
|
388
|
+
projectContext: null,
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
return {
|
|
392
|
+
readiness: {
|
|
393
|
+
status: "not_found",
|
|
394
|
+
ready: false,
|
|
395
|
+
reason: `Project context for ${projectName} was not found or is incomplete.`,
|
|
396
|
+
fallback: "Use local repo instructions, project-context files, or confirm the named project exists before retrying.",
|
|
397
|
+
},
|
|
398
|
+
project: {
|
|
399
|
+
name: projectName,
|
|
400
|
+
source: "named",
|
|
401
|
+
},
|
|
402
|
+
projectContext: null,
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
return {
|
|
406
|
+
readiness: {
|
|
407
|
+
status: "ready",
|
|
408
|
+
ready: true,
|
|
409
|
+
reason: `Project context for ${projectName} is available.`,
|
|
410
|
+
fallback: "None needed.",
|
|
411
|
+
},
|
|
412
|
+
project: {
|
|
413
|
+
name: projectName,
|
|
414
|
+
source: "named",
|
|
415
|
+
},
|
|
416
|
+
projectContext: ctx,
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
const current = getCurrentProject();
|
|
420
|
+
if (!current) {
|
|
421
|
+
// No managed project matches the cwd — the repo overlay alone may
|
|
422
|
+
// still provide the context union.
|
|
423
|
+
const repoCtx = (0, projectContext_1.readMergedProjectContext)({});
|
|
424
|
+
if (repoCtx) {
|
|
425
|
+
return {
|
|
426
|
+
readiness: {
|
|
427
|
+
status: "ready",
|
|
428
|
+
ready: true,
|
|
429
|
+
reason: `Project context for ${repoCtx.meta.name} is available.`,
|
|
430
|
+
fallback: "None needed.",
|
|
431
|
+
},
|
|
432
|
+
project: {
|
|
433
|
+
name: repoCtx.meta.name,
|
|
434
|
+
source: "current",
|
|
435
|
+
},
|
|
436
|
+
projectContext: repoCtx,
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
return {
|
|
440
|
+
readiness: {
|
|
441
|
+
status: "not_found",
|
|
442
|
+
ready: false,
|
|
443
|
+
reason: "No current project was detected from the working directory.",
|
|
444
|
+
fallback: "Use local project-context files in the current repo, pass an explicit project name, or fall back to public identity and stack files.",
|
|
445
|
+
},
|
|
446
|
+
project: {
|
|
447
|
+
name: null,
|
|
448
|
+
source: "current",
|
|
449
|
+
},
|
|
450
|
+
projectContext: null,
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
const ctx = (0, projectContext_1.readMergedProjectContext)({ projectName: current.name }) || (0, project_1.readProjectContext)(current.dir);
|
|
454
|
+
if (!ctx) {
|
|
455
|
+
return {
|
|
456
|
+
readiness: {
|
|
457
|
+
status: "not_found",
|
|
458
|
+
ready: false,
|
|
459
|
+
reason: `Project context for ${current.name} was detected but could not be read.`,
|
|
460
|
+
fallback: "Use repo-local instructions and project-context markdown files directly, then repair the named project bundle before retrying.",
|
|
461
|
+
},
|
|
462
|
+
project: {
|
|
463
|
+
name: current.name,
|
|
464
|
+
source: "current",
|
|
465
|
+
},
|
|
466
|
+
projectContext: null,
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
return {
|
|
470
|
+
readiness: {
|
|
471
|
+
status: "ready",
|
|
472
|
+
ready: true,
|
|
473
|
+
reason: `Project context for ${current.name} is available.`,
|
|
474
|
+
fallback: "None needed.",
|
|
475
|
+
},
|
|
476
|
+
project: {
|
|
477
|
+
name: current.name,
|
|
478
|
+
source: "current",
|
|
479
|
+
},
|
|
480
|
+
projectContext: ctx,
|
|
481
|
+
};
|
|
482
|
+
}
|
|
172
483
|
async function apiRequest(path, opts) {
|
|
173
484
|
const config = (0, config_1.readGlobalConfig)();
|
|
174
485
|
const siteUrl = (0, config_1.getConvexSiteUrl)();
|
|
@@ -183,6 +494,36 @@ async function apiRequest(path, opts) {
|
|
|
183
494
|
});
|
|
184
495
|
return res.json();
|
|
185
496
|
}
|
|
497
|
+
/**
|
|
498
|
+
* Agent attribution. Resolution order:
|
|
499
|
+
* 1. YOUMD_AGENT_NAME env (set by generated MCP configs per host)
|
|
500
|
+
* 2. MCP clientInfo.name from the initialize handshake
|
|
501
|
+
* 3. "MCP Agent" — honest unknown instead of misattributing to one host
|
|
502
|
+
*/
|
|
503
|
+
let mcpClientName = null;
|
|
504
|
+
function setMcpClientName(name) {
|
|
505
|
+
mcpClientName = typeof name === "string" && name.trim() ? name.trim() : null;
|
|
506
|
+
}
|
|
507
|
+
/** Friendly display names for well-known MCP clientInfo.name values. */
|
|
508
|
+
const CLIENT_NAME_MAP = {
|
|
509
|
+
"claude-code": "Claude Code",
|
|
510
|
+
"claude": "Claude Code",
|
|
511
|
+
"claude-desktop": "Claude Desktop",
|
|
512
|
+
"cursor": "Cursor",
|
|
513
|
+
"cursor-vscode": "Cursor",
|
|
514
|
+
"codex": "Codex",
|
|
515
|
+
"codex-cli": "Codex",
|
|
516
|
+
"windsurf": "Windsurf",
|
|
517
|
+
};
|
|
518
|
+
function resolveAgentName() {
|
|
519
|
+
const envName = process.env.YOUMD_AGENT_NAME?.trim();
|
|
520
|
+
if (envName)
|
|
521
|
+
return envName;
|
|
522
|
+
if (mcpClientName) {
|
|
523
|
+
return CLIENT_NAME_MAP[mcpClientName.toLowerCase()] || mcpClientName;
|
|
524
|
+
}
|
|
525
|
+
return "MCP Agent";
|
|
526
|
+
}
|
|
186
527
|
/**
|
|
187
528
|
* Fire-and-forget activity logger. Logs every MCP tool call so the user can
|
|
188
529
|
* see which agents are using their you.md identity via `youmd logs` or the
|
|
@@ -199,10 +540,10 @@ async function logMcpActivity(action, resource, details) {
|
|
|
199
540
|
headers: {
|
|
200
541
|
Authorization: `Bearer ${config.token}`,
|
|
201
542
|
"Content-Type": "application/json",
|
|
202
|
-
"User-Agent": "youmd-mcp/0.6.
|
|
543
|
+
"User-Agent": "youmd-mcp/0.6.23",
|
|
203
544
|
},
|
|
204
545
|
body: JSON.stringify({
|
|
205
|
-
agentName:
|
|
546
|
+
agentName: resolveAgentName(),
|
|
206
547
|
agentSource: "mcp",
|
|
207
548
|
action,
|
|
208
549
|
resource,
|
|
@@ -217,7 +558,7 @@ async function logMcpActivity(action, resource, details) {
|
|
|
217
558
|
}
|
|
218
559
|
}
|
|
219
560
|
// Valid memory categories enforced by add_memory
|
|
220
|
-
|
|
561
|
+
exports.MEMORY_CATEGORIES = [
|
|
221
562
|
"fact",
|
|
222
563
|
"preference",
|
|
223
564
|
"decision",
|
|
@@ -233,24 +574,43 @@ const MEMORY_CATEGORIES = [
|
|
|
233
574
|
* first call an agent makes so it can orient on the user in <1 tool round.
|
|
234
575
|
*/
|
|
235
576
|
function buildWhoamiSummary() {
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
577
|
+
return buildWhoamiSummaryFromYouJson(getYouJson());
|
|
578
|
+
}
|
|
579
|
+
/**
|
|
580
|
+
* Pure whoami summary from a youJson bundle. Mirror of the canonical
|
|
581
|
+
* extractIdentityCore + renderIdentitySummary in convex/lib/agentContext.ts;
|
|
582
|
+
* exported for the agent-context parity test.
|
|
583
|
+
*/
|
|
584
|
+
function buildWhoamiSummaryFromYouJson(youJson) {
|
|
585
|
+
const identity = asRecord(youJson.identity);
|
|
586
|
+
const preferences = asRecord(youJson.preferences);
|
|
587
|
+
const agentPrefs = asRecord(preferences.agent);
|
|
588
|
+
const directives = asRecord(youJson.agent_directives);
|
|
241
589
|
const projects = Array.isArray(youJson.projects) ? youJson.projects : [];
|
|
242
|
-
const
|
|
243
|
-
const
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
const
|
|
590
|
+
const bio = asRecord(identity.bio);
|
|
591
|
+
const name = typeof identity.name === "string"
|
|
592
|
+
? identity.name
|
|
593
|
+
: typeof youJson.username === "string" ? youJson.username : "(unknown)";
|
|
594
|
+
const role = typeof identity.tagline === "string"
|
|
595
|
+
? identity.tagline
|
|
596
|
+
: typeof bio.short === "string" ? bio.short : "";
|
|
597
|
+
const stack = typeof directives.default_stack === "string" ? directives.default_stack : "";
|
|
598
|
+
const tone = typeof agentPrefs.tone === "string" ? agentPrefs.tone : "";
|
|
599
|
+
const avoidList = Array.isArray(agentPrefs.avoid)
|
|
600
|
+
? agentPrefs.avoid.filter((item) => typeof item === "string")
|
|
601
|
+
: [];
|
|
247
602
|
const avoid = avoidList.join(", ");
|
|
248
603
|
const topProjects = projects
|
|
249
604
|
.slice(0, 3)
|
|
250
|
-
.map((
|
|
605
|
+
.map((project) => {
|
|
606
|
+
if (typeof project === "string")
|
|
607
|
+
return project;
|
|
608
|
+
const record = asRecord(project);
|
|
609
|
+
return typeof record.name === "string" ? record.name : "";
|
|
610
|
+
})
|
|
251
611
|
.filter(Boolean)
|
|
252
612
|
.join(", ");
|
|
253
|
-
const goal = directives.current_goal
|
|
613
|
+
const goal = typeof directives.current_goal === "string" ? directives.current_goal : "";
|
|
254
614
|
const lines = [];
|
|
255
615
|
lines.push(`Name: ${name}`);
|
|
256
616
|
if (role)
|
|
@@ -282,67 +642,317 @@ function buildIdentityMarkdown() {
|
|
|
282
642
|
return md;
|
|
283
643
|
// Fallback: synthesize from youJson if you.md is missing
|
|
284
644
|
const youJson = getYouJson();
|
|
285
|
-
const identity = youJson.identity
|
|
645
|
+
const identity = asRecord(youJson.identity);
|
|
646
|
+
const bio = asRecord(identity.bio);
|
|
286
647
|
const parts = [];
|
|
287
|
-
|
|
288
|
-
|
|
648
|
+
const title = typeof identity.name === "string"
|
|
649
|
+
? identity.name
|
|
650
|
+
: typeof youJson.username === "string" ? youJson.username : "Identity";
|
|
651
|
+
parts.push(`# ${title}`);
|
|
652
|
+
if (typeof identity.tagline === "string")
|
|
289
653
|
parts.push(identity.tagline);
|
|
290
|
-
|
|
291
|
-
|
|
654
|
+
const about = typeof bio.long === "string"
|
|
655
|
+
? bio.long
|
|
656
|
+
: typeof bio.medium === "string"
|
|
657
|
+
? bio.medium
|
|
658
|
+
: typeof bio.short === "string" ? bio.short : "";
|
|
659
|
+
if (about) {
|
|
660
|
+
parts.push(`\n## About\n\n${about}`);
|
|
292
661
|
}
|
|
293
662
|
return parts.join("\n") + "\n";
|
|
294
663
|
}
|
|
295
|
-
/**
|
|
296
|
-
* Find the nearest project-context/ directory by walking up from cwd.
|
|
297
|
-
* Also treats a `.youmd-project` marker file as a valid project root.
|
|
298
|
-
* Returns the directory that contains project-context/ or the marker.
|
|
299
|
-
*/
|
|
300
|
-
function findLocalProjectContextRoot(startDir = process.cwd()) {
|
|
301
|
-
let dir = startDir;
|
|
302
|
-
// Walk up at most 8 levels to avoid runaway searches
|
|
303
|
-
for (let i = 0; i < 8; i++) {
|
|
304
|
-
const contextDir = path.join(dir, "project-context");
|
|
305
|
-
const marker = path.join(dir, ".youmd-project");
|
|
306
|
-
if (fs.existsSync(contextDir) && fs.statSync(contextDir).isDirectory()) {
|
|
307
|
-
return dir;
|
|
308
|
-
}
|
|
309
|
-
if (fs.existsSync(marker)) {
|
|
310
|
-
return dir;
|
|
311
|
-
}
|
|
312
|
-
const parent = path.dirname(dir);
|
|
313
|
-
if (parent === dir)
|
|
314
|
-
break;
|
|
315
|
-
dir = parent;
|
|
316
|
-
}
|
|
317
|
-
return null;
|
|
318
|
-
}
|
|
319
664
|
/**
|
|
320
665
|
* Returns true if the current cwd has local project context available
|
|
321
666
|
* (either via project-context/ directory or a .youmd-project marker).
|
|
667
|
+
* Detection and reads route through the single project-context engine
|
|
668
|
+
* (lib/projectContext.ts) — repo files overlay the global store.
|
|
322
669
|
*/
|
|
323
670
|
function hasLocalProjectContext() {
|
|
324
|
-
return
|
|
671
|
+
return (0, projectContext_1.findRepoContextRoot)() !== null;
|
|
325
672
|
}
|
|
326
673
|
/**
|
|
327
|
-
* Read a single project-context markdown file
|
|
328
|
-
*
|
|
674
|
+
* Read a single project-context markdown file via the engine's overlay
|
|
675
|
+
* union (repo project-context/ wins over the global overlay; legacy
|
|
676
|
+
* top-level repo files still resolve). Returns empty string if not found.
|
|
329
677
|
*/
|
|
330
678
|
function readLocalProjectContextFile(name) {
|
|
331
|
-
const
|
|
332
|
-
|
|
333
|
-
|
|
679
|
+
const resolved = (0, projectContext_1.resolveProjectContext)();
|
|
680
|
+
return (0, projectContext_1.readContextFile)(resolved, name)?.content || "";
|
|
681
|
+
}
|
|
682
|
+
function firstContentLines(content, limit = 3) {
|
|
683
|
+
const lines = content
|
|
684
|
+
.split("\n")
|
|
685
|
+
.map((line) => line.trim())
|
|
686
|
+
.filter((line) => line && !line.startsWith("---") && !line.startsWith("#") && !line.startsWith("<!--"));
|
|
687
|
+
return lines.slice(0, limit).join(" ").slice(0, 280);
|
|
688
|
+
}
|
|
689
|
+
function readBriefFile(root, relativePath) {
|
|
690
|
+
const filePath = path.join(root, relativePath);
|
|
691
|
+
if (!fs.existsSync(filePath)) {
|
|
692
|
+
return { path: relativePath, exists: false };
|
|
693
|
+
}
|
|
694
|
+
try {
|
|
695
|
+
const stat = fs.statSync(filePath);
|
|
696
|
+
if (stat.isDirectory()) {
|
|
697
|
+
return { path: relativePath, exists: true, summary: "directory" };
|
|
698
|
+
}
|
|
699
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
700
|
+
return {
|
|
701
|
+
path: relativePath,
|
|
702
|
+
exists: true,
|
|
703
|
+
chars: content.length,
|
|
704
|
+
summary: firstContentLines(content),
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
catch {
|
|
708
|
+
return { path: relativePath, exists: true, summary: "unreadable" };
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
function readProjectFile(root, relativePath) {
|
|
712
|
+
const filePath = path.join(root, relativePath);
|
|
713
|
+
return readFileOr(filePath, "");
|
|
714
|
+
}
|
|
715
|
+
function collectInstructionFiles(root) {
|
|
334
716
|
const candidates = [
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
717
|
+
"AGENTS.md",
|
|
718
|
+
"CLAUDE.md",
|
|
719
|
+
".you/AGENT.md",
|
|
720
|
+
".you/STACK-MAP.md",
|
|
721
|
+
".cursor/rules/youmd.mdc",
|
|
722
|
+
".cursor/rules/youmd.md",
|
|
723
|
+
".claude/skills/youmd",
|
|
724
|
+
".codex/skills/youmd",
|
|
339
725
|
];
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
726
|
+
return candidates
|
|
727
|
+
.map((candidate) => readBriefFile(root, candidate))
|
|
728
|
+
.filter((file) => file.exists);
|
|
729
|
+
}
|
|
730
|
+
function collectContextFiles(root) {
|
|
731
|
+
const candidates = [
|
|
732
|
+
"project-context/CURRENT_STATE.md",
|
|
733
|
+
"project-context/feature-requests-active.md",
|
|
734
|
+
"project-context/TODO.md",
|
|
735
|
+
"project-context/FEATURES.md",
|
|
736
|
+
"project-context/CHANGELOG.md",
|
|
737
|
+
"project-context/PRD.md",
|
|
738
|
+
"project-context/ARCHITECTURE.md",
|
|
739
|
+
"project-context/STYLE_GUIDE.md",
|
|
740
|
+
"project-context/BRANDING.md",
|
|
741
|
+
];
|
|
742
|
+
return candidates
|
|
743
|
+
.map((candidate) => readBriefFile(root, candidate))
|
|
744
|
+
.filter((file) => file.exists);
|
|
745
|
+
}
|
|
746
|
+
function extractOpenTodos(content, limit = 8) {
|
|
747
|
+
const todos = [];
|
|
748
|
+
for (const line of content.split("\n")) {
|
|
749
|
+
const match = line.match(/^\s*-\s+\[\s\]\s+(.+)$/);
|
|
750
|
+
if (match)
|
|
751
|
+
todos.push(match[1].trim());
|
|
752
|
+
if (todos.length >= limit)
|
|
753
|
+
break;
|
|
754
|
+
}
|
|
755
|
+
return todos;
|
|
756
|
+
}
|
|
757
|
+
function extractActiveRequests(content, limit = 6) {
|
|
758
|
+
const requests = [];
|
|
759
|
+
const chunks = content.split(/\n(?=###\s+)/g);
|
|
760
|
+
for (const chunk of chunks) {
|
|
761
|
+
const titleMatch = chunk.match(/^###\s+(.+)$/m);
|
|
762
|
+
if (!titleMatch)
|
|
763
|
+
continue;
|
|
764
|
+
const statusMatch = chunk.match(/\*\*Status:\*\*\s*([^\n]+)/);
|
|
765
|
+
const status = statusMatch?.[1]?.trim() || "UNKNOWN";
|
|
766
|
+
if (/^(DONE|VERIFIED BY USER|DECIDED|N\/A)/i.test(status))
|
|
767
|
+
continue;
|
|
768
|
+
requests.push(`${titleMatch[1].trim()} [${status}]`);
|
|
769
|
+
if (requests.length >= limit)
|
|
770
|
+
break;
|
|
771
|
+
}
|
|
772
|
+
return requests;
|
|
773
|
+
}
|
|
774
|
+
function extractSectionBullets(content, heading, limit = 6) {
|
|
775
|
+
const lines = content.split("\n");
|
|
776
|
+
const start = lines.findIndex((line) => line.trim().toLowerCase() === `## ${heading.toLowerCase()}`);
|
|
777
|
+
if (start < 0)
|
|
778
|
+
return [];
|
|
779
|
+
const bullets = [];
|
|
780
|
+
for (const line of lines.slice(start + 1)) {
|
|
781
|
+
if (/^##\s+/.test(line))
|
|
782
|
+
break;
|
|
783
|
+
const match = line.match(/^\s*-\s+(.+)$/);
|
|
784
|
+
if (match)
|
|
785
|
+
bullets.push(match[1].trim());
|
|
786
|
+
if (bullets.length >= limit)
|
|
787
|
+
break;
|
|
788
|
+
}
|
|
789
|
+
return bullets;
|
|
790
|
+
}
|
|
791
|
+
function buildProjectBrief() {
|
|
792
|
+
const detected = (0, config_1.detectProjectContext)();
|
|
793
|
+
const fallbackRoot = (0, projectContext_1.findRepoContextRoot)();
|
|
794
|
+
const root = detected?.root || fallbackRoot;
|
|
795
|
+
if (!root)
|
|
796
|
+
return null;
|
|
797
|
+
const activeRequests = extractActiveRequests(readProjectFile(root, "project-context/feature-requests-active.md"));
|
|
798
|
+
const openTodos = extractOpenTodos(readProjectFile(root, "project-context/TODO.md"));
|
|
799
|
+
const knownIssues = extractSectionBullets(readProjectFile(root, "project-context/CURRENT_STATE.md"), "Known Issues");
|
|
800
|
+
return {
|
|
801
|
+
name: detected?.name || path.basename(root),
|
|
802
|
+
root,
|
|
803
|
+
marker: detected?.marker || "project-context",
|
|
804
|
+
instructionFiles: collectInstructionFiles(root),
|
|
805
|
+
contextFiles: collectContextFiles(root),
|
|
806
|
+
activeRequests,
|
|
807
|
+
openTodos,
|
|
808
|
+
knownIssues,
|
|
809
|
+
};
|
|
810
|
+
}
|
|
811
|
+
async function buildAgentBrief(options = {}) {
|
|
812
|
+
const project = buildProjectBrief();
|
|
813
|
+
const installedSkills = getInstalledSkills().map((skill) => skill.name).sort();
|
|
814
|
+
const nextMoves = [];
|
|
815
|
+
if (project?.activeRequests.length) {
|
|
816
|
+
nextMoves.push(`Address the top active request: ${project.activeRequests[0]}`);
|
|
817
|
+
}
|
|
818
|
+
if (project?.openTodos.length) {
|
|
819
|
+
nextMoves.push(`Use the first open TODO as backlog context: ${project.openTodos[0]}`);
|
|
820
|
+
}
|
|
821
|
+
if (project && project.contextFiles.length === 0) {
|
|
822
|
+
nextMoves.push("Bootstrap project-context with: youmd skill init-project --mode additive");
|
|
823
|
+
}
|
|
824
|
+
if (!installedSkills.includes("youstack-start")) {
|
|
825
|
+
nextMoves.push("Install the YouStack starter skill with: youmd skill install youstack-start");
|
|
826
|
+
}
|
|
827
|
+
if (nextMoves.length === 0) {
|
|
828
|
+
nextMoves.push("Read the project instructions, pick the highest-signal task, and act end-to-end.");
|
|
829
|
+
}
|
|
830
|
+
const brief = {
|
|
831
|
+
generatedAt: new Date().toISOString(),
|
|
832
|
+
user: {
|
|
833
|
+
summary: buildWhoamiSummary(),
|
|
834
|
+
authenticated: (0, config_1.isAuthenticated)(),
|
|
835
|
+
bundleDir: getBundleDir(),
|
|
836
|
+
bundleInitialized: activeBundleExists(),
|
|
837
|
+
},
|
|
838
|
+
environment: {
|
|
839
|
+
cwd: process.cwd(),
|
|
840
|
+
mcpVersion: MCP_SERVER_VERSION,
|
|
841
|
+
},
|
|
842
|
+
project,
|
|
843
|
+
skills: {
|
|
844
|
+
installed: installedSkills,
|
|
845
|
+
recommended: [
|
|
846
|
+
"youstack-start",
|
|
847
|
+
"project-context-init",
|
|
848
|
+
"proactive-context-fill",
|
|
849
|
+
"voice-sync",
|
|
850
|
+
"you-logs",
|
|
851
|
+
],
|
|
852
|
+
},
|
|
853
|
+
nextMoves: nextMoves.slice(0, 5),
|
|
854
|
+
reminders: [
|
|
855
|
+
"Read the whole user request before acting; split multi-part asks into tracked items.",
|
|
856
|
+
"Prefer local project instructions and project-context files before asking for context.",
|
|
857
|
+
"When making changes, update TODO, FEATURES, CHANGELOG, active requests, and PROMPTS before closing.",
|
|
858
|
+
"Do not claim files were written or pushed unless the tool call actually succeeded.",
|
|
859
|
+
],
|
|
860
|
+
};
|
|
861
|
+
// Memories are included by default (parity with hosted get_agent_brief:
|
|
862
|
+
// the brief must surface memory CONTENT, not a count — opt out explicitly).
|
|
863
|
+
if (options.includeMemories !== false) {
|
|
864
|
+
const memoryResult = await fetchMemoriesEnvelope(undefined, exports.AGENT_BRIEF_MEMORY_CAP * 3);
|
|
865
|
+
brief.memoriesReadiness = memoryResult.readiness;
|
|
866
|
+
brief.memories = orderBriefMemories(asBriefMemories(memoryResult.memories))
|
|
867
|
+
.slice(0, exports.AGENT_BRIEF_MEMORY_CAP);
|
|
868
|
+
}
|
|
869
|
+
return brief;
|
|
870
|
+
}
|
|
871
|
+
function renderFileList(files) {
|
|
872
|
+
if (files.length === 0)
|
|
873
|
+
return "- none detected";
|
|
874
|
+
return files.map((file) => {
|
|
875
|
+
const detail = file.summary ? ` - ${file.summary}` : "";
|
|
876
|
+
const chars = typeof file.chars === "number" ? ` (${file.chars} chars)` : "";
|
|
877
|
+
return `- ${file.path}${chars}${detail}`;
|
|
878
|
+
}).join("\n");
|
|
879
|
+
}
|
|
880
|
+
function renderList(items, empty) {
|
|
881
|
+
if (items.length === 0)
|
|
882
|
+
return `- ${empty}`;
|
|
883
|
+
return items.map((item) => `- ${item}`).join("\n");
|
|
884
|
+
}
|
|
885
|
+
function formatAgentBriefMarkdown(brief, maxChars = 6000) {
|
|
886
|
+
const project = brief.project;
|
|
887
|
+
const lines = [];
|
|
888
|
+
lines.push("# YouStack Agent Brief");
|
|
889
|
+
lines.push("");
|
|
890
|
+
lines.push("## User");
|
|
891
|
+
lines.push(brief.user.summary || "(no identity summary available)");
|
|
892
|
+
lines.push("");
|
|
893
|
+
lines.push(`- authenticated: ${brief.user.authenticated ? "yes" : "no"}`);
|
|
894
|
+
lines.push(`- bundle: ${brief.user.bundleInitialized ? brief.user.bundleDir : "not initialized"}`);
|
|
895
|
+
lines.push(`- cwd: ${brief.environment.cwd}`);
|
|
896
|
+
lines.push("");
|
|
897
|
+
lines.push("## Project");
|
|
898
|
+
if (project) {
|
|
899
|
+
lines.push(`- name: ${project.name}`);
|
|
900
|
+
lines.push(`- root: ${project.root}`);
|
|
901
|
+
lines.push(`- marker: ${project.marker}`);
|
|
902
|
+
lines.push("");
|
|
903
|
+
lines.push("### Instruction Files");
|
|
904
|
+
lines.push(renderFileList(project.instructionFiles));
|
|
905
|
+
lines.push("");
|
|
906
|
+
lines.push("### Project Context");
|
|
907
|
+
lines.push(renderFileList(project.contextFiles));
|
|
908
|
+
lines.push("");
|
|
909
|
+
lines.push("### Active Requests");
|
|
910
|
+
lines.push(renderList(project.activeRequests, "none detected"));
|
|
911
|
+
lines.push("");
|
|
912
|
+
lines.push("### Open TODOs");
|
|
913
|
+
lines.push(renderList(project.openTodos, "none detected"));
|
|
914
|
+
if (project.knownIssues.length > 0) {
|
|
915
|
+
lines.push("");
|
|
916
|
+
lines.push("### Known Issues");
|
|
917
|
+
lines.push(renderList(project.knownIssues, "none detected"));
|
|
343
918
|
}
|
|
344
919
|
}
|
|
345
|
-
|
|
920
|
+
else {
|
|
921
|
+
lines.push("- no local project detected");
|
|
922
|
+
}
|
|
923
|
+
lines.push("");
|
|
924
|
+
lines.push("## Skills");
|
|
925
|
+
lines.push(`- installed: ${brief.skills.installed.length > 0 ? brief.skills.installed.join(", ") : "none"}`);
|
|
926
|
+
lines.push(`- recommended: ${brief.skills.recommended.join(", ")}`);
|
|
927
|
+
lines.push("");
|
|
928
|
+
if (brief.memories) {
|
|
929
|
+
// Render actual memory content lines, not a bare count (parity with the
|
|
930
|
+
// hosted get_agent_brief).
|
|
931
|
+
lines.push(`## Memories (${brief.memories.length})`);
|
|
932
|
+
if (brief.memories.length === 0) {
|
|
933
|
+
lines.push("- none recorded yet");
|
|
934
|
+
}
|
|
935
|
+
else {
|
|
936
|
+
for (const memory of brief.memories) {
|
|
937
|
+
lines.push(`- [${memory.category}] ${briefOneLine(memory.content)}`);
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
if (brief.memoriesReadiness && !brief.memoriesReadiness.ready) {
|
|
941
|
+
lines.push(`- readiness: ${brief.memoriesReadiness.status} (${brief.memoriesReadiness.reason})`);
|
|
942
|
+
lines.push(`- fallback: ${brief.memoriesReadiness.fallback}`);
|
|
943
|
+
}
|
|
944
|
+
lines.push("");
|
|
945
|
+
}
|
|
946
|
+
lines.push("## Next Moves");
|
|
947
|
+
lines.push(renderList(brief.nextMoves, "read instructions, then act"));
|
|
948
|
+
lines.push("");
|
|
949
|
+
lines.push("## Reminders");
|
|
950
|
+
lines.push(renderList(brief.reminders, "none"));
|
|
951
|
+
let markdown = lines.join("\n");
|
|
952
|
+
if (markdown.length > maxChars) {
|
|
953
|
+
markdown = markdown.slice(0, Math.max(0, maxChars - 32)).trimEnd() + "\n\n[truncated]";
|
|
954
|
+
}
|
|
955
|
+
return markdown;
|
|
346
956
|
}
|
|
347
957
|
// ---------------------------------------------------------------------------
|
|
348
958
|
// MCP Server
|
|
@@ -350,13 +960,18 @@ function readLocalProjectContextFile(name) {
|
|
|
350
960
|
async function startMcpServer() {
|
|
351
961
|
const server = new index_js_1.Server({
|
|
352
962
|
name: "youmd",
|
|
353
|
-
version:
|
|
963
|
+
version: MCP_SERVER_VERSION,
|
|
354
964
|
}, {
|
|
355
965
|
capabilities: {
|
|
356
966
|
resources: {},
|
|
357
967
|
tools: {},
|
|
358
968
|
},
|
|
359
969
|
});
|
|
970
|
+
// Capture clientInfo.name from the initialize handshake for activity
|
|
971
|
+
// attribution (fallback when YOUMD_AGENT_NAME isn't set by the host config).
|
|
972
|
+
server.oninitialized = () => {
|
|
973
|
+
setMcpClientName(server.getClientVersion()?.name);
|
|
974
|
+
};
|
|
360
975
|
// ── LIST RESOURCES ─────────────────────────────────────────────────
|
|
361
976
|
server.setRequestHandler(types_js_1.ListResourcesRequestSchema, async () => {
|
|
362
977
|
const resources = [];
|
|
@@ -373,6 +988,27 @@ async function startMcpServer() {
|
|
|
373
988
|
description: "Human-readable you.md identity (you.md format)",
|
|
374
989
|
mimeType: "text/markdown",
|
|
375
990
|
});
|
|
991
|
+
resources.push({
|
|
992
|
+
uri: "youmd://agent/brief",
|
|
993
|
+
name: "agent-brief",
|
|
994
|
+
description: "YouStack startup brief for local agents: compact identity, project instructions, active requests, open TODOs, installed skills, and next moves",
|
|
995
|
+
mimeType: "text/markdown",
|
|
996
|
+
});
|
|
997
|
+
const currentStack = tryLoadCurrentYouStack();
|
|
998
|
+
if (currentStack) {
|
|
999
|
+
resources.push({
|
|
1000
|
+
uri: "youmd://stacks/current/manifest",
|
|
1001
|
+
name: "stacks/current/manifest",
|
|
1002
|
+
description: "Current local YouStack manifest discovered from cwd",
|
|
1003
|
+
mimeType: "application/json",
|
|
1004
|
+
});
|
|
1005
|
+
resources.push({
|
|
1006
|
+
uri: "youmd://stacks/current/capabilities",
|
|
1007
|
+
name: "stacks/current/capabilities",
|
|
1008
|
+
description: "Current local YouStack capability map",
|
|
1009
|
+
mimeType: "application/json",
|
|
1010
|
+
});
|
|
1011
|
+
}
|
|
376
1012
|
// Profile sections
|
|
377
1013
|
const sections = getSectionFiles();
|
|
378
1014
|
for (const section of sections) {
|
|
@@ -385,6 +1021,12 @@ async function startMcpServer() {
|
|
|
385
1021
|
}
|
|
386
1022
|
// Memories (if authenticated)
|
|
387
1023
|
if ((0, config_1.isAuthenticated)()) {
|
|
1024
|
+
resources.push({
|
|
1025
|
+
uri: "youmd://private-context",
|
|
1026
|
+
name: "private-context",
|
|
1027
|
+
description: "Protected private context — notes, internal links, and private projects with readiness/fallback guidance",
|
|
1028
|
+
mimeType: "application/json",
|
|
1029
|
+
});
|
|
388
1030
|
resources.push({
|
|
389
1031
|
uri: "youmd://memories",
|
|
390
1032
|
name: "memories",
|
|
@@ -505,9 +1147,55 @@ async function startMcpServer() {
|
|
|
505
1147
|
}],
|
|
506
1148
|
};
|
|
507
1149
|
}
|
|
1150
|
+
// youmd://agent/brief
|
|
1151
|
+
if (uri === "youmd://agent/brief") {
|
|
1152
|
+
const brief = await buildAgentBrief();
|
|
1153
|
+
void logMcpActivity("read", "agent/brief");
|
|
1154
|
+
return {
|
|
1155
|
+
contents: [{
|
|
1156
|
+
uri,
|
|
1157
|
+
mimeType: "text/markdown",
|
|
1158
|
+
text: formatAgentBriefMarkdown(brief),
|
|
1159
|
+
}],
|
|
1160
|
+
};
|
|
1161
|
+
}
|
|
1162
|
+
if (uri === "youmd://stacks/current/manifest") {
|
|
1163
|
+
const loaded = tryLoadCurrentYouStack();
|
|
1164
|
+
if (!loaded)
|
|
1165
|
+
throw new Error("no local YouStack manifest found from current directory");
|
|
1166
|
+
void logMcpActivity("read", "stacks/current/manifest");
|
|
1167
|
+
return {
|
|
1168
|
+
contents: [{
|
|
1169
|
+
uri,
|
|
1170
|
+
mimeType: "application/json",
|
|
1171
|
+
text: JSON.stringify({
|
|
1172
|
+
manifestPath: loaded.manifestPath,
|
|
1173
|
+
rootDir: loaded.rootDir,
|
|
1174
|
+
manifest: loaded.manifest,
|
|
1175
|
+
validation: loaded.validation,
|
|
1176
|
+
}, null, 2),
|
|
1177
|
+
}],
|
|
1178
|
+
};
|
|
1179
|
+
}
|
|
1180
|
+
if (uri === "youmd://stacks/current/capabilities") {
|
|
1181
|
+
const loaded = tryLoadCurrentYouStack();
|
|
1182
|
+
if (!loaded)
|
|
1183
|
+
throw new Error("no local YouStack manifest found from current directory");
|
|
1184
|
+
void logMcpActivity("read", "stacks/current/capabilities");
|
|
1185
|
+
return {
|
|
1186
|
+
contents: [{
|
|
1187
|
+
uri,
|
|
1188
|
+
mimeType: "application/json",
|
|
1189
|
+
text: JSON.stringify({
|
|
1190
|
+
stack: loaded.manifest.slug,
|
|
1191
|
+
capabilities: (0, youstack_1.getYouStackCapabilities)(loaded.manifest),
|
|
1192
|
+
}, null, 2),
|
|
1193
|
+
}],
|
|
1194
|
+
};
|
|
1195
|
+
}
|
|
508
1196
|
// youmd://memories
|
|
509
1197
|
if (uri === "youmd://memories" || uri === "youmd://memories/all") {
|
|
510
|
-
const memories = await
|
|
1198
|
+
const memories = await fetchMemoriesEnvelope(undefined, 50);
|
|
511
1199
|
return {
|
|
512
1200
|
contents: [{
|
|
513
1201
|
uri,
|
|
@@ -519,7 +1207,7 @@ async function startMcpServer() {
|
|
|
519
1207
|
// youmd://memories/{category}
|
|
520
1208
|
const memCatMatch = uri.match(/^youmd:\/\/memories\/(.+)$/);
|
|
521
1209
|
if (memCatMatch) {
|
|
522
|
-
const memories = await
|
|
1210
|
+
const memories = await fetchMemoriesEnvelope(memCatMatch[1], 50);
|
|
523
1211
|
return {
|
|
524
1212
|
contents: [{
|
|
525
1213
|
uri,
|
|
@@ -528,6 +1216,16 @@ async function startMcpServer() {
|
|
|
528
1216
|
}],
|
|
529
1217
|
};
|
|
530
1218
|
}
|
|
1219
|
+
if (uri === "youmd://private-context") {
|
|
1220
|
+
const privateContext = await fetchPrivateContextEnvelope();
|
|
1221
|
+
return {
|
|
1222
|
+
contents: [{
|
|
1223
|
+
uri,
|
|
1224
|
+
mimeType: "application/json",
|
|
1225
|
+
text: JSON.stringify(privateContext, null, 2),
|
|
1226
|
+
}],
|
|
1227
|
+
};
|
|
1228
|
+
}
|
|
531
1229
|
// youmd://project/current/{file} — local project-context files from cwd
|
|
532
1230
|
const localProjMatch = uri.match(/^youmd:\/\/project\/current\/(prd|todo|features|decisions|changelog)$/);
|
|
533
1231
|
if (localProjMatch) {
|
|
@@ -545,10 +1243,7 @@ async function startMcpServer() {
|
|
|
545
1243
|
}
|
|
546
1244
|
// youmd://projects/current
|
|
547
1245
|
if (uri === "youmd://projects/current") {
|
|
548
|
-
const
|
|
549
|
-
if (!current)
|
|
550
|
-
throw new Error("no project detected in current directory");
|
|
551
|
-
const ctx = (0, project_1.readProjectContext)(current.dir);
|
|
1246
|
+
const ctx = fetchProjectContextEnvelope();
|
|
552
1247
|
return {
|
|
553
1248
|
contents: [{
|
|
554
1249
|
uri,
|
|
@@ -560,11 +1255,7 @@ async function startMcpServer() {
|
|
|
560
1255
|
// youmd://projects/{name}
|
|
561
1256
|
const projMatch = uri.match(/^youmd:\/\/projects\/(.+)$/);
|
|
562
1257
|
if (projMatch) {
|
|
563
|
-
const
|
|
564
|
-
if (!root)
|
|
565
|
-
throw new Error("no projects directory found");
|
|
566
|
-
const projDir = (0, project_1.getProjectDir)(root, projMatch[1]);
|
|
567
|
-
const ctx = (0, project_1.readProjectContext)(projDir);
|
|
1258
|
+
const ctx = fetchProjectContextEnvelope(projMatch[1]);
|
|
568
1259
|
return {
|
|
569
1260
|
contents: [{
|
|
570
1261
|
uri,
|
|
@@ -610,42 +1301,33 @@ async function startMcpServer() {
|
|
|
610
1301
|
server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => {
|
|
611
1302
|
return {
|
|
612
1303
|
tools: [
|
|
1304
|
+
// whoami, get_identity, list_skills, list_projects, get_section,
|
|
1305
|
+
// get_project_context, get_stack_manifest, get_stack_capabilities,
|
|
1306
|
+
// route_stack_request, smoke_stack — migrated to registry.ts (T14).
|
|
613
1307
|
{
|
|
614
|
-
name: "
|
|
615
|
-
description: "Return a
|
|
616
|
-
inputSchema: {
|
|
617
|
-
type: "object",
|
|
618
|
-
properties: {},
|
|
619
|
-
},
|
|
620
|
-
},
|
|
621
|
-
{
|
|
622
|
-
name: "get_identity",
|
|
623
|
-
description: "Get the user's complete you.md identity bundle. Returns compact (default), full JSON, or human-readable markdown. For the fastest orient, call whoami first; call get_identity when you need full detail (preferences, voice, directives, projects).",
|
|
1308
|
+
name: "get_agent_brief",
|
|
1309
|
+
description: "Return a YouStack startup brief for local agents. Use immediately after whoami when starting Claude Code, Codex, Cursor, or another MCP-backed session. It combines compact identity, the user's recent durable memories (rendered inline by default), current repo instructions, project-context active requests, open TODOs, installed skills, and recommended next moves so the agent can act without asking the user to re-explain the project.",
|
|
624
1310
|
inputSchema: {
|
|
625
1311
|
type: "object",
|
|
626
1312
|
properties: {
|
|
627
1313
|
format: {
|
|
628
1314
|
type: "string",
|
|
629
|
-
enum: ["
|
|
630
|
-
description: "Output format
|
|
1315
|
+
enum: ["markdown", "json"],
|
|
1316
|
+
description: "Output format. markdown is default and best for agent context; json is best for automation.",
|
|
631
1317
|
},
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
type: "object",
|
|
640
|
-
properties: {
|
|
641
|
-
section: {
|
|
642
|
-
type: "string",
|
|
643
|
-
description: "Section path: profile/about, profile/projects, profile/now, profile/values, profile/links, preferences/agent, preferences/writing, voice/voice, directives/agent",
|
|
1318
|
+
includeMemories: {
|
|
1319
|
+
type: "boolean",
|
|
1320
|
+
description: "Include up to 20 memories (category + content, durable categories first, then newest) plus retrieval readiness/fallback guidance. Default true.",
|
|
1321
|
+
},
|
|
1322
|
+
maxChars: {
|
|
1323
|
+
type: "number",
|
|
1324
|
+
description: "Maximum markdown characters when format=markdown. Default 6000.",
|
|
644
1325
|
},
|
|
645
1326
|
},
|
|
646
|
-
required: ["section"],
|
|
647
1327
|
},
|
|
648
1328
|
},
|
|
1329
|
+
// get_stack_manifest, get_stack_capabilities, route_stack_request,
|
|
1330
|
+
// smoke_stack, get_identity, get_section — migrated to registry.ts (T14).
|
|
649
1331
|
{
|
|
650
1332
|
name: "update_section",
|
|
651
1333
|
description: "Update a section of the user's identity. Writes markdown content to the local .youmd/ directory. Use after the user explicitly asks to change their profile, preferences, voice, or directives. Always confirm changes with the user before calling. Does NOT auto-push — call push_bundle afterward if the user wants to publish.",
|
|
@@ -664,60 +1346,8 @@ async function startMcpServer() {
|
|
|
664
1346
|
required: ["section", "content"],
|
|
665
1347
|
},
|
|
666
1348
|
},
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
description: "Save a memory about the user — facts, preferences, decisions, or context learned during this conversation. Memories persist across sessions and inform ALL future agent interactions. Use proactively when you learn something important about the user (a preference, a decision, a project detail). Requires authentication.",
|
|
670
|
-
inputSchema: {
|
|
671
|
-
type: "object",
|
|
672
|
-
properties: {
|
|
673
|
-
category: {
|
|
674
|
-
type: "string",
|
|
675
|
-
enum: [...MEMORY_CATEGORIES],
|
|
676
|
-
description: "Memory category. Must be one of: fact, preference, decision, project, goal, insight, context, relationship.",
|
|
677
|
-
},
|
|
678
|
-
content: {
|
|
679
|
-
type: "string",
|
|
680
|
-
description: "The memory content — be specific and actionable",
|
|
681
|
-
},
|
|
682
|
-
tags: {
|
|
683
|
-
type: "array",
|
|
684
|
-
items: { type: "string" },
|
|
685
|
-
description: "Optional tags for searchability",
|
|
686
|
-
},
|
|
687
|
-
},
|
|
688
|
-
required: ["category", "content"],
|
|
689
|
-
},
|
|
690
|
-
},
|
|
691
|
-
{
|
|
692
|
-
name: "search_memories",
|
|
693
|
-
description: "Search the user's memories by category or list all active memories. Returns an array of memory objects with category, content, tags, and timestamps. Use to check what you already know before asking the user a question they may have answered before.",
|
|
694
|
-
inputSchema: {
|
|
695
|
-
type: "object",
|
|
696
|
-
properties: {
|
|
697
|
-
category: {
|
|
698
|
-
type: "string",
|
|
699
|
-
description: "Filter by category (optional): fact, preference, decision, project, goal, insight, context",
|
|
700
|
-
},
|
|
701
|
-
limit: {
|
|
702
|
-
type: "number",
|
|
703
|
-
description: "Max results (default 30)",
|
|
704
|
-
},
|
|
705
|
-
},
|
|
706
|
-
},
|
|
707
|
-
},
|
|
708
|
-
{
|
|
709
|
-
name: "get_project_context",
|
|
710
|
-
description: "Get the full project context for the current or named project — PRD, TODO, features, decisions, changelog, and project memories. Returns a JSON object with all project-context/ files. Use when starting work on a project to understand what has been built, what is planned, and what decisions have been made.",
|
|
711
|
-
inputSchema: {
|
|
712
|
-
type: "object",
|
|
713
|
-
properties: {
|
|
714
|
-
project: {
|
|
715
|
-
type: "string",
|
|
716
|
-
description: "Project name. Omit to auto-detect from current directory.",
|
|
717
|
-
},
|
|
718
|
-
},
|
|
719
|
-
},
|
|
720
|
-
},
|
|
1349
|
+
// add_memory, search_memories, get_private_context — migrated to registry.ts (T14).
|
|
1350
|
+
// get_project_context — migrated to registry.ts (T14).
|
|
721
1351
|
{
|
|
722
1352
|
name: "add_project_memory",
|
|
723
1353
|
description: "Save a memory scoped to a specific project. Unlike add_memory (which is global), project memories are stored locally in the project-context/ directory and only surface when working on that project. Use for architecture decisions, bug context, and feature-specific learnings.",
|
|
@@ -740,20 +1370,7 @@ async function startMcpServer() {
|
|
|
740
1370
|
required: ["category", "content"],
|
|
741
1371
|
},
|
|
742
1372
|
},
|
|
743
|
-
|
|
744
|
-
name: "use_skill",
|
|
745
|
-
description: "Render an identity-aware skill — returns the skill content with the user's identity interpolated into {{var}} placeholders. Returns rendered markdown with instructions the agent should follow. Use when the user asks to generate a CLAUDE.md, sync voice, scaffold a project, or run a self-improvement review.",
|
|
746
|
-
inputSchema: {
|
|
747
|
-
type: "object",
|
|
748
|
-
properties: {
|
|
749
|
-
name: {
|
|
750
|
-
type: "string",
|
|
751
|
-
description: "Skill name: voice-sync, claude-md-generator, project-context-init, meta-improve, proactive-context-fill, you-logs",
|
|
752
|
-
},
|
|
753
|
-
},
|
|
754
|
-
required: ["name"],
|
|
755
|
-
},
|
|
756
|
-
},
|
|
1373
|
+
// use_skill — migrated to registry.ts (T14).
|
|
757
1374
|
{
|
|
758
1375
|
name: "compile_bundle",
|
|
759
1376
|
description: "Recompile the local identity bundle from profile/, preferences/, voice/, directives/ files into you.json + you.md. Call after update_section to regenerate the compiled bundle. Returns compilation stats including version, section count, and files read.",
|
|
@@ -783,132 +1400,80 @@ async function startMcpServer() {
|
|
|
783
1400
|
properties: {},
|
|
784
1401
|
},
|
|
785
1402
|
},
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
type: "object",
|
|
791
|
-
properties: {},
|
|
792
|
-
},
|
|
793
|
-
},
|
|
794
|
-
{
|
|
795
|
-
name: "add_source",
|
|
796
|
-
description: "Register an identity data source (LinkedIn, GitHub, X, website, blog, YouTube). Links an external profile to the user's identity so it can be scraped and indexed. Requires authentication. Use when the user wants to connect a new social profile or website to their identity.",
|
|
797
|
-
inputSchema: {
|
|
798
|
-
type: "object",
|
|
799
|
-
properties: {
|
|
800
|
-
sourceType: {
|
|
801
|
-
type: "string",
|
|
802
|
-
enum: ["website", "linkedin", "x", "github", "blog", "youtube"],
|
|
803
|
-
description: "Type of source to register",
|
|
804
|
-
},
|
|
805
|
-
sourceUrl: {
|
|
806
|
-
type: "string",
|
|
807
|
-
description: "Full URL to the source (e.g., https://github.com/username)",
|
|
808
|
-
},
|
|
809
|
-
},
|
|
810
|
-
required: ["sourceType", "sourceUrl"],
|
|
811
|
-
},
|
|
812
|
-
},
|
|
813
|
-
{
|
|
814
|
-
name: "create_context_link",
|
|
815
|
-
description: "Generate a shareable context link for agents. The link gives any agent temporary or permanent read access to the user's identity context. Use when the user wants to share their identity with a third-party agent or service. Returns a URL that can be passed to any agent.",
|
|
816
|
-
inputSchema: {
|
|
817
|
-
type: "object",
|
|
818
|
-
properties: {
|
|
819
|
-
scope: {
|
|
820
|
-
type: "string",
|
|
821
|
-
enum: ["public", "full"],
|
|
822
|
-
description: "Access scope: public (profile only) or full (includes memories, preferences, directives). Default: public",
|
|
823
|
-
},
|
|
824
|
-
ttl: {
|
|
825
|
-
type: "string",
|
|
826
|
-
enum: ["1h", "24h", "7d", "30d", "never"],
|
|
827
|
-
description: "Time-to-live for the link. Default: 24h",
|
|
828
|
-
},
|
|
829
|
-
},
|
|
830
|
-
},
|
|
831
|
-
},
|
|
832
|
-
{
|
|
833
|
-
name: "list_projects",
|
|
834
|
-
description: "List all detected projects with their metadata. Returns project names found in the projects root directory. Use to discover available projects before calling get_project_context with a specific project name.",
|
|
835
|
-
inputSchema: {
|
|
836
|
-
type: "object",
|
|
837
|
-
properties: {},
|
|
838
|
-
},
|
|
839
|
-
},
|
|
840
|
-
{
|
|
841
|
-
name: "get_remote_status",
|
|
842
|
-
description: "Check sync status between local identity bundle and the remote you.md server. Returns whether the user is authenticated, whether the local bundle exists, and the current version info. Use to diagnose sync issues or confirm a push was successful.",
|
|
843
|
-
inputSchema: {
|
|
844
|
-
type: "object",
|
|
845
|
-
properties: {},
|
|
846
|
-
},
|
|
847
|
-
},
|
|
848
|
-
{
|
|
849
|
-
name: "get_activity_log",
|
|
850
|
-
description: "Get the user's recent agent activity log. Use this to see which agents have connected to their you.md identity and what they did. Returns an array of activity events with agent name, action, resource, timestamp. Useful for: showing the user proof their identity context is being used by other agents, debugging integration issues, auditing access.",
|
|
851
|
-
inputSchema: {
|
|
852
|
-
type: "object",
|
|
853
|
-
properties: {
|
|
854
|
-
limit: { type: "number", description: "Max events (default 30)" },
|
|
855
|
-
agentName: { type: "string", description: "Filter by agent name (e.g. 'Claude Code')" },
|
|
856
|
-
action: { type: "string", description: "Filter by action (read|write|push|memory_add)" },
|
|
857
|
-
},
|
|
858
|
-
},
|
|
859
|
-
},
|
|
1403
|
+
// list_skills — migrated to registry.ts (T14).
|
|
1404
|
+
// add_source, create_context_link — migrated to registry.ts (T14).
|
|
1405
|
+
// list_projects — migrated to registry.ts (T14).
|
|
1406
|
+
// get_remote_status, get_activity_log — migrated to registry.ts (T14).
|
|
860
1407
|
],
|
|
1408
|
+
// T14: registry tools appended; inline tools above remain until migration.
|
|
1409
|
+
...registry_js_1.CLI_MCP_TOOLS.map(({ name, description, inputSchema }) => ({
|
|
1410
|
+
name,
|
|
1411
|
+
description,
|
|
1412
|
+
inputSchema,
|
|
1413
|
+
})),
|
|
861
1414
|
};
|
|
862
1415
|
});
|
|
863
1416
|
// ── CALL TOOL ──────────────────────────────────────────────────────
|
|
864
1417
|
server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
865
1418
|
const { name, arguments: args } = request.params;
|
|
1419
|
+
// T14: Try registry dispatch first (grows as tools are migrated out of the switch).
|
|
1420
|
+
const registrySpec = registry_js_1.CLI_MCP_TOOLS.find((t) => t.name === name);
|
|
1421
|
+
if (registrySpec) {
|
|
1422
|
+
const ctx = {
|
|
1423
|
+
logActivity: (action, resource, details) => {
|
|
1424
|
+
void logMcpActivity(action, resource, details);
|
|
1425
|
+
},
|
|
1426
|
+
authenticated: (0, config_1.isAuthenticated)(),
|
|
1427
|
+
// Injected helpers — avoids circular import (registry cannot import server).
|
|
1428
|
+
fetchMemoriesEnvelope,
|
|
1429
|
+
fetchPrivateContextEnvelope,
|
|
1430
|
+
apiRequest,
|
|
1431
|
+
memoryCategories: exports.MEMORY_CATEGORIES,
|
|
1432
|
+
resolveAgentName,
|
|
1433
|
+
getInstalledSkills,
|
|
1434
|
+
activeBundleExists,
|
|
1435
|
+
getYouJson,
|
|
1436
|
+
fetchActivityLog: (params) => {
|
|
1437
|
+
const config = (0, config_1.readGlobalConfig)();
|
|
1438
|
+
return fetch(`${(0, config_1.getConvexSiteUrl)()}/api/v1/me/activity?${params}`, {
|
|
1439
|
+
headers: { Authorization: `Bearer ${config.token}` },
|
|
1440
|
+
signal: AbortSignal.timeout(10000),
|
|
1441
|
+
});
|
|
1442
|
+
},
|
|
1443
|
+
};
|
|
1444
|
+
const result = await registrySpec.handler((args || {}), ctx);
|
|
1445
|
+
return { content: result.content, isError: result.isError };
|
|
1446
|
+
}
|
|
1447
|
+
// whoami, get_identity, get_section, list_skills, list_projects,
|
|
1448
|
+
// get_project_context, get_stack_manifest, get_stack_capabilities,
|
|
1449
|
+
// route_stack_request, smoke_stack — dispatched via registry above (T14).
|
|
866
1450
|
switch (name) {
|
|
867
|
-
case "
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
};
|
|
875
|
-
|
|
876
|
-
case "get_identity": {
|
|
877
|
-
const format = args?.format || "compact";
|
|
878
|
-
void logMcpActivity("read", "identity:" + format);
|
|
879
|
-
if (format === "markdown") {
|
|
880
|
-
return { content: [{ type: "text", text: buildIdentityMarkdown() }] };
|
|
881
|
-
}
|
|
882
|
-
if (format === "full" || format === "json") {
|
|
1451
|
+
case "get_agent_brief": {
|
|
1452
|
+
const briefArgs = (args || {});
|
|
1453
|
+
const includeMemories = briefArgs.includeMemories !== false;
|
|
1454
|
+
const brief = await buildAgentBrief({ includeMemories });
|
|
1455
|
+
void logMcpActivity("read", "agent/brief", {
|
|
1456
|
+
format: briefArgs.format || "markdown",
|
|
1457
|
+
includeMemories,
|
|
1458
|
+
});
|
|
1459
|
+
if (briefArgs.format === "json") {
|
|
883
1460
|
return {
|
|
884
1461
|
content: [{
|
|
885
1462
|
type: "text",
|
|
886
|
-
text: JSON.stringify(
|
|
1463
|
+
text: JSON.stringify(brief, null, 2),
|
|
887
1464
|
}],
|
|
888
1465
|
};
|
|
889
1466
|
}
|
|
890
|
-
|
|
1467
|
+
const maxChars = typeof briefArgs.maxChars === "number" && briefArgs.maxChars > 500
|
|
1468
|
+
? Math.min(briefArgs.maxChars, 20000)
|
|
1469
|
+
: 6000;
|
|
891
1470
|
return {
|
|
892
1471
|
content: [{
|
|
893
1472
|
type: "text",
|
|
894
|
-
text:
|
|
1473
|
+
text: formatAgentBriefMarkdown(brief, maxChars),
|
|
895
1474
|
}],
|
|
896
1475
|
};
|
|
897
1476
|
}
|
|
898
|
-
case "get_section": {
|
|
899
|
-
const section = args.section;
|
|
900
|
-
const parts = section.split("/");
|
|
901
|
-
if (parts.length !== 2) {
|
|
902
|
-
return { content: [{ type: "text", text: "invalid section path — use format: dir/slug (e.g., profile/about)" }], isError: true };
|
|
903
|
-
}
|
|
904
|
-
const filePath = path.join(getBundleDir(), parts[0], `${parts[1]}.md`);
|
|
905
|
-
const content = readFileOr(filePath, "");
|
|
906
|
-
if (!content) {
|
|
907
|
-
return { content: [{ type: "text", text: `section not found: ${section}` }], isError: true };
|
|
908
|
-
}
|
|
909
|
-
void logMcpActivity("read_section", section);
|
|
910
|
-
return { content: [{ type: "text", text: content }] };
|
|
911
|
-
}
|
|
912
1477
|
case "update_section": {
|
|
913
1478
|
const section = args.section;
|
|
914
1479
|
const content = args.content;
|
|
@@ -926,78 +1491,7 @@ async function startMcpServer() {
|
|
|
926
1491
|
void logMcpActivity("write", section);
|
|
927
1492
|
return { content: [{ type: "text", text: `updated ${section}` }] };
|
|
928
1493
|
}
|
|
929
|
-
|
|
930
|
-
const { category, content: memContent, tags } = args;
|
|
931
|
-
if (!(0, config_1.isAuthenticated)()) {
|
|
932
|
-
return { content: [{ type: "text", text: "not authenticated — run youmd login first" }], isError: true };
|
|
933
|
-
}
|
|
934
|
-
// Validate category against known enum. Reject unknown categories so
|
|
935
|
-
// we don't pollute the memory store with one-off tags masquerading as
|
|
936
|
-
// categories.
|
|
937
|
-
if (!category || !MEMORY_CATEGORIES.includes(category)) {
|
|
938
|
-
return {
|
|
939
|
-
content: [{
|
|
940
|
-
type: "text",
|
|
941
|
-
text: `invalid category: ${category || "(missing)"}. valid categories: ${MEMORY_CATEGORIES.join(", ")}`,
|
|
942
|
-
}],
|
|
943
|
-
isError: true,
|
|
944
|
-
};
|
|
945
|
-
}
|
|
946
|
-
try {
|
|
947
|
-
const result = await apiRequest("/api/v1/me/memories", {
|
|
948
|
-
method: "POST",
|
|
949
|
-
body: {
|
|
950
|
-
memories: [{
|
|
951
|
-
category,
|
|
952
|
-
content: memContent,
|
|
953
|
-
source: "mcp",
|
|
954
|
-
sourceAgent: "youmd-mcp",
|
|
955
|
-
tags,
|
|
956
|
-
}],
|
|
957
|
-
},
|
|
958
|
-
});
|
|
959
|
-
void logMcpActivity("memory_add", category);
|
|
960
|
-
return { content: [{ type: "text", text: `memory saved: [${category}] ${memContent.slice(0, 80)}${memContent.length > 80 ? "..." : ""}` }] };
|
|
961
|
-
}
|
|
962
|
-
catch (err) {
|
|
963
|
-
return { content: [{ type: "text", text: `failed to save memory: ${err instanceof Error ? err.message : "unknown error"}` }], isError: true };
|
|
964
|
-
}
|
|
965
|
-
}
|
|
966
|
-
case "search_memories": {
|
|
967
|
-
const { category, limit } = (args || {});
|
|
968
|
-
const memories = await fetchMemories(category, limit || 30);
|
|
969
|
-
void logMcpActivity("read", "memories");
|
|
970
|
-
return {
|
|
971
|
-
content: [{
|
|
972
|
-
type: "text",
|
|
973
|
-
text: JSON.stringify(memories, null, 2),
|
|
974
|
-
}],
|
|
975
|
-
};
|
|
976
|
-
}
|
|
977
|
-
case "get_project_context": {
|
|
978
|
-
const projectName = args?.project;
|
|
979
|
-
try {
|
|
980
|
-
if (projectName) {
|
|
981
|
-
const root = (0, project_1.findProjectsRoot)();
|
|
982
|
-
if (!root)
|
|
983
|
-
throw new Error("no projects directory found");
|
|
984
|
-
const projDir = (0, project_1.getProjectDir)(root, projectName);
|
|
985
|
-
const ctx = (0, project_1.readProjectContext)(projDir);
|
|
986
|
-
void logMcpActivity("read", "project/" + projectName);
|
|
987
|
-
return { content: [{ type: "text", text: JSON.stringify(ctx, null, 2) }] };
|
|
988
|
-
}
|
|
989
|
-
const current = getCurrentProject();
|
|
990
|
-
if (!current) {
|
|
991
|
-
return { content: [{ type: "text", text: "no project detected in current directory" }], isError: true };
|
|
992
|
-
}
|
|
993
|
-
const ctx = (0, project_1.readProjectContext)(current.dir);
|
|
994
|
-
void logMcpActivity("read", "project/current");
|
|
995
|
-
return { content: [{ type: "text", text: JSON.stringify(ctx, null, 2) }] };
|
|
996
|
-
}
|
|
997
|
-
catch (err) {
|
|
998
|
-
return { content: [{ type: "text", text: `project error: ${err instanceof Error ? err.message : "unknown"}` }], isError: true };
|
|
999
|
-
}
|
|
1000
|
-
}
|
|
1494
|
+
// add_memory, search_memories, get_private_context — dispatched via registry (T14).
|
|
1001
1495
|
case "add_project_memory": {
|
|
1002
1496
|
const { project: projName, category, content: memContent } = args;
|
|
1003
1497
|
try {
|
|
@@ -1022,17 +1516,7 @@ async function startMcpServer() {
|
|
|
1022
1516
|
return { content: [{ type: "text", text: `error: ${err instanceof Error ? err.message : "unknown"}` }], isError: true };
|
|
1023
1517
|
}
|
|
1024
1518
|
}
|
|
1025
|
-
|
|
1026
|
-
const skillName = args.name;
|
|
1027
|
-
const skills = getInstalledSkills();
|
|
1028
|
-
const skill = skills.find((s) => s.name === skillName);
|
|
1029
|
-
if (!skill) {
|
|
1030
|
-
const available = skills.map((s) => s.name).join(", ") || "none installed";
|
|
1031
|
-
return { content: [{ type: "text", text: `skill not found: ${skillName}. available: ${available}` }], isError: true };
|
|
1032
|
-
}
|
|
1033
|
-
void logMcpActivity("skill_use", "skill/" + skillName);
|
|
1034
|
-
return { content: [{ type: "text", text: skill.rendered || skill.raw }] };
|
|
1035
|
-
}
|
|
1519
|
+
// use_skill — dispatched via registry (T14).
|
|
1036
1520
|
case "compile_bundle": {
|
|
1037
1521
|
try {
|
|
1038
1522
|
const { compileBundle, writeBundle } = await Promise.resolve().then(() => __importStar(require("../lib/compiler")));
|
|
@@ -1138,138 +1622,7 @@ async function startMcpServer() {
|
|
|
1138
1622
|
};
|
|
1139
1623
|
}
|
|
1140
1624
|
}
|
|
1141
|
-
|
|
1142
|
-
const skills = getInstalledSkills();
|
|
1143
|
-
if (skills.length === 0) {
|
|
1144
|
-
return { content: [{ type: "text", text: "no skills installed. run: youmd skill install voice-sync" }] };
|
|
1145
|
-
}
|
|
1146
|
-
const list = skills.map((s) => `- ${s.name}`).join("\n");
|
|
1147
|
-
void logMcpActivity("read", "skills");
|
|
1148
|
-
return { content: [{ type: "text", text: `installed skills:\n${list}` }] };
|
|
1149
|
-
}
|
|
1150
|
-
case "add_source": {
|
|
1151
|
-
if (!(0, config_1.isAuthenticated)()) {
|
|
1152
|
-
return { content: [{ type: "text", text: "not authenticated — run youmd login first" }], isError: true };
|
|
1153
|
-
}
|
|
1154
|
-
const { sourceType, sourceUrl } = args;
|
|
1155
|
-
try {
|
|
1156
|
-
const result = await apiRequest("/api/v1/me/sources", {
|
|
1157
|
-
method: "POST",
|
|
1158
|
-
body: { sourceType, sourceUrl },
|
|
1159
|
-
});
|
|
1160
|
-
void logMcpActivity("write", "source/" + sourceType);
|
|
1161
|
-
return { content: [{ type: "text", text: `source registered: [${sourceType}] ${sourceUrl}` }] };
|
|
1162
|
-
}
|
|
1163
|
-
catch (err) {
|
|
1164
|
-
return { content: [{ type: "text", text: `failed to add source: ${err instanceof Error ? err.message : "unknown error"}` }], isError: true };
|
|
1165
|
-
}
|
|
1166
|
-
}
|
|
1167
|
-
case "create_context_link": {
|
|
1168
|
-
if (!(0, config_1.isAuthenticated)()) {
|
|
1169
|
-
return { content: [{ type: "text", text: "not authenticated — run youmd login first" }], isError: true };
|
|
1170
|
-
}
|
|
1171
|
-
const { scope, ttl } = (args || {});
|
|
1172
|
-
try {
|
|
1173
|
-
const result = await apiRequest("/api/v1/me/context-links", {
|
|
1174
|
-
method: "POST",
|
|
1175
|
-
body: { scope: scope || "public", ttl: ttl || "24h" },
|
|
1176
|
-
});
|
|
1177
|
-
const link = result.url || result.link || JSON.stringify(result);
|
|
1178
|
-
void logMcpActivity("write", "context-link", { scope: scope || "public" });
|
|
1179
|
-
return { content: [{ type: "text", text: `context link created: ${link}\nscope: ${scope || "public"}, ttl: ${ttl || "24h"}` }] };
|
|
1180
|
-
}
|
|
1181
|
-
catch (err) {
|
|
1182
|
-
return { content: [{ type: "text", text: `failed to create context link: ${err instanceof Error ? err.message : "unknown error"}` }], isError: true };
|
|
1183
|
-
}
|
|
1184
|
-
}
|
|
1185
|
-
case "list_projects": {
|
|
1186
|
-
try {
|
|
1187
|
-
const root = (0, project_1.findProjectsRoot)();
|
|
1188
|
-
if (!root) {
|
|
1189
|
-
return { content: [{ type: "text", text: "no projects directory found" }], isError: true };
|
|
1190
|
-
}
|
|
1191
|
-
const projects = (0, project_1.listProjects)(root);
|
|
1192
|
-
if (projects.length === 0) {
|
|
1193
|
-
return { content: [{ type: "text", text: "no projects detected. create one with: youmd project init <name>" }] };
|
|
1194
|
-
}
|
|
1195
|
-
const current = getCurrentProject();
|
|
1196
|
-
const list = projects.map((p) => {
|
|
1197
|
-
const marker = current && current.name === p ? " (current)" : "";
|
|
1198
|
-
return `- ${p}${marker}`;
|
|
1199
|
-
}).join("\n");
|
|
1200
|
-
void logMcpActivity("read", "projects");
|
|
1201
|
-
return { content: [{ type: "text", text: `projects:\n${list}` }] };
|
|
1202
|
-
}
|
|
1203
|
-
catch (err) {
|
|
1204
|
-
return { content: [{ type: "text", text: `error listing projects: ${err instanceof Error ? err.message : "unknown"}` }], isError: true };
|
|
1205
|
-
}
|
|
1206
|
-
}
|
|
1207
|
-
case "get_remote_status": {
|
|
1208
|
-
const authenticated = (0, config_1.isAuthenticated)();
|
|
1209
|
-
const bundleExists = (0, config_1.localBundleExists)();
|
|
1210
|
-
const youJson = getYouJson();
|
|
1211
|
-
const version = youJson?.version || "unknown";
|
|
1212
|
-
const status = {
|
|
1213
|
-
authenticated,
|
|
1214
|
-
localBundleExists: bundleExists,
|
|
1215
|
-
localVersion: version,
|
|
1216
|
-
};
|
|
1217
|
-
if (authenticated) {
|
|
1218
|
-
try {
|
|
1219
|
-
const remote = await apiRequest("/api/v1/me/status");
|
|
1220
|
-
status.remote = remote;
|
|
1221
|
-
}
|
|
1222
|
-
catch {
|
|
1223
|
-
status.remote = "unreachable";
|
|
1224
|
-
}
|
|
1225
|
-
}
|
|
1226
|
-
void logMcpActivity("read", "status");
|
|
1227
|
-
return {
|
|
1228
|
-
content: [{
|
|
1229
|
-
type: "text",
|
|
1230
|
-
text: JSON.stringify(status, null, 2),
|
|
1231
|
-
}],
|
|
1232
|
-
};
|
|
1233
|
-
}
|
|
1234
|
-
case "get_activity_log": {
|
|
1235
|
-
if (!(0, config_1.isAuthenticated)()) {
|
|
1236
|
-
return { content: [{ type: "text", text: "not authenticated — run youmd login first" }], isError: true };
|
|
1237
|
-
}
|
|
1238
|
-
const config = (0, config_1.readGlobalConfig)();
|
|
1239
|
-
const params = new URLSearchParams();
|
|
1240
|
-
const activityArgs = (args || {});
|
|
1241
|
-
if (activityArgs.limit)
|
|
1242
|
-
params.set("limit", String(activityArgs.limit));
|
|
1243
|
-
if (activityArgs.agentName)
|
|
1244
|
-
params.set("agent", String(activityArgs.agentName));
|
|
1245
|
-
if (activityArgs.action)
|
|
1246
|
-
params.set("action", String(activityArgs.action));
|
|
1247
|
-
const res = await fetch(`${(0, config_1.getConvexSiteUrl)()}/api/v1/me/activity?${params}`, {
|
|
1248
|
-
headers: { Authorization: `Bearer ${config.token}` },
|
|
1249
|
-
signal: AbortSignal.timeout(10000),
|
|
1250
|
-
});
|
|
1251
|
-
if (!res.ok) {
|
|
1252
|
-
return { content: [{ type: "text", text: `failed to fetch activity log: ${res.status}` }], isError: true };
|
|
1253
|
-
}
|
|
1254
|
-
const data = await res.json();
|
|
1255
|
-
const events = data.activity || [];
|
|
1256
|
-
if (events.length === 0) {
|
|
1257
|
-
return { content: [{ type: "text", text: "No activity yet. Agents will appear here when they connect to your you.md identity." }] };
|
|
1258
|
-
}
|
|
1259
|
-
const formatted = events.slice(0, 30).reverse().map((e) => {
|
|
1260
|
-
const time = new Date(e.createdAt).toTimeString().slice(0, 5);
|
|
1261
|
-
const versions = e.bundleVersionBefore && e.bundleVersionAfter
|
|
1262
|
-
? ` v${e.bundleVersionBefore}→v${e.bundleVersionAfter}`
|
|
1263
|
-
: '';
|
|
1264
|
-
return `${time} ${e.agentName.padEnd(16)} ${e.action.padEnd(12)} ${e.resource || ''}${versions}`;
|
|
1265
|
-
}).join('\n');
|
|
1266
|
-
return {
|
|
1267
|
-
content: [{
|
|
1268
|
-
type: "text",
|
|
1269
|
-
text: `── recent agent activity (${events.length} events) ──\n\n${formatted}`,
|
|
1270
|
-
}],
|
|
1271
|
-
};
|
|
1272
|
-
}
|
|
1625
|
+
// add_source, create_context_link, get_remote_status, get_activity_log — dispatched via registry (T14).
|
|
1273
1626
|
default:
|
|
1274
1627
|
throw new Error(`unknown tool: ${name}`);
|
|
1275
1628
|
}
|