@vendian/cli 0.0.37 → 0.0.38

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-wrapper.mjs CHANGED
@@ -494,17 +494,35 @@ function spawnForward(command, args, options = {}) {
494
494
 
495
495
  // src/python.js
496
496
  import fs2 from "node:fs";
497
- function findPython(platform = process.platform) {
498
- const candidates = platform === "win32" ? [
499
- { command: "py", args: ["-3.11"] },
500
- { command: "python", args: [] },
501
- { command: "python3", args: [] }
502
- ] : [
497
+ function pythonCandidates(platform = process.platform) {
498
+ if (platform === "win32") {
499
+ return [
500
+ { command: "py", args: ["-3.11"] },
501
+ { command: "python", args: [] },
502
+ { command: "python3", args: [] }
503
+ ];
504
+ }
505
+ if (platform === "darwin") {
506
+ return [
507
+ { command: "/opt/homebrew/bin/python3.12", args: [] },
508
+ { command: "/opt/homebrew/bin/python3.11", args: [] },
509
+ { command: "/usr/local/bin/python3.12", args: [] },
510
+ { command: "/usr/local/bin/python3.11", args: [] },
511
+ { command: "python3.12", args: [] },
512
+ { command: "python3.11", args: [] },
513
+ { command: "python3", args: [] },
514
+ { command: "python", args: [] }
515
+ ];
516
+ }
517
+ return [
503
518
  { command: "python3.12", args: [] },
504
519
  { command: "python3.11", args: [] },
505
520
  { command: "python3", args: [] },
506
521
  { command: "python", args: [] }
507
522
  ];
523
+ }
524
+ function findPython(platform = process.platform) {
525
+ const candidates = pythonCandidates(platform);
508
526
  for (const candidate of candidates) {
509
527
  const version = runCapture(candidate.command, [
510
528
  ...candidate.args,
@@ -2363,10 +2381,11 @@ function buildLocalServeEventStreamArgs({ agentsDir: agentsDir2 = "./agents", co
2363
2381
  }
2364
2382
 
2365
2383
  // src/version.js
2366
- var CLI_VERSION = true ? "0.0.37" : process.env.npm_package_version || "0.0.0-dev";
2384
+ var CLI_VERSION = true ? "0.0.38" : process.env.npm_package_version || "0.0.0-dev";
2367
2385
 
2368
2386
  // src/dev-server.js
2369
2387
  var __dirname = path8.dirname(fileURLToPath(import.meta.url));
2388
+ var DEV_UI_HOST = "127.0.0.1";
2370
2389
  var UI_DIR = fs11.existsSync(path8.join(__dirname, "dev-ui")) ? path8.join(__dirname, "dev-ui") : fs11.existsSync(path8.join(__dirname, "src", "dev-ui")) ? path8.join(__dirname, "src", "dev-ui") : path8.join(__dirname, "dev-ui");
2371
2390
  var serveChild = null;
2372
2391
  var serveState = initialServeState();
@@ -2388,8 +2407,8 @@ async function startDevServer({
2388
2407
  agentsDir = candidates[0]?.absolutePath || path8.resolve("./agents");
2389
2408
  }
2390
2409
  const server = http2.createServer(handleRequest);
2391
- server.listen(port, "127.0.0.1", () => {
2392
- const url = `http://localhost:${port}`;
2410
+ server.listen(port, DEV_UI_HOST, () => {
2411
+ const url = devUiUrl(port);
2393
2412
  console.log("");
2394
2413
  console.log(" \x1B[1;36m\u26A1 Vendian Dev UI\x1B[0m");
2395
2414
  console.log(` \x1B[90m${"\u2500".repeat(44)}\x1B[0m`);
@@ -2679,17 +2698,31 @@ async function apiValidate(req, res, body) {
2679
2698
  });
2680
2699
  }
2681
2700
  }
2701
+ function resolveServeStartTarget(body = {}, current = {}) {
2702
+ const targetDir = body.agentsDir || current.agentsDir || "";
2703
+ const targetCollection = body.collectionId || current.collectionId || "";
2704
+ if (!targetCollection) {
2705
+ return { ok: false, error: "Choose a workspace before starting local serve." };
2706
+ }
2707
+ return { ok: true, agentsDir: targetDir, collectionId: targetCollection };
2708
+ }
2682
2709
  async function apiServeStart(req, res, body) {
2683
2710
  if (serveChild && !serveChild.killed) {
2684
2711
  return jsonResponse(res, { ok: false, error: "Already serving" });
2685
2712
  }
2686
- const targetDir = body.agentsDir || agentsDir;
2687
- const targetCollection = body.collectionId || collectionId;
2713
+ const target = resolveServeStartTarget(body, { agentsDir, collectionId });
2714
+ if (!target.ok) {
2715
+ return jsonResponse(res, target, 400);
2716
+ }
2688
2717
  try {
2718
+ const targetDir = target.agentsDir;
2719
+ const targetCollection = target.collectionId;
2689
2720
  const invocation = await preparePythonVendianInvocation(
2690
2721
  buildLocalServeEventStreamArgs({ agentsDir: targetDir, collectionId: targetCollection }),
2691
2722
  { onProgress: null }
2692
2723
  );
2724
+ agentsDir = targetDir;
2725
+ collectionId = targetCollection;
2693
2726
  serveState = initialServeState();
2694
2727
  serveLogs = [];
2695
2728
  logStore = createServeLogStore({ agentsDir: targetDir, collectionId: targetCollection });
@@ -2913,6 +2946,9 @@ function formatDaemonEvent(event) {
2913
2946
  return event.type;
2914
2947
  }
2915
2948
  }
2949
+ function devUiUrl(port, host = DEV_UI_HOST) {
2950
+ return `http://${host}:${port}`;
2951
+ }
2916
2952
  function openUrl(url) {
2917
2953
  const cmd = process.platform === "win32" ? "start" : process.platform === "darwin" ? "open" : "xdg-open";
2918
2954
  const args = process.platform === "win32" ? ["/c", "start", "", url] : [url];
@@ -148,10 +148,26 @@ body { font-family: var(--font); background: var(--bg-page); color: var(--text);
148
148
  .serve-controls { display: flex; align-items: center; gap: 12px; }
149
149
  .serve-status { display: flex; align-items: center; gap: 8px; font-size: 13px; font-weight: 500; padding: 7px 14px; border-radius: 7px; background: var(--bg-card); border: 1px solid var(--border); color: var(--text-secondary); }
150
150
  .serve-status .dot { width: 9px; height: 9px; border-radius: 50%; }
151
+ .serve-section-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; margin-bottom: 10px; }
152
+ .serve-section-title { font-size: 11px; font-weight: 700; color: var(--text-dim); text-transform: uppercase; letter-spacing: 0.5px; }
153
+ .serve-section-desc { font-size: 12.5px; color: var(--text-secondary); margin-top: 3px; }
154
+ .serve-section-meta { font-size: 11.5px; color: var(--text-dim); white-space: nowrap; padding-top: 1px; }
155
+ #workspace-picker { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 10px; margin-bottom: 6px; }
151
156
  .serve-select-bar { display: flex; align-items: center; gap: 9px; padding: 10px 14px; border-radius: 8px; background: var(--bg-card); border: 1px solid var(--border); box-shadow: var(--shadow); margin-bottom: 14px; font-size: 13px; font-weight: 600; cursor: pointer; user-select: none; }
152
157
  .serve-select-bar:hover { background: var(--bg-hover); }
153
158
  .serve-select-bar input { width: 16px; height: 16px; accent-color: var(--accent-solid); cursor: pointer; }
154
159
  .serve-select-bar .count { margin-left: auto; font-weight: 400; color: var(--text-dim); font-size: 12px; }
160
+ .workspace-card { background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: 14px 16px; text-align: left; cursor: pointer; transition: all var(--transition); box-shadow: var(--shadow); font: inherit; color: inherit; }
161
+ .workspace-card:hover { border-color: var(--border-hover); transform: translateY(-1px); box-shadow: var(--shadow-lg); }
162
+ .workspace-card.selected { border-color: var(--accent); background: var(--accent-bg); }
163
+ .workspace-card-title-row { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
164
+ .workspace-card-title { font-size: 13.5px; font-weight: 650; color: var(--text); }
165
+ .workspace-card-slug { font-size: 11px; color: var(--text-dim); margin-top: 2px; }
166
+ .workspace-card-id { font-size: 11px; font-family: var(--mono); color: var(--text-dim); margin-top: 10px; overflow: hidden; text-overflow: ellipsis; }
167
+ .workspace-card-badge { font-size: 10px; font-weight: 700; padding: 3px 8px; border-radius: 4px; border: 1px solid var(--border); color: var(--text-dim); background: var(--bg-elevated); white-space: nowrap; }
168
+ .workspace-card.selected .workspace-card-badge { color: var(--accent); border-color: var(--accent-border); background: white; }
169
+ .workspace-empty { padding: 14px 16px; border-radius: var(--radius); background: var(--bg-card); border: 1px dashed var(--border); color: var(--text-secondary); font-size: 12.5px; }
170
+ .workspace-empty.workspace-error { color: var(--red); border-color: var(--red-border); background: var(--red-bg); }
155
171
  .serve-folder { margin-bottom: 20px; }
156
172
  .serve-folder-title { display: flex; align-items: center; gap: 6px; margin-bottom: 8px; font-size: 10.5px; font-weight: 700; color: var(--text-dim); text-transform: uppercase; letter-spacing: 0.5px; }
157
173
  .serve-folder-title svg { width: 12px; height: 12px; opacity: 0.5; }
package/dev-ui/index.html CHANGED
@@ -94,6 +94,21 @@
94
94
  </div>
95
95
 
96
96
  <div id="serve-picker-section">
97
+ <div class="serve-section-head">
98
+ <div>
99
+ <div class="serve-section-title">Workspace</div>
100
+ <div class="serve-section-desc">Choose the Vendian workspace that should own this daemon and its local runs.</div>
101
+ </div>
102
+ <div class="serve-section-meta" id="workspace-summary">Loading workspaces…</div>
103
+ </div>
104
+ <div id="workspace-picker"></div>
105
+
106
+ <div class="serve-section-head" style="margin-top:20px">
107
+ <div>
108
+ <div class="serve-section-title">Agents</div>
109
+ <div class="serve-section-desc">Pick which local agents this serve session should expose.</div>
110
+ </div>
111
+ </div>
97
112
  <div class="serve-select-bar" id="serve-select-bar">
98
113
  <input type="checkbox" id="cb-all" checked>
99
114
  <label for="cb-all" style="cursor:pointer">Select all</label>
package/dev-ui/js/app.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { state, api } from './state.js';
2
2
  import { renderAgents } from './agents.js';
3
3
  import { populateValidateGrid, runValidateAll, triggerValidateAgent } from './validate.js';
4
- import { populateServePicker, toggleAll, startServe, stopServe, setServing, clearLogs } from './serve.js';
4
+ import { populateServePicker, toggleAll, startServe, stopServe, setServing, clearLogs, loadWorkspaces } from './serve.js';
5
5
  import { loadAuth } from './settings.js';
6
6
 
7
7
  // ─── Navigation ───
@@ -20,6 +20,10 @@ window.addEventListener('navigate', (e) => {
20
20
  if (page === 'validate' && path) triggerValidateAgent(path);
21
21
  });
22
22
 
23
+ window.addEventListener('auth-changed', () => {
24
+ loadWorkspaces();
25
+ });
26
+
23
27
  // ─── Load agents ───
24
28
  async function loadAgents() {
25
29
  try {
@@ -92,6 +96,8 @@ document.getElementById('serve-select-bar').addEventListener('click', (e) => {
92
96
  // ─── Init ───
93
97
  loadAgents();
94
98
  loadAuth();
99
+ loadWorkspaces();
95
100
  checkStatus();
96
101
  setInterval(checkStatus, 4000);
97
102
  setInterval(loadAgents, 8000);
103
+ setInterval(loadWorkspaces, 15000);
@@ -1,5 +1,80 @@
1
1
  import { state, api, esc, groupByFolder, formatTime } from './state.js';
2
2
 
3
+ export async function loadWorkspaces() {
4
+ state.workspacesLoading = true;
5
+ state.workspacesError = '';
6
+ renderWorkspacePicker();
7
+ try {
8
+ const result = await api('/workspaces');
9
+ state.workspaces = Array.isArray(result.workspaces) ? result.workspaces : [];
10
+ const hasSelected = state.workspaces.some((workspace) => workspace.id === state.selectedWorkspaceId);
11
+ if (!hasSelected) {
12
+ state.selectedWorkspaceId = state.workspaces[0]?.id || '';
13
+ }
14
+ if (!result.ok) {
15
+ state.workspacesError = result.error || 'Could not load workspaces.';
16
+ }
17
+ } catch (error) {
18
+ state.workspaces = [];
19
+ state.selectedWorkspaceId = '';
20
+ state.workspacesError = error?.message || 'Could not load workspaces.';
21
+ } finally {
22
+ state.workspacesLoading = false;
23
+ renderWorkspacePicker();
24
+ }
25
+ }
26
+
27
+ export function renderWorkspacePicker() {
28
+ const container = document.getElementById('workspace-picker');
29
+ const summary = document.getElementById('workspace-summary');
30
+ if (!container || !summary) return;
31
+
32
+ if (state.workspacesLoading) {
33
+ summary.textContent = 'Loading workspaces…';
34
+ container.innerHTML = '<div class="workspace-empty">Loading workspaces from Vendian…</div>';
35
+ return;
36
+ }
37
+
38
+ if (state.workspacesError) {
39
+ summary.textContent = 'Workspace required';
40
+ container.innerHTML = `<div class="workspace-empty workspace-error">${esc(state.workspacesError)}</div>`;
41
+ return;
42
+ }
43
+
44
+ if (!state.workspaces.length) {
45
+ summary.textContent = 'No workspace available';
46
+ container.innerHTML = '<div class="workspace-empty">Sign in to a Vendian environment to load workspaces.</div>';
47
+ return;
48
+ }
49
+
50
+ const selected = state.workspaces.find((workspace) => workspace.id === state.selectedWorkspaceId) || state.workspaces[0];
51
+ if (selected && state.selectedWorkspaceId !== selected.id) {
52
+ state.selectedWorkspaceId = selected.id || '';
53
+ }
54
+ summary.textContent = `${state.workspaces.length} workspace${state.workspaces.length === 1 ? '' : 's'}`;
55
+ container.innerHTML = state.workspaces.map((workspace) => {
56
+ const selectedClass = workspace.id === state.selectedWorkspaceId ? ' selected' : '';
57
+ const slug = workspace.slug && workspace.slug !== workspace.name ? `<div class="workspace-card-slug">${esc(workspace.slug)}</div>` : '';
58
+ return `<button type="button" class="workspace-card${selectedClass}" data-workspace-id="${esc(workspace.id)}">
59
+ <div class="workspace-card-title-row">
60
+ <div>
61
+ <div class="workspace-card-title">${esc(workspace.name || workspace.id)}</div>
62
+ ${slug}
63
+ </div>
64
+ <div class="workspace-card-badge">${workspace.id === state.selectedWorkspaceId ? 'Selected' : 'Choose'}</div>
65
+ </div>
66
+ <div class="workspace-card-id">${esc(workspace.id)}</div>
67
+ </button>`;
68
+ }).join('');
69
+
70
+ container.querySelectorAll('[data-workspace-id]').forEach((button) => {
71
+ button.addEventListener('click', () => {
72
+ state.selectedWorkspaceId = button.dataset.workspaceId || '';
73
+ renderWorkspacePicker();
74
+ });
75
+ });
76
+ }
77
+
3
78
  // ─── Picker ───
4
79
  let userSelections = null; // null = all selected (default), Set = explicit selections
5
80
 
@@ -85,8 +160,9 @@ export function getSelectedPaths() {
85
160
  // ─── Controls ───
86
161
  export async function startServe() {
87
162
  const selected = getSelectedPaths();
163
+ if (!state.selectedWorkspaceId) { alert('Choose a workspace before starting local serve.'); return; }
88
164
  if (!selected.length) { alert('Select at least one agent.'); return; }
89
- const body = {};
165
+ const body = { collectionId: state.selectedWorkspaceId };
90
166
  if (selected.length < state.agents.length) {
91
167
  body.agentsDir = commonParent(selected);
92
168
  }
@@ -78,7 +78,10 @@ function promptLogin(key) {
78
78
 
79
79
  async function switchBackend(key) {
80
80
  const d = await api('/auth/switch', { method: 'POST', body: JSON.stringify({ backend: key }) });
81
- if (d.ok) await loadAuth();
81
+ if (d.ok) {
82
+ await loadAuth();
83
+ window.dispatchEvent(new Event('auth-changed'));
84
+ }
82
85
  else if (d.needsLogin) promptLogin(key);
83
86
  else alert(d.error || 'Switch failed');
84
87
  }
@@ -2,6 +2,10 @@
2
2
  export const state = {
3
3
  agents: [],
4
4
  authData: {},
5
+ workspaces: [],
6
+ selectedWorkspaceId: '',
7
+ workspacesLoading: false,
8
+ workspacesError: '',
5
9
  logOffset: 0,
6
10
  pollTimer: null,
7
11
  isServing: false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vendian/cli",
3
- "version": "0.0.37",
3
+ "version": "0.0.38",
4
4
  "description": "Public Vendian CLI bootstrapper and launcher",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,