@sage-protocol/cli 0.4.1 → 0.4.3

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.
@@ -59,6 +59,7 @@ const {
59
59
  getToolMeta,
60
60
  } = require('./services/mcp/sage-tool-registry');
61
61
  const { createToolDispatcher } = require('./services/mcp/tool-dispatcher');
62
+ const ProjectContextService = require('./services/project-context');
62
63
  const { createProposalLister } = require('./services/mcp/proposal-lister');
63
64
  const fs = require('fs');
64
65
  const path = require('path');
@@ -84,6 +85,18 @@ class SageMCPServer {
84
85
  };
85
86
 
86
87
  this.tools = [
88
+ {
89
+ name: 'get_project_context',
90
+ description: `Get context about the current Sage project (root, DAO, counts, etc).
91
+ When to use:
92
+ - At the start of a session to understand where you are
93
+ - Before suggesting tools to check if workspace/wallet is available`,
94
+ inputSchema: {
95
+ type: 'object',
96
+ properties: {},
97
+ required: []
98
+ }
99
+ },
87
100
  {
88
101
  name: 'suggest_sage_tools',
89
102
  description: `Suggest the most relevant Sage MCP tools for a given goal.
@@ -255,7 +268,7 @@ Next steps:
255
268
  properties: {
256
269
  query: { type: 'string', description: 'Free-text search applied to names, descriptions, and tags' },
257
270
  source: { type: 'string', enum: ['local', 'onchain', 'all'], description: 'Data source preference (default: all)' },
258
- subdao: { type: 'string', description: 'Optional SubDAO address when source=onchain' },
271
+ subdao: { type: 'string', description: 'Optional DAO address when source=onchain' },
259
272
  tags: { type: 'array', items: { type: 'string' }, description: 'Optional tag filters (matches any tag)' },
260
273
  includeContent: { type: 'boolean', description: 'Include raw prompt content when supported (default: false)' },
261
274
  page: { type: 'number', description: 'Results page (default: 1)' },
@@ -349,7 +362,7 @@ Next steps:
349
362
  },
350
363
  {
351
364
  name: 'search_onchain_prompts',
352
- description: 'Search for prompts directly on-chain from LibraryRegistry and SubDAO registries',
365
+ description: 'Search for prompts directly on-chain from LibraryRegistry and DAO registries',
353
366
  inputSchema: {
354
367
  type: 'object',
355
368
  properties: {
@@ -359,7 +372,7 @@ Next steps:
359
372
  },
360
373
  subdao: {
361
374
  type: 'string',
362
- description: 'Filter by specific SubDAO address (optional)'
375
+ description: 'Filter by specific DAO address (optional)'
363
376
  },
364
377
  tags: {
365
378
  type: 'array',
@@ -503,13 +516,13 @@ Next steps:
503
516
  },
504
517
  {
505
518
  name: 'list_subdaos',
506
- description: 'List all available SubDAOs in the Sage Protocol',
519
+ description: 'List all available DAOs in the Sage Protocol',
507
520
  inputSchema: {
508
521
  type: 'object',
509
522
  properties: {
510
523
  limit: {
511
524
  type: 'number',
512
- description: 'Maximum number of SubDAOs to return (default: 20)'
525
+ description: 'Maximum number of DAOs to return (default: 20)'
513
526
  }
514
527
  },
515
528
  required: []
@@ -531,10 +544,10 @@ Next steps:
531
544
  },
532
545
  {
533
546
  name: 'refresh_library_bindings',
534
- description: 'Refresh cached per-library registry mappings (optional: target a specific SubDAO)',
547
+ description: 'Refresh cached per-library registry mappings (optional: target a specific DAO)',
535
548
  inputSchema: {
536
549
  type: 'object',
537
- properties: { subdao: { type: 'string', description: 'Optional SubDAO address (0x...)' } },
550
+ properties: { subdao: { type: 'string', description: 'Optional DAO address (0x...)' } },
538
551
  required: []
539
552
  }
540
553
  },
@@ -558,10 +571,10 @@ Next steps:
558
571
  },
559
572
  {
560
573
  name: 'list_subdao_libraries',
561
- description: 'List per-library PromptRegistry mappings for a SubDAO',
574
+ description: 'List per-library PromptRegistry mappings for a DAO',
562
575
  inputSchema: {
563
576
  type: 'object',
564
- properties: { subdao: { type: 'string', description: 'SubDAO address (0x...)' } },
577
+ properties: { subdao: { type: 'string', description: 'DAO address (0x...)' } },
565
578
  required: ['subdao']
566
579
  }
567
580
  },
@@ -589,13 +602,13 @@ Next steps:
589
602
  },
590
603
  {
591
604
  name: 'propose_manifest',
592
- description: `Generate the proposal payload and CLI commands for a SubDAO update.
605
+ description: `Generate the proposal payload and CLI commands for a DAO governance update.
593
606
  Note: This tool does NOT sign transactions. It generates the hex data and CLI commands for you to execute in your terminal.`,
594
607
  inputSchema: {
595
608
  type: 'object',
596
609
  properties: {
597
610
  cid: { type: 'string', description: 'IPFS CID of the manifest' },
598
- subdao: { type: 'string', description: 'SubDAO address' },
611
+ subdao: { type: 'string', description: 'DAO address' },
599
612
  description: { type: 'string', description: 'Proposal description' },
600
613
  promptCount: { type: 'number', description: 'Number of prompts (optional override)' },
601
614
  manifest: { type: 'object', description: 'Optional manifest object for context' }
@@ -639,11 +652,11 @@ Note: This tool does NOT sign transactions. It generates the hex data and CLI co
639
652
  },
640
653
  {
641
654
  name: 'list_proposals',
642
- description: 'List active proposals for a SubDAO',
655
+ description: 'List active governance proposals for a DAO',
643
656
  inputSchema: {
644
657
  type: 'object',
645
658
  properties: {
646
- subdao: { type: 'string', description: 'SubDAO address' },
659
+ subdao: { type: 'string', description: 'DAO address' },
647
660
  state: { type: 'string', description: 'Filter by state (Active, Executed, etc)' },
648
661
  limit: { type: 'number', description: 'Max items (default 10)' }
649
662
  },
@@ -662,7 +675,7 @@ Note: This tool does NOT sign transactions. It prepares everything so you can ex
662
675
  type: 'object',
663
676
  properties: {
664
677
  manifest: { type: 'object', description: 'Manifest JSON' },
665
- subdao: { type: 'string', description: 'Optional SubDAO address for hints' },
678
+ subdao: { type: 'string', description: 'Optional DAO address for hints' },
666
679
  description: { type: 'string', description: 'Optional description override' },
667
680
  dry_run: { type: 'boolean', description: 'Validate and build proposal payload without uploading to IPFS (no network calls)' }
668
681
  },
@@ -813,7 +826,7 @@ Note: This tool does NOT sign transactions. It prepares everything so you can ex
813
826
  },
814
827
  {
815
828
  name: 'suggest_subdaos_for_library',
816
- description: 'Suggest SubDAOs that might be a good fit for publishing a given local library, and provide CLI workflows for creating your own SubDAO and pushing the library.',
829
+ description: 'Suggest DAOs that might be a good fit for publishing a given local library, and provide CLI workflows for creating your own DAO and pushing the library.',
817
830
  inputSchema: {
818
831
  type: 'object',
819
832
  properties: {
@@ -823,7 +836,7 @@ Note: This tool does NOT sign transactions. It prepares everything so you can ex
823
836
  },
824
837
  limit: {
825
838
  type: 'number',
826
- description: 'Max SubDAOs to return (default 5)',
839
+ description: 'Max DAOs to return (default 5)',
827
840
  default: 5
828
841
  },
829
842
  mode_filter: {
@@ -848,36 +861,15 @@ Note: This tool does NOT sign transactions. It prepares everything so you can ex
848
861
  },
849
862
  target: {
850
863
  type: 'string',
851
- description: 'Target SubDAO address or "auto" to pick a likely candidate',
864
+ description: 'Target DAO address or "auto" to pick a likely candidate',
852
865
  default: 'auto'
853
866
  }
854
867
  },
855
868
  required: ['library']
856
869
  }
857
870
  },
858
- {
859
- name: 'list_workspace_skills',
860
- description: 'List local workspace skills from prompts/ (e.g. prompts/skills/*.md)',
861
- inputSchema: {
862
- type: 'object',
863
- properties: {},
864
- required: []
865
- }
866
- },
867
- {
868
- name: 'get_workspace_skill',
869
- description: 'Load a workspace skill by key and return its content',
870
- inputSchema: {
871
- type: 'object',
872
- properties: {
873
- key: {
874
- type: 'string',
875
- description: 'Skill key relative to prompts dir (e.g. skills/backend-dev-guidelines)'
876
- }
877
- },
878
- required: ['key']
879
- }
880
- }
871
+ // NOTE: list_workspace_skills and get_workspace_skill have been removed.
872
+ // Use list_prompts({ kind: 'skill' }) and get_prompt({ key }) instead.
881
873
  ];
882
874
 
883
875
  // Structured logger to stderr (stdout must remain JSON-RPC only)
@@ -905,12 +897,19 @@ Note: This tool does NOT sign transactions. It prepares everything so you can ex
905
897
  'function getLibraryRegistry(string) view returns (address)'
906
898
  ];
907
899
 
908
- // Prefer subgraph endpoint from several env keys (works with app config)
909
- this.subgraphUrl = process.env.SUBGRAPH_URL || process.env.NEXT_PUBLIC_GRAPH_ENDPOINT || process.env.NEXT_PUBLIC_SUBGRAPH_URL || '';
900
+ // Prefer subgraph endpoint from config (includes default Goldsky endpoint)
901
+ const config = require('./config');
902
+ this.subgraphUrl = config.resolveSubgraphUrl ? config.resolveSubgraphUrl() : (
903
+ process.env.SUBGRAPH_URL || process.env.NEXT_PUBLIC_GRAPH_ENDPOINT || process.env.NEXT_PUBLIC_SUBGRAPH_URL ||
904
+ 'https://api.goldsky.com/api/public/project_cmhxp0fppsbdd01q56xp2gqw9/subgraphs/sxxx-protocol/1.0.2/gn'
905
+ );
910
906
 
911
907
  // Local library helper for unified search
912
908
  this.libraryManager = new LibraryManager();
913
909
 
910
+ // Project context service for get_project_context tool
911
+ this.projectContextService = new ProjectContextService();
912
+
914
913
  // Simple rate limiter (token bucket per tool)
915
914
  this.rateConfig = { capacity: 20, refillPerSec: 0.33 }; // ~20/min
916
915
  this.rateLimiter = createRateLimiter(this.rateConfig);
@@ -1104,8 +1103,6 @@ Note: This tool does NOT sign transactions. It prepares everything so you can ex
1104
1103
  'tool:list_proposals': (params) => this.listProposals(params),
1105
1104
  'tool:publish_manifest_flow': (params) => this.publishManifestFlow(params),
1106
1105
  'tool:refresh_library_bindings': (params) => this.refreshLibraryBindings(params),
1107
- 'tool:list_workspace_skills': (params) => this.listWorkspaceSkills(params),
1108
- 'tool:get_workspace_skill': (params) => this.getWorkspaceSkill(params),
1109
1106
  'tool:update_library_metadata': (params) => this.updateLibraryMetadata(params),
1110
1107
  'tool:bulk_update_prompts': (params) => this.bulkUpdatePrompts(params),
1111
1108
  'tool:list_templates': (params) => this.listTemplates(params),
@@ -1115,6 +1112,7 @@ Note: This tool does NOT sign transactions. It prepares everything so you can ex
1115
1112
  'tool:suggest_subdaos_for_library': (params) => this.suggestSubdaosForLibrary(params),
1116
1113
  'tool:generate_publishing_commands': (params) => this.generatePublishingCommands(params),
1117
1114
  'tool:suggest_sage_tools': (params) => this.suggestSageTools(params),
1115
+ 'tool:get_project_context': (params) => this.getProjectContext(params),
1118
1116
  });
