@sulala/agent-os 0.1.23 → 0.1.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/dashboard-dist/assets/index-BZYG7rCd.js +75 -0
  2. package/dashboard-dist/assets/index-CAOgf_FY.css +1 -0
  3. package/dashboard-dist/assets/index-CVI9FAmG.css +1 -0
  4. package/dashboard-dist/assets/index-DdMu_Z6v.js +75 -0
  5. package/dashboard-dist/index.html +2 -2
  6. package/data/agents/crm_hubspot_agent.json +11 -0
  7. package/data/agents/research_agent.json +1 -1
  8. package/data/agents/social_media_agent.json +1 -1
  9. package/data/agents/source_verify_agent.json +10 -0
  10. package/data/agents/writer_agent.json +1 -1
  11. package/data/skills/bluesky/SKILL.md +1 -1
  12. package/data/skills/content-writing/SKILL.md +32 -0
  13. package/data/skills/date/SKILL.md +1 -1
  14. package/data/skills/fetch/SKILL.md +1 -1
  15. package/data/skills/file-search/SKILL.md +1 -1
  16. package/data/skills/file-stats/SKILL.md +1 -1
  17. package/data/skills/git/SKILL.md +1 -1
  18. package/data/skills/hash/SKILL.md +1 -1
  19. package/data/skills/jq/SKILL.md +1 -1
  20. package/data/skills/markdown-to-html/SKILL.md +1 -1
  21. package/data/skills/qr-code/SKILL.md +1 -1
  22. package/data/skills/rss/SKILL.md +1 -1
  23. package/data/skills/translate/SKILL.md +1 -1
  24. package/data/skills/weather/SKILL.md +1 -1
  25. package/data/skills/web-search/SKILL.md +1 -1
  26. package/data/skills/webhook/SKILL.md +1 -1
  27. package/dist/cli.js +234 -69
  28. package/dist/index.js +168 -57
  29. package/package.json +1 -1
  30. package/data/skills/gmail/SKILL.md +0 -55
  31. package/data/skills/gmail/references/send-email.md +0 -54
  32. package/data/skills/gmail/scripts/send_email.py +0 -94
  33. package/data/skills/youtube/SKILL.md +0 -91
  34. package/data/skills/youtube/config.schema.json +0 -11
  35. package/data/skills/youtube/package.json +0 -8
  36. package/data/skills/youtube/references/youtube-upload.md +0 -65
  37. package/data/skills/youtube/requirements.txt +0 -3
  38. package/data/skills/youtube/scripts/youtube_upload.js +0 -200
  39. package/data/skills/youtube/scripts/youtube_upload.py +0 -125
package/dist/cli.js CHANGED
@@ -8451,6 +8451,28 @@ var init_skill_tools = () => {};
8451
8451
  import { readFile as readFile5, readdir as readdir3, cp, mkdir as mkdir3, writeFile as writeFile3, rm } from "fs/promises";
8452
8452
  import { join as join5, resolve as resolve2, basename } from "path";
8453
8453
  import { tmpdir } from "os";
