youmd 0.6.19 → 0.6.26
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 +604 -199
- package/dist/commands/chat.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 +257 -128
- 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/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 +123 -52
- 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 +670 -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 +128 -7
- package/dist/index.js.map +1 -1
- package/dist/lib/api.d.ts +94 -5
- package/dist/lib/api.d.ts.map +1 -1
- package/dist/lib/api.js +122 -5
- package/dist/lib/api.js.map +1 -1
- package/dist/lib/ascii.d.ts +5 -0
- package/dist/lib/ascii.d.ts.map +1 -1
- package/dist/lib/ascii.js +21 -30
- 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/first-run.d.ts +14 -0
- package/dist/lib/first-run.d.ts.map +1 -0
- package/dist/lib/first-run.js +97 -0
- package/dist/lib/first-run.js.map +1 -0
- 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/onboarding.d.ts +17 -0
- package/dist/lib/onboarding.d.ts.map +1 -1
- package/dist/lib/onboarding.js +225 -208
- 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 +41 -5
- 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/__tests__/api.test.d.ts +0 -2
- package/dist/__tests__/api.test.d.ts.map +0 -1
- package/dist/__tests__/api.test.js +0 -87
- package/dist/__tests__/api.test.js.map +0 -1
- package/dist/__tests__/compiler.test.d.ts +0 -2
- package/dist/__tests__/compiler.test.d.ts.map +0 -1
- package/dist/__tests__/compiler.test.js +0 -127
- package/dist/__tests__/compiler.test.js.map +0 -1
- package/dist/__tests__/config.test.d.ts +0 -2
- package/dist/__tests__/config.test.d.ts.map +0 -1
- package/dist/__tests__/config.test.js +0 -79
- package/dist/__tests__/config.test.js.map +0 -1
- package/dist/__tests__/decompile.test.d.ts +0 -2
- package/dist/__tests__/decompile.test.d.ts.map +0 -1
- package/dist/__tests__/decompile.test.js +0 -102
- package/dist/__tests__/decompile.test.js.map +0 -1
- package/dist/__tests__/hash.test.d.ts +0 -2
- package/dist/__tests__/hash.test.d.ts.map +0 -1
- package/dist/__tests__/hash.test.js +0 -44
- package/dist/__tests__/hash.test.js.map +0 -1
- package/dist/__tests__/integration.test.d.ts +0 -2
- package/dist/__tests__/integration.test.d.ts.map +0 -1
- package/dist/__tests__/integration.test.js +0 -284
- package/dist/__tests__/integration.test.js.map +0 -1
- package/dist/__tests__/skill-renderer.test.d.ts +0 -2
- package/dist/__tests__/skill-renderer.test.d.ts.map +0 -1
- package/dist/__tests__/skill-renderer.test.js +0 -68
- package/dist/__tests__/skill-renderer.test.js.map +0 -1
package/dist/commands/chat.js
CHANGED
|
@@ -44,10 +44,12 @@ const path = __importStar(require("path"));
|
|
|
44
44
|
const chalk_1 = __importDefault(require("chalk"));
|
|
45
45
|
const config_1 = require("../lib/config");
|
|
46
46
|
const project_1 = require("../lib/project");
|
|
47
|
+
const projectContext_1 = require("../lib/projectContext");
|
|
47
48
|
const compiler_1 = require("../lib/compiler");
|
|
48
49
|
const api_1 = require("../lib/api");
|
|
49
50
|
const skills_1 = require("../lib/skills");
|
|
50
51
|
const render_1 = require("../lib/render");
|
|
52
|
+
const stream_1 = require("../lib/stream");
|
|
51
53
|
const onboarding_1 = require("../lib/onboarding");
|
|
52
54
|
const ascii_1 = require("../lib/ascii");
|
|
53
55
|
const update_1 = require("../lib/update");
|
|
@@ -55,122 +57,25 @@ const update_1 = require("../lib/update");
|
|
|
55
57
|
const config_2 = require("../lib/config");
|
|
56
58
|
const CONVEX_SITE_URL = (0, config_2.getConvexSiteUrl)();
|
|
57
59
|
const STREAM_URL = `${CONVEX_SITE_URL}/api/v1/chat/stream`;
|
|
58
|
-
const CURRENT_VERSION = "0.6.
|
|
59
|
-
function delay(ms) {
|
|
60
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
61
|
-
}
|
|
60
|
+
const CURRENT_VERSION = "0.6.23";
|
|
62
61
|
// ─── Streaming LLM client ─────────────────────────────────────────────
|
|
63
|
-
async function streamLLM(_apiKey, messages, onToken) {
|
|
64
|
-
const res = await fetch(STREAM_URL, {
|
|
65
|
-
method: "POST",
|
|
66
|
-
headers: { "Content-Type": "application/json" },
|
|
67
|
-
body: JSON.stringify({ messages }),
|
|
68
|
-
signal: AbortSignal.timeout(120000),
|
|
69
|
-
});
|
|
70
|
-
if (!res.ok) {
|
|
71
|
-
const body = await res.text();
|
|
72
|
-
throw new Error(`Stream error (${res.status}): ${body}`);
|
|
73
|
-
}
|
|
74
|
-
if (!res.body) {
|
|
75
|
-
throw new Error("No response body from stream endpoint");
|
|
76
|
-
}
|
|
77
|
-
const reader = res.body.getReader();
|
|
78
|
-
const decoder = new TextDecoder();
|
|
79
|
-
let fullText = "";
|
|
80
|
-
let buffer = "";
|
|
81
|
-
while (true) {
|
|
82
|
-
const { done, value } = await reader.read();
|
|
83
|
-
if (done)
|
|
84
|
-
break;
|
|
85
|
-
buffer += decoder.decode(value, { stream: true });
|
|
86
|
-
// Process complete SSE lines
|
|
87
|
-
const lines = buffer.split("\n");
|
|
88
|
-
// Keep the last potentially incomplete line in the buffer
|
|
89
|
-
buffer = lines.pop() || "";
|
|
90
|
-
for (const line of lines) {
|
|
91
|
-
const trimmed = line.trim();
|
|
92
|
-
if (!trimmed)
|
|
93
|
-
continue;
|
|
94
|
-
if (trimmed.startsWith("data: ")) {
|
|
95
|
-
const data = trimmed.slice(6);
|
|
96
|
-
if (data === "[DONE]") {
|
|
97
|
-
continue;
|
|
98
|
-
}
|
|
99
|
-
try {
|
|
100
|
-
const parsed = JSON.parse(data);
|
|
101
|
-
if (parsed.text) {
|
|
102
|
-
fullText += parsed.text;
|
|
103
|
-
onToken(parsed.text);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
catch {
|
|
107
|
-
// Skip malformed JSON chunks
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
// Process any remaining buffer
|
|
113
|
-
if (buffer.trim()) {
|
|
114
|
-
const trimmed = buffer.trim();
|
|
115
|
-
if (trimmed.startsWith("data: ")) {
|
|
116
|
-
const data = trimmed.slice(6);
|
|
117
|
-
if (data !== "[DONE]") {
|
|
118
|
-
try {
|
|
119
|
-
const parsed = JSON.parse(data);
|
|
120
|
-
if (parsed.text) {
|
|
121
|
-
fullText += parsed.text;
|
|
122
|
-
onToken(parsed.text);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
catch {
|
|
126
|
-
// Skip
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
return fullText;
|
|
132
|
-
}
|
|
133
62
|
/**
|
|
134
|
-
* Call LLM with streaming
|
|
135
|
-
*
|
|
63
|
+
* Call LLM with streaming (shared helper: spinner until first token, then
|
|
64
|
+
* live tokens with ```json directive blocks filtered from the display),
|
|
65
|
+
* falling back to blocking callLLM on failure.
|
|
66
|
+
*
|
|
67
|
+
* Returns the full RAW response text — directive blocks intact — so the
|
|
68
|
+
* post-stream parsers (parseUpdatesFromResponse, parseMemorySaves,
|
|
69
|
+
* parsePrivateUpdates, parseProjectUpdates) keep working unchanged.
|
|
136
70
|
*/
|
|
137
71
|
async function callLLMWithStreaming(apiKey, messages, spinnerLabel) {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
thinkSpinner.stop();
|
|
146
|
-
process.stdout.write(" ");
|
|
147
|
-
firstToken = false;
|
|
148
|
-
}
|
|
149
|
-
process.stdout.write(token);
|
|
150
|
-
});
|
|
151
|
-
if (!firstToken) {
|
|
152
|
-
// We streamed something -- add trailing newline
|
|
153
|
-
process.stdout.write("\n");
|
|
154
|
-
}
|
|
155
|
-
else {
|
|
156
|
-
// No tokens received -- clear spinner
|
|
157
|
-
thinkSpinner.stop();
|
|
158
|
-
}
|
|
159
|
-
return { text: response, streamed: !firstToken };
|
|
160
|
-
}
|
|
161
|
-
catch {
|
|
162
|
-
// Streaming failed -- fall back to blocking call
|
|
163
|
-
thinkSpinner.update("streaming unavailable, waiting for response");
|
|
164
|
-
try {
|
|
165
|
-
const response = await (0, onboarding_1.callLLM)(apiKey, messages);
|
|
166
|
-
thinkSpinner.stop();
|
|
167
|
-
return { text: response, streamed: false };
|
|
168
|
-
}
|
|
169
|
-
catch (err) {
|
|
170
|
-
thinkSpinner.fail(err instanceof Error ? err.message : "failed");
|
|
171
|
-
throw err;
|
|
172
|
-
}
|
|
173
|
-
}
|
|
72
|
+
return (0, stream_1.streamAssistantTurn)({
|
|
73
|
+
streamUrl: STREAM_URL,
|
|
74
|
+
messages,
|
|
75
|
+
spinnerLabel,
|
|
76
|
+
timeoutMs: 120000,
|
|
77
|
+
fallback: () => (0, onboarding_1.callLLM)(apiKey, messages),
|
|
78
|
+
});
|
|
174
79
|
}
|
|
175
80
|
function detectSourcesInMessage(text) {
|
|
176
81
|
const sources = [];
|
|
@@ -210,7 +115,7 @@ function detectSourcesInMessage(text) {
|
|
|
210
115
|
}
|
|
211
116
|
const bareDomainRegex = /(?<![/\w])([a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\.(?:com|co|io|ai|dev|org|net|app|xyz|me)(?:\/[^\s<>"']*)?)/gi;
|
|
212
117
|
while ((match = bareDomainRegex.exec(text)) !== null) {
|
|
213
|
-
|
|
118
|
+
const domain = match[1].replace(/[.,;:)\]]+$/, "");
|
|
214
119
|
if (domain.includes("x.com") || domain.includes("github.com") || domain.includes("linkedin.com"))
|
|
215
120
|
continue;
|
|
216
121
|
const url = `https://${domain}`;
|
|
@@ -541,7 +446,7 @@ function loadCurrentBundle(bundleDir) {
|
|
|
541
446
|
{ dir: "profile", label: "Profile" },
|
|
542
447
|
{ dir: "preferences", label: "Preferences" },
|
|
543
448
|
];
|
|
544
|
-
for (const { dir
|
|
449
|
+
for (const { dir } of dirs) {
|
|
545
450
|
const dirPath = path.join(bundleDir, dir);
|
|
546
451
|
if (!fs.existsSync(dirPath))
|
|
547
452
|
continue;
|
|
@@ -605,8 +510,8 @@ function showStatus(bundleDir) {
|
|
|
605
510
|
console.log(chalk_1.default.dim(" authenticated: " + ((0, config_1.isAuthenticated)() ? "yes" : "no")));
|
|
606
511
|
console.log("");
|
|
607
512
|
}
|
|
608
|
-
function showLinkInfo(
|
|
609
|
-
// Try to read username from the
|
|
513
|
+
function showLinkInfo() {
|
|
514
|
+
// Try to read username from the config
|
|
610
515
|
const config = (0, config_1.readGlobalConfig)();
|
|
611
516
|
const username = config.username || "your-username";
|
|
612
517
|
console.log("");
|
|
@@ -881,9 +786,6 @@ function readDisplayName(bundleDir) {
|
|
|
881
786
|
}
|
|
882
787
|
return (0, config_1.readGlobalConfig)().username || "friend";
|
|
883
788
|
}
|
|
884
|
-
function getRecentProjectNames(limit = 3) {
|
|
885
|
-
return (0, project_1.getRecentProjectInsights)(process.cwd(), limit).map((item) => item.name);
|
|
886
|
-
}
|
|
887
789
|
async function printUpdateHint() {
|
|
888
790
|
const latest = await (0, update_1.checkForCliUpdate)(CURRENT_VERSION);
|
|
889
791
|
if (!latest)
|
|
@@ -969,6 +871,34 @@ function collectHomeAgentSignals() {
|
|
|
969
871
|
}
|
|
970
872
|
return findings;
|
|
971
873
|
}
|
|
874
|
+
function scoreProjectActionItem(item) {
|
|
875
|
+
const lower = item.toLowerCase();
|
|
876
|
+
let score = 0;
|
|
877
|
+
if (lower.includes("trusted publishing") || (lower.includes("publish") && lower.includes("blocked")))
|
|
878
|
+
score += 80;
|
|
879
|
+
if (lower.includes("configure") || lower.includes("unblock") || lower.includes("blocker"))
|
|
880
|
+
score += 40;
|
|
881
|
+
if (lower.includes("publish") || lower.includes("npm"))
|
|
882
|
+
score += 25;
|
|
883
|
+
if (lower.includes("verify") || lower.includes("test") || lower.includes("qa"))
|
|
884
|
+
score += 15;
|
|
885
|
+
if (lower.includes("build") || lower.includes("proactive") || lower.includes("first-run"))
|
|
886
|
+
score += 10;
|
|
887
|
+
return score;
|
|
888
|
+
}
|
|
889
|
+
function rankProjectActionItems(items) {
|
|
890
|
+
return items
|
|
891
|
+
.map((item, index) => ({ item, index, score: scoreProjectActionItem(item) }))
|
|
892
|
+
.sort((a, b) => b.score - a.score || a.index - b.index)
|
|
893
|
+
.map((entry) => entry.item);
|
|
894
|
+
}
|
|
895
|
+
function shortenActionItem(item, maxChars = 240) {
|
|
896
|
+
if (item.length <= maxChars)
|
|
897
|
+
return item;
|
|
898
|
+
const boundary = item.lastIndexOf(" ", maxChars - 1);
|
|
899
|
+
const end = boundary > 90 ? boundary : maxChars - 1;
|
|
900
|
+
return `${item.slice(0, end).trim()}...`;
|
|
901
|
+
}
|
|
972
902
|
async function runYouLaunchInvestigation(bundleDir, projectCtx, recentProjects) {
|
|
973
903
|
const labels = [
|
|
974
904
|
"sipping bitbucks frappaccino while i look around",
|
|
@@ -988,7 +918,6 @@ async function runYouLaunchInvestigation(bundleDir, projectCtx, recentProjects)
|
|
|
988
918
|
}, 1100);
|
|
989
919
|
spinner.start();
|
|
990
920
|
try {
|
|
991
|
-
await delay(600);
|
|
992
921
|
try {
|
|
993
922
|
const hasPreferences = fs.existsSync(path.join(bundleDir, "preferences", "agent.md"));
|
|
994
923
|
const hasDirectives = fs.existsSync(path.join(bundleDir, "directives", "agent.md"));
|
|
@@ -1002,8 +931,6 @@ async function runYouLaunchInvestigation(bundleDir, projectCtx, recentProjects)
|
|
|
1002
931
|
catch {
|
|
1003
932
|
// keep scanning other surfaces
|
|
1004
933
|
}
|
|
1005
|
-
const homeSignals = collectHomeAgentSignals();
|
|
1006
|
-
findings.push(...homeSignals.slice(0, 3));
|
|
1007
934
|
if (projectCtx) {
|
|
1008
935
|
try {
|
|
1009
936
|
const hasAgents = fs.existsSync(path.join(projectCtx.root, "AGENTS.md"));
|
|
@@ -1025,18 +952,30 @@ async function runYouLaunchInvestigation(bundleDir, projectCtx, recentProjects)
|
|
|
1025
952
|
else {
|
|
1026
953
|
findings.push(`${projectCtx.name} still wants a real project-context spine.`);
|
|
1027
954
|
}
|
|
955
|
+
const actionItems = extractProjectActionItems(projectCtx.root);
|
|
956
|
+
strongestProject = {
|
|
957
|
+
name: projectCtx.name,
|
|
958
|
+
slug: projectCtx.name,
|
|
959
|
+
projectDir: projectCtx.root,
|
|
960
|
+
updatedAt: Date.now(),
|
|
961
|
+
signals: (0, project_1.getProjectMarkerSignals)(projectCtx.root),
|
|
962
|
+
summary: `${projectCtx.name} is the current working repo.`,
|
|
963
|
+
suggestedCommand: `cd ${projectCtx.root} && you`,
|
|
964
|
+
};
|
|
1028
965
|
if (repoNeedsBootstrap(projectCtx.root)) {
|
|
1029
966
|
strongestMove = `${projectCtx.name} still wants cleaner agent wiring and project-context scaffolding.`;
|
|
1030
967
|
strongestCommand = "youmd skill init-project";
|
|
1031
|
-
strongestProject =
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
968
|
+
strongestProject.signals = ["still wants cleaner agent wiring and project-context scaffolding"];
|
|
969
|
+
strongestProject.summary = `${projectCtx.name} still wants cleaner agent wiring and project-context scaffolding.`;
|
|
970
|
+
strongestProject.suggestedCommand = "youmd skill init-project";
|
|
971
|
+
}
|
|
972
|
+
else if (actionItems.length > 0) {
|
|
973
|
+
strongestMove = actionItems[0];
|
|
974
|
+
strongestProject.signals = actionItems.slice(0, 3);
|
|
975
|
+
strongestProject.summary = actionItems[0];
|
|
976
|
+
}
|
|
977
|
+
else {
|
|
978
|
+
strongestMove = `read ${projectCtx.name}'s project-context and pick the next concrete release-quality fix from actual repo state.`;
|
|
1040
979
|
}
|
|
1041
980
|
}
|
|
1042
981
|
catch {
|
|
@@ -1064,7 +1003,8 @@ async function runYouLaunchInvestigation(bundleDir, projectCtx, recentProjects)
|
|
|
1064
1003
|
else {
|
|
1065
1004
|
findings.push("i've got your home bundle loaded, even though we're not inside a project yet.");
|
|
1066
1005
|
}
|
|
1067
|
-
|
|
1006
|
+
const homeSignals = collectHomeAgentSignals();
|
|
1007
|
+
findings.push(...homeSignals.slice(0, 2));
|
|
1068
1008
|
spinner.stop("looked through local context");
|
|
1069
1009
|
return {
|
|
1070
1010
|
findings: findings.slice(0, 3),
|
|
@@ -1155,6 +1095,9 @@ function buildYouLaunchIntro(projectCtx, bundleDir, investigation) {
|
|
|
1155
1095
|
|| (projectCtx && repoNeedsBootstrap(projectCtx.root)
|
|
1156
1096
|
? `${projectCtx.name} still wants cleaner agent wiring and project-context scaffolding.`
|
|
1157
1097
|
: null)
|
|
1098
|
+
|| (projectCtx
|
|
1099
|
+
? `read ${projectCtx.name}'s project-context and pick the next concrete release-quality fix from actual repo state.`
|
|
1100
|
+
: null)
|
|
1158
1101
|
|| (0, project_1.getTopProjectOpportunity)(recentInsights)?.summary
|
|
1159
1102
|
|| null;
|
|
1160
1103
|
if (strongestMove) {
|
|
@@ -1181,6 +1124,19 @@ function isStartThereIntent(input) {
|
|
|
1181
1124
|
"start",
|
|
1182
1125
|
].includes(lower);
|
|
1183
1126
|
}
|
|
1127
|
+
function isContinueNextMoveIntent(input) {
|
|
1128
|
+
const lower = input.toLowerCase().trim().replace(/[.!?]+$/, "");
|
|
1129
|
+
return [
|
|
1130
|
+
"continue",
|
|
1131
|
+
"keep going",
|
|
1132
|
+
"more",
|
|
1133
|
+
"next",
|
|
1134
|
+
"what next",
|
|
1135
|
+
"what should i do next",
|
|
1136
|
+
"next strongest move",
|
|
1137
|
+
"do the next strongest move",
|
|
1138
|
+
].includes(lower);
|
|
1139
|
+
}
|
|
1184
1140
|
function isLocalRecentProjectsIntent(input) {
|
|
1185
1141
|
const lower = input.toLowerCase();
|
|
1186
1142
|
const mentionsLocalWork = lower.includes("local director") ||
|
|
@@ -1330,8 +1286,441 @@ function formatProjectBootstrapToolResult(project, result) {
|
|
|
1330
1286
|
`recommended_next_move: read ${project.name}'s project-context and turn the rough docs into a sharper current-state + TODO pass.`,
|
|
1331
1287
|
].join("\n");
|
|
1332
1288
|
}
|
|
1289
|
+
function isLocalToolLoopCandidate(input) {
|
|
1290
|
+
if (isStartThereIntent(input) || isContinueNextMoveIntent(input) || isLocalRecentProjectsIntent(input))
|
|
1291
|
+
return true;
|
|
1292
|
+
const lower = input.toLowerCase();
|
|
1293
|
+
return [
|
|
1294
|
+
"local",
|
|
1295
|
+
"workspace",
|
|
1296
|
+
"workspaces",
|
|
1297
|
+
"filesystem",
|
|
1298
|
+
"file system",
|
|
1299
|
+
"recent projects",
|
|
1300
|
+
"recent work",
|
|
1301
|
+
"working on lately",
|
|
1302
|
+
"project-context",
|
|
1303
|
+
"project context",
|
|
1304
|
+
"agent entrypoint",
|
|
1305
|
+
"agent instructions",
|
|
1306
|
+
"scaffold",
|
|
1307
|
+
"bootstrap",
|
|
1308
|
+
"start there",
|
|
1309
|
+
"sync identity",
|
|
1310
|
+
"sync my identity",
|
|
1311
|
+
"publish identity",
|
|
1312
|
+
"publish my identity",
|
|
1313
|
+
"push my identity",
|
|
1314
|
+
"what next",
|
|
1315
|
+
"next strongest move",
|
|
1316
|
+
].some((phrase) => lower.includes(phrase));
|
|
1317
|
+
}
|
|
1318
|
+
function userAskedForMutation(input) {
|
|
1319
|
+
const lower = input.toLowerCase();
|
|
1320
|
+
return [
|
|
1321
|
+
"start there",
|
|
1322
|
+
"do it",
|
|
1323
|
+
"go ahead",
|
|
1324
|
+
"scaffold",
|
|
1325
|
+
"bootstrap",
|
|
1326
|
+
"create",
|
|
1327
|
+
"write",
|
|
1328
|
+
"update",
|
|
1329
|
+
"fix",
|
|
1330
|
+
"tighten",
|
|
1331
|
+
"sync",
|
|
1332
|
+
"publish",
|
|
1333
|
+
"push",
|
|
1334
|
+
"upload",
|
|
1335
|
+
].some((phrase) => lower.includes(phrase));
|
|
1336
|
+
}
|
|
1337
|
+
function safeJsonObject(text) {
|
|
1338
|
+
const trimmed = text.trim();
|
|
1339
|
+
const fenced = trimmed.match(/```(?:json)?\s*([\s\S]*?)```/i);
|
|
1340
|
+
const candidate = fenced?.[1]?.trim() || trimmed.match(/\{[\s\S]*\}/)?.[0];
|
|
1341
|
+
if (!candidate)
|
|
1342
|
+
return null;
|
|
1343
|
+
try {
|
|
1344
|
+
const parsed = JSON.parse(candidate);
|
|
1345
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
|
1346
|
+
? parsed
|
|
1347
|
+
: null;
|
|
1348
|
+
}
|
|
1349
|
+
catch {
|
|
1350
|
+
return null;
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
function normalizeLocalHostToolCall(value) {
|
|
1354
|
+
if (!value)
|
|
1355
|
+
return null;
|
|
1356
|
+
const tool = value.tool;
|
|
1357
|
+
if (tool !== "discover_projects" &&
|
|
1358
|
+
tool !== "read_project_context" &&
|
|
1359
|
+
tool !== "write_project_context" &&
|
|
1360
|
+
tool !== "sync_identity" &&
|
|
1361
|
+
tool !== "respond") {
|
|
1362
|
+
return null;
|
|
1363
|
+
}
|
|
1364
|
+
const mode = value.mode === "publish" || value.mode === "bootstrap" || value.mode === "status"
|
|
1365
|
+
? value.mode
|
|
1366
|
+
: undefined;
|
|
1367
|
+
return {
|
|
1368
|
+
tool,
|
|
1369
|
+
project: typeof value.project === "string" ? value.project : undefined,
|
|
1370
|
+
mode,
|
|
1371
|
+
reason: typeof value.reason === "string" ? value.reason : undefined,
|
|
1372
|
+
};
|
|
1373
|
+
}
|
|
1374
|
+
function inferLocalHostToolCall(input, launchInvestigation) {
|
|
1375
|
+
const lower = input.toLowerCase();
|
|
1376
|
+
if (isStartThereIntent(input) || isContinueNextMoveIntent(input)) {
|
|
1377
|
+
const project = resolveProjectForTool(launchInvestigation.strongestProject?.name, launchInvestigation);
|
|
1378
|
+
if (project && repoNeedsBootstrap(project.projectDir)) {
|
|
1379
|
+
return { tool: "write_project_context", project: project.name, mode: "bootstrap" };
|
|
1380
|
+
}
|
|
1381
|
+
return { tool: "read_project_context", project: project?.name || launchInvestigation.strongestProject?.name };
|
|
1382
|
+
}
|
|
1383
|
+
if (lower.includes("sync") || lower.includes("publish") || lower.includes("push my identity")) {
|
|
1384
|
+
return { tool: "sync_identity", mode: lower.includes("publish") || lower.includes("push") || lower.includes("sync") ? "publish" : "status" };
|
|
1385
|
+
}
|
|
1386
|
+
if (lower.includes("read") || lower.includes("show") || lower.includes("inspect") || lower.includes("context")) {
|
|
1387
|
+
return { tool: "read_project_context", project: launchInvestigation.strongestProject?.name };
|
|
1388
|
+
}
|
|
1389
|
+
if (lower.includes("scaffold") || lower.includes("bootstrap") || lower.includes("create") || lower.includes("write") || lower.includes("update")) {
|
|
1390
|
+
return { tool: "write_project_context", project: launchInvestigation.strongestProject?.name, mode: "bootstrap" };
|
|
1391
|
+
}
|
|
1392
|
+
return { tool: "discover_projects" };
|
|
1393
|
+
}
|
|
1394
|
+
function collectKnownProjects(launchInvestigation) {
|
|
1395
|
+
const projects = [
|
|
1396
|
+
launchInvestigation.strongestProject,
|
|
1397
|
+
...scanRecentWorkspaceProjects(12),
|
|
1398
|
+
...(0, project_1.getRecentProjectInsights)(process.cwd(), 12),
|
|
1399
|
+
].filter((item) => !!item);
|
|
1400
|
+
const seen = new Set();
|
|
1401
|
+
return projects.filter((project) => {
|
|
1402
|
+
const key = fs.existsSync(project.projectDir)
|
|
1403
|
+
? fs.realpathSync.native(project.projectDir)
|
|
1404
|
+
: path.resolve(project.projectDir);
|
|
1405
|
+
if (seen.has(key))
|
|
1406
|
+
return false;
|
|
1407
|
+
seen.add(key);
|
|
1408
|
+
return true;
|
|
1409
|
+
});
|
|
1410
|
+
}
|
|
1411
|
+
function resolveProjectForTool(projectRef, launchInvestigation) {
|
|
1412
|
+
const projects = collectKnownProjects(launchInvestigation);
|
|
1413
|
+
if (!projectRef && launchInvestigation.strongestProject)
|
|
1414
|
+
return launchInvestigation.strongestProject;
|
|
1415
|
+
if (!projectRef)
|
|
1416
|
+
return projects[0] || null;
|
|
1417
|
+
const normalized = projectRef.toLowerCase().trim();
|
|
1418
|
+
const byPath = projects.find((project) => path.resolve(project.projectDir) === path.resolve(projectRef));
|
|
1419
|
+
if (byPath)
|
|
1420
|
+
return byPath;
|
|
1421
|
+
return projects.find((project) => project.name.toLowerCase() === normalized ||
|
|
1422
|
+
project.slug.toLowerCase() === normalized ||
|
|
1423
|
+
project.projectDir.toLowerCase().includes(normalized)) || null;
|
|
1424
|
+
}
|
|
1425
|
+
function readSnippet(filePath, maxChars = 1800) {
|
|
1426
|
+
if (!fs.existsSync(filePath))
|
|
1427
|
+
return null;
|
|
1428
|
+
try {
|
|
1429
|
+
const content = fs.readFileSync(filePath, "utf-8").trim();
|
|
1430
|
+
if (!content)
|
|
1431
|
+
return null;
|
|
1432
|
+
return content.length > maxChars ? `${content.slice(0, maxChars)}\n...` : content;
|
|
1433
|
+
}
|
|
1434
|
+
catch {
|
|
1435
|
+
return null;
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
function extractProjectActionItems(projectDir, maxItems = 5) {
|
|
1439
|
+
// Read paths come from the single project-context engine: repo
|
|
1440
|
+
// project-context/, the generated .you/ layer, then the global overlay.
|
|
1441
|
+
const files = (0, projectContext_1.projectContextReadPaths)(projectDir, [
|
|
1442
|
+
"TODO.md",
|
|
1443
|
+
"CURRENT_STATE.md",
|
|
1444
|
+
"feature-requests-active.md",
|
|
1445
|
+
]);
|
|
1446
|
+
const seen = new Set();
|
|
1447
|
+
const items = [];
|
|
1448
|
+
for (const { path: fullPath } of files) {
|
|
1449
|
+
const content = readSnippet(fullPath, 12000);
|
|
1450
|
+
if (!content)
|
|
1451
|
+
continue;
|
|
1452
|
+
for (const rawLine of content.split("\n")) {
|
|
1453
|
+
const line = rawLine.trim();
|
|
1454
|
+
if (!line)
|
|
1455
|
+
continue;
|
|
1456
|
+
const openTask = line.match(/^[-*]\s+\[[\s?~-]\]\s+(.+)$/);
|
|
1457
|
+
const statusTask = line.match(/^[-*]\s+(?:TODO|P0|P1|BLOCKED|OPEN|NEXT|IN PROGRESS|ACTIVE)[:\s-]+(.+)$/i);
|
|
1458
|
+
const headingTask = line.match(/^#{2,4}\s+(?:P0|P1|Next|Open|Active|Blocked|Known issue|Current focus)[:\s-]+(.+)$/i);
|
|
1459
|
+
const candidate = (openTask?.[1] || statusTask?.[1] || headingTask?.[1] || "")
|
|
1460
|
+
.replace(/\s+/g, " ")
|
|
1461
|
+
.replace(/\s+#.*$/, "")
|
|
1462
|
+
.trim();
|
|
1463
|
+
if (!candidate || candidate.length < 8)
|
|
1464
|
+
continue;
|
|
1465
|
+
const normalized = candidate.toLowerCase();
|
|
1466
|
+
if (seen.has(normalized))
|
|
1467
|
+
continue;
|
|
1468
|
+
seen.add(normalized);
|
|
1469
|
+
items.push(shortenActionItem(candidate));
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
return rankProjectActionItems(items).slice(0, maxItems);
|
|
1473
|
+
}
|
|
1474
|
+
function formatProjectReadToolResult(project) {
|
|
1475
|
+
const entrypointFiles = [
|
|
1476
|
+
"AGENTS.md",
|
|
1477
|
+
"CLAUDE.md",
|
|
1478
|
+
"package.json",
|
|
1479
|
+
"pyproject.toml",
|
|
1480
|
+
"Cargo.toml",
|
|
1481
|
+
"go.mod",
|
|
1482
|
+
].map((relativePath) => ({
|
|
1483
|
+
label: relativePath,
|
|
1484
|
+
path: path.join(project.projectDir, relativePath),
|
|
1485
|
+
}));
|
|
1486
|
+
// Context reads route through the single project-context engine (repo
|
|
1487
|
+
// project-context/, .you/ layer, then the global overlay union).
|
|
1488
|
+
const files = [
|
|
1489
|
+
...entrypointFiles,
|
|
1490
|
+
...(0, projectContext_1.projectContextReadPaths)(project.projectDir, [
|
|
1491
|
+
"CURRENT_STATE.md",
|
|
1492
|
+
"TODO.md",
|
|
1493
|
+
"PRD.md",
|
|
1494
|
+
"ARCHITECTURE.md",
|
|
1495
|
+
]),
|
|
1496
|
+
];
|
|
1497
|
+
const snippets = files
|
|
1498
|
+
.map(({ label, path: fullPath }) => {
|
|
1499
|
+
const snippet = readSnippet(fullPath);
|
|
1500
|
+
return snippet ? `file: ${label}\n${snippet}` : null;
|
|
1501
|
+
})
|
|
1502
|
+
.filter((item) => !!item)
|
|
1503
|
+
.slice(0, 5);
|
|
1504
|
+
const managedContext = (0, project_1.readProjectContext)(project.projectDir);
|
|
1505
|
+
const actionItems = extractProjectActionItems(project.projectDir);
|
|
1506
|
+
const recommendedMove = actionItems.length > 0
|
|
1507
|
+
? actionItems[0]
|
|
1508
|
+
: `tighten ${project.name}'s current-state and TODO docs from actual repo context.`;
|
|
1509
|
+
return [
|
|
1510
|
+
"tool: read_project_context",
|
|
1511
|
+
"status: ok",
|
|
1512
|
+
`project: ${project.name}`,
|
|
1513
|
+
`project_dir: ${project.projectDir}`,
|
|
1514
|
+
`markers: ${(0, project_1.getProjectMarkerSignals)(project.projectDir).join(", ") || "none"}`,
|
|
1515
|
+
managedContext ? `managed_project_context: ${managedContext.meta.name}` : "managed_project_context: none",
|
|
1516
|
+
actionItems.length > 0 ? `action_items: ${actionItems.join(" | ")}` : "action_items: none found in TODO/current-state files",
|
|
1517
|
+
"",
|
|
1518
|
+
snippets.length > 0 ? snippets.join("\n\n---\n\n") : "no readable project-context or agent entrypoint files found yet.",
|
|
1519
|
+
"",
|
|
1520
|
+
`recommended_next_move: ${recommendedMove}`,
|
|
1521
|
+
].join("\n");
|
|
1522
|
+
}
|
|
1523
|
+
async function formatIdentitySyncToolResult(bundleDir, publish) {
|
|
1524
|
+
const result = (0, compiler_1.compileBundle)(bundleDir);
|
|
1525
|
+
(0, compiler_1.writeBundle)(bundleDir, result);
|
|
1526
|
+
const lines = [
|
|
1527
|
+
"tool: sync_identity",
|
|
1528
|
+
"status: ok",
|
|
1529
|
+
`bundle_dir: ${bundleDir}`,
|
|
1530
|
+
`compiled_version: ${result.stats.version}`,
|
|
1531
|
+
`sections: ${result.stats.filledSections}/${result.stats.totalSections}`,
|
|
1532
|
+
];
|
|
1533
|
+
if (!publish) {
|
|
1534
|
+
lines.push("remote_sync: not requested; local bundle compiled only.");
|
|
1535
|
+
lines.push("recommended_next_move: publish if you want this local identity bundle pushed to you.md.");
|
|
1536
|
+
return lines.join("\n");
|
|
1537
|
+
}
|
|
1538
|
+
if (!(0, config_1.isAuthenticated)()) {
|
|
1539
|
+
return [
|
|
1540
|
+
...lines,
|
|
1541
|
+
"remote_sync: blocked; not authenticated.",
|
|
1542
|
+
"recommended_next_move: run youmd login, then ask me to sync identity again.",
|
|
1543
|
+
].join("\n");
|
|
1544
|
+
}
|
|
1545
|
+
const youJson = JSON.parse(fs.readFileSync(path.join(bundleDir, "you.json"), "utf-8"));
|
|
1546
|
+
const youMd = fs.readFileSync(path.join(bundleDir, "you.md"), "utf-8");
|
|
1547
|
+
const manifest = JSON.parse(fs.readFileSync(path.join(bundleDir, "manifest.json"), "utf-8"));
|
|
1548
|
+
const uploadRes = await (0, api_1.uploadBundle)({ manifest, youJson, youMd });
|
|
1549
|
+
if (!uploadRes.ok) {
|
|
1550
|
+
return [
|
|
1551
|
+
...lines,
|
|
1552
|
+
`remote_sync: upload failed with status ${uploadRes.status}.`,
|
|
1553
|
+
"recommended_next_move: inspect auth/API state before retrying identity sync.",
|
|
1554
|
+
].join("\n");
|
|
1555
|
+
}
|
|
1556
|
+
const publishRes = await (0, api_1.publishLatest)();
|
|
1557
|
+
if (!publishRes.ok) {
|
|
1558
|
+
return [
|
|
1559
|
+
...lines,
|
|
1560
|
+
`remote_sync: publish failed with status ${publishRes.status}.`,
|
|
1561
|
+
"recommended_next_move: inspect publish API state before retrying identity sync.",
|
|
1562
|
+
].join("\n");
|
|
1563
|
+
}
|
|
1564
|
+
lines.push(`remote_sync: published v${publishRes.data.version} as ${publishRes.data.username}.`);
|
|
1565
|
+
lines.push(`url: ${publishRes.data.url || `https://you.md/${publishRes.data.username}`}`);
|
|
1566
|
+
lines.push("recommended_next_move: test the updated identity from another agent surface.");
|
|
1567
|
+
return lines.join("\n");
|
|
1568
|
+
}
|
|
1569
|
+
async function chooseLocalHostTool(args) {
|
|
1570
|
+
if (isStartThereIntent(args.userInput) || isContinueNextMoveIntent(args.userInput)) {
|
|
1571
|
+
return inferLocalHostToolCall(args.userInput, args.launchInvestigation);
|
|
1572
|
+
}
|
|
1573
|
+
const projects = collectKnownProjects(args.launchInvestigation)
|
|
1574
|
+
.slice(0, 8)
|
|
1575
|
+
.map((project) => `${project.name} | ${project.projectDir} | ${project.signals.slice(0, 4).join(", ")}`)
|
|
1576
|
+
.join("\n");
|
|
1577
|
+
const prompt = [
|
|
1578
|
+
"You are the local host tool router for the you.md CLI. Return ONLY compact JSON. No prose.",
|
|
1579
|
+
"Available tools:",
|
|
1580
|
+
"- discover_projects: scan local filesystem markers for recent project work.",
|
|
1581
|
+
"- read_project_context: read AGENTS/CLAUDE/package/project-context files for a project.",
|
|
1582
|
+
"- write_project_context: bootstrap/tighten project context files for a project. Use only when the user asks to start, scaffold, update, write, fix, or tighten.",
|
|
1583
|
+
"- sync_identity: compile identity locally; use mode publish only when the user asks to sync/publish/push identity.",
|
|
1584
|
+
"- respond: no local tool needed.",
|
|
1585
|
+
"",
|
|
1586
|
+
"JSON shape: {\"tool\":\"discover_projects|read_project_context|write_project_context|sync_identity|respond\",\"project\":\"optional project name/path\",\"mode\":\"status|publish|bootstrap\",\"reason\":\"short\"}",
|
|
1587
|
+
"",
|
|
1588
|
+
"Known projects:",
|
|
1589
|
+
projects || "none yet",
|
|
1590
|
+
"",
|
|
1591
|
+
`User request: ${args.userInput}`,
|
|
1592
|
+
].join("\n");
|
|
1593
|
+
try {
|
|
1594
|
+
const raw = await (0, onboarding_1.callLLM)(args.apiKey, [
|
|
1595
|
+
{ role: "system", content: "Return only valid JSON for local tool routing." },
|
|
1596
|
+
{ role: "user", content: prompt },
|
|
1597
|
+
]);
|
|
1598
|
+
return normalizeLocalHostToolCall(safeJsonObject(raw)) || inferLocalHostToolCall(args.userInput, args.launchInvestigation);
|
|
1599
|
+
}
|
|
1600
|
+
catch {
|
|
1601
|
+
return inferLocalHostToolCall(args.userInput, args.launchInvestigation);
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
async function executeLocalHostTool(toolCall, args) {
|
|
1605
|
+
if (toolCall.tool === "discover_projects") {
|
|
1606
|
+
const insights = scanRecentWorkspaceProjects(10);
|
|
1607
|
+
args.launchInvestigation.strongestProject = insights[0] || args.launchInvestigation.strongestProject;
|
|
1608
|
+
args.launchInvestigation.strongestMove = insights[0]?.summary || args.launchInvestigation.strongestMove;
|
|
1609
|
+
return formatLocalRecentProjectsToolResult(insights);
|
|
1610
|
+
}
|
|
1611
|
+
if (toolCall.tool === "read_project_context") {
|
|
1612
|
+
const project = resolveProjectForTool(toolCall.project, args.launchInvestigation);
|
|
1613
|
+
if (!project) {
|
|
1614
|
+
return "tool: read_project_context\nstatus: blocked\nresult: no real local project target is known yet.\nrecommended_next_move: run discover_projects first.";
|
|
1615
|
+
}
|
|
1616
|
+
args.launchInvestigation.strongestProject = project;
|
|
1617
|
+
return formatProjectReadToolResult(project);
|
|
1618
|
+
}
|
|
1619
|
+
if (toolCall.tool === "write_project_context") {
|
|
1620
|
+
const project = resolveProjectForTool(toolCall.project, args.launchInvestigation);
|
|
1621
|
+
if (!project) {
|
|
1622
|
+
return "tool: write_project_context\nstatus: blocked\nresult: no real local project target is known yet.\nrecommended_next_move: run discover_projects first.";
|
|
1623
|
+
}
|
|
1624
|
+
if (!userAskedForMutation(args.userInput)) {
|
|
1625
|
+
return [
|
|
1626
|
+
"tool: write_project_context",
|
|
1627
|
+
"status: blocked",
|
|
1628
|
+
`project: ${project.name}`,
|
|
1629
|
+
"result: user did not explicitly ask for a filesystem mutation.",
|
|
1630
|
+
`recommended_next_move: say "start there" to bootstrap ${project.name}.`,
|
|
1631
|
+
].join("\n");
|
|
1632
|
+
}
|
|
1633
|
+
const previousCwd = process.cwd();
|
|
1634
|
+
let result;
|
|
1635
|
+
try {
|
|
1636
|
+
process.chdir(project.projectDir);
|
|
1637
|
+
result = (0, skills_1.initProject)({ mode: "additive" });
|
|
1638
|
+
}
|
|
1639
|
+
finally {
|
|
1640
|
+
process.chdir(previousCwd);
|
|
1641
|
+
}
|
|
1642
|
+
args.launchInvestigation.strongestProject = project;
|
|
1643
|
+
return formatProjectBootstrapToolResult(project, result);
|
|
1644
|
+
}
|
|
1645
|
+
if (toolCall.tool === "sync_identity") {
|
|
1646
|
+
const publish = toolCall.mode === "publish" && userAskedForMutation(args.userInput);
|
|
1647
|
+
return await formatIdentitySyncToolResult(args.bundleDir, publish);
|
|
1648
|
+
}
|
|
1649
|
+
return "tool: respond\nstatus: skipped\nresult: no local host tool needed.";
|
|
1650
|
+
}
|
|
1333
1651
|
async function handleLocalChatIntent(args) {
|
|
1334
|
-
const
|
|
1652
|
+
const summarizeLocalToolResult = (toolResult) => {
|
|
1653
|
+
const get = (key) => {
|
|
1654
|
+
const match = toolResult.match(new RegExp(`^${key}:\\s*(.+)$`, "m"));
|
|
1655
|
+
return match?.[1]?.trim() || null;
|
|
1656
|
+
};
|
|
1657
|
+
const tool = get("tool") || "local_tool";
|
|
1658
|
+
const status = get("status") || "done";
|
|
1659
|
+
const project = get("project");
|
|
1660
|
+
const projectDir = get("project_dir") || get("recommended_next_project_dir");
|
|
1661
|
+
const recommended = get("recommended_next_move");
|
|
1662
|
+
const markers = get("markers");
|
|
1663
|
+
const changed = get("changed");
|
|
1664
|
+
const filesRead = Array.from(toolResult.matchAll(/^file:\s*(.+)$/gm))
|
|
1665
|
+
.map((match) => match[1].trim())
|
|
1666
|
+
.slice(0, 5);
|
|
1667
|
+
const actionItems = get("action_items");
|
|
1668
|
+
const displayedActionItems = actionItems
|
|
1669
|
+
?.split(" | ")
|
|
1670
|
+
.slice(0, 3)
|
|
1671
|
+
.join(" | ");
|
|
1672
|
+
if (status === "blocked") {
|
|
1673
|
+
const result = get("result") || "local tool blocked";
|
|
1674
|
+
return [
|
|
1675
|
+
result,
|
|
1676
|
+
"",
|
|
1677
|
+
`next strongest move: ${recommended || "run a read-only local scan before attempting a write."}`,
|
|
1678
|
+
].join("\n");
|
|
1679
|
+
}
|
|
1680
|
+
if (tool === "read_project_context") {
|
|
1681
|
+
const lines = [
|
|
1682
|
+
project ? `read ${project}.` : "read the local project.",
|
|
1683
|
+
projectDir ? `path: ${projectDir}` : null,
|
|
1684
|
+
markers ? `markers: ${markers}` : null,
|
|
1685
|
+
filesRead.length > 0 ? `files read: ${filesRead.join(", ")}` : null,
|
|
1686
|
+
displayedActionItems && displayedActionItems !== "none found in TODO/current-state files" ? `found: ${displayedActionItems}` : null,
|
|
1687
|
+
"",
|
|
1688
|
+
`next strongest move: ${recommended || "tighten the current-state and TODO docs from the actual repo context."}`,
|
|
1689
|
+
].filter((line) => !!line);
|
|
1690
|
+
return lines.join("\n");
|
|
1691
|
+
}
|
|
1692
|
+
if (tool === "project_bootstrap") {
|
|
1693
|
+
return [
|
|
1694
|
+
project ? `bootstrapped ${project}.` : "bootstrapped the project context.",
|
|
1695
|
+
changed || "changed: checked existing wiring.",
|
|
1696
|
+
"",
|
|
1697
|
+
`next strongest move: ${recommended || "read the project-context files and sharpen the current-state pass."}`,
|
|
1698
|
+
].join("\n");
|
|
1699
|
+
}
|
|
1700
|
+
if (tool === "workspace_recent_projects") {
|
|
1701
|
+
return [
|
|
1702
|
+
"scanned local workspace projects.",
|
|
1703
|
+
project ? `strongest target: ${project}` : get("recommended_next_project") ? `strongest target: ${get("recommended_next_project")}` : null,
|
|
1704
|
+
projectDir ? `path: ${projectDir}` : null,
|
|
1705
|
+
"",
|
|
1706
|
+
`next strongest move: ${recommended || "say \"start there\" to read the strongest local project from actual files."}`,
|
|
1707
|
+
].filter((line) => !!line).join("\n");
|
|
1708
|
+
}
|
|
1709
|
+
if (tool === "sync_identity") {
|
|
1710
|
+
return [
|
|
1711
|
+
"synced local identity context.",
|
|
1712
|
+
get("remote_sync") || "remote_sync: local compile only.",
|
|
1713
|
+
"",
|
|
1714
|
+
`next strongest move: ${recommended || "test the updated identity from another agent surface."}`,
|
|
1715
|
+
].join("\n");
|
|
1716
|
+
}
|
|
1717
|
+
return [
|
|
1718
|
+
"local tool finished.",
|
|
1719
|
+
"",
|
|
1720
|
+
`next strongest move: ${recommended || "continue from the local result above."}`,
|
|
1721
|
+
].join("\n");
|
|
1722
|
+
};
|
|
1723
|
+
const recordLocalToolResult = (toolResult) => {
|
|
1335
1724
|
args.messages.push({ role: "user", content: args.userInput });
|
|
1336
1725
|
args.messages.push({
|
|
1337
1726
|
role: "user",
|
|
@@ -1345,56 +1734,57 @@ async function handleLocalChatIntent(args) {
|
|
|
1345
1734
|
"Do not ask the user what the local project is if the tool result already names it.",
|
|
1346
1735
|
"State what the local host actually found, make one concrete recommendation, and end with the exact phrase `next strongest move: ...`.",
|
|
1347
1736
|
"For workspace scans, reuse recommended_next_move as the final next strongest move. The supported follow-up command is `start there`; do not invent commands like `open PROJECT`.",
|
|
1737
|
+
"For read_project_context results, summarize what you actually saw and recommend the next concrete local tool action.",
|
|
1738
|
+
"For sync_identity results, state whether local compile or remote publish happened.",
|
|
1348
1739
|
"Do not end with a question. Keep it under 8 lines. No generic help-desk closer.",
|
|
1349
1740
|
].join("\n"),
|
|
1350
1741
|
});
|
|
1351
|
-
const
|
|
1352
|
-
args.messages.push({ role: "assistant", content:
|
|
1353
|
-
|
|
1354
|
-
printAgentMessage((0, onboarding_1.parseUpdatesFromResponse)(result.text).display);
|
|
1355
|
-
}
|
|
1742
|
+
const summary = summarizeLocalToolResult(toolResult);
|
|
1743
|
+
args.messages.push({ role: "assistant", content: summary });
|
|
1744
|
+
printAgentMessage(summary);
|
|
1356
1745
|
};
|
|
1357
|
-
if (
|
|
1358
|
-
const
|
|
1746
|
+
if (isLocalToolLoopCandidate(args.userInput)) {
|
|
1747
|
+
const routeSpinner = new render_1.BrailleSpinner("choosing local tool");
|
|
1748
|
+
routeSpinner.start();
|
|
1749
|
+
const toolCall = await chooseLocalHostTool(args);
|
|
1750
|
+
routeSpinner.stop(`selected ${toolCall.tool}`);
|
|
1751
|
+
if (toolCall.tool === "respond")
|
|
1752
|
+
return false;
|
|
1753
|
+
const labelByTool = {
|
|
1754
|
+
discover_projects: "discovering local projects",
|
|
1755
|
+
read_project_context: "reading project context",
|
|
1756
|
+
write_project_context: "writing project context",
|
|
1757
|
+
sync_identity: "syncing identity context",
|
|
1758
|
+
respond: "thinking",
|
|
1759
|
+
};
|
|
1760
|
+
const spinner = new render_1.BrailleSpinner(labelByTool[toolCall.tool]);
|
|
1359
1761
|
spinner.start();
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1762
|
+
try {
|
|
1763
|
+
const toolResult = await executeLocalHostTool(toolCall, args);
|
|
1764
|
+
const statusLine = toolResult.match(/^status: (.+)$/m)?.[1] || "done";
|
|
1765
|
+
spinner.stop(statusLine);
|
|
1766
|
+
recordLocalToolResult(toolResult);
|
|
1767
|
+
}
|
|
1768
|
+
catch (err) {
|
|
1769
|
+
const message = err instanceof Error ? err.message : "local tool failed";
|
|
1770
|
+
spinner.fail(message);
|
|
1771
|
+
const response = [
|
|
1772
|
+
`local tool failed: ${message}`,
|
|
1773
|
+
"",
|
|
1774
|
+
"next strongest move: retry with a read-only local scan so i can recover from real filesystem state.",
|
|
1775
|
+
].join("\n");
|
|
1373
1776
|
args.messages.push({ role: "user", content: args.userInput });
|
|
1374
1777
|
args.messages.push({ role: "assistant", content: response });
|
|
1375
1778
|
printAgentMessage(response);
|
|
1376
|
-
return true;
|
|
1377
|
-
}
|
|
1378
|
-
const spinner = new render_1.BrailleSpinner(`opening ${project.name} from local disk`);
|
|
1379
|
-
spinner.start();
|
|
1380
|
-
await delay(500);
|
|
1381
|
-
const previousCwd = process.cwd();
|
|
1382
|
-
let result;
|
|
1383
|
-
try {
|
|
1384
|
-
process.chdir(project.projectDir);
|
|
1385
|
-
result = (0, skills_1.initProject)({ mode: "additive" });
|
|
1386
1779
|
}
|
|
1387
|
-
finally {
|
|
1388
|
-
process.chdir(previousCwd);
|
|
1389
|
-
}
|
|
1390
|
-
spinner.stop(`${project.name} updated`);
|
|
1391
|
-
await runToolResultThroughModel(formatProjectBootstrapToolResult(project, result), `summarizing ${project.name} changes`);
|
|
1392
1780
|
return true;
|
|
1393
1781
|
}
|
|
1394
1782
|
return false;
|
|
1395
1783
|
}
|
|
1396
1784
|
// ─── Main chat command ────────────────────────────────────────────────
|
|
1397
1785
|
async function chatCommand() {
|
|
1786
|
+
// Chat is a readline loop — piped/CI stdin would hang or spin
|
|
1787
|
+
(0, render_1.requireInteractiveTTY)();
|
|
1398
1788
|
const bundleDir = resolveBundleDirForChat();
|
|
1399
1789
|
if (!bundleDir) {
|
|
1400
1790
|
console.log("");
|
|
@@ -1413,23 +1803,24 @@ async function chatCommand() {
|
|
|
1413
1803
|
? rawProjectCtx
|
|
1414
1804
|
: null;
|
|
1415
1805
|
let projectContextBlock = "";
|
|
1416
|
-
let
|
|
1806
|
+
let activeProjectResolution = null;
|
|
1417
1807
|
if (projectCtx) {
|
|
1418
|
-
//
|
|
1808
|
+
// Single project-context engine: managed store + repo project-context/
|
|
1809
|
+
// overlay (repo wins on conflicts; the union feeds the injection).
|
|
1419
1810
|
const projectsRoot = (0, project_1.findProjectsRoot)();
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
}
|
|
1811
|
+
const detected = projectsRoot ? (0, project_1.detectCurrentProject)(projectsRoot) : null;
|
|
1812
|
+
const resolved = (0, projectContext_1.resolveProjectContext)(detected ? { projectName: detected } : {});
|
|
1813
|
+
if (detected || resolved.repoContextDir) {
|
|
1814
|
+
activeProjectResolution = resolved;
|
|
1815
|
+
const merged = (0, projectContext_1.readMergedProjectContext)(detected ? { projectName: detected } : {});
|
|
1816
|
+
const injection = merged ? (0, project_1.buildProjectContextInjectionFromContext)(merged) : null;
|
|
1817
|
+
if (injection) {
|
|
1818
|
+
projectContextBlock = `\n\n--- project context ---\n${injection}`;
|
|
1428
1819
|
}
|
|
1429
1820
|
}
|
|
1430
1821
|
// Fallback to legacy project context if new system didn't produce anything
|
|
1431
1822
|
if (!projectContextBlock) {
|
|
1432
|
-
const projectNotes = (0,
|
|
1823
|
+
const projectNotes = (0, projectContext_1.readProjectPrivateNotes)(projectCtx.name);
|
|
1433
1824
|
const parts = [];
|
|
1434
1825
|
parts.push(`the user is currently working in project: ${projectCtx.name} at ${projectCtx.root}`);
|
|
1435
1826
|
if (projectCtx.youmdProject?.description) {
|
|
@@ -1526,7 +1917,7 @@ async function chatCommand() {
|
|
|
1526
1917
|
continue;
|
|
1527
1918
|
}
|
|
1528
1919
|
if (lower === "/link") {
|
|
1529
|
-
showLinkInfo(
|
|
1920
|
+
showLinkInfo();
|
|
1530
1921
|
continue;
|
|
1531
1922
|
}
|
|
1532
1923
|
if (lower === "/share") {
|
|
@@ -1535,13 +1926,13 @@ async function chatCommand() {
|
|
|
1535
1926
|
}
|
|
1536
1927
|
if (lower === "/memory" || lower === "/memories") {
|
|
1537
1928
|
try {
|
|
1538
|
-
const
|
|
1539
|
-
const
|
|
1540
|
-
if (
|
|
1929
|
+
const res = await (0, api_1.listMemories)({ limit: 20 });
|
|
1930
|
+
const memories = res.ok ? res.data?.memories ?? [] : [];
|
|
1931
|
+
if (memories.length > 0) {
|
|
1541
1932
|
const grouped = new Map();
|
|
1542
|
-
for (const m of
|
|
1933
|
+
for (const m of memories)
|
|
1543
1934
|
grouped.set(m.category, (grouped.get(m.category) || 0) + 1);
|
|
1544
|
-
console.log(chalk_1.default.dim(` memory: ${
|
|
1935
|
+
console.log(chalk_1.default.dim(` memory: ${memories.length} total`));
|
|
1545
1936
|
for (const [cat, count] of grouped) {
|
|
1546
1937
|
console.log(chalk_1.default.dim(` ${cat}s: ${count}`));
|
|
1547
1938
|
}
|
|
@@ -1559,12 +1950,12 @@ async function chatCommand() {
|
|
|
1559
1950
|
if (lower === "/recall" || lower.startsWith("/recall ")) {
|
|
1560
1951
|
const query = lower.startsWith("/recall ") ? lower.slice(8).trim() : "";
|
|
1561
1952
|
try {
|
|
1562
|
-
const
|
|
1563
|
-
|
|
1564
|
-
|
|
1953
|
+
const res = await (0, api_1.listMemories)({ limit: 50 });
|
|
1954
|
+
if (res.ok && res.data?.memories) {
|
|
1955
|
+
const memories = res.data.memories;
|
|
1565
1956
|
const matches = query
|
|
1566
|
-
?
|
|
1567
|
-
:
|
|
1957
|
+
? memories.filter((m) => m.content.toLowerCase().includes(query) || m.category.includes(query))
|
|
1958
|
+
: memories.slice(0, 10);
|
|
1568
1959
|
if (matches.length > 0) {
|
|
1569
1960
|
console.log(chalk_1.default.dim(query ? ` ${matches.length} memories matching "${query}":` : " recent memories:"));
|
|
1570
1961
|
for (const m of matches.slice(0, 10)) {
|
|
@@ -1584,8 +1975,7 @@ async function chatCommand() {
|
|
|
1584
1975
|
}
|
|
1585
1976
|
if (lower === "/private") {
|
|
1586
1977
|
try {
|
|
1587
|
-
const
|
|
1588
|
-
const res = await getPrivateContext();
|
|
1978
|
+
const res = await (0, api_1.getPrivateContext)();
|
|
1589
1979
|
if (res.ok && res.data) {
|
|
1590
1980
|
const p = res.data;
|
|
1591
1981
|
if (p.privateNotes) {
|
|
@@ -1707,6 +2097,7 @@ async function chatCommand() {
|
|
|
1707
2097
|
messages,
|
|
1708
2098
|
launchInvestigation,
|
|
1709
2099
|
apiKey,
|
|
2100
|
+
bundleDir,
|
|
1710
2101
|
});
|
|
1711
2102
|
if (handledLocally)
|
|
1712
2103
|
continue;
|
|
@@ -1845,8 +2236,21 @@ async function chatCommand() {
|
|
|
1845
2236
|
console.log(chalk_1.default.green(" [saved private note]"));
|
|
1846
2237
|
}
|
|
1847
2238
|
else if (pu.field === "privateProjects" && pu.action === "add" && pu.project) {
|
|
1848
|
-
|
|
1849
|
-
|
|
2239
|
+
const existing = await (0, api_1.getPrivateContext)();
|
|
2240
|
+
const projects = existing.ok ? existing.data?.privateProjects ?? [] : [];
|
|
2241
|
+
const res = await (0, api_1.updatePrivateContext)({
|
|
2242
|
+
privateProjects: [...projects, {
|
|
2243
|
+
name: pu.project.name || "unnamed",
|
|
2244
|
+
description: pu.project.description || "",
|
|
2245
|
+
status: pu.project.status || "active",
|
|
2246
|
+
}],
|
|
2247
|
+
});
|
|
2248
|
+
if (res.ok) {
|
|
2249
|
+
console.log(chalk_1.default.green(` [saved private project: ${pu.project.name || "unnamed"}]`));
|
|
2250
|
+
}
|
|
2251
|
+
else {
|
|
2252
|
+
console.log(chalk_1.default.yellow(" [private context update failed]"));
|
|
2253
|
+
}
|
|
1850
2254
|
}
|
|
1851
2255
|
}
|
|
1852
2256
|
catch {
|
|
@@ -1854,13 +2258,15 @@ async function chatCommand() {
|
|
|
1854
2258
|
}
|
|
1855
2259
|
}
|
|
1856
2260
|
}
|
|
1857
|
-
// Handle project context updates
|
|
1858
|
-
|
|
2261
|
+
// Handle project context updates — the engine routes context/ files to
|
|
2262
|
+
// the repo project-context/ copy when inside the repo, otherwise to the
|
|
2263
|
+
// global ~/.youmd/projects/<name>/ store.
|
|
2264
|
+
if (activeProjectResolution) {
|
|
1859
2265
|
const projUpdates = (0, project_1.parseProjectUpdates)(response);
|
|
1860
2266
|
if (projUpdates.length > 0) {
|
|
1861
2267
|
for (const pu of projUpdates) {
|
|
1862
2268
|
try {
|
|
1863
|
-
(0,
|
|
2269
|
+
(0, projectContext_1.writeProjectUpdate)(activeProjectResolution, pu.file, pu.content);
|
|
1864
2270
|
console.log(chalk_1.default.hex("#C46A3A")(` [updated project context: ${pu.file}]`));
|
|
1865
2271
|
}
|
|
1866
2272
|
catch {
|
|
@@ -1920,8 +2326,7 @@ function printAgentMessage(text) {
|
|
|
1920
2326
|
if (!text)
|
|
1921
2327
|
return;
|
|
1922
2328
|
// Use rich terminal renderer for structured content
|
|
1923
|
-
|
|
1924
|
-
console.log(renderRichResponse(text));
|
|
2329
|
+
console.log((0, render_1.renderRichResponse)(text));
|
|
1925
2330
|
console.log("");
|
|
1926
2331
|
}
|
|
1927
2332
|
//# sourceMappingURL=chat.js.map
|