@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 +47 -11
- package/dev-ui/css/app.css +16 -0
- package/dev-ui/index.html +15 -0
- package/dev-ui/js/app.js +7 -1
- package/dev-ui/js/serve.js +77 -1
- package/dev-ui/js/settings.js +4 -1
- package/dev-ui/js/state.js +4 -0
- package/package.json +1 -1
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
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
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.
|
|
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,
|
|
2392
|
-
const url =
|
|
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
|
|
2687
|
-
|
|
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];
|
package/dev-ui/css/app.css
CHANGED
|
@@ -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);
|
package/dev-ui/js/serve.js
CHANGED
|
@@ -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
|
}
|
package/dev-ui/js/settings.js
CHANGED
|
@@ -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)
|
|
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
|
}
|
package/dev-ui/js/state.js
CHANGED