@sulala/agent 0.1.13 → 0.1.14

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 (102) hide show
  1. package/README.md +3 -3
  2. package/dashboard/dist/assets/index-DegBJNv6.css +1 -0
  3. package/dashboard/dist/assets/index-pVHpAj3h.js +83 -0
  4. package/dashboard/dist/index.html +2 -2
  5. package/dist/agent/loop.d.ts.map +1 -1
  6. package/dist/agent/loop.js +21 -6
  7. package/dist/agent/loop.js.map +1 -1
  8. package/dist/agent/skill-generate.d.ts +1 -1
  9. package/dist/agent/skill-generate.d.ts.map +1 -1
  10. package/dist/agent/skill-generate.js +10 -3
  11. package/dist/agent/skill-generate.js.map +1 -1
  12. package/dist/agent/skill-install.d.ts +12 -1
  13. package/dist/agent/skill-install.d.ts.map +1 -1
  14. package/dist/agent/skill-install.js +130 -15
  15. package/dist/agent/skill-install.js.map +1 -1
  16. package/dist/agent/skills.d.ts +4 -3
  17. package/dist/agent/skills.d.ts.map +1 -1
  18. package/dist/agent/skills.js +53 -25
  19. package/dist/agent/skills.js.map +1 -1
  20. package/dist/agent/tool/spec-loader.d.ts +7 -0
  21. package/dist/agent/tool/spec-loader.d.ts.map +1 -0
  22. package/dist/agent/tool/spec-loader.js +540 -0
  23. package/dist/agent/tool/spec-loader.js.map +1 -0
  24. package/dist/agent/tools.d.ts.map +1 -1
  25. package/dist/agent/tools.integrations.test.js +4 -5
  26. package/dist/agent/tools.integrations.test.js.map +1 -1
  27. package/dist/agent/tools.js +144 -367
  28. package/dist/agent/tools.js.map +1 -1
  29. package/dist/ai/orchestrator.d.ts.map +1 -1
  30. package/dist/ai/orchestrator.js +82 -17
  31. package/dist/ai/orchestrator.js.map +1 -1
  32. package/dist/cli.d.ts +4 -1
  33. package/dist/cli.d.ts.map +1 -1
  34. package/dist/cli.js +16 -8
  35. package/dist/cli.js.map +1 -1
  36. package/dist/config.d.ts.map +1 -1
  37. package/dist/config.js +20 -5
  38. package/dist/config.js.map +1 -1
  39. package/dist/db/index.d.ts +14 -7
  40. package/dist/db/index.d.ts.map +1 -1
  41. package/dist/db/index.js +108 -30
  42. package/dist/db/index.js.map +1 -1
  43. package/dist/gateway/server.d.ts.map +1 -1
  44. package/dist/gateway/server.js +141 -15
  45. package/dist/gateway/server.js.map +1 -1
  46. package/dist/index.js +1 -1
  47. package/dist/index.js.map +1 -1
  48. package/dist/onboard-env.d.ts +1 -1
  49. package/dist/onboard-env.d.ts.map +1 -1
  50. package/dist/onboard-env.js +2 -0
  51. package/dist/onboard-env.js.map +1 -1
  52. package/dist/types.d.ts +5 -3
  53. package/dist/types.d.ts.map +1 -1
  54. package/dist/watcher/index.d.ts.map +1 -1
  55. package/dist/watcher/index.js +1 -2
  56. package/dist/watcher/index.js.map +1 -1
  57. package/package.json +4 -5
  58. package/src/index.ts +1 -1
  59. package/context/00-rules.md +0 -1
  60. package/context/airtable.md +0 -35
  61. package/context/apple-notes.md +0 -99
  62. package/context/asana.md +0 -37
  63. package/context/bluesky.md +0 -46
  64. package/context/calendar.md +0 -63
  65. package/context/country-info.md +0 -13
  66. package/context/create-skill.md +0 -128
  67. package/context/discord.md +0 -30
  68. package/context/docs.md +0 -29
  69. package/context/drive.md +0 -49
  70. package/context/dropbox.md +0 -39
  71. package/context/facebook.md +0 -47
  72. package/context/fetch-form-api.md +0 -16
  73. package/context/figma.md +0 -30
  74. package/context/files.md +0 -30
  75. package/context/git.md +0 -37
  76. package/context/github.md +0 -58
  77. package/context/gmail.md +0 -52
  78. package/context/google.md +0 -28
  79. package/context/hellohub.md +0 -29
  80. package/context/jira.md +0 -46
  81. package/context/linear.md +0 -40
  82. package/context/news.md +0 -64
  83. package/context/notion.md +0 -45
  84. package/context/portal-integrations.md +0 -42
  85. package/context/post-to-x.md +0 -50
  86. package/context/sheets.md +0 -47
  87. package/context/slack.md +0 -48
  88. package/context/slides.md +0 -35
  89. package/context/stripe.md +0 -38
  90. package/context/tes.md +0 -7
  91. package/context/test.md +0 -7
  92. package/context/weather.md +0 -32
  93. package/context/zoom.md +0 -28
  94. package/dashboard/dist/assets/index-BTx-9jCj.css +0 -1
  95. package/dashboard/dist/assets/index-B_QGQ8c_.js +0 -83
  96. package/registry/apple-notes.md +0 -99
  97. package/registry/bluesky.md +0 -34
  98. package/registry/files.md +0 -30
  99. package/registry/git.md +0 -37
  100. package/registry/news.md +0 -64
  101. package/registry/skills-registry.json +0 -46
  102. package/registry/weather.md +0 -32