1119
1117
 
1120
1118
  const toolHandlers = {
@@ -1282,93 +1280,9 @@ Note: This tool does NOT sign transactions. It prepares everything so you can ex
1282
1280
  }
1283
1281
 
1284
1282
  // ───────────────────────── Workspace skills (repo-tied skills) ─────────────────────────
1285
-
1286
- /**
1287
- * List skills defined in the current project's prompt workspace.
1288
- * Convention:
1289
- * - Workspace file: .sage/workspace.json (optional)
1290
- * - Prompts dir: workspace.promptsDir or "prompts"
1291
- * - Skill files live under promptsDir/skills/*.md
1292
- */
1293
- listWorkspaceSkills() {
1294
- try {
1295
- const { readWorkspace, DEFAULT_DIR } = require('./services/prompts/workspace');
1296
- const { findWorkspaceSkills } = require('./services/skills/discovery');
1297
- const ws = readWorkspace() || {};
1298
- const promptsDir = ws.promptsDir || DEFAULT_DIR || 'prompts';
1299
- const results = findWorkspaceSkills({ promptsDir });
1300
- if (!results.length) {
1301
- return {
1302
- content: [
1303
- {
1304
- type: 'text',
1305
- text: 'No skills found. Create prompts/skills/<name>.md or prompts/skills/<name>/SKILL.md to define skills for this repo.',
1306
- },
1307
- { type: 'text', text: '```json\n' + JSON.stringify({ skills: [] }, null, 2) + '\n```' },
1308
- ],
1309
- };
1310
- }
1311
- const textLines = results
1312
- .map(
1313
- (s, idx) =>
1314
- `${idx + 1}. **${s.name}** (${s.key})\n šŸ“ ${path.relative(process.cwd(), s.path)}${s.summary ? `\n šŸ“ ${s.summary}` : ''
1315
- }${s.tags && s.tags.length ? `\n šŸ”– ${s.tags.join(', ')}` : ''}`,
1316
- )
1317
- .join('\n\n');
1318
- return {
1319
- content: [
1320
- { type: 'text', text: `Workspace skills (${results.length})\n\n${textLines}` },
1321
- { type: 'text', text: '```json\n' + JSON.stringify({ skills: results }, null, 2) + '\n```' },
1322
- ],
1323
- };
1324
- } catch (error) {
1325
- return {
1326
- content: [{ type: 'text', text: `Error listing workspace skills: ${error.message}` }],
1327
- };
1328
- }
1329
- }
1330
-
1331
- /**
1332
- * Load a workspace skill by key and return its content.
1333
- * Key is relative to prompts dir, e.g. "skills/backend-dev-guidelines".
1334
- */
1335
- getWorkspaceSkill({ key }) {
1336
- try {
1337
- if (!key || !String(key).trim()) {
1338
- return { content: [{ type: 'text', text: 'get_workspace_skill: key is required' }] };
1339
- }
1340
- const { readWorkspace, DEFAULT_DIR } = require('./services/prompts/workspace');
1341
- const { resolveSkillFileByKey } = require('./services/skills/discovery');
1342
- const ws = readWorkspace() || {};
1343
- const promptsDir = ws.promptsDir || DEFAULT_DIR || 'prompts';
1344
- const safeKey = String(key).trim().replace(/^\/+/, '').replace(/\.md$/i, '');
1345
- const resolved = resolveSkillFileByKey({ promptsDir, key: safeKey });
1346
- if (!resolved || !fs.existsSync(resolved.path)) {
1347
- const expectedFlat = path.join(process.cwd(), promptsDir, `${safeKey}.md`);
1348
- const expectedDir = path.join(process.cwd(), promptsDir, safeKey, 'SKILL.md');
1349
- return {
1350
- content: [
1351
- {
1352
- type: 'text',
1353
- text: `Workspace skill not found for key '${safeKey}'. Expected at ${path.relative(process.cwd(), expectedFlat)} or ${path.relative(process.cwd(), expectedDir)}`,
1354
- },
1355
- ],
1356
- };
1357
- }
1358
- const body = fs.readFileSync(resolved.path, 'utf8');
1359
- return {
1360
- content: [
1361
- {
1362
- type: 'text',
1363
- text: `Loaded workspace skill '${safeKey}' from ${path.relative(process.cwd(), resolved.path)}.\n\n${body}`,
1364
- },
1365
- { type: 'text', text: '```json\n' + JSON.stringify({ key: safeKey, path: resolved.path, baseDir: resolved.baseDir, body }, null, 2) + '\n```' },
1366
- ],
1367
- };
1368
- } catch (error) {
1369
- return { content: [{ type: 'text', text: `Error loading workspace skill: ${error.message}` }] };
1370
- }
1371
- }
1283
+ // NOTE: listWorkspaceSkills and getWorkspaceSkill have been removed.
1284
+ // Use list_prompts({ kind: 'skill' }) and get_prompt({ key }) instead.
1285
+ // ─────────────────────────────────────────────────────────────────────────────────────────
1372
1286
 
