drafted 1.11.10 → 1.11.11

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.
Files changed (2) hide show
  1. package/cli/drafted.mjs +18 -12
  2. package/package.json +1 -1
package/cli/drafted.mjs CHANGED
@@ -234,14 +234,17 @@ function withQuery(base, params) {
234
234
  }
235
235
 
236
236
  // GET a Drafted API path with the stored session and return parsed JSON.
237
- // Strictly read-only: callers only ever issue GETs through this seam. On any
238
- // failure it emits a structured error (for --json) and exits non-zero.
239
- async function readApiGet(command, apiPath) {
237
+ // Strictly read-only: callers only ever issue GETs through this seam. An
238
+ // optional --org override is forwarded as the X-Drafted-Org header (the server
239
+ // validates it against the caller's memberships). On any failure it emits a
240
+ // structured error (for --json) and exits non-zero.
241
+ async function readApiGet(command, apiPath, org) {
240
242
  requireLogin();
241
243
  const serverUrl = getServerUrl();
244
+ const headers = org ? { 'X-Drafted-Org': org } : {};
242
245
  let res;
243
246
  try {
244
- res = await authFetch(`${serverUrl}${apiPath}`);
247
+ res = await authFetch(`${serverUrl}${apiPath}`, { headers });
245
248
  } catch (err) {
246
249
  jsonOut(false, command, `Could not reach server at ${serverUrl}: ${err.message}`);
247
250
  console.error(`❌ Could not reach server at ${serverUrl}: ${err.message}`);
@@ -1145,10 +1148,9 @@ program
1145
1148
  //
1146
1149
  // Project scoping: --project <id> selects the project (passed through as a query
1147
1150
  // param). When omitted, the server resolves the caller's active project. Org is
1148
- // the logged-in session's active org identical to `add`. (Cross-org reads
1149
- // require switching org first; a per-request --org override is a deliberate
1150
- // follow-up, not added here to avoid implying access the /api/fs/ routes don't
1151
- // yet scope.)
1151
+ // the logged-in session's active org unless --org <id|name> is given, which the
1152
+ // server validates against the caller's memberships and scopes per-request via
1153
+ // the X-Drafted-Org header without mutating the shared session's active org.
1152
1154
 
1153
1155
  // Command: ls (alias: list) — list layers / lanes / frames
1154
1156
  program
@@ -1156,6 +1158,7 @@ program
1156
1158
  .alias('list')
1157
1159
  .description('List layers, lanes, or frames in a project (read-only)')
1158
1160
  .option('--project <id>', 'Target project ID (defaults to your active project)')
1161
+ .option('--org <org>', 'Resolve against this org (id or name); scopes per-request without switching the session')
1159
1162
  .option('--recursive', 'Recurse into subdirectories (forces summary mode)')
1160
1163
  .option('--summary', 'Include size, updatedAt, and title for frames')
1161
1164
  .option('--pattern <glob>', 'Filter frame filenames (e.g. "*.html")')
@@ -1163,7 +1166,7 @@ program
1163
1166
  const params = { path: path || '/', projectId: options.project, pattern: options.pattern };
1164
1167
  if (options.recursive) { params.recursive = 'true'; params.summary = 'true'; }
1165
1168
  else if (options.summary) params.summary = 'true';
1166
- const data = await readApiGet('ls', withQuery('/api/fs/', params));
1169
+ const data = await readApiGet('ls', withQuery('/api/fs/', params), options.org);
1167
1170
  jsonOut(true, 'ls', data);
1168
1171
  printLs(data);
1169
1172
  });
@@ -1174,6 +1177,7 @@ program
1174
1177
  .aliases(['get', 'cat'])
1175
1178
  .description("Read a frame's content (HTML/text/markdown), or binary metadata, by path / frame URL / ID (read-only)")
1176
1179
  .option('--project <id>', 'Target project ID (defaults to your active project)')
1180
+ .option('--org <org>', 'Resolve against this org (id or name); scopes per-request without switching the session')
1177
1181
  .option('--lines <range>', 'Line range to read, e.g. "1-40" or "20"')
1178
1182
  .action(async (pathOrId, options) => {
1179
1183
  let apiPath;
@@ -1184,7 +1188,7 @@ program
1184
1188
  console.error(`❌ ${err.message}`);
1185
1189
  process.exit(1);
1186
1190
  }
1187
- const data = await readApiGet('read', apiPath);
1191
+ const data = await readApiGet('read', apiPath, options.org);
1188
1192
  jsonOut(true, 'read', data);
1189
1193
  if (data.type === 'binary') {
1190
1194
  console.log(`# ${data.path} (binary)`);
@@ -1206,10 +1210,11 @@ assetCmd
1206
1210
  .command('list')
1207
1211
  .description('List supporting assets (CSS/JS/images/fonts) in a project')
1208
1212
  .requiredOption('--project <id>', 'Target project ID')
1213
+ .option('--org <org>', 'Resolve against this org (id or name); scopes per-request without switching the session')
1209
1214
  .option('--frame <id>', 'Only assets associated with this frame ID')
1210
1215
  .action(async (options) => {
1211
1216
  const apiPath = withQuery(`/api/projects/${encodeURIComponent(options.project)}/assets`, { frameId: options.frame });
1212
- const data = await readApiGet('asset-list', apiPath);
1217
+ const data = await readApiGet('asset-list', apiPath, options.org);
1213
1218
  jsonOut(true, 'asset-list', data);
1214
1219
  const assets = data.assets || [];
1215
1220
  if (assets.length === 0) { console.log('No assets'); return; }
@@ -1223,8 +1228,9 @@ assetCmd
1223
1228
  .command('get <asset_path>')
1224
1229
  .description("Show a single asset's metadata (path, type, size, frame). Metadata only — asset bytes are not served over the API.")
1225
1230
  .requiredOption('--project <id>', 'Target project ID')
1231
+ .option('--org <org>', 'Resolve against this org (id or name); scopes per-request without switching the session')
1226
1232
  .action(async (assetPath, options) => {
1227
- const data = await readApiGet('asset-get', `/api/projects/${encodeURIComponent(options.project)}/assets`);
1233
+ const data = await readApiGet('asset-get', `/api/projects/${encodeURIComponent(options.project)}/assets`, options.org);
1228
1234
  const wanted = assetPath.replace(/^\/+/, '');
1229
1235
  const asset = (data.assets || []).find(a => a.path === assetPath || a.path === wanted);
1230
1236
  if (!asset) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "drafted",
3
- "version": "1.11.10",
3
+ "version": "1.11.11",
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": [