@a83/orbiter-admin 0.3.34 → 0.3.35

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@a83/orbiter-admin",
3
- "version": "0.3.34",
3
+ "version": "0.3.35",
4
4
  "description": "Standalone admin server for Orbiter CMS",
5
5
  "type": "module",
6
6
  "main": "./src/server.js",
package/public/style.css CHANGED
@@ -2046,65 +2046,73 @@ a.xfce-sb-logo:hover { opacity: .8; }
2046
2046
  opacity: 1; transform: translateX(-50%) translateY(0); pointer-events: auto;
2047
2047
  }
2048
2048
 
2049
- /* ── Command Palette ─────────────────────────────────────── */
2050
- .xfce-palette {
2049
+ /* ── Orbiter Terminal ────────────────────────────────────── */
2050
+ .xfce-terminal {
2051
2051
  display: none; position: fixed; inset: 0; z-index: 10000;
2052
2052
  background: rgba(0,0,0,0);
2053
2053
  align-items: flex-end; justify-content: center;
2054
2054
  padding-bottom: calc(var(--dock-h, 76px) + 20px);
2055
2055
  transition: background .18s;
2056
2056
  }
2057
- .xfce-palette.open {
2057
+ .xfce-terminal.open {
2058
2058
  display: flex;
2059
2059
  background: rgba(0,0,0,.28);
2060
2060
  }
2061
2061
 
2062
- .xfce-palette-inner {
2063
- width: min(560px, 92vw);
2064
- background: var(--glass-bg, color-mix(in srgb, var(--bg1) 90%, transparent));
2065
- border: 1px solid var(--line);
2062
+ .xfce-term-inner {
2063
+ width: min(680px, 92vw); max-height: min(480px, 60vh);
2064
+ background: #0a0a14;
2065
+ border: 1px solid rgba(120,90,210,.25);
2066
+ border-top: 2px solid var(--accent, #7c6fcd);
2066
2067
  border-radius: 12px;
2067
- box-shadow: 0 -2px 32px rgba(0,0,0,.25), 0 0 0 1px color-mix(in srgb,var(--accent) 20%,transparent);
2068
+ box-shadow: 0 -2px 48px rgba(0,0,0,.5);
2068
2069
  overflow: hidden;
2069
- transform: translateY(14px);
2070
- opacity: 0;
2070
+ display: flex; flex-direction: column;
2071
+ transform: translateY(14px); opacity: 0;
2071
2072
  transition: transform .2s cubic-bezier(.34,1.15,.64,1), opacity .15s;
2072
2073
  }
2073
- .xfce-palette.open .xfce-palette-inner {
2074
- transform: translateY(0);
2075
- opacity: 1;
2074
+ .xfce-terminal.open .xfce-term-inner {
2075
+ transform: translateY(0); opacity: 1;
2076
2076
  }
2077
2077
 
2078
- .xfce-palette-bar {
2079
- display: flex; align-items: center; gap: 8px;
2080
- padding: 10px 14px; border-bottom: 1px solid var(--line);
2078
+ .xfce-term-bar {
2079
+ display: flex; align-items: center; justify-content: space-between;
2080
+ padding: 8px 14px; border-bottom: 1px solid rgba(255,255,255,.06); flex-shrink: 0;
2081
2081
  }
2082
- .xfce-palette-cmd { color: var(--accent); font-size: 13px; flex-shrink: 0; }
2083
- .xfce-palette-inp {
2084
- flex: 1; background: none; border: none; outline: none;
2085
- font-family: var(--mono); font-size: 13px; color: var(--heading);
2082
+ .xfce-term-title {
2083
+ font-family: var(--mono); font-size: 9px; letter-spacing: .12em;
2084
+ text-transform: uppercase; color: var(--accent, #7c6fcd);
2085
+ }
2086
+ .xfce-term-esc {
2087
+ font-family: var(--mono); font-size: 8px; color: rgba(150,150,200,.5);
2088
+ background: rgba(255,255,255,.07); border: 1px solid rgba(255,255,255,.1);
2089
+ border-radius: 3px; padding: 1px 5px;
2086
2090
  }
2087
- .xfce-palette-inp::placeholder { color: var(--muted); }
2088
- .xfce-palette-hint { font-size: 8px; color: var(--muted); font-family: var(--mono); white-space: nowrap; }
2089
2091
 
2090
- .xfce-palette-results {
2091
- max-height: 360px; overflow-y: auto; padding: 6px 0;
2092
+ .xfce-term-output {
2093
+ flex: 1; overflow-y: auto; padding: 10px 0;
2094
+ font-family: var(--mono); font-size: 11px; line-height: 1.7;
2092
2095
  }
2093
- .xfce-pal-group {
2094
- font-size: 8px; font-family: var(--mono); color: var(--muted);
2095
- letter-spacing: .08em; text-transform: uppercase;
2096
- padding: 8px 14px 4px;
2096
+ .xfce-term-line { padding: 0 16px; color: rgba(200,200,230,.75); white-space: pre; }
2097
+ .xfce-term-cmd { color: rgba(200,200,230,.9); }
2098
+ .xfce-term-ok { color: #5dc97e; }
2099
+ .xfce-term-err { color: #e06c6c; }
2100
+ .xfce-term-dim { color: rgba(150,150,190,.6); }
2101
+ .xfce-term-muted { color: rgba(120,120,170,.45); }
2102
+
2103
+ .xfce-term-input-row {
2104
+ display: flex; align-items: center; gap: 8px;
2105
+ padding: 10px 16px; border-top: 1px solid rgba(255,255,255,.06); flex-shrink: 0;
2097
2106
  }
2098
- .xfce-pal-item {
2099
- display: flex; align-items: center; gap: 10px;
2100
- padding: 7px 14px; cursor: pointer; transition: background .1s;
2107
+ .xfce-term-prompt {
2108
+ font-family: var(--mono); font-size: 11px;
2109
+ color: var(--accent, #7c6fcd); white-space: nowrap; flex-shrink: 0;
2101
2110
  }
2102
- .xfce-pal-item:hover, .xfce-pal-item.pal-active {
2103
- background: color-mix(in srgb, var(--accent) 12%, transparent);
2111
+ .xfce-term-input {
2112
+ flex: 1; background: none; border: none; outline: none;
2113
+ font-family: var(--mono); font-size: 11px; color: rgba(220,220,255,.9);
2114
+ caret-color: var(--accent, #7c6fcd);
2104
2115
  }
2105
- .xfce-pal-icon { width: 20px; text-align: center; font-size: 13px; color: var(--accent); flex-shrink: 0; }
2106
- .xfce-pal-label { font-size: 12px; color: var(--heading); font-family: var(--mono); }
2107
- .xfce-pal-empty { padding: 20px 14px; color: var(--muted); font-size: 11px; font-family: var(--mono); }
2108
2116
 
2109
2117
  /* ── Toast host (above dock) ─────────────────────────────── */
2110
2118
  .xfce-toast-host {
package/public/xfce.js CHANGED
@@ -28,10 +28,8 @@
28
28
  { icon: '☑', label: 'To-do', pane: 'todos' },
29
29
  ];
30
30
 
31
- // palette items populated after /api/info loads
32
- var _palItems = NAV.concat(TOOLS).map(function (n) {
33
- return { icon: n.icon, label: n.label, href: n.href, group: n.key in { schema:1, build:1, import:1 } ? 'Tools' : 'Nav' };
34
- });
31
+ // collections loaded by /api/info — used by terminal
32
+ var _termCols = [];
35
33
 
36
34
  // ── Helpers ───────────────────────────────────────────────────────────
37
35
  function el(tag, cls, html) {
@@ -96,7 +94,7 @@
96
94
  // Palette trigger in status bar
97
95
  document.getElementById('xfce-sb-palette-btn').addEventListener('click', function (e) {
98
96
  e.stopPropagation();
99
- openPalette();
97
+ openTerminal();
100
98
  });
101
99
  }
102
100
 
@@ -162,7 +160,7 @@
162
160
  palBtn.addEventListener('click', function (e) {
163
161
  e.stopPropagation();
164
162
  toolsPopup.classList.remove('open');
165
- openPalette();
163
+ openTerminal();
166
164
  });
167
165
  toolsPopup.appendChild(palBtn);
168
166
  document.body.appendChild(toolsPopup);
@@ -212,128 +210,277 @@
212
210
  if (colCreateEl) colCreateEl.classList.remove('visible');
213
211
  }
214
212
 
215
- // ── Command Palette ───────────────────────────────────────────────────
216
- var palette, paletteInp, paletteResults, palActive = -1;
213
+ // ── Orbiter Terminal ─────────────────────────────────────────────────
214
+ var terminal, termOutput, termInput;
215
+ var termHistory = [], termHistIdx = -1;
216
+ var TERM_CMDS = ['help','clear','info','ls','go','new','search','build','export'];
217
+ var NAV_DEST = {
218
+ dashboard: '/dashboard.html', media: '/media.html', settings: '/settings.html',
219
+ users: '/users.html', schema: '/schema.html', build: '/build.html',
220
+ import: '/import.html', account: '/account.html',
221
+ };
217
222
 
218
- function buildPalette() {
219
- palette = el('div', 'xfce-palette');
220
- palette.id = 'xfce-palette';
221
- palette.innerHTML = [
222
- '<div class="xfce-palette-inner">',
223
- '<div class="xfce-palette-bar">',
224
- '<span class="xfce-palette-cmd">⌘</span>',
225
- '<input id="xfce-palette-inp" class="xfce-palette-inp" placeholder="Go to page or collection…" autocomplete="off" spellcheck="false" />',
226
- '<span class="xfce-palette-hint">ESC to close</span>',
223
+ function buildTerminal() {
224
+ terminal = el('div', 'xfce-terminal');
225
+ terminal.id = 'xfce-terminal';
226
+ terminal.innerHTML = [
227
+ '<div class="xfce-term-inner">',
228
+ '<div class="xfce-term-bar">',
229
+ '<span class="xfce-term-title">◈ orbiter terminal</span>',
230
+ '<kbd class="xfce-term-esc">ESC</kbd>',
231
+ '</div>',
232
+ '<div id="xfce-term-output" class="xfce-term-output"></div>',
233
+ '<div class="xfce-term-input-row">',
234
+ '<span class="xfce-term-prompt">orbiter $</span>',
235
+ '<input id="xfce-term-input" class="xfce-term-input" autocomplete="off" spellcheck="false" />',
227
236
  '</div>',
228
- '<div id="xfce-palette-results" class="xfce-palette-results"></div>',
229
237
  '</div>',
230
238
  ].join('');
231
- document.body.appendChild(palette);
232
-
233
- paletteInp = document.getElementById('xfce-palette-inp');
234
- paletteResults = document.getElementById('xfce-palette-results');
235
-
236
- paletteInp.addEventListener('input', function () {
237
- palActive = -1;
238
- renderPalette(paletteInp.value);
239
- });
240
-
241
- paletteInp.addEventListener('keydown', function (e) {
242
- var items = paletteResults.querySelectorAll('.xfce-pal-item');
243
- if (e.key === 'ArrowDown') {
244
- e.preventDefault();
245
- palActive = Math.min(palActive + 1, items.length - 1);
246
- updatePalActive(items);
239
+ document.body.appendChild(terminal);
240
+
241
+ termOutput = document.getElementById('xfce-term-output');
242
+ termInput = document.getElementById('xfce-term-input');
243
+
244
+ termPrint('Orbiter Admin · type \'help\' for available commands', 'muted');
245
+ termPrint('', '');
246
+
247
+ termInput.addEventListener('keydown', function (e) {
248
+ if (e.key === 'Enter') {
249
+ var line = termInput.value.trim();
250
+ termInput.value = '';
251
+ if (!line) return;
252
+ termHistIdx = -1;
253
+ if (!termHistory.length || termHistory[0] !== line) termHistory.unshift(line);
254
+ if (termHistory.length > 50) termHistory.pop();
255
+ termPrint('orbiter $ ' + line, 'cmd');
256
+ execCmd(line);
247
257
  } else if (e.key === 'ArrowUp') {
248
258
  e.preventDefault();
249
- palActive = Math.max(palActive - 1, 0);
250
- updatePalActive(items);
251
- } else if (e.key === 'Enter') {
252
- var active = paletteResults.querySelector('.xfce-pal-item.pal-active');
253
- if (active) { location.href = active.dataset.href; closePalette(); }
254
- else {
255
- var first = paletteResults.querySelector('.xfce-pal-item');
256
- if (first) { location.href = first.dataset.href; closePalette(); }
257
- }
259
+ if (termHistIdx < termHistory.length - 1) { termHistIdx++; termInput.value = termHistory[termHistIdx]; }
260
+ } else if (e.key === 'ArrowDown') {
261
+ e.preventDefault();
262
+ if (termHistIdx > 0) { termHistIdx--; termInput.value = termHistory[termHistIdx]; }
263
+ else { termHistIdx = -1; termInput.value = ''; }
264
+ } else if (e.key === 'Tab') {
265
+ e.preventDefault();
266
+ termTabComplete();
258
267
  } else if (e.key === 'Escape') {
259
- closePalette();
268
+ closeTerminal();
260
269
  }
261
270
  });
262
271
 
263
- palette.addEventListener('click', function (e) {
264
- if (e.target === palette) closePalette();
272
+ terminal.addEventListener('click', function (e) {
273
+ if (e.target === terminal) closeTerminal();
265
274
  });
266
275
  }
267
276
 
268
- function updatePalActive(items) {
269
- items.forEach(function (it, i) {
270
- it.classList.toggle('pal-active', i === palActive);
271
- if (i === palActive) it.scrollIntoView({ block: 'nearest' });
272
- });
277
+ function termPrint(text, cls) {
278
+ var line = document.createElement('div');
279
+ line.className = 'xfce-term-line' + (cls ? ' xfce-term-' + cls : '');
280
+ line.textContent = text;
281
+ termOutput.appendChild(line);
282
+ termOutput.scrollTop = termOutput.scrollHeight;
273
283
  }
274
284
 
275
- function renderPalette(q) {
276
- q = (q || '').toLowerCase().trim();
277
- var filtered = q
278
- ? _palItems.filter(function (it) { return it.label.toLowerCase().includes(q); })
279
- : _palItems;
280
-
281
- if (!filtered.length) {
282
- paletteResults.innerHTML = '<div class="xfce-pal-empty">No results</div>';
283
- return;
285
+ function termClear() { termOutput.innerHTML = ''; }
286
+
287
+ function execCmd(raw) {
288
+ var parts = raw.trim().split(/\s+/);
289
+ var cmd = parts[0].toLowerCase();
290
+ var args = parts.slice(1);
291
+ switch (cmd) {
292
+ case 'help': termHelp(); break;
293
+ case 'clear': termClear(); break;
294
+ case 'info': termInfo(); break;
295
+ case 'ls': termLs(args); break;
296
+ case 'go': termGo(args); break;
297
+ case 'new': termNew(args); break;
298
+ case 'search': termSearch(args.join(' ')); break;
299
+ case 'build': termBuild(); break;
300
+ case 'export': termExport(args); break;
301
+ default: termPrint('command not found: ' + cmd + ' (try \'help\')', 'err');
284
302
  }
303
+ termPrint('', '');
304
+ }
285
305
 
286
- // Group items
287
- var groups = {};
288
- filtered.forEach(function (it) {
289
- if (!groups[it.group]) groups[it.group] = [];
290
- groups[it.group].push(it);
291
- });
306
+ function termHelp() {
307
+ termPrint('', '');
308
+ [
309
+ ' go <page|collection> navigate to a page or collection',
310
+ ' new <collection> open editor for a new entry',
311
+ ' ls [collection] list collections or recent entries',
312
+ ' search <term> full-text search across all content',
313
+ ' info show pod and version info',
314
+ ' build trigger deploy webhook',
315
+ ' export <col> [flags] download entries flags: --md --drafts',
316
+ ' clear clear terminal',
317
+ ' help show this message',
318
+ '',
319
+ ' shortcuts: ↑↓ history · Tab completion · ⌘K toggle',
320
+ ].forEach(function (l) { termPrint(l, 'dim'); });
321
+ }
292
322
 
293
- var html = '';
294
- Object.keys(groups).forEach(function (g) {
295
- html += '<div class="xfce-pal-group">' + g + '</div>';
296
- groups[g].forEach(function (it) {
297
- html += '<div class="xfce-pal-item" data-href="' + it.href + '">'
298
- + '<span class="xfce-pal-icon">' + it.icon + '</span>'
299
- + '<span class="xfce-pal-label">' + it.label + '</span>'
300
- + '</div>';
323
+ function termInfo() {
324
+ fetch('/api/info', { credentials: 'include' })
325
+ .then(function (r) { return r.ok ? r.json() : null; })
326
+ .then(function (d) {
327
+ if (!d) { termPrint('error fetching info', 'err'); return; }
328
+ var total = (d.collections || []).reduce(function (s, c) { return s + (c.total || 0); }, 0);
329
+ termPrint('', '');
330
+ termPrint(' pod ' + d.podPath.split('/').pop(), 'dim');
331
+ termPrint(' format v' + d.formatVersion, 'dim');
332
+ termPrint(' admin v' + d.adminVersion, 'dim');
333
+ termPrint(' collections ' + (d.collections || []).length, 'dim');
334
+ termPrint(' published ' + total + ' entries', 'dim');
301
335
  });
302
- });
303
- paletteResults.innerHTML = html;
336
+ }
304
337
 
305
- paletteResults.querySelectorAll('.xfce-pal-item').forEach(function (item) {
306
- item.addEventListener('click', function () {
307
- location.href = item.dataset.href;
308
- closePalette();
338
+ function termLs(args) {
339
+ if (!args.length) {
340
+ if (!_termCols.length) { termPrint('no collections loaded yet', 'muted'); return; }
341
+ termPrint('', '');
342
+ _termCols.forEach(function (col) {
343
+ var info = col.total + ' entr' + (col.total === 1 ? 'y' : 'ies');
344
+ if (col.drafts > 0) info += ' ' + col.drafts + ' draft' + (col.drafts === 1 ? '' : 's');
345
+ termPrint(' ' + col.id.padEnd(22) + info, 'dim');
309
346
  });
310
- item.addEventListener('mouseenter', function () {
311
- var items = paletteResults.querySelectorAll('.xfce-pal-item');
312
- palActive = Array.from(items).indexOf(item);
313
- updatePalActive(items);
347
+ return;
348
+ }
349
+ var colId = args[0];
350
+ fetch('/api/collections/' + encodeURIComponent(colId) + '/entries?status=published&limit=25', { credentials: 'include' })
351
+ .then(function (r) { return r.ok ? r.json() : null; })
352
+ .then(function (d) {
353
+ if (!d) { termPrint('collection not found: ' + colId, 'err'); return; }
354
+ var entries = d.entries || (Array.isArray(d) ? d : []);
355
+ termPrint('', '');
356
+ if (!entries.length) { termPrint(' (no published entries)', 'muted'); return; }
357
+ entries.forEach(function (e) {
358
+ var date = (e.updated_at || e.created_at || '').substring(0, 10);
359
+ termPrint(' ' + (e.slug || e.id || '').padEnd(36) + date, 'dim');
360
+ });
361
+ if (d.total > entries.length) termPrint(' … ' + (d.total - entries.length) + ' more', 'muted');
314
362
  });
315
- });
316
363
  }
317
364
 
318
- function openPalette() {
319
- if (!palette) buildPalette();
320
- palette.style.display = 'flex';
321
- paletteInp.value = '';
322
- palActive = -1;
323
- renderPalette('');
365
+ function termGo(args) {
366
+ if (!args.length) { termPrint('usage: go <page|collection>', 'err'); return; }
367
+ var dest = args[0].toLowerCase();
368
+ if (NAV_DEST[dest]) {
369
+ termPrint('→ ' + dest, 'ok');
370
+ closeTerminal();
371
+ setTimeout(function () { location.href = NAV_DEST[dest]; }, 180);
372
+ return;
373
+ }
374
+ var col = _termCols.find(function (c) { return c.id === dest || c.label.toLowerCase() === dest; });
375
+ if (col) {
376
+ termPrint('→ ' + col.label, 'ok');
377
+ closeTerminal();
378
+ var href = col.singleton
379
+ ? '/editor.html?collection=' + encodeURIComponent(col.id) + '&singleton=1'
380
+ : '/entries.html?col=' + encodeURIComponent(col.id) + '&label=' + encodeURIComponent(col.label);
381
+ setTimeout(function () { location.href = href; }, 180);
382
+ return;
383
+ }
384
+ termPrint('not found: ' + dest, 'err');
385
+ }
386
+
387
+ function termNew(args) {
388
+ if (!args.length) { termPrint('usage: new <collection>', 'err'); return; }
389
+ var dest = args[0].toLowerCase();
390
+ var col = _termCols.find(function (c) { return c.id === dest || c.label.toLowerCase() === dest; });
391
+ if (!col) { termPrint('collection not found: ' + dest, 'err'); return; }
392
+ termPrint('→ new entry in ' + col.label, 'ok');
393
+ closeTerminal();
394
+ setTimeout(function () { location.href = '/editor.html?collection=' + encodeURIComponent(col.id); }, 180);
395
+ }
396
+
397
+ function termSearch(term) {
398
+ if (!term) { termPrint('usage: search <term>', 'err'); return; }
399
+ termPrint('searching…', 'muted');
400
+ fetch('/api/search?q=' + encodeURIComponent(term), { credentials: 'include' })
401
+ .then(function (r) { return r.ok ? r.json() : null; })
402
+ .then(function (d) {
403
+ if (!d) { termPrint('search error', 'err'); return; }
404
+ var results = d.results || (Array.isArray(d) ? d : []);
405
+ termPrint('', '');
406
+ if (!results.length) { termPrint(' no results for "' + term + '"', 'muted'); return; }
407
+ results.slice(0, 15).forEach(function (r) {
408
+ termPrint(' › ' + (r.collection || '') + '/' + (r.slug || r.id || ''), 'ok');
409
+ if (r.title) termPrint(' ' + r.title, 'dim');
410
+ });
411
+ if (results.length > 15) termPrint(' … ' + (results.length - 15) + ' more', 'muted');
412
+ });
413
+ }
414
+
415
+ function termBuild() {
416
+ termPrint('triggering build…', 'muted');
417
+ fetch('/api/build', { method: 'POST', credentials: 'include' })
418
+ .then(function (r) { return r.json(); })
419
+ .then(function (d) {
420
+ if (d.ok || d.message) termPrint('✓ ' + (d.message || 'build triggered'), 'ok');
421
+ else termPrint('build: ' + (d.error || JSON.stringify(d)), 'err');
422
+ })
423
+ .catch(function () { termPrint('build request failed', 'err'); });
424
+ }
425
+
426
+ function termExport(args) {
427
+ if (!args.length) { termPrint('usage: export <collection> [--md] [--drafts]', 'err'); return; }
428
+ var colId = args[0];
429
+ var format = args.indexOf('--md') !== -1 ? 'md' : 'json';
430
+ var drafts = args.indexOf('--drafts') !== -1 ? '1' : '0';
431
+ var col = _termCols.find(function (c) { return c.id === colId; });
432
+ if (!col) { termPrint('collection not found: ' + colId, 'err'); return; }
433
+ termPrint('exporting ' + col.label + ' as ' + format + '…', 'muted');
434
+ fetch('/api/terminal/export?col=' + encodeURIComponent(colId) + '&format=' + format + '&drafts=' + drafts, { credentials: 'include' })
435
+ .then(function (r) {
436
+ if (!r.ok) return r.json().then(function (e) { throw new Error(e.error || r.status); });
437
+ return r.blob().then(function (blob) {
438
+ var a = document.createElement('a');
439
+ a.href = URL.createObjectURL(blob);
440
+ a.download = colId + '.' + format;
441
+ document.body.appendChild(a); a.click(); a.remove();
442
+ termPrint('✓ downloaded ' + colId + '.' + format, 'ok');
443
+ });
444
+ })
445
+ .catch(function (err) { termPrint('export failed: ' + err.message, 'err'); });
446
+ }
447
+
448
+ function termTabComplete() {
449
+ var val = termInput.value;
450
+ var parts = val.split(/\s+/);
451
+ if (parts.length === 1) {
452
+ var p = parts[0].toLowerCase();
453
+ var m = TERM_CMDS.filter(function (c) { return c.startsWith(p); });
454
+ if (m.length === 1) termInput.value = m[0] + ' ';
455
+ else if (m.length > 1) termPrint(m.join(' '), 'muted');
456
+ } else if (parts.length === 2) {
457
+ var cmd2 = parts[0].toLowerCase(), p2 = parts[1].toLowerCase();
458
+ var pool = _termCols.map(function (c) { return c.id; });
459
+ if (cmd2 === 'go') pool = pool.concat(Object.keys(NAV_DEST));
460
+ if (['go','new','ls','export'].indexOf(cmd2) !== -1) {
461
+ var m2 = pool.filter(function (c) { return c.startsWith(p2); });
462
+ if (m2.length === 1) termInput.value = parts[0] + ' ' + m2[0];
463
+ else if (m2.length > 1) termPrint(m2.join(' '), 'muted');
464
+ }
465
+ }
466
+ }
467
+
468
+ function openTerminal() {
469
+ if (!terminal) buildTerminal();
470
+ terminal.style.display = 'flex';
324
471
  requestAnimationFrame(function () {
325
472
  requestAnimationFrame(function () {
326
- palette.classList.add('open');
327
- setTimeout(function () { paletteInp.focus(); }, 40);
473
+ terminal.classList.add('open');
474
+ setTimeout(function () { termInput.focus(); }, 40);
328
475
  });
329
476
  });
330
477
  }
331
478
 
332
- function closePalette() {
333
- if (!palette) return;
334
- palette.classList.remove('open');
479
+ function closeTerminal() {
480
+ if (!terminal) return;
481
+ terminal.classList.remove('open');
335
482
  setTimeout(function () {
336
- if (!palette.classList.contains('open')) palette.style.display = 'none';
483
+ if (!terminal.classList.contains('open')) terminal.style.display = 'none';
337
484
  }, 200);
338
485
  }
339
486
 
@@ -821,8 +968,8 @@
821
968
 
822
969
  colGroup.appendChild(item);
823
970
 
824
- // Add to palette
825
- _palItems.push({ icon: abbr, label: col.label, href: href, group: 'Collections' });
971
+ // Add to terminal collection list
972
+ _termCols.push({ id: col.id, label: col.label, total: col.total || 0, drafts: col.drafts || 0, singleton: !!col.singleton });
826
973
  });
827
974
  }
828
975
 
@@ -880,11 +1027,11 @@
880
1027
  document.addEventListener('keydown', function (e) {
881
1028
  var mod = e.metaKey || e.ctrlKey;
882
1029
 
883
- // ⌘K — command palette
1030
+ // ⌘K — terminal
884
1031
  if (mod && !e.shiftKey && (e.key === 'k' || e.key === 'K')) {
885
1032
  e.preventDefault();
886
- if (palette && palette.classList.contains('open')) closePalette();
887
- else openPalette();
1033
+ if (terminal && terminal.classList.contains('open')) closeTerminal();
1034
+ else openTerminal();
888
1035
  return;
889
1036
  }
890
1037
 
@@ -0,0 +1,42 @@
1
+ import { Hono } from 'hono';
2
+ import { openPod } from '@a83/orbiter-core';
3
+
4
+ export const terminalRoutes = new Hono();
5
+
6
+ // GET /api/terminal/export?col=<id>&format=json|md&drafts=0|1
7
+ terminalRoutes.get('/export', (c) => {
8
+ const podPath = c.get('podPath');
9
+ const colId = c.req.query('col');
10
+ const format = c.req.query('format') || 'json';
11
+ const inclDrafts = c.req.query('drafts') === '1';
12
+
13
+ if (!colId) return c.json({ error: 'col is required' }, 400);
14
+
15
+ const db = openPod(podPath);
16
+ const col = db.getCollections().find(col => col.id === colId);
17
+ if (!col) { db.close(); return c.json({ error: 'Collection not found' }, 404); }
18
+
19
+ const entries = inclDrafts
20
+ ? db.getEntries(colId)
21
+ : db.getEntries(colId, { status: 'published' });
22
+ db.close();
23
+
24
+ if (format === 'md') {
25
+ const md = entries.map(e =>
26
+ `---\nslug: ${JSON.stringify(e.slug)}\nstatus: ${JSON.stringify(e.status)}\n---\n\n${e.content || ''}`
27
+ ).join('\n\n---\n\n');
28
+ return new Response(md, {
29
+ headers: {
30
+ 'Content-Type': 'text/markdown; charset=utf-8',
31
+ 'Content-Disposition': `attachment; filename="${colId}.md"`,
32
+ },
33
+ });
34
+ }
35
+
36
+ return new Response(JSON.stringify({ collection: colId, entries }, null, 2), {
37
+ headers: {
38
+ 'Content-Type': 'application/json',
39
+ 'Content-Disposition': `attachment; filename="${colId}.json"`,
40
+ },
41
+ });
42
+ });
package/src/server.js CHANGED
@@ -24,6 +24,7 @@ import { infoRoutes } from './routes/info.js';
24
24
  import { importRoutes } from './routes/import.js';
25
25
  import { commentRoutes } from './routes/comments.js';
26
26
  import { lockRoutes } from './routes/locks.js';
27
+ import { terminalRoutes } from './routes/terminal.js';
27
28
  import { requireAuth } from './middleware/auth.js';
28
29
  import { csrfMiddleware } from './middleware/csrf.js';
29
30
 
@@ -78,6 +79,7 @@ export function createApp(podPath) {
78
79
  api.route('/collections', commentRoutes);
79
80
  api.route('/', commentRoutes);
80
81
  api.route('/locks', lockRoutes);
82
+ api.route('/terminal', terminalRoutes);
81
83
 
82
84
  app.route('/api', api);
83
85