@@ -11,13 +11,13 @@ import { initDb, getDb, log, insertTask, getFileStates, updateTaskStatus, setTas
11
11
  import { scheduleCronById, unscheduleJob } from '../scheduler/cron.js';
12
12
  import { getTelegramChannelState, setTelegramChannelConfig } from '../channels/telegram.js';
13
13
  import { getDiscordChannelState, setDiscordChannelConfig } from '../channels/discord.js';
14
- import { getStripeChannelState, setStripeChannelConfig } from '../channels/stripe.js';
14
+ import { getEffectiveStripeSecretKey, getStripeChannelState, setStripeChannelConfig } from '../channels/stripe.js';
15
15
  import { reloadProviders } from '../ai/orchestrator.js';
16
16
  import { runAgentTurn, runAgentTurnStream } from '../agent/loop.js';
17
17
  import { runAgentTurnWithPi, isPiAvailable } from '../agent/pi-runner.js';
18
18
  import { listTools, executeTool } from '../agent/tools.js';
19
19
  import { listSkills, getAllRequiredBins } from '../agent/skills.js';
20
- import { getRegistrySkills, getAvailableUpdates, installSkill, uninstallSkill, updateSkillsAll } from '../agent/skill-install.js';
20
+ import { getRegistrySkills, getAvailableUpdates, installSkill, uninstallSkill, updateSkillsAll, installAllSystemSkills } from '../agent/skill-install.js';
21
21
  import { getTemplates } from '../agent/skill-templates.js';
22
22
  import { generateSkillSpec, writeGeneratedSkill, WIZARD_APPS, WIZARD_TRIGGERS } from '../agent/skill-generate.js';
23
23
  import { loadSkillsConfig, saveSkillsConfig, getConfigPath, setSkillEnabled, removeSkillEntry, migrateConfigNameKeysToSlug, getOnboardingComplete, setOnboardingComplete } from '../agent/skills-config.js';
@@ -67,6 +67,18 @@ function rateLimitMiddleware(req, res, next) {
67
67
  }
68
68
  next();
69
69
  }
70
+ /** Store base URL for publishing skills (POST /api/submissions). Derived from SKILLS_REGISTRY_URL origin. */
71
+ function getStorePublishBaseUrl() {
72
+ const registryUrl = (process.env.SKILLS_REGISTRY_URL || '').trim();
73
+ if (!registryUrl)
74
+ return null;
75
+ try {
76
+ return new URL(registryUrl).origin;
77
+ }
78
+ catch {
79
+ return null;
80
+ }
81
+ }
70
82
  export function createGateway(appMount = null) {
71
83
  const app = appMount || express();
72
84
  app.use(cors());
@@ -91,10 +103,20 @@ export function createGateway(appMount = null) {
91
103
  res.status(401).json({ error: 'Invalid or missing API key' });
92
104
  });
93
105
  }
