@sage-protocol/cli 0.2.9 → 0.3.2
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/cli/commands/config.js +28 -0
- package/dist/cli/commands/doctor.js +87 -8
- package/dist/cli/commands/gov-config.js +81 -0
- package/dist/cli/commands/governance.js +152 -72
- package/dist/cli/commands/library.js +9 -0
- package/dist/cli/commands/proposals.js +187 -17
- package/dist/cli/commands/skills.js +737 -0
- package/dist/cli/commands/subdao.js +96 -132
- package/dist/cli/config/playbooks.json +62 -0
- package/dist/cli/config.js +15 -0
- package/dist/cli/governance-manager.js +25 -4
- package/dist/cli/index.js +6 -7
- package/dist/cli/library-manager.js +79 -0
- package/dist/cli/mcp-server-stdio.js +1387 -166
- package/dist/cli/schemas/manifest.schema.json +55 -0
- package/dist/cli/services/doctor/fixers.js +134 -0
- package/dist/cli/services/governance/doctor.js +140 -0
- package/dist/cli/services/governance/playbooks.js +97 -0
- package/dist/cli/services/mcp/bulk-operations.js +272 -0
- package/dist/cli/services/mcp/dependency-analyzer.js +202 -0
- package/dist/cli/services/mcp/library-listing.js +2 -2
- package/dist/cli/services/mcp/local-prompt-collector.js +1 -0
- package/dist/cli/services/mcp/manifest-downloader.js +5 -3
- package/dist/cli/services/mcp/manifest-fetcher.js +17 -1
- package/dist/cli/services/mcp/manifest-workflows.js +127 -15
- package/dist/cli/services/mcp/quick-start.js +287 -0
- package/dist/cli/services/mcp/stdio-runner.js +30 -5
- package/dist/cli/services/mcp/template-manager.js +156 -0
- package/dist/cli/services/mcp/templates/default-templates.json +84 -0
- package/dist/cli/services/mcp/tool-args-validator.js +56 -0
- package/dist/cli/services/mcp/trending-formatter.js +1 -1
- package/dist/cli/services/mcp/unified-prompt-search.js +2 -2
- package/dist/cli/services/metaprompt/designer.js +12 -5
- package/dist/cli/services/skills/discovery.js +99 -0
- package/dist/cli/services/subdao/applier.js +229 -0
- package/dist/cli/services/subdao/planner.js +142 -0
- package/dist/cli/subdao-manager.js +14 -0
- package/dist/cli/utils/aliases.js +28 -6
- package/dist/cli/utils/contract-error-decoder.js +61 -0
- package/dist/cli/utils/suggestions.js +25 -13
- package/package.json +3 -2
- package/src/schemas/manifest.schema.json +55 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { LibraryManager } = require('../../library-manager');
|
|
4
|
+
|
|
5
|
+
function createDependencyAnalyzer({
|
|
6
|
+
libraryManager = new LibraryManager(),
|
|
7
|
+
fsModule = fs,
|
|
8
|
+
pathModule = path,
|
|
9
|
+
logger = console,
|
|
10
|
+
} = {}) {
|
|
11
|
+
function resolvePinnedLibrary(libraryId) {
|
|
12
|
+
const all = libraryManager.listPinned() || [];
|
|
13
|
+
const id = String(libraryId || '').trim();
|
|
14
|
+
if (!id) {
|
|
15
|
+
throw new Error('library parameter is required');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const byCid = all.find((row) => row.cid === id);
|
|
19
|
+
if (byCid) {
|
|
20
|
+
const { manifest, path: manifestPath } = libraryManager.loadPinned(byCid.cid);
|
|
21
|
+
return { row: byCid, manifest, manifestPath };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const lower = id.toLowerCase();
|
|
25
|
+
const matches = all
|
|
26
|
+
.map((row) => {
|
|
27
|
+
try {
|
|
28
|
+
const { manifest, path: manifestPath } = libraryManager.loadPinned(row.cid);
|
|
29
|
+
const libName = String(manifest?.library?.name || row.name || '').toLowerCase();
|
|
30
|
+
return { row, manifest, manifestPath, libName };
|
|
31
|
+
} catch (e) {
|
|
32
|
+
logger?.warn?.('dep_analyzer_manifest_load_failed', { cid: row.cid, err: e.message || String(e) });
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
})
|
|
36
|
+
.filter(Boolean)
|
|
37
|
+
.filter((entry) => entry.libName === lower);
|
|
38
|
+
|
|
39
|
+
if (matches.length === 0) {
|
|
40
|
+
throw new Error(`No pinned library found matching "${libraryId}". Try using the CID from ~/.sage/libraries/*.json.`);
|
|
41
|
+
}
|
|
42
|
+
if (matches.length > 1) {
|
|
43
|
+
const cids = matches.map((m) => `${m.row.cid} (${m.manifest?.library?.name || m.row.name || 'unnamed'})`);
|
|
44
|
+
throw new Error(
|
|
45
|
+
`Ambiguous library match for "${libraryId}". Candidates:\n- ${cids.join('\n- ')}\nPass an explicit CID to disambiguate.`,
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return matches[0];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function extractVariables(content) {
|
|
53
|
+
const text = String(content || '');
|
|
54
|
+
const pattern = /\$\{([A-Za-z0-9_]+)\}/g;
|
|
55
|
+
const vars = [];
|
|
56
|
+
let match;
|
|
57
|
+
while ((match = pattern.exec(text)) !== null) {
|
|
58
|
+
if (!vars.includes(match[1])) {
|
|
59
|
+
vars.push(match[1]);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return vars;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function groupInconsistencies(variableUsage) {
|
|
66
|
+
const names = Object.keys(variableUsage || {});
|
|
67
|
+
const lowerMap = new Map();
|
|
68
|
+
for (const name of names) {
|
|
69
|
+
const base = name.toLowerCase();
|
|
70
|
+
if (!lowerMap.has(base)) lowerMap.set(base, []);
|
|
71
|
+
lowerMap.get(base).push(name);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const groups = [];
|
|
75
|
+
for (const [base, variants] of lowerMap.entries()) {
|
|
76
|
+
if (variants.length <= 1) continue;
|
|
77
|
+
groups.push({
|
|
78
|
+
base,
|
|
79
|
+
variants,
|
|
80
|
+
totalPrompts: variants.reduce((acc, v) => acc + (variableUsage[v]?.count || 0), 0),
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
return groups;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function analyzeDependencies({ library, analysis_type = 'variables' } = {}) {
|
|
87
|
+
const { row, manifest, manifestPath } = resolvePinnedLibrary(library);
|
|
88
|
+
const prompts = Array.isArray(manifest.prompts) ? manifest.prompts : [];
|
|
89
|
+
const libDir = pathModule.dirname(manifestPath);
|
|
90
|
+
|
|
91
|
+
const perPrompt = [];
|
|
92
|
+
const variableUsage = {};
|
|
93
|
+
|
|
94
|
+
for (const prompt of prompts) {
|
|
95
|
+
if (!prompt || typeof prompt !== 'object') continue;
|
|
96
|
+
const key = prompt.key || '(no-key)';
|
|
97
|
+
let content = '';
|
|
98
|
+
|
|
99
|
+
if (Array.isArray(prompt.files) && prompt.files.length) {
|
|
100
|
+
const firstFile = prompt.files[0];
|
|
101
|
+
const candidatePath = pathModule.isAbsolute(firstFile)
|
|
102
|
+
? firstFile
|
|
103
|
+
: pathModule.join(libDir, firstFile);
|
|
104
|
+
try {
|
|
105
|
+
content = fsModule.readFileSync(candidatePath, 'utf8');
|
|
106
|
+
} catch (error) {
|
|
107
|
+
logger?.debug?.('dep_analyzer_read_failed', { file: candidatePath, err: error.message || String(error) });
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const vars = extractVariables(content);
|
|
112
|
+
perPrompt.push({
|
|
113
|
+
key,
|
|
114
|
+
name: prompt.name || key,
|
|
115
|
+
variables: vars,
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
for (const v of vars) {
|
|
119
|
+
if (!variableUsage[v]) {
|
|
120
|
+
variableUsage[v] = { count: 0, prompts: [] };
|
|
121
|
+
}
|
|
122
|
+
variableUsage[v].count += 1;
|
|
123
|
+
variableUsage[v].prompts.push(key);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const sharedVariables = Object.keys(variableUsage).filter((v) => variableUsage[v].count > 1);
|
|
128
|
+
const uniqueVariables = Object.keys(variableUsage).filter((v) => variableUsage[v].count === 1);
|
|
129
|
+
const inconsistencyGroups = groupInconsistencies(variableUsage);
|
|
130
|
+
|
|
131
|
+
const lines = [];
|
|
132
|
+
lines.push(`📊 Variable Usage Analysis for "${manifest.library?.name || row.name || library}"`);
|
|
133
|
+
lines.push('');
|
|
134
|
+
if (!perPrompt.length) {
|
|
135
|
+
lines.push('No prompts found in this library.');
|
|
136
|
+
} else {
|
|
137
|
+
lines.push(`Prompts analyzed: ${perPrompt.length}`);
|
|
138
|
+
lines.push(`Total distinct variables: ${Object.keys(variableUsage).length}`);
|
|
139
|
+
lines.push('');
|
|
140
|
+
if (sharedVariables.length) {
|
|
141
|
+
lines.push('Shared variables (used in multiple prompts):');
|
|
142
|
+
sharedVariables.slice(0, 10).forEach((v) => {
|
|
143
|
+
lines.push(`- ${v} (${variableUsage[v].count} prompts)`);
|
|
144
|
+
});
|
|
145
|
+
if (sharedVariables.length > 10) {
|
|
146
|
+
lines.push(`… and ${sharedVariables.length - 10} more`);
|
|
147
|
+
}
|
|
148
|
+
lines.push('');
|
|
149
|
+
} else {
|
|
150
|
+
lines.push('No shared variables detected across prompts.');
|
|
151
|
+
lines.push('');
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (inconsistencyGroups.length) {
|
|
155
|
+
lines.push('Potential naming inconsistency groups:');
|
|
156
|
+
inconsistencyGroups.slice(0, 5).forEach((g) => {
|
|
157
|
+
lines.push(`- ${g.base}: ${g.variants.join(', ')} (approx. ${g.totalPrompts} prompt usages)`);
|
|
158
|
+
});
|
|
159
|
+
if (inconsistencyGroups.length > 5) {
|
|
160
|
+
lines.push(`… and ${inconsistencyGroups.length - 5} more groups`);
|
|
161
|
+
}
|
|
162
|
+
lines.push('');
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
content: [
|
|
168
|
+
{ type: 'text', text: lines.join('\n') },
|
|
169
|
+
{
|
|
170
|
+
type: 'text',
|
|
171
|
+
text:
|
|
172
|
+
'```json\n' +
|
|
173
|
+
JSON.stringify(
|
|
174
|
+
{
|
|
175
|
+
library: {
|
|
176
|
+
cid: row.cid,
|
|
177
|
+
name: manifest.library?.name || row.name || library,
|
|
178
|
+
},
|
|
179
|
+
perPrompt,
|
|
180
|
+
variableUsage,
|
|
181
|
+
sharedVariables,
|
|
182
|
+
uniqueVariables,
|
|
183
|
+
inconsistencyGroups,
|
|
184
|
+
},
|
|
185
|
+
null,
|
|
186
|
+
2,
|
|
187
|
+
) +
|
|
188
|
+
'\n```',
|
|
189
|
+
},
|
|
190
|
+
],
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return {
|
|
195
|
+
analyzeDependencies,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
module.exports = {
|
|
200
|
+
createDependencyAnalyzer,
|
|
201
|
+
};
|
|
202
|
+
|
|
@@ -49,7 +49,7 @@ function createLibraryLister({
|
|
|
49
49
|
return {
|
|
50
50
|
content: [
|
|
51
51
|
{ type: 'text', text: formatted },
|
|
52
|
-
{ type: '
|
|
52
|
+
{ type: 'text', text: '```json\n' + JSON.stringify(data ?? {}, null, DEFAULT_INDENT) + '\n```' },
|
|
53
53
|
],
|
|
54
54
|
};
|
|
55
55
|
}
|
|
@@ -64,7 +64,7 @@ function createLibraryLister({
|
|
|
64
64
|
return {
|
|
65
65
|
content: [
|
|
66
66
|
{ type: 'text', text: formatted },
|
|
67
|
-
{ type: '
|
|
67
|
+
{ type: 'text', text: '```json\n' + JSON.stringify(data ?? {}, null, DEFAULT_INDENT) + '\n```' },
|
|
68
68
|
],
|
|
69
69
|
};
|
|
70
70
|
} catch (error) {
|
|
@@ -96,6 +96,7 @@ function collectLocalPrompts({
|
|
|
96
96
|
if (normalizedQuery) {
|
|
97
97
|
const matchesQuery =
|
|
98
98
|
promptName.toLowerCase().includes(normalizedQuery) ||
|
|
99
|
+
(prompt.key && prompt.key.toLowerCase().includes(normalizedQuery)) ||
|
|
99
100
|
promptDescription.toLowerCase().includes(normalizedQuery) ||
|
|
100
101
|
loweredPromptTags.some((tag) => tag.includes(normalizedQuery));
|
|
101
102
|
if (!matchesQuery) continue; // eslint-disable-line no-continue
|
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
const DEFAULT_GATEWAYS = [
|
|
2
|
+
'https://cloudflare-ipfs.com',
|
|
3
|
+
'https://gateway.pinata.cloud',
|
|
4
|
+
'https://ipfs.io',
|
|
2
5
|
'https://dweb.link',
|
|
3
6
|
'https://nftstorage.link',
|
|
4
|
-
'https://ipfs.io',
|
|
5
7
|
];
|
|
6
8
|
|
|
7
9
|
function buildGatewayList({ cid, preferredGateway }) {
|
|
8
10
|
const trimmedPreferred = preferredGateway ? preferredGateway.replace(/\/$/, '') : '';
|
|
9
11
|
const candidates = [];
|
|
10
12
|
if (trimmedPreferred) {
|
|
11
|
-
candidates.push(`${trimmedPreferred}/ipfs/${cid}`);
|
|
13
|
+
candidates.push(`${trimmedPreferred} /ipfs/${cid} `);
|
|
12
14
|
}
|
|
13
15
|
for (const base of DEFAULT_GATEWAYS) {
|
|
14
16
|
const formattedBase = base.replace(/\/$/, '');
|
|
15
|
-
const url = `${formattedBase}/ipfs/${cid}`;
|
|
17
|
+
const url = `${formattedBase} /ipfs/${cid} `;
|
|
16
18
|
if (!candidates.includes(url)) {
|
|
17
19
|
candidates.push(url);
|
|
18
20
|
}
|
|
@@ -22,6 +22,20 @@ function createManifestFetcher({
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
async function downloadManifest(manifestCid) {
|
|
25
|
+
// Check if this is a local manifest (stored in ~/.sage/libraries/)
|
|
26
|
+
if (manifestCid && manifestCid.startsWith('local_')) {
|
|
27
|
+
try {
|
|
28
|
+
const { loadPinnedManifest } = require('../library/local-cache');
|
|
29
|
+
const cliConfig = require('../../utils/cli-config');
|
|
30
|
+
const { manifest } = loadPinnedManifest({ config: cliConfig, cid: manifestCid });
|
|
31
|
+
return manifest;
|
|
32
|
+
} catch (error) {
|
|
33
|
+
logger?.debug?.('local_manifest_load_failed', { cid: manifestCid, err: error.message || String(error) });
|
|
34
|
+
throw new Error(`Failed to load local manifest ${manifestCid}: ${error.message}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// For IPFS CIDs, try gateways
|
|
25
39
|
const gateways = buildGatewayList(manifestCid);
|
|
26
40
|
let lastError;
|
|
27
41
|
for (const gateway of gateways) {
|
|
@@ -84,7 +98,9 @@ function createManifestFetcher({
|
|
|
84
98
|
let content = '';
|
|
85
99
|
if (includeContent && prompt?.cid && ipfsManager) {
|
|
86
100
|
try {
|
|
87
|
-
|
|
101
|
+
// Download prompt JSON directly from IPFS and extract content
|
|
102
|
+
const data = await ipfsManager.downloadJson(prompt.cid);
|
|
103
|
+
content = data?.content || data?.prompt?.content || JSON.stringify(data);
|
|
88
104
|
} catch (error) {
|
|
89
105
|
logger?.debug?.('prompt_content_fetch_failed', { cid: prompt.cid, err: error.message || String(error) });
|
|
90
106
|
}
|
|
@@ -18,6 +18,63 @@ function defaultAddFormats(ajv) {
|
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
// Lightweight embedded fallback schema for environments where docs/schemas are not present
|
|
22
|
+
const FALLBACK_MANIFEST_SCHEMA = {
|
|
23
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
24
|
+
$id: 'https://sage-protocol.org/schemas/manifest.schema.json',
|
|
25
|
+
title: 'Sage Library Manifest',
|
|
26
|
+
type: 'object',
|
|
27
|
+
required: ['version', 'library', 'prompts'],
|
|
28
|
+
additionalProperties: true,
|
|
29
|
+
properties: {
|
|
30
|
+
version: {
|
|
31
|
+
anyOf: [
|
|
32
|
+
{ type: 'string', const: '2.0.0' },
|
|
33
|
+
{ type: 'integer', const: 2 },
|
|
34
|
+
],
|
|
35
|
+
},
|
|
36
|
+
library: {
|
|
37
|
+
type: 'object',
|
|
38
|
+
required: ['name'],
|
|
39
|
+
additionalProperties: true,
|
|
40
|
+
properties: {
|
|
41
|
+
name: { type: 'string', minLength: 1 },
|
|
42
|
+
description: { type: 'string' },
|
|
43
|
+
previous: { type: 'string' },
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
prompts: {
|
|
47
|
+
type: 'array',
|
|
48
|
+
minItems: 0,
|
|
49
|
+
items: {
|
|
50
|
+
type: 'object',
|
|
51
|
+
required: ['key'],
|
|
52
|
+
additionalProperties: true,
|
|
53
|
+
properties: {
|
|
54
|
+
key: { type: 'string', minLength: 1 },
|
|
55
|
+
cid: { type: 'string', minLength: 46 },
|
|
56
|
+
name: { type: 'string' },
|
|
57
|
+
description: { type: 'string' },
|
|
58
|
+
tags: {
|
|
59
|
+
type: 'array',
|
|
60
|
+
items: { type: 'string', minLength: 1 },
|
|
61
|
+
},
|
|
62
|
+
files: {
|
|
63
|
+
type: 'array',
|
|
64
|
+
items: { type: 'string', minLength: 1 },
|
|
65
|
+
minItems: 0,
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
compositions: { type: 'object' },
|
|
71
|
+
dependencies: {
|
|
72
|
+
type: 'array',
|
|
73
|
+
items: { type: 'string' },
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
|
|
21
78
|
function createManifestWorkflows({
|
|
22
79
|
provider,
|
|
23
80
|
addressResolution,
|
|
@@ -27,8 +84,11 @@ function createManifestWorkflows({
|
|
|
27
84
|
logger = console,
|
|
28
85
|
processEnv = process.env,
|
|
29
86
|
schemaPaths = [
|
|
87
|
+
// Package-relative path (for published CLI)
|
|
88
|
+
path.join(__dirname, '..', '..', 'schemas', 'manifest.schema.json'),
|
|
89
|
+
// Monorepo docs locations (dev)
|
|
30
90
|
path.join(process.cwd(), 'docs', 'schemas', 'manifest.schema.json'),
|
|
31
|
-
path.join(__dirname, '..', '..', '..', 'docs', 'schemas', 'manifest.schema.json'),
|
|
91
|
+
path.join(__dirname, '..', '..', '..', '..', 'docs', 'schemas', 'manifest.schema.json'),
|
|
32
92
|
],
|
|
33
93
|
fsModule = fs,
|
|
34
94
|
pathModule = path,
|
|
@@ -53,7 +113,9 @@ function createManifestWorkflows({
|
|
|
53
113
|
logger?.warn?.('manifest_schema_load_failed', { path: schemaPath, err: error.message || String(error) });
|
|
54
114
|
}
|
|
55
115
|
}
|
|
56
|
-
|
|
116
|
+
// Fallback: embedded schema so MCP/CLI can still validate outside the monorepo.
|
|
117
|
+
logger?.warn?.('manifest_schema_fallback_used');
|
|
118
|
+
return FALLBACK_MANIFEST_SCHEMA;
|
|
57
119
|
}
|
|
58
120
|
|
|
59
121
|
async function validateManifest({ manifest }) {
|
|
@@ -67,8 +129,23 @@ function createManifestWorkflows({
|
|
|
67
129
|
if (ok) {
|
|
68
130
|
return { content: [{ type: 'text', text: '✅ Manifest is valid' }] };
|
|
69
131
|
}
|
|
70
|
-
const
|
|
71
|
-
|
|
132
|
+
const errors = validate.errors || [];
|
|
133
|
+
const lines = errors.map((err) => `- ${err.instancePath || '/'} ${err.message || 'invalid'}`).join('\n');
|
|
134
|
+
|
|
135
|
+
// Add targeted hints for common pitfalls (version, library)
|
|
136
|
+
const hasVersionError = errors.some((e) => (e.instancePath === '/version'));
|
|
137
|
+
const hasLibraryError = errors.some((e) => e.instancePath === '' && /library/.test(e.message || ''));
|
|
138
|
+
|
|
139
|
+
let hint = '';
|
|
140
|
+
if (hasVersionError) {
|
|
141
|
+
hint += '\n\nHint: use "version": 2 or "version": "2.0.0" for v2 manifests.';
|
|
142
|
+
}
|
|
143
|
+
if (hasLibraryError || !manifest?.library) {
|
|
144
|
+
hint += '\nHint: manifest must include a top-level "library" object, e.g.:\n' +
|
|
145
|
+
'{\n "version": 2,\n "library": { "name": "My Library", "description": "..." },\n "prompts": []\n}';
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return { content: [{ type: 'text', text: `❌ Manifest validation failed:\n${lines}${hint}` }] };
|
|
72
149
|
} catch (error) {
|
|
73
150
|
return { content: [{ type: 'text', text: `Error validating manifest: ${error.message}` }] };
|
|
74
151
|
}
|
|
@@ -81,7 +158,15 @@ function createManifestWorkflows({
|
|
|
81
158
|
const cid = await ipfs.uploadJson(manifest, name);
|
|
82
159
|
return { content: [{ type: 'text', text: `✅ Manifest uploaded to IPFS\nCID: ${cid}` }], cid };
|
|
83
160
|
} catch (error) {
|
|
84
|
-
|
|
161
|
+
const msg = error?.message || String(error);
|
|
162
|
+
let help = msg;
|
|
163
|
+
if (/All IPFS providers failed/i.test(msg)) {
|
|
164
|
+
help = 'All IPFS providers failed. Ensure your IPFS settings are configured.\n' +
|
|
165
|
+
'- For worker: set SAGE_IPFS_WORKER_URL (and SAGE_IPFS_UPLOAD_TOKEN if required)\n' +
|
|
166
|
+
'- For Pinata: set PINATA_JWT or PINATA_API_KEY/SECRET\n' +
|
|
167
|
+
'- Or switch provider via SAGE_IPFS_PROVIDER=pinata|worker';
|
|
168
|
+
}
|
|
169
|
+
return { content: [{ type: 'text', text: `Error uploading manifest: ${help}` }] };
|
|
85
170
|
}
|
|
86
171
|
}
|
|
87
172
|
|
|
@@ -117,7 +202,7 @@ function createManifestWorkflows({
|
|
|
117
202
|
let mode = { operator: false, governance: 'Unknown' };
|
|
118
203
|
try {
|
|
119
204
|
mode = await detectGovMode({ provider, subdao, governor, timelock });
|
|
120
|
-
} catch (_) {}
|
|
205
|
+
} catch (_) { }
|
|
121
206
|
|
|
122
207
|
const payload = { targets, values, calldatas, description: desc };
|
|
123
208
|
const hints = [];
|
|
@@ -202,7 +287,7 @@ function createManifestWorkflows({
|
|
|
202
287
|
}
|
|
203
288
|
}
|
|
204
289
|
|
|
205
|
-
async function publishManifestFlow({ manifest, subdao = '', description = '' }) {
|
|
290
|
+
async function publishManifestFlow({ manifest, subdao = '', description = '', dry_run = false }) {
|
|
206
291
|
try {
|
|
207
292
|
const validation = await validateManifest({ manifest });
|
|
208
293
|
const firstText = validation.content?.[0]?.text || '';
|
|
@@ -210,22 +295,49 @@ function createManifestWorkflows({
|
|
|
210
295
|
return { content: [{ type: 'text', text: `❌ Validation failed. Fix issues first.\n\n${firstText}` }] };
|
|
211
296
|
}
|
|
212
297
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
298
|
+
let cid = null;
|
|
299
|
+
let pushText = '';
|
|
300
|
+
if (!dry_run) {
|
|
301
|
+
const pushed = await pushManifestToIpfs({ manifest });
|
|
302
|
+
pushText = pushed.content?.[0]?.text || '';
|
|
303
|
+
const match = /CID:\s*([^\s]+)/.exec(pushText);
|
|
304
|
+
cid = match?.[1] || pushed.cid;
|
|
305
|
+
if (!cid) {
|
|
306
|
+
return { content: [{ type: 'text', text: '❌ Failed to upload manifest (no CID)' }] };
|
|
307
|
+
}
|
|
218
308
|
}
|
|
219
309
|
|
|
220
310
|
const proposed = await proposeManifest({
|
|
221
|
-
cid,
|
|
311
|
+
cid: cid || '(dry-run)',
|
|
222
312
|
subdao,
|
|
223
313
|
description,
|
|
224
314
|
promptCount: Array.isArray(manifest?.prompts) ? manifest.prompts.length : 0,
|
|
225
315
|
manifest,
|
|
226
316
|
});
|
|
227
|
-
const
|
|
228
|
-
|
|
317
|
+
const textLines = [];
|
|
318
|
+
textLines.push('✅ Manifest valid');
|
|
319
|
+
if (cid) textLines.push(`CID: ${cid}`);
|
|
320
|
+
if (dry_run) {
|
|
321
|
+
textLines.push('');
|
|
322
|
+
textLines.push('Dry run: no IPFS upload performed. Use this payload and CLI hints to publish when ready.');
|
|
323
|
+
textLines.push('');
|
|
324
|
+
textLines.push('When you are ready to publish from your terminal:');
|
|
325
|
+
textLines.push('1) Save this manifest JSON to a file (e.g. ./manifest.json).');
|
|
326
|
+
if (subdao) {
|
|
327
|
+
textLines.push(`2) Upload & schedule/propose via CLI:`);
|
|
328
|
+
textLines.push(` sage library push ./manifest.json --subdao ${subdao} --pin --wait`);
|
|
329
|
+
} else {
|
|
330
|
+
textLines.push('2) Upload & schedule/propose via CLI (replace SUBDAO):');
|
|
331
|
+
textLines.push(' sage library push ./manifest.json --subdao 0xYourSubDAO --pin --wait');
|
|
332
|
+
}
|
|
333
|
+
} else if (pushText) {
|
|
334
|
+
textLines.push('');
|
|
335
|
+
textLines.push(pushText);
|
|
336
|
+
}
|
|
337
|
+
textLines.push('');
|
|
338
|
+
textLines.push(proposed.content?.[0]?.text || '');
|
|
339
|
+
|
|
340
|
+
return { content: [{ type: 'text', text: textLines.join('\n') }], cid, payload: proposed.payload };
|
|
229
341
|
} catch (error) {
|
|
230
342
|
return { content: [{ type: 'text', text: `Error publishing manifest flow: ${error.message}` }] };
|
|
231
343
|
}
|