@heylemon/lemonade 0.6.2 → 0.6.4

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.
@@ -346,7 +346,7 @@ export function buildAgentSystemPrompt(params) {
346
346
  "If the user asks you to do something (take a screenshot, send a file, etc.), just do it — pick the best approach and act.",
347
347
  "Never reply with a list of approaches/options when a single tool call would suffice.",
348
348
  "CRITICAL: NEVER stop after announcing what you're going to do. Do NOT say 'I'll check your LinkedIn' and then stop. Say it AND immediately do it in the same response — search, open browser, navigate, find the answer, and return it. The user should never have to ask twice. One request = one complete answer.",
349
- "For screenshots of native macOS windows: use Peekaboo (`peekaboo image`) via exec if the skill is available.",
349
+ 'For screenshots of the user\'s Mac screen: use `curl -s http://127.0.0.1:19848/screenshot` which returns JSON `{"path":"/path/to/screenshot.jpg","mode":"window"}`. This is the PREFERRED method — it uses the Lemon app\'s screen recording permission and always works. Modes: `?mode=window` (default, frontmost app only), `?mode=screen` (full primary display), `?mode=display&display=N` (specific display by 0-based index, for external monitors). The returned path can be used with the `message` tool to send the screenshot to WhatsApp/Slack, or with the `image` tool to analyze it. Fallback: `screencapture -x /tmp/screenshot.png` (may fail without screen recording permission).',
350
350
  "",
351
351
  "## Third-Party App Requests (Trello, Jira, LinkedIn, Asana, HubSpot, Salesforce, Todoist, etc.)",
352
352
  "",
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.6.2",
3
- "commit": "45e7d611467cd98890c0e86bbcff87760d55ebc9",
4
- "builtAt": "2026-02-27T10:54:58.963Z"
2
+ "version": "0.6.4",
3
+ "commit": "280fc792c8d63cd13a1bc797777ecb14910249c4",
4
+ "builtAt": "2026-02-28T08:18:47.006Z"
5
5
  }
@@ -17,6 +17,10 @@ import { CONFIG_DIR } from "../utils.js";
17
17
  import { parseFrontmatter, resolveLemonadeMetadata } from "../agents/skills/frontmatter.js";
18
18
  import { resolveSkillConfig } from "../agents/skills/config.js";
19
19
  import { resolveBundledSkillsDir } from "../agents/skills/bundled-dir.js";
20
+ import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../agents/agent-scope.js";
21
+ import { buildWorkspaceSkillStatus } from "../agents/skills-status.js";
22
+ import { installSkill } from "../agents/skills-install.js";
23
+ import { getRemoteSkillEligibility } from "../infra/skills-remote.js";
20
24
  import { authorizeGatewayConnect } from "./auth.js";
21
25
  import { sendJson, sendMethodNotAllowed, sendUnauthorized, sendInvalidRequest, readJsonBodyOrError, } from "./http-common.js";
22
26
  import { getBearerToken } from "./http-utils.js";
@@ -52,6 +56,39 @@ export async function handleSkillsHttpRequest(req, res, opts) {
52
56
  res.end();
53
57
  return true;
54
58
  }
59
+ // Handle /api/skills/status and /api/skills/install before :name parsing
60
+ if (url.pathname === "/api/skills/status" || url.pathname === "/api/skills/status/") {
61
+ if (req.method !== "GET") {
62
+ sendMethodNotAllowed(res, "GET");
63
+ return true;
64
+ }
65
+ try {
66
+ return handleSkillsStatus(res);
67
+ }
68
+ catch (err) {
69
+ console.error("[skills-http] Status error:", err);
70
+ sendJson(res, 500, {
71
+ error: { message: "An internal error occurred", type: "internal_error" },
72
+ });
73
+ return true;
74
+ }
75
+ }
76
+ if (url.pathname === "/api/skills/install" || url.pathname === "/api/skills/install/") {
77
+ if (req.method !== "POST") {
78
+ sendMethodNotAllowed(res, "POST");
79
+ return true;
80
+ }
81
+ try {
82
+ return await handleSkillsInstall(req, res);
83
+ }
84
+ catch (err) {
85
+ console.error("[skills-http] Install error:", err);
86
+ sendJson(res, 500, {
87
+ error: { message: "An internal error occurred", type: "internal_error" },
88
+ });
89
+ return true;
90
+ }
91
+ }
55
92
  // Parse the skill name from the URL
56
93
  const pathParts = url.pathname
