drafted 1.8.5 → 1.8.6
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/cli/drafted.mjs +27 -10
- package/package.json +1 -1
package/cli/drafted.mjs
CHANGED
|
@@ -1223,15 +1223,29 @@ function bundleHash(files) {
|
|
|
1223
1223
|
return h.digest('hex').slice(0, 12);
|
|
1224
1224
|
}
|
|
1225
1225
|
|
|
1226
|
-
|
|
1226
|
+
// Build an `unresolvable` reason from a failed resolve response (404/403),
|
|
1227
|
+
// naming the org when --org was used so Causeway can render `✗ unresolvable
|
|
1228
|
+
// (org X)` instead of a silent miss.
|
|
1229
|
+
async function unresolvableReason(res, org) {
|
|
1230
|
+
let why = 'skill not found';
|
|
1231
|
+
try { const e = await res.json(); if (e && e.error) why = e.error; } catch { /* keep default */ }
|
|
1232
|
+
if (org && !why.toLowerCase().includes(String(org).toLowerCase())) return `${why} (org ${org})`;
|
|
1233
|
+
return why;
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
async function syncOneSkill(ref, outDir, org) {
|
|
1227
1237
|
const at = ref.lastIndexOf('@');
|
|
1228
1238
|
const idOrSlug = at > 0 ? ref.slice(0, at) : ref;
|
|
1229
1239
|
const server = getServerUrl().replace(/\/$/, '');
|
|
1240
|
+
// --org binds resolution to a specific org per-request (X-Drafted-Org header);
|
|
1241
|
+
// the server validates it against the caller's memberships and never mutates
|
|
1242
|
+
// the shared session's active org. Omitted -> ambient session org (unchanged).
|
|
1243
|
+
const orgHeaders = org ? { 'X-Drafted-Org': org } : {};
|
|
1230
1244
|
const loadUrl = UUID_RE.test(idOrSlug)
|
|
1231
1245
|
? `${server}/api/skills/${idOrSlug}`
|
|
1232
1246
|
: `${server}/api/skills/slug/${encodeURIComponent(idOrSlug)}`;
|
|
1233
|
-
const res = await authFetch(loadUrl);
|
|
1234
|
-
if (res.status === 404) return { ref, slug: '', hash: '', status: 'unresolvable', error:
|
|
1247
|
+
const res = await authFetch(loadUrl, { headers: orgHeaders });
|
|
1248
|
+
if (res.status === 404 || res.status === 403) return { ref, slug: '', hash: '', status: 'unresolvable', error: await unresolvableReason(res, org) };
|
|
1235
1249
|
if (!res.ok) return { ref, slug: '', hash: '', status: 'error', error: `HTTP ${res.status}` };
|
|
1236
1250
|
const skill = await res.json();
|
|
1237
1251
|
const slug = skill.slug || idOrSlug;
|
|
@@ -1240,7 +1254,7 @@ async function syncOneSkill(ref, outDir) {
|
|
|
1240
1254
|
for (const p of Array.isArray(skill.files) ? skill.files : []) {
|
|
1241
1255
|
if (typeof p !== 'string' || p.includes('..')) continue;
|
|
1242
1256
|
const encoded = p.split('/').map(encodeURIComponent).join('/');
|
|
1243
|
-
const fr = await authFetch(`${server}/api/skills/${skill.id}/files/${encoded}
|
|
1257
|
+
const fr = await authFetch(`${server}/api/skills/${skill.id}/files/${encoded}`, { headers: orgHeaders });
|
|
1244
1258
|
if (!fr.ok) return { ref, slug, hash: '', status: 'error', error: `file ${p}: HTTP ${fr.status}` };
|
|
1245
1259
|
const fdata = await fr.json();
|
|
1246
1260
|
files[p] = typeof fdata === 'string' ? fdata : (fdata.content || '');
|
|
@@ -1262,6 +1276,7 @@ skillCmd
|
|
|
1262
1276
|
.description('Materialize pinned skills into a local cache dir (Causeway seam)')
|
|
1263
1277
|
.option('--ref <ref>', 'skill ref slug[@hash] (repeatable)', (v, acc) => { acc.push(v); return acc; }, [])
|
|
1264
1278
|
.requiredOption('--out <dir>', 'output directory for bundles')
|
|
1279
|
+
.option('--org <org>', 'resolve against this Drafted org (id or name); scopes per-request without switching the session')
|
|
1265
1280
|
.option('--format <fmt>', 'output format: json or text', 'text')
|
|
1266
1281
|
.action(async (opts) => {
|
|
1267
1282
|
requireLogin();
|
|
@@ -1271,7 +1286,7 @@ skillCmd
|
|
|
1271
1286
|
let allOk = refs.length > 0;
|
|
1272
1287
|
for (const ref of refs) {
|
|
1273
1288
|
try {
|
|
1274
|
-
const r = await syncOneSkill(ref, opts.out);
|
|
1289
|
+
const r = await syncOneSkill(ref, opts.out, opts.org);
|
|
1275
1290
|
results.push(r);
|
|
1276
1291
|
if (r.status !== 'ok') allOk = false;
|
|
1277
1292
|
} catch (err) {
|
|
@@ -1360,21 +1375,22 @@ skillCmd
|
|
|
1360
1375
|
// check reports the current canonical hash for refs WITHOUT materializing — the
|
|
1361
1376
|
// Causeway freshness probe. Hash is computed the same way as sync, so a check
|
|
1362
1377
|
// hash and a sync hash for the same skill match.
|
|
1363
|
-
async function checkOneSkill(ref) {
|
|
1378
|
+
async function checkOneSkill(ref, org) {
|
|
1364
1379
|
const at = ref.lastIndexOf('@');
|
|
1365
1380
|
const idOrSlug = at > 0 ? ref.slice(0, at) : ref;
|
|
1366
1381
|
const server = getServerUrl().replace(/\/$/, '');
|
|
1382
|
+
const orgHeaders = org ? { 'X-Drafted-Org': org } : {};
|
|
1367
1383
|
const loadUrl = UUID_RE.test(idOrSlug)
|
|
1368
1384
|
? `${server}/api/skills/${idOrSlug}`
|
|
1369
1385
|
: `${server}/api/skills/slug/${encodeURIComponent(idOrSlug)}`;
|
|
1370
|
-
const res = await authFetch(loadUrl);
|
|
1371
|
-
if (res.status === 404) return { ref, slug: '', hash: '', status: 'unresolvable' };
|
|
1386
|
+
const res = await authFetch(loadUrl, { headers: orgHeaders });
|
|
1387
|
+
if (res.status === 404 || res.status === 403) return { ref, slug: '', hash: '', status: 'unresolvable', error: await unresolvableReason(res, org) };
|
|
1372
1388
|
if (!res.ok) return { ref, slug: '', hash: '', status: 'error', error: `HTTP ${res.status}` };
|
|
1373
1389
|
const skill = await res.json();
|
|
1374
1390
|
const files = { 'SKILL.md': synthesizeSkillMd(skill) };
|
|
1375
1391
|
for (const p of Array.isArray(skill.files) ? skill.files : []) {
|
|
1376
1392
|
if (typeof p !== 'string' || p.includes('..')) continue;
|
|
1377
|
-
const fr = await authFetch(`${server}/api/skills/${skill.id}/files/${p.split('/').map(encodeURIComponent).join('/')}
|
|
1393
|
+
const fr = await authFetch(`${server}/api/skills/${skill.id}/files/${p.split('/').map(encodeURIComponent).join('/')}`, { headers: orgHeaders });
|
|
1378
1394
|
if (!fr.ok) return { ref, slug: skill.slug, hash: '', status: 'error', error: `file ${p}: HTTP ${fr.status}` };
|
|
1379
1395
|
const fdata = await fr.json();
|
|
1380
1396
|
files[p] = typeof fdata === 'string' ? fdata : (fdata.content || '');
|
|
@@ -1386,12 +1402,13 @@ skillCmd
|
|
|
1386
1402
|
.command('check')
|
|
1387
1403
|
.description('Report the current canonical hash for refs without materializing (freshness)')
|
|
1388
1404
|
.option('--ref <ref>', 'skill ref (repeatable)', (v, acc) => { acc.push(v); return acc; }, [])
|
|
1405
|
+
.option('--org <org>', 'resolve against this Drafted org (id or name); scopes per-request without switching the session')
|
|
1389
1406
|
.option('--format <fmt>', 'output format: json or text', 'text')
|
|
1390
1407
|
.action(async (opts) => {
|
|
1391
1408
|
requireLogin();
|
|
1392
1409
|
const results = [];
|
|
1393
1410
|
for (const ref of (opts.ref || [])) {
|
|
1394
|
-
try { results.push(await checkOneSkill(ref)); }
|
|
1411
|
+
try { results.push(await checkOneSkill(ref, opts.org)); }
|
|
1395
1412
|
catch (e) { results.push({ ref, slug: '', hash: '', status: 'error', error: String((e && e.message) || e) }); }
|
|
1396
1413
|
}
|
|
1397
1414
|
if (opts.format === 'json') console.log(JSON.stringify(results));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "drafted",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.6",
|
|
4
4
|
"description": "Drafted — visual thinking surface for humans and AI agents. Renders HTML, markdown, images, and code as frames on a zoomable canvas, with MCP tools for AI agents and real-time sync for humans.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|