1373
1287
  async getPromptByName(params = {}) {
1374
1288
  const name = String(params.name || '').trim();
@@ -1461,38 +1375,88 @@ Note: This tool does NOT sign transactions. It prepares everything so you can ex
1461
1375
  return this.searchPromptsUnifiedHandler(options);
1462
1376
  }
1463
1377
 
1464
- async listPrompts({ source = 'local', library = '', limit = 20 } = {}) {
1378
+ async listPrompts({ source = 'local', library = '', limit = 20, kind, publishable } = {}) {
1465
1379
  try {
1466
- const results = await this.searchPromptsUnifiedHandler({
1467
- query: '',
1468
- source,
1469
- includeContent: false,
1470
- page: 1,
1471
- pageSize: limit
1472
- });
1473
-
1474
- if (library && results?.content?.[1]?.text) {
1475
- const jsonMatch = results.content[1].text.match(/```json\n([\s\S]+)\n```/);
1476
- if (jsonMatch) {
1477
- const data = JSON.parse(jsonMatch[1]);
1478
- const libraryLower = library.toLowerCase();
1479
- data.results = data.results.filter(r =>
1480
- r.library?.name?.toLowerCase().includes(libraryLower) ||
1481
- r.library?.cid?.toLowerCase().includes(libraryLower)
1482
- );
1483
- data.total = data.results.length;
1380
+ // If source is 'workspace' or we want to include workspace artifacts, use ArtifactManager
1381
+ const includeWorkspace = source === 'local' || source === 'workspace' || source === 'all';
1382
+
1383
+ let workspaceResults = [];
1384
+ if (includeWorkspace) {
1385
+ const ArtifactManager = require('./services/artifact-manager');
1386
+ const artifactManager = new ArtifactManager();
1387
+ const filter = {};
1388
+ if (kind) filter.kind = kind;
1389
+ if (publishable !== undefined) filter.publishable = publishable;
1390
+
1391
+ const artifacts = await artifactManager.listArtifacts(filter);
1392
+ workspaceResults = artifacts.map(a => ({
1393
+ resultType: 'prompt',
1394
+ origin: 'workspace',
1395
+ source: 'Workspace',
1396
+ key: a.key,
1397
+ name: a.meta?.title || a.key,
1398
+ description: a.meta?.summary || '',
1399
+ tags: a.meta?.tags || [],
1400
+ content: a.body,
1401
+ kind: a.kind,
1402
+ publishable: a.publishable,
1403
+ publishing: a.publishing,
1404
+ targets: a.targets,
1405
+ tools: a.meta?.tools || [],
1406
+ filePath: a.filePath,
1407
+ }));
1408
+ }
1409
+
1410
+ // Also get library prompts if not workspace-only
1411
+ let libraryResults = [];
1412
+ if (source !== 'workspace') {
1413
+ const results = await this.searchPromptsUnifiedHandler({
1414
+ query: '',
1415
+ source: source === 'workspace' ? 'local' : source,
1416
+ includeContent: false,
1417
+ page: 1,
1418
+ pageSize: limit
1419
+ });
1484
1420
 
1485
- const formatted = this.formatUnifiedResults(data.results, { total: data.total, page: 1, pageSize: limit });
1486
- return {
1487
- content: [
1488
- { type: 'text', text: formatted },
1489
- { type: 'text', text: '```json\n' + JSON.stringify(data, null, 2) + '\n```' }
1490
- ]
1491
- };
1421
+ if (results?.content?.[1]?.text) {
1422
+ const jsonMatch = results.content[1].text.match(/```json\n([\s\S]+)\n```/);
1423
+ if (jsonMatch) {
1424
+ const data = JSON.parse(jsonMatch[1]);
1425
+ libraryResults = (data.results || []).map(r => ({
1426
+ ...r,
1427
+ kind: r.kind || 'prompt',
1428
+ publishable: true, // library prompts are published
1429
+ publishing: { status: 'published' }
1430
+ }));
1431
+ }
1492
1432
  }
1493
1433
  }
1494
1434
 
1495
- return results;
1435
+ // Merge and filter
1436
+ let allResults = [...workspaceResults, ...libraryResults];
1437
+
1438
+ // Filter by library if specified
1439
+ if (library) {
1440
+ const libraryLower = library.toLowerCase();
1441
+ allResults = allResults.filter(r =>
1442
+ r.library?.name?.toLowerCase().includes(libraryLower) ||
1443
+ r.library?.cid?.toLowerCase().includes(libraryLower) ||
1444
+ r.origin === 'workspace' // workspace prompts don't have library
1445
+ );
1446
+ }
1447
+
1448
+ // Apply limit
1449
+ allResults = allResults.slice(0, limit);
1450
+
1451
+ const data = { results: allResults, total: allResults.length, page: 1, pageSize: limit };
1452
+ const formatted = this.formatUnifiedResults(allResults, { total: data.total, page: 1, pageSize: limit });
1453
+
1454
+ return {
1455
+ content: [
1456
+ { type: 'text', text: formatted },
1457
+ { type: 'text', text: '```json\n' + JSON.stringify(data, null, 2) + '\n```' }
1458
+ ]
1459
+ };
1496
1460
  } catch (error) {
1497
1461
  return { content: [{ type: 'text', text: `Error listing prompts: ${error.message}` }] };
1498
1462
  }
@@ -1504,6 +1468,43 @@ Note: This tool does NOT sign transactions. It prepares everything so you can ex
1504
1468
  return { content: [{ type: 'text', text: 'Error: key parameter is required' }] };
1505
1469
  }
1506
1470
 
1471
+ // First try workspace artifacts (ArtifactManager)
1472
+ const ArtifactManager = require('./services/artifact-manager');
1473
+ const artifactManager = new ArtifactManager();
1474
+ const artifact = await artifactManager.getArtifact(key);
1475
+
1476
+ if (artifact) {
1477
+ const prompt = {
1478
+ resultType: 'prompt',
1479
+ origin: 'workspace',
1480
+ source: 'Workspace',
1481
+ key: artifact.key,
1482
+ name: artifact.meta?.title || artifact.key,
1483
+ description: artifact.meta?.summary || '',
1484
+ tags: artifact.meta?.tags || [],
1485
+ content: artifact.body,
1486
+ kind: artifact.kind,
1487
+ publishable: artifact.publishable,
1488
+ publishing: artifact.publishing,
1489
+ targets: artifact.targets,
1490
+ tools: artifact.meta?.tools || [],
1491
+ filePath: artifact.filePath,
1492
+ };
1493
+
1494
+ const kindBadge = prompt.kind !== 'prompt' ? ` [${prompt.kind}]` : '';
1495
+ const publishBadge = prompt.publishable ? '' : ' (local only)';
1496
+ const toolsLine = prompt.tools?.length ? `\nšŸ› ļø Tools: ${prompt.tools.join(', ')}` : '';
1497
+ const text = `**${prompt.name}**${kindBadge}${publishBadge}\n\nšŸ”‘ Key: ${prompt.key}\nšŸ“‚ Source: Workspace\nšŸ“„ ${prompt.description || 'No description'}\nšŸ·ļø Tags: ${prompt.tags?.join(', ') || 'None'}${toolsLine}\nšŸ“Œ Kind: ${prompt.kind}\nšŸ“¤ Publishable: ${prompt.publishable}\nšŸ“Š Status: ${prompt.publishing?.status || 'unknown'}\n\n**Content:**\n\`\`\`\n${prompt.content || '(No content)'}\n\`\`\``;
1498
+
1499
+ return {
1500
+ content: [
1501
+ { type: 'text', text },
1502
+ { type: 'text', text: '```json\n' + JSON.stringify(prompt, null, 2) + '\n```' }
1503
+ ]
1504
+ };
1505
+ }
1506
+
1507
+ // Fall back to library search
1507
1508
  const results = await this.searchPromptsUnifiedHandler({
1508
1509
  query: key,
1509
1510
  source: 'local',
@@ -1532,7 +1533,12 @@ Note: This tool does NOT sign transactions. It prepares everything so you can ex
1532
1533
  return { content: [{ type: 'text', text: `No prompt found with key "${key}"` }] };
1533
1534
  }
1534
1535
 
1535
- const text = `**${prompt.name}**\n\nšŸ”‘ Key: ${prompt.key}\nšŸ“š Library: ${prompt.library?.name || 'Unknown'}\nšŸ“„ ${prompt.description || 'No description'}\nšŸ·ļø Tags: ${prompt.tags?.join(', ') || 'None'}\n\n**Content:**\n\`\`\`\n${prompt.content || '(No content)'}\n\`\`\``;
1536
+ // Add kind/publishable for library prompts
1537
+ prompt.kind = prompt.kind || 'prompt';
1538
+ prompt.publishable = true;
1539
+ prompt.publishing = { status: 'published' };
1540
+
1541
+ const text = `**${prompt.name}**\n\nšŸ”‘ Key: ${prompt.key}\nšŸ“š Library: ${prompt.library?.name || 'Unknown'}\nšŸ“„ ${prompt.description || 'No description'}\nšŸ·ļø Tags: ${prompt.tags?.join(', ') || 'None'}\nšŸ“Œ Kind: ${prompt.kind}\n\n**Content:**\n\`\`\`\n${prompt.content || '(No content)'}\n\`\`\``;
1536
1542
 
1537
1543
  return {
1538
1544
  content: [
@@ -1665,6 +1671,61 @@ Note: This tool does NOT sign transactions. It prepares everything so you can ex
1665
1671
  }
1666
1672
  }
1667
1673
 
1674
+ async getProjectContext(params) {
1675
+ try {
1676
+ const ctx = await this.projectContextService.getProjectContext(params || {});
1677
+ const lines = [];
1678
+ lines.push('āœ… Project context');
1679
+ lines.push(`Project root: ${ctx.projectRoot || 'not found'}`);
1680
+ lines.push(`Prompts dir: ${ctx.promptsDir || 'prompts'}`);
1681
+ if (ctx.subdao) {
1682
+ lines.push(`SubDAO: ${ctx.subdao}`);
1683
+ }
1684
+ if (ctx.registry) {
1685
+ lines.push(`Library registry: ${ctx.registry}`);
1686
+ }
1687
+ if (ctx.network) {
1688
+ lines.push(`Network: ${ctx.network}`);
1689
+ }
1690
+ if (ctx.rpcUrl) {
1691
+ lines.push(`RPC URL: ${ctx.rpcUrl}`);
1692
+ }
1693
+ if (ctx.workspace) {
1694
+ lines.push(
1695
+ `Workspace artifacts: prompts=${ctx.workspace.promptCount}, skills=${ctx.workspace.skillCount}, snippets=${ctx.workspace.snippetCount}, total=${ctx.workspace.total}`
1696
+ );
1697
+ }
1698
+ if (ctx.agentSurfaces) {
1699
+ const surfaces = [];
1700
+ if (ctx.agentSurfaces.cursorRulesDir) {
1701
+ surfaces.push(`cursor rules at ${ctx.agentSurfaces.cursorRulesDir}`);
1702
+ }
1703
+ if (ctx.agentSurfaces.claudeManifest) {
1704
+ surfaces.push(`CLAUDE manifest at ${ctx.agentSurfaces.claudeManifest}`);
1705
+ }
1706
+ if (ctx.agentSurfaces.copilotPromptsDir) {
1707
+ surfaces.push(`Copilot prompts at ${ctx.agentSurfaces.copilotPromptsDir}`);
1708
+ }
1709
+ if (Array.isArray(ctx.agentSurfaces.agentsFiles) && ctx.agentSurfaces.agentsFiles.length) {
1710
+ surfaces.push(`AGENTS files: ${ctx.agentSurfaces.agentsFiles.join(', ')}`);
1711
+ }
1712
+ if (surfaces.length) {
1713
+ lines.push('Agent surfaces:');
1714
+ surfaces.forEach((s) => lines.push(`- ${s}`));
1715
+ }
1716
+ }
1717
+
1718
+ return {
1719
+ content: [
1720
+ { type: 'text', text: lines.join('\n') },
1721
+ { type: 'text', text: '```json\n' + JSON.stringify(ctx, null, 2) + '\n```' },
1722
+ ],
1723
+ };
1724
+ } catch (error) {
1725
+ return { content: [{ type: 'text', text: `Error retrieving project context: ${error.message}` }] };
1726
+ }
1727
+ }
1728
+
1668
1729
  async listTemplates(params = {}) {
1669
1730
  try {
1670
1731
  const result = this.templateManager.listTemplates(params || {});
@@ -3207,7 +3268,7 @@ Use \`help(topic="create")\` for more details.
3207
3268
  description: t.description,
3208
3269
  recommended_slots: t.recommended_slots?.map(s => s.label) || []
3209
3270
  }));
3210
-
3271
+
3211
3272
  return {
3212
3273
  content: [
3213
3274
  {
@@ -3234,7 +3295,7 @@ Use \`help(topic="create")\` for more details.
3234
3295
 
3235
3296
  // 1. Validate Template
3236
3297
  const templateDef = templates[template] || templates['custom'];
3237
-
3298
+
3238
3299
  // 2. Build Prompt (One-Shot)
3239
3300
  // Note: run_persona_interview assumes the *agent* has already done the planning/filling.
3240
3301
  // It bypasses the SlotPlanner and just builds the artifact.
@@ -3242,7 +3303,7 @@ Use \`help(topic="create")\` for more details.
3242
3303
  // We need the full slot definitions to build dynamic sections.
3243
3304
  // Since we skipped the planner, we'll use the template's recommended slots as the definition.
3244
3305
  const slots = templateDef.recommended_slots || [];
3245
-
3306
+
3246
3307
  const systemPrompt = builder.buildSystemPrompt(template, slots, answers || {});
3247
3308
 
3248
3309
  let result = {
@@ -3255,17 +3316,17 @@ Use \`help(topic="create")\` for more details.
3255
3316
  if (save) {
3256
3317
  const persistence = new MetapromptPersistence(config);
3257
3318
  const slug = saveKey || `${template}-${Date.now().toString().slice(-6)}`;
3258
-
3319
+
3259
3320
  // Save dummy history so the artifact exists
3260
3321
  const historyPaths = persistence.saveMetaprompt(slug, {
3261
3322
  templateKey: template,
3262
3323
  transcript: [{ role: 'system', content: 'Generated via MCP run_persona_interview (one-shot).' }],
3263
3324
  answers: answers || {}
3264
3325
  });
3265
-
3326
+
3266
3327
  // Save Skill
3267
3328
  const skillPath = persistence.saveSkill(slug, systemPrompt);
3268
-
3329
+
3269
3330
  result.slug = slug;
3270
3331
  result.paths = {
3271
3332
  metaprompt: historyPaths.metaprompt,