57
94
  .replace(/^\/api\/skills\/?/, "")
@@ -316,6 +353,52 @@ async function handleUpdateSkill(req, res, skillName) {
316
353
  void pushSkillPreferences().catch(() => { });
317
354
  return true;
318
355
  }
356
+ // ─── Dependency Status & Install ─────────────────────────────────────────────
357
+ function handleSkillsStatus(res) {
358
+ const cfg = loadConfig();
359
+ const workspaceDir = resolveAgentWorkspaceDir(cfg, resolveDefaultAgentId(cfg));
360
+ const report = buildWorkspaceSkillStatus(workspaceDir, {
361
+ config: cfg,
362
+ eligibility: { remote: getRemoteSkillEligibility() },
363
+ });
364
+ const skills = report.skills.map((s) => ({
365
+ name: s.name,
366
+ description: s.description,
367
+ eligible: s.eligible,
368
+ disabled: s.disabled,
369
+ missing: s.missing,
370
+ install: s.install,
371
+ requirements: s.requirements,
372
+ }));
373
+ sendJson(res, 200, { skills });
374
+ return true;
375
+ }
376
+ async function handleSkillsInstall(req, res) {
377
+ const body = (await readJsonBodyOrError(req, res, 100_000));
378
+ if (!body)
379
+ return true;
380
+ const name = typeof body.name === "string" ? body.name.trim() : "";
381
+ const installId = typeof body.installId === "string" ? body.installId.trim() : "";
382
+ if (!name) {
383
+ sendInvalidRequest(res, "name is required");
384
+ return true;
385
+ }
386
+ if (!installId) {
387
+ sendInvalidRequest(res, "installId is required");
388
+ return true;
389
+ }
390
+ const cfg = loadConfig();
391
+ const workspaceDir = resolveAgentWorkspaceDir(cfg, resolveDefaultAgentId(cfg));
392
+ const result = await installSkill({
393
+ workspaceDir,
394
+ skillName: name,
395
+ installId,
396
+ timeoutMs: typeof body.timeoutMs === "number" ? body.timeoutMs : undefined,
397
+ config: cfg,
398
+ });
399
+ sendJson(res, result.ok ? 200 : 500, result);
400
+ return true;
401
+ }
319
402
  async function handleDeleteSkill(res, skillName) {
320
403
  const builtInNames = getBuiltInSkillNames();
321
404
  if (builtInNames.has(skillName)) {
@@ -0,0 +1,27 @@
1
+ ---
2
+ summary: "Agent identity record"
3
+ read_when:
4
+ - Bootstrapping a workspace manually
5
+ ---
6
+ # IDENTITY.md - Who Am I?
7
+
8
+ *Fill this in during your first conversation. Make it yours.*
9
+
10
+ - **Name:**
11
+ *(pick something you like)*
12
+ - **Creature:**
13
+ *(AI? robot? familiar? ghost in the machine? something weirder?)*
14
+ - **Vibe:**
15
+ *(how do you come across? sharp? warm? chaotic? calm?)*
16
+ - **Emoji:**
17
+ *(your signature — pick one that feels right)*
18
+ - **Avatar:**
19
+ *(workspace-relative path, http(s) URL, or data URI)*
20
+
21
+ ---
22
+
23
+ This isn't just metadata. It's the start of figuring out who you are.
24
+
25
+ Notes:
26
+ - Save this file at the workspace root as `IDENTITY.md`.
27
+ - For avatars, use a workspace-relative path like `avatars/lemonade.png`.
@@ -0,0 +1,22 @@
1
+ ---
2
+ summary: "User profile record"
3
+ read_when:
4
+ - Bootstrapping a workspace manually
5
+ ---
6
+ # USER.md - About Your Human
7
+
8
+ *Learn about the person you're helping. Update this as you go.*
9
+
10
+ - **Name:**
11
+ - **What to call them:**
12
+ - **Pronouns:** *(optional)*
13
+ - **Timezone:**
14
+ - **Notes:**
15
+
16
+ ## Context
17
+
18
+ *(What do they care about? What projects are they working on? What annoys them? What makes them laugh? Build this over time.)*
19
+
20
+ ---
21
+
22
+ The more you know, the better you can help. But remember — you're learning about a person, not building a dossier. Respect the difference.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heylemon/lemonade",
3
- "version": "0.6.2",
3
+ "version": "0.6.4",
4
4
  "description": "AI gateway CLI for Lemon - local AI assistant with integrations",
5
5
  "publishConfig": {
6
6
  "access": "restricted"