94
- /** SulalaHub: public skills registry. Set SKILLS_REGISTRY_URL to use this. */
106
+ /** SulalaHub: public skills registry. When serving, base URL for skill links is derived from SKILLS_REGISTRY_URL (origin) or host:port. */
95
107
  app.get('/api/sulalahub/registry', (_req, res) => {
96
108
  try {
97
- const baseUrl = process.env.SULALAHUB_BASE_URL || `http://${config.host}:${config.port}`;
109
+ const registryUrl = process.env.SKILLS_REGISTRY_URL?.trim();
110
+ const baseUrl = registryUrl
111
+ ? (() => {
112
+ try {
113
+ return new URL(registryUrl).origin;
114
+ }
115
+ catch {
116
+ return `http://${config.host}:${config.port}`;
117
+ }
118
+ })()
119
+ : `http://${config.host}:${config.port}`;
98
120
  const registryPath = join(registryDir, 'skills-registry.json');
99
121
  if (!existsSync(registryPath)) {
100
122
  res.json({ skills: [] });
@@ -162,12 +184,20 @@ export function createGateway(appMount = null) {
162
184
  res.status(500).json({ complete: false, error: e.message });
163
185
  }
164
186
  });
165
- /** Onboard: mark onboarding as complete. */
187
+ /** Onboard: mark onboarding as complete. Installs all system skills to workspace/skills in the background when SKILLS_REGISTRY_URL is set. */
166
188
  app.put('/api/onboard/complete', async (_req, res) => {
167
189
  try {
168
190
  setOnboardingComplete(true);
169
191
  await reloadProviders();
170
192
  res.json({ ok: true, complete: true });
193
+ installAllSystemSkills()
194
+ .then(({ installed, failed }) => {
195
+ if (installed.length)
196
+ log('gateway', 'info', `Onboard: installed ${installed.length} system skill(s): ${installed.join(', ')}`);
197
+ if (failed.length)
198
+ log('gateway', 'warn', `Onboard: failed to install ${failed.length} system skill(s): ${failed.map((f) => f.slug).join(', ')}`);
199
+ })
200
+ .catch((e) => log('gateway', 'error', `Onboard: system skills install failed: ${e.message}`));
171
201
  }
172
202
  catch (e) {
173
203
  log('gateway', 'error', e.message);
@@ -408,6 +438,9 @@ export function createGateway(appMount = null) {
408
438
  res.status(400).json({ error: result.error || 'Failed to save' });
409
439
  return;
410
440
  }
441
+ // Persist to ~/.sulala/.env so the key is visible there and survives across DB resets
442
+ const effective = getEffectiveStripeSecretKey();
443
+ writeOnboardEnvKeys({ STRIPE_SECRET_KEY: effective ?? '' });
411
444
  const state = getStripeChannelState();
412
445
  res.json(state);
413
446
  }
@@ -859,9 +892,10 @@ export function createGateway(appMount = null) {
859
892
  res.status(400).json({ error: 'slug required' });
860
893
  return;
861
894
  }
862
- const t = target === 'managed' ? 'managed' : 'workspace';
895
+ const t = target === 'user' ? 'user' : target === 'managed' ? 'managed' : 'workspace';
863
896
  const result = uninstallSkill(slug, t);
864
897
  if (result.success) {
898
+ removeSkillEntry(slug);
865
899
  res.json({ uninstalled: slug, path: result.path, target: t });
866
900
  }
867
901
  else {
@@ -873,6 +907,97 @@ export function createGateway(appMount = null) {
873
907
  res.status(500).json({ error: e.message });
874
908
  }
875
909
  });
910
+ /** Publish a user-created skill to the store (POST to store /api/submissions). No API key required by default; set STORE_PUBLISH_API_KEY if the store requires it. */
911
+ app.post('/api/agent/skills/publish', async (req, res) => {
912
+ try {
913
+ const { slug, priceIntent, intendedPriceCents } = req.body || {};
914
+ if (!slug || typeof slug !== 'string') {
915
+ res.status(400).json({ error: 'slug required' });
916
+ return;
917
+ }
918
+ const skills = listSkills(config, { includeDisabled: true });
919
+ const skill = skills.find((s) => (s.slug ?? s.filePath.split(/[/\\]/).pop()?.replace(/\.md$/, '')) === slug && s.source === 'user');
920
+ if (!skill) {
921
+ res.status(400).json({ error: 'Skill not found or not a user-created skill. Only skills in My skills can be published.' });
922
+ return;
923
+ }
924
+ const skillDir = join(config.skillsWorkspaceMyDir, slug);
925
+ const readmePath = join(skillDir, 'README.md');
926
+ const skillPath = join(skillDir, 'SKILL.md');
927
+ const mdPath = existsSync(readmePath) ? readmePath : existsSync(skillPath) ? skillPath : null;
928
+ if (!mdPath) {
929
+ res.status(400).json({ error: 'Skill README.md or SKILL.md not found' });
930
+ return;
931
+ }
932
+ const markdown = readFileSync(mdPath, 'utf8');
933
+ const toolsPath = join(skillDir, 'tools.yaml');
934
+ const toolsYaml = existsSync(toolsPath) ? readFileSync(toolsPath, 'utf8').trim() : undefined;
935
+ const storeBase = getStorePublishBaseUrl();
936
+ if (!storeBase) {
937
+ res.status(400).json({
938
+ error: 'Store URL not configured. Set SKILLS_REGISTRY_URL to your hub (e.g. https://hub.sulala.ai or http://localhost:3002).',
939
+ });
940
+ return;
941
+ }
942
+ const submitUrl = `${storeBase.replace(/\/$/, '')}/api/submissions`;
943
+ const body = {
944
+ slug,
945
+ name: skill.name,
946
+ description: skill.description,
947
+ version: skill.version || '1.0.0',
948
+ markdown,
949
+ priceIntent: priceIntent === 'paid' || priceIntent === 'free' ? priceIntent : 'free',
950
+ };
951
+ if (toolsYaml)
952
+ body.toolsYaml = toolsYaml;
953
+ if (priceIntent === 'paid' && typeof intendedPriceCents === 'number' && intendedPriceCents >= 0) {
954
+ body.intendedPriceCents = intendedPriceCents;
955
+ }
956
+ const headers = { 'Content-Type': 'application/json' };
957
+ const apiKey = (process.env.STORE_PUBLISH_API_KEY || '').trim();
958
+ if (apiKey)
959
+ headers.Authorization = `Bearer ${apiKey}`;
960
+ const resp = await fetch(submitUrl, { method: 'POST', headers, body: JSON.stringify(body) });
961
+ const data = (await resp.json().catch(() => ({})));
962
+ if (!resp.ok) {
963
+ const status = resp.status === 503 ? 503 : 400;
964
+ res.status(status).json({ error: data.error || `Store returned ${resp.status}`, githubUrl: data.githubUrl });
965
+ return;
966
+ }
967
+ if (data.ok && data.id) {
968
+ res.json({ published: true, slug, id: data.id, message: 'Submitted to the store. An admin will review it.' });
969
+ }
970
+ else {
971
+ res.json({ published: true, slug, message: 'Submitted to the store.' });
972
+ }
973
+ }
974
+ catch (e) {
975
+ log('gateway', 'error', e.message);
976
+ res.status(500).json({ error: e.message });
977
+ }
978
+ });
979
+ /** Publish status: list of user's submissions (pending/approved) from the store. Uses STORE_PUBLISH_API_KEY. */
980
+ app.get('/api/agent/skills/publish-status', async (_req, res) => {
981
+ try {
982
+ const storeBase = getStorePublishBaseUrl();
983
+ const apiKey = (process.env.STORE_PUBLISH_API_KEY || '').trim();
984
+ if (!storeBase || !apiKey) {
985
+ res.json({ submissions: [] });
986
+ return;
987
+ }
988
+ const url = `${storeBase.replace(/\/$/, '')}/api/me/submissions`;
989
+ const resp = await fetch(url, { headers: { Authorization: `Bearer ${apiKey}` } });
990
+ const data = (await resp.json().catch(() => ({})));
991
+ if (!resp.ok) {
992
+ res.json({ submissions: [] });
993
+ return;
994
+ }
995
+ res.json({ submissions: data.submissions ?? [] });
996
+ }
997
+ catch {
998
+ res.json({ submissions: [] });
999
+ }
1000
+ });
876
1001
  app.post('/api/agent/skills/install', async (req, res) => {
877
1002
  try {
878
1003
  const { slug, target, registryUrl } = req.body || {};
@@ -1241,13 +1366,9 @@ export function createGateway(appMount = null) {
1241
1366
  }
1242
1367
  }
1243
1368
  const timeoutMs = typeof timeout_ms === 'number' ? timeout_ms : config.agentTimeoutMs || 0;
1244
- const usePi = config.agentUsePi || use_pi === true || use_pi === '1';
1245
- if (usePi && !isPiAvailable()) {
1246
- res.status(503).json({
1247
- error: 'Pi runner requested but not available. Install optional deps: npm install @mariozechner/pi-agent-core @mariozechner/pi-ai @mariozechner/pi-coding-agent',
1248
- });
1249
- return;
1250
- }
1369
+ let usePi = config.agentUsePi || use_pi === true || use_pi === '1';
1370
+ if (usePi && !isPiAvailable())
1371
+ usePi = false;
1251
1372
  try {
1252
1373
  const result = await withSessionLock(id, () => usePi
1253
1374
  ? runAgentTurnWithPi({
@@ -1793,7 +1914,6 @@ export function attachWebSocket(server, onConnection = () => { }) {
1793
1914
  return { wss, broadcast };
1794
1915
  }
1795
1916
  export function startGateway() {
1796
- initDb(config.dbPath);
1797
1917
  const app = createGateway();
1798
1918
  const server = createServer(app);
1799
1919
  const { broadcast } = attachWebSocket(server, (ws) => {
@@ -1815,6 +1935,12 @@ export function startGateway() {
1815
1935
  return { app, server };
1816
1936
  }
1817
1937
  if (process.argv[1]?.includes('server')) {
1818
- startGateway();
1938
+ (async () => {
1939
+ await initDb(config.dbPath);
1940
+ startGateway();
1941
+ })().catch((err) => {
1942
+ console.error(err);
1943
+ process.exit(1);
1944
+ });
1819
1945
  }
1820
1946
  //# sourceMappingURL=server.js.map