@sage-protocol/cli 0.4.1 → 0.4.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.
@@ -136,98 +136,222 @@ async function importOnchainPrompt(nameOrKey, opts) {
136
136
  }
137
137
  }
138
138
 
139
- async function importSkillPrompt(name, opts) {
140
- const { enterJsonMode, withJsonVersion } = require('../utils/json-output');
141
- enterJsonMode(opts);
142
- const ws = readWorkspace();
143
- if (!ws) throw new Error('Workspace not found. Run `sage prompts init` first.');
144
- const candidates = [];
145
- if (opts.fromDir) {
146
- candidates.push(opts.fromDir);
147
- } else {
148
- const cwd = process.cwd();
149
- const home = (os.homedir && os.homedir()) || null;
150
- const roots = [];
151
- roots.push(path.join(cwd, '.agent', 'skills'));
152
- roots.push(path.join(cwd, '.claude', 'skills'));
153
- if (home) {
154
- roots.push(path.join(home, '.agent', 'skills'));
155
- roots.push(path.join(home, '.claude', 'skills'));
156
- }
157
- for (const root of roots) {
158
- candidates.push(path.join(root, name));
159
- }
139
+
140
+ // --- Unified Artifact Commands ---
141
+
142
+ async function createArtifact(opts) {
143
+ const ArtifactManager = require('../services/artifact-manager');
144
+ const manager = new ArtifactManager();
145
+ await manager.initialize();
146
+
147
+ const kind = opts.kind || 'prompt';
148
+ const name = opts.name;
149
+ if (!name) {
150
+ console.error('❌ Name is required');
151
+ process.exit(1);
160
152
  }
161
- let skillDir = null;
162
- for (const dir of candidates) {
163
- if (!dir) continue;
164
- const skillPath = path.join(dir, 'SKILL.md');
165
- if (fs.existsSync(skillPath)) {
166
- skillDir = dir;
167
- break;
168
- }
153
+
154
+ const targets = opts.targets ? opts.targets.split(',') : [];
155
+
156
+ const artifact = await manager.saveArtifact({
157
+ key: name,
158
+ kind,
159
+ body: opts.content || `You are a ${kind}...`,
160
+ meta: {
161
+ description: opts.description || '',
162
+ tags: opts.tags ? opts.tags.split(',') : []
163
+ },
164
+ targets
165
+ });
166
+
167
+ console.log(`✅ Created ${kind}: ${artifact.key}`);
168
+ console.log(` Path: ${artifact.filePath}`);
169
+ }
170
+
171
+ async function listArtifacts(opts) {
172
+ const ArtifactManager = require('../services/artifact-manager');
173
+ const manager = new ArtifactManager();
174
+ await manager.initialize();
175
+
176
+ const filter = {};
177
+ if (opts.kind) filter.kind = opts.kind;
178
+ if (opts.scope) filter.scope = opts.scope;
179
+
180
+ const artifacts = await manager.listArtifacts(filter);
181
+
182
+ if (opts.json) {
183
+ console.log(JSON.stringify(artifacts, null, 2));
184
+ return;
169
185
  }
170
- if (!skillDir) {
171
- const msg = `Skill '${name}' not found. Install it with OpenSkills (e.g. openskills install anthropics/skills) and try again, or pass --from-dir.`;
172
- if (opts.json) {
173
- console.log(JSON.stringify(withJsonVersion({ ok: false, reason: msg }), null, 2));
174
- } else {
175
- console.error('❌', msg);
176
- }
186
+
187
+ console.log(`Found ${artifacts.length} artifacts:`);
188
+ const grouped = {};
189
+ artifacts.forEach(a => {
190
+ if (!grouped[a.kind]) grouped[a.kind] = [];
191
+ grouped[a.kind].push(a);
192
+ });
193
+
194
+ Object.keys(grouped).forEach(kind => {
195
+ console.log(`\n${kind.toUpperCase()}:`);
196
+ grouped[kind].forEach(a => {
197
+ console.log(` - ${a.key} [${a.scope}] ${a.targets.length ? `(targets: ${a.targets.join(',')})` : ''}`);
198
+ });
199
+ });
200
+ }
201
+
202
+ async function exportArtifacts(opts) {
203
+ const ArtifactManager = require('../services/artifact-manager');
204
+ const manager = new ArtifactManager();
205
+ await manager.initialize();
206
+
207
+ const target = opts.target;
208
+ if (!target) {
209
+ console.error('❌ Target is required (cursor, claude)');
177
210
  process.exit(1);
178
211
  }
179
- const skillPath = path.join(skillDir, 'SKILL.md');
180
- const raw = fs.readFileSync(skillPath, 'utf8');
181
- let frontmatter = {};
182
- let body = raw;
183
- if (raw.startsWith('---')) {
184
- const end = raw.indexOf('\n---', 3);
185
- if (end !== -1) {
186
- const header = raw.slice(3, end).split(/\r?\n/);
187
- header.forEach((line) => {
188
- const m = line.match(/^(\w+)\s*:\s*(.+)$/);
189
- if (m) {
190
- const key = m[1].trim();
191
- const value = m[2].trim();
192
- frontmatter[key] = value;
193
- }
194
- });
195
- body = raw.slice(end + 4);
196
- }
212
+
213
+ const artifacts = await manager.listArtifacts();
214
+ const relevant = artifacts.filter(a => a.targets.includes(target) || (a.meta && a.meta.targets && a.meta.targets.includes(target)));
215
+
216
+ if (relevant.length === 0) {
217
+ console.log(`No artifacts found for target: ${target}`);
218
+ return;
197
219
  }
198
- const skillName = String(frontmatter.name || name).trim();
199
- const description = frontmatter.description ? String(frontmatter.description).trim() : '';
200
- const relKey = path.posix.join('skills', skillName);
201
- const destDir = path.join(ws.promptsDir, 'skills');
202
- fs.mkdirSync(destDir, { recursive: true });
203
- const destFile = path.join(destDir, `${skillName}.md`);
204
- const headerLines = [
205
- `<!-- Imported from SKILL '${name}' at ${skillPath} -->`,
206
- description ? `<!-- ${description} -->` : null,
207
- '',
208
- ].filter(Boolean);
209
- const outBody = `${headerLines.join('\n')}\n${body.trim()}\n`;
210
- fs.writeFileSync(destFile, outBody, 'utf8');
211
- const result = {
212
- ok: true,
213
- mode: 'skill',
214
- key: relKey,
215
- name: skillName,
216
- description: description || null,
217
- file: destFile,
218
- };
219
- if (opts.json) {
220
- console.log(JSON.stringify(withJsonVersion(result), null, 2));
221
- } else {
222
- console.log(`✅ Imported skill '${skillName}' into workspace`);
223
- console.log(` - key : ${relKey}`);
224
- console.log(` - file: ${destFile}`);
220
+
221
+ console.log(`Exporting ${relevant.length} artifacts to ${target}...`);
222
+
223
+ if (target === 'cursor') {
224
+ const cursorDir = path.join(process.cwd(), '.cursor', 'rules');
225
+ if (!fs.existsSync(cursorDir)) fs.mkdirSync(cursorDir, { recursive: true });
226
+
227
+ for (const a of relevant) {
228
+ const filename = `${a.key.replace(/\//g, '-')}.mdc`;
229
+ const content = `---\ndescription: ${a.meta.description || a.key}\nglobs: ${a.meta.globs || '*'}\n---\n\n${a.body}`;
230
+ fs.writeFileSync(path.join(cursorDir, filename), content);
231
+ console.log(` - Wrote ${filename}`);
232
+ }
233
+ } else if (target === 'claude') {
234
+ const claudeFile = path.join(process.cwd(), 'CLAUDE.md');
235
+ let content = fs.existsSync(claudeFile) ? fs.readFileSync(claudeFile, 'utf8') : '';
236
+
237
+ // Simple append for now, ideally we use markers
238
+ const markerStart = '<!-- SAGE_START -->';
239
+ const markerEnd = '<!-- SAGE_END -->';
240
+
241
+ let newSection = `${markerStart}\n\n`;
242
+ for (const a of relevant) {
243
+ newSection += `## ${a.key}\n${a.body}\n\n`;
244
+ }
245
+ newSection += `${markerEnd}`;
246
+
247
+ if (content.includes(markerStart)) {
248
+ const regex = new RegExp(`${markerStart}[\\s\\S]*?${markerEnd}`);
249
+ content = content.replace(regex, newSection);
250
+ } else {
251
+ content += `\n\n${newSection}`;
252
+ }
253
+
254
+ fs.writeFileSync(claudeFile, content);
255
+ console.log(` - Updated CLAUDE.md`);
225
256
  }
226
257
  }
227
258
 
228
259
  function register(program) {
229
260
  const cmd = new Command('prompts').description('Prompt-first workspace commands');
230
261
 
262
+ cmd
263
+ .command('new')
264
+ .description('Create a new artifact (prompt, snippet, skill)')
265
+ .option('--kind <kind>', 'Artifact kind (prompt, snippet, skill)', 'prompt')
266
+ .option('--name <name>', 'Artifact name/key (required)')
267
+ .option('--content <content>', 'Initial content')
268
+ .option('--description <desc>', 'Description')
269
+ .option('--tags <tags>', 'Comma-separated tags')
270
+ .option('--targets <targets>', 'Export targets (cursor, claude)')
271
+ .option('--publishable', 'Mark as publishable (default: true for prompts, false for others)')
272
+ .option('--no-publishable', 'Mark as not publishable')
273
+ .action(async (opts) => {
274
+ // Handle publishable flag: if --publishable is set, true. If --no-publishable, false.
275
+ // If neither, undefined (let ArtifactManager decide based on kind).
276
+ let publishable = undefined;
277
+ if (opts.publishable === true) publishable = true;
278
+ if (opts.publishable === false) publishable = false; // commander handles --no-X as false
279
+
280
+ const ArtifactManager = require('../services/artifact-manager');
281
+ const manager = new ArtifactManager();
282
+ await manager.initialize();
283
+
284
+ const kind = opts.kind || 'prompt';
285
+ const name = opts.name;
286
+ if (!name) {
287
+ console.error('❌ Name is required');
288
+ process.exit(1);
289
+ }
290
+
291
+ const targets = opts.targets ? opts.targets.split(',') : [];
292
+
293
+ const artifact = await manager.saveArtifact({
294
+ key: name,
295
+ kind,
296
+ body: opts.content || `You are a ${kind}...`,
297
+ meta: {
298
+ description: opts.description || '',
299
+ tags: opts.tags ? opts.tags.split(',') : []
300
+ },
301
+ targets,
302
+ publishable
303
+ });
304
+
305
+ console.log(`✅ Created ${kind}: ${artifact.key}`);
306
+ console.log(` Path: ${artifact.filePath}`);
307
+ console.log(` Publishable: ${artifact.publishable}`);
308
+ });
309
+
310
+ cmd
311
+ .command('list')
312
+ .description('List all artifacts in the project')
313
+ .option('--kind <kind>', 'Filter by kind')
314
+ .option('--publishable', 'Filter by publishable status')
315
+ .option('--no-publishable', 'Filter by non-publishable status')
316
+ .option('--json', 'Output JSON')
317
+ .action(async (opts) => {
318
+ const ArtifactManager = require('../services/artifact-manager');
319
+ const manager = new ArtifactManager();
320
+ await manager.initialize();
321
+
322
+ const filter = {};
323
+ if (opts.kind) filter.kind = opts.kind;
324
+ if (opts.publishable === true) filter.publishable = true;
325
+ if (opts.publishable === false) filter.publishable = false;
326
+
327
+ const artifacts = await manager.listArtifacts(filter);
328
+
329
+ if (opts.json) {
330
+ console.log(JSON.stringify(artifacts, null, 2));
331
+ return;
332
+ }
333
+
334
+ console.log(`Found ${artifacts.length} artifacts:`);
335
+ const grouped = {};
336
+ artifacts.forEach(a => {
337
+ if (!grouped[a.kind]) grouped[a.kind] = [];
338
+ grouped[a.kind].push(a);
339
+ });
340
+
341
+ Object.keys(grouped).forEach(kind => {
342
+ console.log(`\n${kind.toUpperCase()}:`);
343
+ grouped[kind].forEach(a => {
344
+ const statusIcon = a.publishable ? '📡' : '🔒';
345
+ const syncStatus = a.publishing?.status === 'published' ? '✅' :
346
+ a.publishing?.status === 'modified' ? '📝' :
347
+ a.publishing?.status === 'new' ? '✨' : '❓';
348
+
349
+ console.log(` ${statusIcon} ${syncStatus} ${a.key} ${a.targets.length ? `(targets: ${a.targets.join(',')})` : ''}`);
350
+ });
351
+ });
352
+ console.log('\nLegend: 📡 Publishable, 🔒 Local-only | ✅ Synced, 📝 Modified, ✨ New');
353
+ });
354
+
231
355
  cmd
232
356
  .command('init')
233
357
  .description('Initialize a prompt workspace (prompts/ + .sage/workspace.json)')
@@ -639,7 +763,7 @@ function register(program) {
639
763
  prompts.length,
640
764
  opts.title,
641
765
  opts.desc || `Manifest CID: ${manifestCID}`,
642
- {
766
+ {
643
767
  libraryId: opts.libraryId || 'main',
644
768
  ctx: { subdaoOpt: opts.dao || opts.subdao }
645
769
  }
@@ -881,11 +1005,14 @@ function register(program) {
881
1005
  .option('--yes', 'Skip interactive confirmations', false)
882
1006
  .option('--json', 'Emit JSON output', false)
883
1007
  .action(async (opts) => {
1008
+ // Declare ws and dest outside try block so they're accessible in catch
1009
+ let ws = null;
1010
+ let dest = null;
884
1011
  try {
885
1012
  const { enterJsonMode, withJsonVersion } = require('../utils/json-output');
886
1013
  const { isTTY, isJSONMode } = require('../utils/output-mode');
887
1014
  enterJsonMode(opts);
888
- const ws = readWorkspace();
1015
+ ws = readWorkspace();
889
1016
  if (!ws) throw new Error('Workspace not found. Run `sage prompts init`.');
890
1017
  const scan = computeChangeSet(ws);
891
1018
  const changed = [...scan.added, ...scan.modified];
@@ -895,7 +1022,30 @@ function register(program) {
895
1022
  console.log(msg);
896
1023
  return;
897
1024
  }
898
- const prompts = changed.map((key) => ({ key, name: key.split('/').pop(), files: [path.join(ws.promptsDir, `${key}.md`)] }));
1025
+ const prompts = [];
1026
+ const ArtifactManager = require('../services/artifact-manager');
1027
+ const am = new ArtifactManager();
1028
+ await am.initialize();
1029
+
1030
+ for (const key of changed) {
1031
+ const artifact = await am.getArtifact(key);
1032
+ if (artifact && artifact.publishable) {
1033
+ prompts.push({ key, name: key.split('/').pop(), files: [path.join(ws.promptsDir, `${key}.md`)] });
1034
+ } else if (artifact) {
1035
+ if (!opts.json) console.log(`ℹ️ Skipping non-publishable artifact: ${key}`);
1036
+ } else {
1037
+ // Fallback for non-artifact files if any (shouldn't happen with strict mode but safe to keep)
1038
+ prompts.push({ key, name: key.split('/').pop(), files: [path.join(ws.promptsDir, `${key}.md`)] });
1039
+ }
1040
+ }
1041
+
1042
+ if (!prompts.length) {
1043
+ const msg = 'No publishable changes detected.';
1044
+ if (opts.json) return console.log(JSON.stringify(withJsonVersion({ ok: false, reason: msg })));
1045
+ console.log(msg);
1046
+ return;
1047
+ }
1048
+
899
1049
  // Workspace size warning & plan details
900
1050
  const fs = require('fs');
901
1051
  let totalBytes = 0;
@@ -952,7 +1102,7 @@ function register(program) {
952
1102
  throw new Error(msg);
953
1103
  }
954
1104
  const provider = process.env.RPC_URL ? new ethers.JsonRpcProvider(process.env.RPC_URL) : null;
955
- let dest = await deriveDestination({ provider, subdaoOpt: subdaoInput, workspaceSubdao: ws.subdao, aliasResolver: resolveAliasOrAddress });
1105
+ dest = await deriveDestination({ provider, subdaoOpt: subdaoInput, workspaceSubdao: ws.subdao, aliasResolver: resolveAliasOrAddress });
956
1106
  // Apply --dest override (CI-oriented)
957
1107
  if (opts.dest) {
958
1108
  const v = String(opts.dest).toLowerCase();
@@ -1167,8 +1317,14 @@ function register(program) {
1167
1317
  }
1168
1318
  });
1169
1319
 
1320
+ // --- Unified Commands ---
1321
+
1322
+
1323
+
1324
+ // --- Legacy / Specific Commands ---
1325
+
1170
1326
  cmd
1171
- .command('export')
1327
+ .command('export-legacy')
1172
1328
  .description('Render a skill for a target environment')
1173
1329
  .argument('<key>', 'Skill key')
1174
1330
  .option('--as <target>', 'Target (cursor|claude|browser|json)', 'cursor')
@@ -1255,7 +1411,6 @@ function register(program) {
1255
1411
  process.exit(1);
1256
1412
  }
1257
1413
  });
1258
-
1259
1414
  program.addCommand(cmd);
1260
1415
  }
1261
1416
 
package/dist/cli/index.js CHANGED
@@ -101,7 +101,7 @@ program
101
101
 
102
102
  // Reordered to highlight consolidated namespaces ('prompts' and 'gov')
103
103
  const commandGroups = [
104
- { title: 'Content (new)', modules: ['prompts', 'project', 'prompt', 'personal', 'interview', 'creator', 'contributor', 'bounty'] },
104
+ { title: 'Content (new)', modules: ['prompts', 'project', 'personal', 'interview', 'creator', 'contributor', 'bounty'] },
105
105
  { title: 'Governance (new)', modules: ['proposals', 'governance', 'dao', 'timelock', 'roles', 'members', 'gov-config', 'stake-status'] },
106
106
  { title: 'Treasury', modules: ['treasury', 'safe', 'boost'] },
107
107
  { title: 'Ops & Utilities', modules: ['config', 'wallet', 'sxxx', 'ipfs', 'ipns', 'context', 'doctor', 'factory', 'upgrade', 'resolve', 'dry-run-queue', 'mcp', 'start', 'wizard', 'init', 'dao-config', 'pin', 'council', 'sbt', 'completion', 'help', 'hook'] },
@@ -196,7 +196,7 @@ process.stderr.write = (chunk, encoding, callback) => {
196
196
  if (typeof callback === 'function') callback();
197
197
  return true;
198
198
  }
199
-
199
+
200
200
  const handled = aliasSupport.handleOutputError(chunk);
201
201
  if (!handled) {
202
202
  return currentStderrWrite(chunk, encoding, callback);