drafted 1.21.0 → 1.21.1
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 +105 -47
- package/package.json +1 -1
package/cli/drafted.mjs
CHANGED
|
@@ -174,6 +174,12 @@ function getServerUrl() {
|
|
|
174
174
|
return `http://localhost:${process.env.DRAFTED_PORT || DEFAULT_PORT}`;
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
+
// Only a LOCAL server has a pid to check; a remote one (drafted.live) is never
|
|
178
|
+
// "not running" from this machine's point of view.
|
|
179
|
+
function localServerDown() {
|
|
180
|
+
return /^http:\/\/localhost:/.test(getServerUrl()) && !isServerRunning();
|
|
181
|
+
}
|
|
182
|
+
|
|
177
183
|
function buildUpdateCommand() {
|
|
178
184
|
const server = getServerUrl().replace(/\/$/, '');
|
|
179
185
|
if (platform() === 'win32') {
|
|
@@ -326,6 +332,52 @@ async function readApiGet(command, apiPath, org) {
|
|
|
326
332
|
return data;
|
|
327
333
|
}
|
|
328
334
|
|
|
335
|
+
// Exit with a structured (--json) and human error. Used where a guess would be
|
|
336
|
+
// a silent wrong answer.
|
|
337
|
+
function failRead(command, msg) {
|
|
338
|
+
jsonOut(false, command, msg);
|
|
339
|
+
console.error(`❌ ${msg}`);
|
|
340
|
+
process.exit(1);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// /api/fs takes a PROJECT-RELATIVE path plus projectId. Handed a canonical
|
|
344
|
+
// /o/<org>/[<folder…>/]projects/<project>/<layer>/... address it ignores the org
|
|
345
|
+
// and project, lists the user's ACTIVE project (a shared row any browser tab or
|
|
346
|
+
// agent rewrites) at a path no layer matches, and answers 200 with no entries —
|
|
347
|
+
// "this project is empty" when it is not. So split the address here, the way
|
|
348
|
+
// the MCP fs tool does, and refuse anything that does not name a project.
|
|
349
|
+
async function resolveCanonicalPath(command, path, projectId) {
|
|
350
|
+
const m = String(path || '').match(/^\/o\/([^/]+)(?:\/(.*))?$/);
|
|
351
|
+
if (!m) return { path, projectId };
|
|
352
|
+
const org = m[1];
|
|
353
|
+
const segs = (m[2] || '').split('/').filter(Boolean);
|
|
354
|
+
const at = segs.indexOf('projects');
|
|
355
|
+
if (at < 0 || at === segs.length - 1) {
|
|
356
|
+
failRead(command, `The CLI lists project content only — use /o/${org}/projects/<project>[/<layer>/<lane>]. `
|
|
357
|
+
+ `To list projects: drafted projects --org ${org}. Wiki, skills and tasks: use the Drafted MCP fs tool.`);
|
|
358
|
+
}
|
|
359
|
+
const data = await readApiGet(command, '/api/projects');
|
|
360
|
+
const inOrg = (data.projects || []).filter(p => p.orgSlug === org || p.orgId === org);
|
|
361
|
+
const folderChain = segs.slice(0, at);
|
|
362
|
+
const tail = segs.slice(at + 1);
|
|
363
|
+
const named = (p, ref) => p.id === ref || [p.slug, p.name].some(v => String(v || '').toLowerCase() === ref.toLowerCase());
|
|
364
|
+
// The project may be folder-qualified on either side of `projects`
|
|
365
|
+
// (/o/<org>/<folder>/projects/<p> or /o/<org>/projects/<folder>/<p>); the bare
|
|
366
|
+
// form is tried first. A folder must match exactly, so a typo never lands in
|
|
367
|
+
// a different project.
|
|
368
|
+
for (let i = 0; i < tail.length; i++) {
|
|
369
|
+
const folder = [...folderChain, ...tail.slice(0, i)].join('/');
|
|
370
|
+
const hit = inOrg.find(p => named(p, tail[i]) && (i === 0 || (p.folder || '') === folder));
|
|
371
|
+
if (!hit) continue;
|
|
372
|
+
if (projectId && projectId !== hit.id) {
|
|
373
|
+
failRead(command, `--project ${projectId} conflicts with the project in the path (${hit.id})`);
|
|
374
|
+
}
|
|
375
|
+
return { path: '/' + tail.slice(i + 1).join('/'), projectId: hit.id };
|
|
376
|
+
}
|
|
377
|
+
failRead(command, `project not found: ${path} — no project "${tail.join('/')}" you can access in org "${org}". `
|
|
378
|
+
+ `List them with: drafted projects --org ${org}`);
|
|
379
|
+
}
|
|
380
|
+
|
|
329
381
|
// Resolve a frame path / frame URL (/f/<uuid>) / bare UUID to its /api/fs read
|
|
330
382
|
// endpoint. Mirrors the MCP `frame(action="read")` resolution so the CLI and MCP
|
|
331
383
|
// accept the same identifiers.
|
|
@@ -724,34 +776,26 @@ program
|
|
|
724
776
|
});
|
|
725
777
|
|
|
726
778
|
// Command: status
|
|
779
|
+
// Reports the server this CLI actually talks to (DRAFTED_SERVER / auth.json /
|
|
780
|
+
// config.json, see getServerUrl). It used to check only for a LOCAL server pid,
|
|
781
|
+
// so a cloud user was told "not running" while every read went to drafted.live.
|
|
727
782
|
program
|
|
728
783
|
.command('status')
|
|
729
|
-
.description('Check server
|
|
784
|
+
.description('Check the Drafted server this CLI talks to, and whether you are signed in')
|
|
730
785
|
.action(async () => {
|
|
731
|
-
const
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
console.log(
|
|
739
|
-
|
|
740
|
-
console.log(` URL: http://localhost:${port}`);
|
|
741
|
-
|
|
742
|
-
// Try to ping server
|
|
743
|
-
try {
|
|
744
|
-
const response = await authFetch(`http://localhost:${port}/status`);
|
|
745
|
-
const data = await response.json();
|
|
746
|
-
console.log(` Status: ${data.status}`);
|
|
747
|
-
} catch (error) {
|
|
748
|
-
console.log(` Status: unreachable (may be starting up)`);
|
|
749
|
-
}
|
|
750
|
-
} else {
|
|
751
|
-
jsonOut(false, 'status', 'Server not running');
|
|
752
|
-
console.log('❌ Drafted server is not running');
|
|
753
|
-
console.log(' Use `drafted start` to start the server');
|
|
786
|
+
const serverUrl = getServerUrl();
|
|
787
|
+
let reachable = false;
|
|
788
|
+
try { reachable = (await fetch(`${serverUrl}/health`, { signal: AbortSignal.timeout(10000) })).ok; } catch { /* unreachable */ }
|
|
789
|
+
const signedIn = !!readAuth()?.sessionId;
|
|
790
|
+
if (!reachable) {
|
|
791
|
+
const hint = /^http:\/\/localhost:/.test(serverUrl) ? ' — start it with `drafted start`' : '';
|
|
792
|
+
jsonOut(false, 'status', `Drafted server unreachable at ${serverUrl}`);
|
|
793
|
+
console.log(`❌ Drafted server unreachable at ${serverUrl}${hint}`);
|
|
794
|
+
process.exit(1);
|
|
754
795
|
}
|
|
796
|
+
jsonOut(true, 'status', { running: true, url: serverUrl, signedIn });
|
|
797
|
+
console.log(`✅ Drafted server reachable at ${serverUrl}`);
|
|
798
|
+
console.log(` Signed in: ${signedIn ? 'yes' : 'no — run `drafted login`'}`);
|
|
755
799
|
});
|
|
756
800
|
|
|
757
801
|
// Command: url
|
|
@@ -759,7 +803,7 @@ program
|
|
|
759
803
|
.command('url')
|
|
760
804
|
.description('Get canvas URL')
|
|
761
805
|
.action(() => {
|
|
762
|
-
if (
|
|
806
|
+
if (localServerDown()) {
|
|
763
807
|
jsonOut(false, 'url', 'Server not running');
|
|
764
808
|
console.error('Error: Server is not running');
|
|
765
809
|
process.exit(1);
|
|
@@ -886,35 +930,47 @@ program
|
|
|
886
930
|
});
|
|
887
931
|
|
|
888
932
|
// Command: list-projects
|
|
933
|
+
// Lists the server's projects. It used to read a local projects.json left over
|
|
934
|
+
// from the self-hosted server, so a signed-in member of an org with hundreds of
|
|
935
|
+
// projects was told "No projects yet".
|
|
889
936
|
program
|
|
890
937
|
.command('projects')
|
|
891
|
-
.description('List
|
|
892
|
-
.
|
|
893
|
-
|
|
894
|
-
const
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
if (projects.
|
|
898
|
-
console.log('No projects
|
|
899
|
-
console.log('Create one with: drafted create-project --name "Project Name"');
|
|
938
|
+
.description('List the projects you can access (read-only)')
|
|
939
|
+
.option('--org <org>', 'Only this org (id, slug or name)')
|
|
940
|
+
.action(async (options) => {
|
|
941
|
+
const data = await readApiGet('projects', '/api/projects', options.org);
|
|
942
|
+
const projects = (data.projects || []).filter(p => !p.trashedAt);
|
|
943
|
+
jsonOut(true, 'projects', { projects, active: data.activeProject || null });
|
|
944
|
+
if (projects.length === 0) {
|
|
945
|
+
console.log(options.org ? `No projects you can access in org "${options.org}"` : 'No projects you can access');
|
|
900
946
|
return;
|
|
901
947
|
}
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
console.log(`
|
|
907
|
-
console.log(` ${
|
|
908
|
-
console.log(` ${project.path}`);
|
|
948
|
+
console.log(`Projects (${projects.length}):\n`);
|
|
949
|
+
for (const p of projects) {
|
|
950
|
+
const active = p.id === data.activeProject ? ' [ACTIVE]' : '';
|
|
951
|
+
console.log(` ${p.name}${active}`);
|
|
952
|
+
console.log(` /o/${p.orgSlug || p.orgId}/projects/${p.folder ? `${p.folder}/` : ''}${p.slug || p.id}`);
|
|
953
|
+
console.log(` ${p.id}`);
|
|
909
954
|
console.log('');
|
|
910
|
-
}
|
|
955
|
+
}
|
|
911
956
|
});
|
|
912
957
|
|
|
913
958
|
// Command: layers
|
|
914
959
|
program
|
|
915
960
|
.command('layers')
|
|
916
|
-
.description(
|
|
917
|
-
.
|
|
961
|
+
.description("List frame layers — the built-in set, or a project's own with --project")
|
|
962
|
+
.option('--project <id>', "List this project's layers (read-only)")
|
|
963
|
+
.option('--org <org>', 'Resolve against this org (id or name); scopes per-request without switching the session')
|
|
964
|
+
.action(async (options) => {
|
|
965
|
+
if (options.project) {
|
|
966
|
+
const data = await readApiGet('layers', withQuery('/api/fs/', { path: '/', projectId: options.project }), options.org);
|
|
967
|
+
const layers = (data.layers || []).map(l => ({ id: String(l.path || '').replace(/^\//, ''), label: l.label, description: l.description, lanes: l.lanes || [] }));
|
|
968
|
+
jsonOut(true, 'layers', { project: data.project || null, layers });
|
|
969
|
+
console.log(`Layers of ${data.project?.name || options.project}:\n`);
|
|
970
|
+
if (layers.length === 0) console.log(' (no layers)');
|
|
971
|
+
for (const l of layers) console.log(` ${l.id} — ${l.label || ''}${l.lanes.length ? ` [${l.lanes.join(' ')}]` : ''}`);
|
|
972
|
+
return;
|
|
973
|
+
}
|
|
918
974
|
const layersArray = Object.entries(LAYERS).map(([key, layer]) => ({
|
|
919
975
|
id: key, label: layer.label, description: layer.description, defaultType: LAYER_TYPE_INFERENCE[key] || 'design'
|
|
920
976
|
}));
|
|
@@ -1194,7 +1250,7 @@ program
|
|
|
1194
1250
|
.action(async (source, options) => {
|
|
1195
1251
|
requireLogin();
|
|
1196
1252
|
|
|
1197
|
-
if (
|
|
1253
|
+
if (localServerDown()) {
|
|
1198
1254
|
jsonOut(false, 'add', 'Server is not running. Start it with `drafted start`');
|
|
1199
1255
|
console.error('Error: Server is not running. Start it with `drafted start`');
|
|
1200
1256
|
process.exit(1);
|
|
@@ -1292,7 +1348,8 @@ program
|
|
|
1292
1348
|
.option('--summary', 'Include size, updatedAt, and title for frames')
|
|
1293
1349
|
.option('--pattern <glob>', 'Filter frame filenames (e.g. "*.html")')
|
|
1294
1350
|
.action(async (path, options) => {
|
|
1295
|
-
const
|
|
1351
|
+
const target = await resolveCanonicalPath('ls', path || '/', options.project);
|
|
1352
|
+
const params = { path: target.path, projectId: target.projectId, pattern: options.pattern };
|
|
1296
1353
|
if (options.recursive) { params.recursive = 'true'; params.summary = 'true'; }
|
|
1297
1354
|
else if (options.summary) params.summary = 'true';
|
|
1298
1355
|
const data = await readApiGet('ls', withQuery('/api/fs/', params), options.org);
|
|
@@ -1309,9 +1366,10 @@ program
|
|
|
1309
1366
|
.option('--org <org>', 'Resolve against this org (id or name); scopes per-request without switching the session')
|
|
1310
1367
|
.option('--lines <range>', 'Line range to read, e.g. "1-40" or "20"')
|
|
1311
1368
|
.action(async (pathOrId, options) => {
|
|
1369
|
+
const target = await resolveCanonicalPath('read', pathOrId, options.project);
|
|
1312
1370
|
let apiPath;
|
|
1313
1371
|
try {
|
|
1314
|
-
apiPath = frameReadPath(
|
|
1372
|
+
apiPath = frameReadPath(target.path, target.projectId, options.lines);
|
|
1315
1373
|
} catch (err) {
|
|
1316
1374
|
jsonOut(false, 'read', err.message);
|
|
1317
1375
|
console.error(`❌ ${err.message}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "drafted",
|
|
3
|
-
"version": "1.21.
|
|
3
|
+
"version": "1.21.1",
|
|
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": [
|