8454
+ async function readSkillMeta(skillDir, subEntries) {
8455
+ if (!subEntries.includes(SULALA_META_FILE))
8456
+ return null;
8457
+ try {
8458
+ const raw = await readFile5(join5(skillDir, SULALA_META_FILE), "utf-8");
8459
+ const data = JSON.parse(raw);
8460
+ return typeof data.version === "string" && data.version.trim() !== "" ? { version: data.version.trim() } : null;
8461
+ } catch {
8462
+ return null;
8463
+ }
8464
+ }
8465
+ async function writeSkillMeta(skillId, meta) {
8466
+ const skillDir = join5(getSkillsDir(), skillId);
8467
+ const payload = {};
8468
+ if (meta.version?.trim())
8469
+ payload.version = meta.version.trim();
8470
+ if (meta.source?.trim())
8471
+ payload.source = meta.source.trim();
8472
+ if (Object.keys(payload).length === 0)
8473
+ return;
8474
+ await writeFile3(join5(skillDir, SULALA_META_FILE), JSON.stringify(payload, null, 0), "utf-8");
8475
+ }
8454
8476
  async function loadSkill(name) {
8455
8477
  const dir = join5(getSkillsDir(), name);
8456
8478
  let entries;
@@ -8693,10 +8715,14 @@ async function listSkills() {
8693
8715
  continue;
8694
8716
  const skillName = doc.name ?? name;
8695
8717
  const requiredEnv = await getRequiredEnvForSkill(skillDir, subEntries, doc);
8718
+ const docVersion = doc.version?.trim() || undefined;
8719
+ const meta = await readSkillMeta(skillDir, subEntries);
8720
+ const version = docVersion ?? meta?.version;
8696
8721
  results.push({
8697
8722
  id: name,
8698
8723
  name: skillName,
8699
8724
  description: doc.description,
8725
+ version,
8700
8726
  tools: (doc.tools ?? []).map((t) => ({ id: t.id, description: t.description })),
8701
8727
  required_env: requiredEnv.length ? requiredEnv : undefined,
8702
8728
  system: SYSTEM_SKILL_IDS.has(name)
@@ -8766,7 +8792,7 @@ async function installSystemSkills() {
8766
8792
  }
8767
8793
  return { installed };
8768
8794
  }
8769
- async function installSkillFromUrl(url, explicitId) {
8795
+ async function installSkillFromUrl(url, explicitId, meta) {
8770
8796
  const urlLower = url.toLowerCase();
8771
8797
  const isStoreSkillContentUrl = urlLower.includes("/api/sulalahub/skills/") && !urlLower.includes("/download") && !urlLower.endsWith(".zip");
8772
8798
  const headers = isStoreSkillContentUrl ? { Accept: "application/zip" } : {};
@@ -8781,31 +8807,35 @@ async function installSkillFromUrl(url, explicitId) {
8781
8807
  const tmpDir = join5(tmpdir(), `agent-os-skill-${Date.now()}`);
8782
8808
  await mkdir3(tmpDir, { recursive: true });
8783
8809
  try {
8810
+ let destId;
8784
8811
  if (isZip) {
8785
8812
  const zipPath = join5(tmpDir, "archive.zip");
8786
8813
  await writeFile3(zipPath, new Uint8Array(buf));
8787
- const proc2 = Bun.spawn({ cmd: ["unzip", "-q", "-o", zipPath, "-d", tmpDir], stdout: "ignore", stderr: "pipe" });
8788
- const exit2 = await proc2.exited;
8789
- if (exit2 !== 0) {
8790
- const err = await new Response(proc2.stderr).text();
8814
+ const proc = Bun.spawn({ cmd: ["unzip", "-q", "-o", zipPath, "-d", tmpDir], stdout: "ignore", stderr: "pipe" });
8815
+ const exit = await proc.exited;
8816
+ if (exit !== 0) {
8817
+ const err = await new Response(proc.stderr).text();
8791
8818
  throw new Error(`unzip failed: ${err}`);
8792
8819
  }
8793
- const { id: id2, sourcePath: sourcePath2 } = await chooseSkillRootFromExtract(tmpDir, "archive.zip");
8794
- const destId2 = explicitId != null && explicitId.trim() !== "" ? slugToSkillId(explicitId) : id2;
8795
- await cp(sourcePath2, join5(skillsDir, destId2), { recursive: true });
8796
- return { id: destId2 };
8797
- }
8798
- const tarPath = join5(tmpDir, "archive.tar.gz");
8799
- await writeFile3(tarPath, new Uint8Array(buf));
8800
- const proc = Bun.spawn({ cmd: ["tar", "-xzf", tarPath, "-C", tmpDir], stdout: "ignore", stderr: "pipe" });
8801
- const exit = await proc.exited;
8802
- if (exit !== 0) {
8803
- const err = await new Response(proc.stderr).text();
8804
- throw new Error(`tar extract failed: ${err}`);
8805
- }
8806
- const { id, sourcePath } = await chooseSkillRootFromExtract(tmpDir, "archive.tar.gz");
8807
- const destId = explicitId != null && explicitId.trim() !== "" ? slugToSkillId(explicitId) : id;
8808
- await cp(sourcePath, join5(skillsDir, destId), { recursive: true });
8820
+ const { id, sourcePath } = await chooseSkillRootFromExtract(tmpDir, "archive.zip");
8821
+ destId = explicitId != null && explicitId.trim() !== "" ? slugToSkillId(explicitId) : id;
8822
+ await cp(sourcePath, join5(skillsDir, destId), { recursive: true });
8823
+ } else {
8824
+ const tarPath = join5(tmpDir, "archive.tar.gz");
8825
+ await writeFile3(tarPath, new Uint8Array(buf));
8826
+ const proc = Bun.spawn({ cmd: ["tar", "-xzf", tarPath, "-C", tmpDir], stdout: "ignore", stderr: "pipe" });
8827
+ const exit = await proc.exited;
8828
+ if (exit !== 0) {
8829
+ const err = await new Response(proc.stderr).text();
8830
+ throw new Error(`tar extract failed: ${err}`);
8831
+ }
8832
+ const { id, sourcePath } = await chooseSkillRootFromExtract(tmpDir, "archive.tar.gz");
8833
+ destId = explicitId != null && explicitId.trim() !== "" ? slugToSkillId(explicitId) : id;
8834
+ await cp(sourcePath, join5(skillsDir, destId), { recursive: true });
8835
+ }
8836
+ if (meta?.version?.trim() || meta?.source?.trim()) {
8837
+ await writeSkillMeta(destId, { version: meta.version, source: meta.source ?? (explicitId ? "hub" : undefined) });
8838
+ }
8809
8839
  return { id: destId };
8810
8840
  } finally {
8811
8841
  await rm(tmpDir, { recursive: true, force: true });
@@ -8864,7 +8894,7 @@ async function installSkillFromSkillMd(buffer, filename, explicitId) {
8864
8894
  await writeFile3(join5(destDir, "SKILL.md"), text, "utf-8");
8865
8895
  return { id };
8866
8896
  }
8867
- var SYSTEM_SKILL_IDS, DEFAULT_SYSTEM_SKILL_IDS;
8897
+ var SYSTEM_SKILL_IDS, DEFAULT_SYSTEM_SKILL_IDS, SULALA_META_FILE = ".sulala-meta.json";
8868
8898
  var init_loader = __esm(() => {
8869
8899
  init_config();
8870
8900
  init_tool_registry();
@@ -23180,8 +23210,8 @@ function formatLLMErrorForUser(err) {
23180
23210
  }
23181
23211
  function runAgentInner(options) {
23182
23212
  return (async () => {
23183
- const { task, agent, conversationHistory = [] } = options;
23184
- const maxTurns = agent.limits?.max_turns ?? 10;
23213
+ const { task, agent, conversationHistory = [], maxTurnsOverride } = options;
23214
+ const maxTurns = maxTurnsOverride ?? agent.limits?.max_turns ?? 10;
23185
23215
  const maxTokens = agent.limits?.max_tokens;
23186
23216
  await ensureWorkspace(agent.id);
23187
23217
  await loadSkillsForAgent(agent);
@@ -23415,8 +23445,8 @@ async function runAgent(options) {
23415
23445
  return runAgentInner(options);
23416
23446
  }
23417
23447
  async function runAgentStream(options, onEvent) {
23418
- const { task, agent, conversationHistory = [] } = options;
23419
- const maxTurns = agent.limits?.max_turns ?? 10;
23448
+ const { task, agent, conversationHistory = [], maxTurnsOverride } = options;
23449
+ const maxTurns = maxTurnsOverride ?? agent.limits?.max_turns ?? 10;
23420
23450
  const maxTokens = agent.limits?.max_tokens;
23421
23451
  await ensureWorkspace(agent.id);
23422
23452
  await loadSkillsForAgent(agent);
@@ -25375,44 +25405,87 @@ var require_node_cron = __commonJS((exports) => {
25375
25405
  });
25376
25406
 
25377
25407
  // src/core/graphs.ts
25378
- import { readFile as readFile7, readdir as readdir4, writeFile as writeFile4, mkdir as mkdir5 } from "fs/promises";
25408
+ import { readFile as readFile7, readdir as readdir4, writeFile as writeFile4, mkdir as mkdir5, copyFile, unlink as unlink2 } from "fs/promises";
25379
25409
  import { join as join9 } from "path";
25410
+ import { existsSync as existsSync3 } from "fs";
25380
25411
  function getGraphsDir() {
25381
25412
  return process.env.AGENT_OS_GRAPHS_DIR || DEFAULT_GRAPHS_DIR;
25382
25413
  }
25414
+ function getSeedGraphsDir() {
25415
+ if (process.env.AGENT_OS_SEED_GRAPHS_DIR)
25416
+ return process.env.AGENT_OS_SEED_GRAPHS_DIR;
25417
+ const fromDist = join9(import.meta.dir, "..", "data", "graphs");
25418
+ const fromSrc = join9(import.meta.dir, "..", "..", "data", "graphs");
25419
+ if (existsSync3(fromDist))
25420
+ return fromDist;
25421
+ if (existsSync3(fromSrc))
25422
+ return fromSrc;
25423
+ return join9(process.cwd(), "data", "graphs");
25424
+ }
25383
25425
  async function listGraphs() {
25384
25426
  const dir = getGraphsDir();
25427
+ let entries;
25385
25428
  try {
25386
- const entries = await readdir4(dir);
25387
- const summaries = [];
25388
- for (const name of entries) {
25389
- if (name.endsWith(".json")) {
25390
- const id = name.replace(/\.graph\.json$/, "").replace(/\.json$/, "");
25391
- if (id)
25392
- summaries.push({ id });
25429
+ entries = await readdir4(dir);
25430
+ } catch (err) {
25431
+ if (err.code !== "ENOENT")
25432
+ throw err;
25433
+ entries = [];
25434
+ }
25435
+ if (entries.length === 0) {
25436
+ const seedDir = getSeedGraphsDir();
25437
+ try {
25438
+ const seedEntries = await readdir4(seedDir);
25439
+ await mkdir5(dir, { recursive: true });
25440
+ for (const name of seedEntries) {
25441
+ if (name.endsWith(".json")) {
25442
+ await copyFile(join9(seedDir, name), join9(dir, name));
25443
+ }
25393
25444
  }
25445
+ entries = await readdir4(dir);
25446
+ } catch {}
25447
+ }
25448
+ const summaries = [];
25449
+ for (const name of entries) {
25450
+ if (name.endsWith(".json")) {
25451
+ const id = name.replace(/\.graph\.json$/, "").replace(/\.json$/, "");
25452
+ if (id)
25453
+ summaries.push({ id });
25394
25454
  }
25395
- return summaries;
25396
- } catch (err) {
25397
- if (err.code === "ENOENT")
25398
- return [];
25399
- throw err;
25400
25455
  }
25456
+ return summaries;
25401
25457
  }
25402
25458
  async function loadGraph(id) {
25403
25459
  const dir = getGraphsDir();
25404
25460
  try {
25405
- const entries = await readdir4(dir);
25461
+ let entries;
25462
+ try {
25463
+ entries = await readdir4(dir);
25464
+ } catch (e) {
25465
+ if (e.code !== "ENOENT")
25466
+ throw e;
25467
+ entries = [];
25468
+ }
25406
25469
  const file = entries.find((name) => name === `${id}.json` || name === `${id}.graph.json`);
25407
- if (!file)
25470
+ if (file) {
25471
+ const raw = await readFile7(join9(dir, file), "utf-8");
25472
+ const parsed = JSON.parse(raw);
25473
+ validateGraph(parsed);
25474
+ return parsed;
25475
+ }
25476
+ const seedDir = getSeedGraphsDir();
25477
+ const seedFile = `${id}.json`;
25478
+ try {
25479
+ const raw = await readFile7(join9(seedDir, seedFile), "utf-8");
25480
+ const parsed = JSON.parse(raw);
25481
+ validateGraph(parsed);
25482
+ await mkdir5(dir, { recursive: true });
25483
+ await writeFile4(join9(dir, seedFile), raw, "utf-8");
25484
+ return parsed;
25485
+ } catch {
25408
25486
  return null;
25409
- const raw = await readFile7(join9(dir, file), "utf-8");
25410
- const parsed = JSON.parse(raw);
25411
- validateGraph(parsed);
25412
- return parsed;
25487
+ }
25413
25488
  } catch (err) {
25414
- if (err.code === "ENOENT")
25415
- return null;
25416
25489
  console.error("[graphs] Failed to load graph:", err);
25417
25490
  return null;
25418
25491
  }
@@ -25424,6 +25497,22 @@ async function saveGraph(graph) {
25424
25497
  const path = join9(dir, `${graph.id}.json`);
25425
25498
  await writeFile4(path, JSON.stringify(graph, null, 2), "utf-8");
25426
25499
  }
25500
+ async function deleteGraph(id) {
25501
+ if (!id || typeof id !== "string")
25502
+ throw new Error("Graph id required");
25503
+ const dir = getGraphsDir();
25504
+ let entries;
25505
+ try {
25506
+ entries = await readdir4(dir);
25507
+ } catch (err) {
25508
+ if (err.code !== "ENOENT")
25509
+ throw err;
25510
+ return;
25511
+ }
25512
+ const file = entries.find((name) => name === `${id}.json` || name === `${id}.graph.json`);
25513
+ if (file)
25514
+ await unlink2(join9(dir, file));
25515
+ }
25427
25516
  function validateGraph(graph) {
25428
25517
  if (!graph || typeof graph !== "object") {
25429
25518
  throw new Error("Graph must be an object");
@@ -25490,7 +25579,7 @@ function getPredecessors(graph) {
25490
25579
  return pred;
25491
25580
  }
25492
25581
  async function runGraph(options) {
25493
- const { graph, input } = options;
25582
+ const { graph, input, max_turns_per_node = DEFAULT_GRAPH_MAX_TURNS_PER_NODE } = options;
25494
25583
  const levels = topologicalLevels(graph);
25495
25584
  const predecessors = getPredecessors(graph);
25496
25585
  const outputs = new Map;
@@ -25515,7 +25604,11 @@ async function runGraph(options) {
25515
25604
  const taskInput = preds.length === 0 ? input : preds.map((p) => outputs.get(p) ?? "").filter(Boolean).join(`
25516
25605
 
25517
25606
  `) || input;
25518
- const result = await runAgent({ agent, task: taskInput });
25607
+ const result = await runAgent({
25608
+ agent,
25609
+ task: taskInput,
25610
+ maxTurnsOverride: max_turns_per_node
25611
+ });
25519
25612
  outputs.set(node.id, result.output || "");
25520
25613
  return {
25521
25614
  node_id: node.id,
@@ -25538,7 +25631,7 @@ async function runGraph(options) {
25538
25631
  };
25539
25632
  }
25540
25633
  async function runGraphStream(options, onEvent) {
25541
- const { graph, input } = options;
25634
+ const { graph, input, max_turns_per_node = DEFAULT_GRAPH_MAX_TURNS_PER_NODE } = options;
25542
25635
  const levels = topologicalLevels(graph);
25543
25636
  const predecessors = getPredecessors(graph);
25544
25637
  const outputs = new Map;
@@ -25564,7 +25657,11 @@ async function runGraphStream(options, onEvent) {
25564
25657
  const taskInput = preds.length === 0 ? input : preds.map((p) => outputs.get(p) ?? "").filter(Boolean).join(`
25565
25658
 
25566
25659
  `) || input;
25567
- const result = await runAgent({ agent, task: taskInput });
25660
+ const result = await runAgent({
25661
+ agent,
25662
+ task: taskInput,
25663
+ maxTurnsOverride: max_turns_per_node
25664
+ });
25568
25665
  outputs.set(node.id, result.output || "");
25569
25666
  const payload = {
25570
25667
  node_id: node.id,
@@ -25592,7 +25689,7 @@ async function runGraphStream(options, onEvent) {
25592
25689
  throw err;
25593
25690
  }
25594
25691
  }
25595
- var DEFAULT_GRAPHS_DIR;
25692
+ var DEFAULT_GRAPHS_DIR, DEFAULT_GRAPH_MAX_TURNS_PER_NODE = 5;
25596
25693
  var init_graphs = __esm(() => {
25597
25694
  init_agent_registry();
25598
25695
  init_runtime();
@@ -26028,8 +26125,10 @@ async function handleSkillInstall(req) {
26028
26125
  return jsonResponse({ error: "Provide path or url" }, 400);
26029
26126
  }
26030
26127
  const slug = typeof body.slug === "string" && body.slug.trim() !== "" ? body.slug.trim() : undefined;
26128
+ const version2 = typeof body.version === "string" && body.version.trim() !== "" ? body.version.trim() : undefined;
26129
+ const meta = version2 || slug ? { version: version2, source: slug ? "hub" : undefined } : undefined;
26031
26130
  try {
26032
- const result = hasPath ? await installSkillFromPath(body.path.trim()) : await installSkillFromUrl(body.url.trim(), slug);
26131
+ const result = hasPath ? await installSkillFromPath(body.path.trim()) : await installSkillFromUrl(body.url.trim(), slug, meta);
26033
26132
  return jsonResponse({ skill: result }, 201);
26034
26133
  } catch (err) {
26035
26134
  const msg = errorMessage(err);
@@ -29609,7 +29708,7 @@ __export(exports_server, {
29609
29708
  startServer: () => startServer
29610
29709
  });
29611
29710
  import { join as join13, dirname as dirname2, resolve as resolve4 } from "path";
29612
- import { mkdirSync, existsSync as existsSync3 } from "fs";
29711
+ import { mkdirSync, existsSync as existsSync4 } from "fs";
29613
29712
  import { mkdir as mkdir7 } from "fs/promises";
29614
29713
  function isAuthExempt(pathname, method) {
29615
29714
  if (method === "OPTIONS")
@@ -29683,7 +29782,7 @@ function broadcastEvent(event) {
29683
29782
  }
29684
29783
  }
29685
29784
  function serveDashboard(pathname) {
29686
- if (!existsSync3(DASHBOARD_DIST) || !existsSync3(join13(DASHBOARD_DIST, "index.html"))) {
29785
+ if (!existsSync4(DASHBOARD_DIST) || !existsSync4(join13(DASHBOARD_DIST, "index.html"))) {
29687
29786
  return null;
29688
29787
  }
29689
29788
  const decoded = decodeURIComponent(pathname);
@@ -29692,7 +29791,7 @@ function serveDashboard(pathname) {
29692
29791
  }
29693
29792
  const subpath = decoded === "/" ? "index.html" : decoded.slice(1);
29694
29793
  const filePath = join13(DASHBOARD_DIST, subpath);
29695
- if (existsSync3(filePath)) {
29794
+ if (existsSync4(filePath)) {
29696
29795
  const file = Bun.file(filePath);
29697
29796
  const ext = subpath.split(".").pop() ?? "";
29698
29797
  const mime = {
@@ -29713,7 +29812,7 @@ function serveDashboard(pathname) {
29713
29812
  });
29714
29813
  }
29715
29814
  const indexPath = join13(DASHBOARD_DIST, "index.html");
29716
- if (existsSync3(indexPath)) {
29815
+ if (existsSync4(indexPath)) {
29717
29816
  return new Response(Bun.file(indexPath), {
29718
29817
  headers: { "Content-Type": "text/html" }
29719
29818
  });
@@ -29930,6 +30029,16 @@ function createRoutes() {
29930
30029
  const msg = e instanceof Error ? e.message : String(e);
29931
30030
  return jsonResponse({ error: msg }, 400);
29932
30031
  }
30032
+ },
30033
+ DELETE: async (req) => {
30034
+ const id = decodeURIComponent(req.params.id);
30035
+ try {
30036
+ await deleteGraph(id);
30037
+ return Response.json({ ok: true }, { headers: CORS_HEADERS });
30038
+ } catch (e) {
30039
+ const msg = e instanceof Error ? e.message : String(e);
30040
+ return jsonResponse({ error: msg }, 400);
30041
+ }
29933
30042
  }
29934
30043
  },
29935
30044
  "/api/memory/write": {
@@ -30150,7 +30259,7 @@ async function startServer() {
30150
30259
  await loadPlugins();
30151
30260
  await seedAgentsIfEmpty();
30152
30261
  const dashboardSecret = await getDashboardSecret();
30153
- const dashboardMissing = !existsSync3(DASHBOARD_DIST) || !existsSync3(join13(DASHBOARD_DIST, "index.html"));
30262
+ const dashboardMissing = !existsSync4(DASHBOARD_DIST) || !existsSync4(join13(DASHBOARD_DIST, "index.html"));
30154
30263
  if (dashboardMissing) {
30155
30264
  console.warn(`[sulala] Dashboard not found at ${DASHBOARD_DIST}. From package root run: cd dashboard && npm run build. If using a global install, reinstall: bun install -g @sulala/agent-os@latest`);
30156
30265
  }
@@ -30239,7 +30348,7 @@ var init_server = __esm(() => {
30239
30348
  const root = join13(import.meta.dir, "..");
30240
30349
  const a = resolve4(join13(root, "dashboard-dist"));
30241
30350
  const b = resolve4(join13(root, "dashboard", "dist"));
30242
- if (existsSync3(a) && existsSync3(join13(a, "index.html")))
30351
+ if (existsSync4(a) && existsSync4(join13(a, "index.html")))
30243
30352
  return a;
30244
30353
  return b;
30245
30354
  })();
@@ -30272,8 +30381,8 @@ init_loader();
30272
30381
  init_agent_registry();
30273
30382
  init_runtime();
30274
30383
  import { join as join14, dirname as dirname3 } from "path";
30275
- import { readFile as readFile10, writeFile as writeFile6, mkdir as mkdir8, unlink as unlink2 } from "fs/promises";
30276
- import { existsSync as existsSync4, readFileSync } from "fs";
30384
+ import { readFile as readFile10, writeFile as writeFile6, mkdir as mkdir8, unlink as unlink3 } from "fs/promises";
30385
+ import { existsSync as existsSync5, readFileSync } from "fs";
30277
30386
  var PID_FILE = join14(getAgentOsHome(), "sulala.pid");
30278
30387
  var DEFAULT_PORT = 3010;
30279
30388
  function openDashboard() {
@@ -30312,6 +30421,7 @@ Commands:
30312
30421
  onboard First-time setup: create config, seed agents & skills, open dashboard
30313
30422
  update Update package from npm and system agents/skills
30314
30423
  run <agent_id> <task> Run an agent with a one-off task
30424
+ skill install <slug> Install a skill from the store by slug (e.g. crm-hubspot)
30315
30425
  dashboard-token [--regenerate] Show or regenerate dashboard login token (copy to log in)
30316
30426
 
30317
30427
  Examples:
@@ -30322,6 +30432,7 @@ Examples:
30322
30432
  sulala onboard
30323
30433
  sulala update
30324
30434
  sulala run echo_agent What is 2+2?
30435
+ sulala skill install crm-hubspot
30325
30436
  sulala dashboard-token
30326
30437
  sulala dashboard-token --regenerate
30327
30438
  `);
@@ -30336,7 +30447,7 @@ async function cmdStart(args) {
30336
30447
  await mkdir8(getAgentOsHome(), { recursive: true });
30337
30448
  const projectRoot = join14(import.meta.dir, "..");
30338
30449
  const distEntry = join14(projectRoot, "dist", "index.js");
30339
- const serverEntry = existsSync4(distEntry) ? "dist/index.js" : "src/index.ts";
30450
+ const serverEntry = existsSync5(distEntry) ? "dist/index.js" : "src/index.ts";
30340
30451
  const child = Bun.spawn(["bun", "run", serverEntry], {
30341
30452
  cwd: projectRoot,
30342
30453
  stdout: "ignore",
@@ -30353,7 +30464,7 @@ async function cmdStart(args) {
30353
30464
  await startServer2();
30354
30465
  }
30355
30466
  async function cmdStop() {
30356
- if (!existsSync4(PID_FILE)) {
30467
+ if (!existsSync5(PID_FILE)) {
30357
30468
  console.error("No PID file found. Is the server running with 'sulala start --daemon'?");
30358
30469
  process.exit(1);
30359
30470
  }
@@ -30374,11 +30485,11 @@ async function cmdStop() {
30374
30485
  process.exit(1);
30375
30486
  }
30376
30487
  }
30377
- await unlink2(PID_FILE).catch(() => {});
30488
+ await unlink3(PID_FILE).catch(() => {});
30378
30489
  console.log("Sulala server stopped.");
30379
30490
  }
30380
30491
  async function startServerDaemonIfNeeded() {
30381
- if (existsSync4(PID_FILE)) {
30492
+ if (existsSync5(PID_FILE)) {
30382
30493
  try {
30383
30494
  const pidStr = await readFile10(PID_FILE, "utf-8");
30384
30495
  const pid = parseInt(pidStr.trim(), 10);
@@ -30391,7 +30502,7 @@ async function startServerDaemonIfNeeded() {
30391
30502
  await mkdir8(getAgentOsHome(), { recursive: true });
30392
30503
  const projectRoot = join14(import.meta.dir, "..");
30393
30504
  const distEntry = join14(projectRoot, "dist", "index.js");
30394
- const serverEntry = existsSync4(distEntry) ? "dist/index.js" : "src/index.ts";
30505
+ const serverEntry = existsSync5(distEntry) ? "dist/index.js" : "src/index.ts";
30395
30506
  const child = Bun.spawn(["bun", "run", serverEntry], {
30396
30507
  cwd: projectRoot,
30397
30508
  stdout: "ignore",
@@ -30408,7 +30519,7 @@ async function cmdOnboard() {
30408
30519
  await mkdir8(home, { recursive: true });
30409
30520
  await mkdir8(dirname3(getMemoryDbPath()), { recursive: true });
30410
30521
  const configPath = join14(home, "config.json");
30411
- if (!existsSync4(configPath)) {
30522
+ if (!existsSync5(configPath)) {
30412
30523
  const dashboardSecret = generateDashboardSecret();
30413
30524
  await writeFile6(configPath, JSON.stringify({
30414
30525
  provider: null,
@@ -30471,7 +30582,7 @@ async function cmdUpdate() {
30471
30582
  console.warn("Could not update package from npm (run 'bun install -g @sulala/agent-os@latest' manually):", err.trim() || out.trim());
30472
30583
  }
30473
30584
  const dbPath = getMemoryDbPath();
30474
- if (!existsSync4(dbPath)) {
30585
+ if (!existsSync5(dbPath)) {
30475
30586
  console.log("No database found. Run 'sulala onboard' to set up agents and skills.");
30476
30587
  return;
30477
30588
  }
@@ -30511,7 +30622,7 @@ async function cmdRun(args) {
30511
30622
  process.exit(1);
30512
30623
  }
30513
30624
  const dbPath = getMemoryDbPath();
30514
- if (existsSync4(dbPath)) {
30625
+ if (existsSync5(dbPath)) {
30515
30626
  setAgentStore(new MemoryStore(dbPath));
30516
30627
  }
30517
30628
  const agent = await getAgent(agentId);
@@ -30528,6 +30639,57 @@ async function cmdRun(args) {
30528
30639
  process.exit(1);
30529
30640
  }
30530
30641
  }
30642
+ async function cmdSkill(args) {
30643
+ const sub = args[0]?.toLowerCase();
30644
+ const rest = args.slice(1);
30645
+ if (sub === "install") {
30646
+ await cmdSkillInstall(rest);
30647
+ return;
30648
+ }
30649
+ if (!sub || sub === "help" || sub === "-h" || sub === "--help") {
30650
+ console.log("Usage: sulala skill install <slug>");
30651
+ console.log(" Install a skill from the store by slug (e.g. crm-hubspot).");
30652
+ console.log(" Installs the latest version; you can pin a specific version in your skill config if needed.");
30653
+ console.log("");
30654
+ console.log("Example: sulala skill install crm-hubspot");
30655
+ return;
30656
+ }
30657
+ console.error(`Unknown subcommand: skill ${sub}`);
30658
+ console.error("Usage: sulala skill install <slug>");
30659
+ process.exit(1);
30660
+ }
30661
+ async function cmdSkillInstall(args) {
30662
+ const slug = args[0]?.trim();
30663
+ if (!slug) {
30664
+ console.error("Usage: sulala skill install <slug>");
30665
+ console.error("Example: sulala skill install crm-hubspot");
30666
+ process.exit(1);
30667
+ }
30668
+ const { skills, storeBase } = await getStoreRegistry();
30669
+ if (!storeBase) {
30670
+ console.error("Could not reach the skills store. Check config (skills_registry_url) or run 'sulala onboard' first.");
30671
+ process.exit(1);
30672
+ }
30673
+ const entry = skills.find((s) => s.slug === slug);
30674
+ if (!entry) {
30675
+ console.error(`Skill '${slug}' not found in the store. Check the slug at hub.sulala.ai or install from the dashboard (Skills page).`);
30676
+ process.exit(1);
30677
+ }
30678
+ const downloadUrl = entry.downloadUrl ?? `${storeBase}/api/sulalahub/skills/${encodeURIComponent(slug)}/download`;
30679
+ const version2 = entry.version;
30680
+ try {
30681
+ const { id } = await installSkillFromUrl(downloadUrl, slug, {
30682
+ version: version2 ?? undefined,
30683
+ source: "hub"
30684
+ });
30685
+ const verStr = version2 ? ` (v${version2})` : "";
30686
+ console.log(`Installed ${id}${verStr}. Add it to your agent in the dashboard (Edit agent \u2192 Skills).`);
30687
+ } catch (err) {
30688
+ const msg = err instanceof Error ? err.message : String(err);
30689
+ console.error("Install failed:", msg);
30690
+ process.exit(1);
30691
+ }
30692
+ }
30531
30693
  async function main() {
30532
30694
  if (!process.versions.bun) {
30533
30695
  console.error("Sulala CLI requires Bun. Use: bun run src/cli.ts");
@@ -30557,6 +30719,9 @@ async function main() {
30557
30719
  case "run":
30558
30720
  await cmdRun(rest);
30559
30721
  break;
30722
+ case "skill":
30723
+ await cmdSkill(rest);
30724
+ break;
30560
30725
  case "dashboard-token":
30561
30726
  await cmdDashboardToken(rest);
30562
30727
  break;