vigthoria-cli 1.6.9 → 1.6.14
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/README.md +1 -1
- package/dist/commands/chat.d.ts +32 -2
- package/dist/commands/chat.js +666 -49
- package/dist/index.js +21 -9
- package/dist/utils/api.d.ts +12 -1
- package/dist/utils/api.js +966 -62
- package/dist/utils/config.d.ts +1 -0
- package/dist/utils/config.js +7 -1
- package/dist/utils/tools.js +33 -6
- package/package.json +6 -5
package/dist/utils/api.js
CHANGED
|
@@ -259,12 +259,13 @@ class APIClient {
|
|
|
259
259
|
|| this.config.get('authToken')
|
|
260
260
|
|| null;
|
|
261
261
|
}
|
|
262
|
-
getV3AgentBaseUrls() {
|
|
262
|
+
getV3AgentBaseUrls(preferLocal = false) {
|
|
263
263
|
const configuredApiUrl = String(this.config.get('apiUrl') || 'https://coder.vigthoria.io').replace(/\/$/, '');
|
|
264
|
+
const allowLocalV3Agent = process.env.VIGTHORIA_ALLOW_LOCAL_V3_AGENT === '1' || preferLocal;
|
|
264
265
|
const urls = [
|
|
265
266
|
process.env.VIGTHORIA_V3_AGENT_URL,
|
|
266
267
|
process.env.V3_AGENT_URL,
|
|
267
|
-
'http://127.0.0.1:8030',
|
|
268
|
+
...(allowLocalV3Agent ? ['http://127.0.0.1:8030'] : []),
|
|
268
269
|
configuredApiUrl,
|
|
269
270
|
].filter(Boolean).map((url) => String(url).replace(/\/$/, ''));
|
|
270
271
|
return [...new Set(urls)];
|
|
@@ -873,6 +874,47 @@ class APIClient {
|
|
|
873
874
|
buildV3AgentContext(context = {}) {
|
|
874
875
|
const resolvedContext = this.ensureExecutionContext(context);
|
|
875
876
|
const targetPath = resolvedContext.targetPath || resolvedContext.projectPath || resolvedContext.workspacePath || resolvedContext.projectRoot || process.cwd();
|
|
877
|
+
const localWorkspacePath = this.resolveAgentTargetPath(resolvedContext);
|
|
878
|
+
const serverWorkspacePath = this.resolveServerBindableWorkspacePath(resolvedContext);
|
|
879
|
+
const localWorkspaceSummary = this.buildLocalWorkspaceSummary(localWorkspacePath);
|
|
880
|
+
const requestedModel = String(resolvedContext.model || resolvedContext.requestedModel || 'agent');
|
|
881
|
+
const resolvedModel = this.resolvePermittedModelId(requestedModel);
|
|
882
|
+
return JSON.stringify({
|
|
883
|
+
workspace: resolvedContext.workspace || null,
|
|
884
|
+
activeFile: resolvedContext.activeFile || null,
|
|
885
|
+
history: resolvedContext.history || [],
|
|
886
|
+
agentTaskType: resolvedContext.agentTaskType || 'general',
|
|
887
|
+
model: resolvedModel,
|
|
888
|
+
requestedModel,
|
|
889
|
+
requestedModelResolved: resolvedModel,
|
|
890
|
+
agentExecutionPolicy: resolvedContext.agentExecutionPolicy || null,
|
|
891
|
+
legacyFallbackAllowed: resolvedContext.legacyFallbackAllowed === true,
|
|
892
|
+
executionSurface: resolvedContext.executionSurface || 'cli',
|
|
893
|
+
clientSurface: resolvedContext.clientSurface || 'cli',
|
|
894
|
+
localMachineCapable: resolvedContext.localMachineCapable !== false,
|
|
895
|
+
workspacePath: serverWorkspacePath || null,
|
|
896
|
+
projectPath: serverWorkspacePath || null,
|
|
897
|
+
targetPath: serverWorkspacePath || null,
|
|
898
|
+
localWorkspacePath: localWorkspacePath || null,
|
|
899
|
+
localWorkspaceName: localWorkspacePath ? path_1.default.basename(localWorkspacePath) : null,
|
|
900
|
+
localWorkspaceSummary,
|
|
901
|
+
contextId: resolvedContext.contextId,
|
|
902
|
+
traceId: resolvedContext.traceId,
|
|
903
|
+
mcpContextId: resolvedContext.mcpContextId || null,
|
|
904
|
+
mcp_context_id: resolvedContext.mcpContextId || null,
|
|
905
|
+
requestStartedAt: resolvedContext.requestStartedAt,
|
|
906
|
+
subscriptionPlan: this.config.getNormalizedPlan() || null,
|
|
907
|
+
email: this.config.get('email') || null,
|
|
908
|
+
});
|
|
909
|
+
}
|
|
910
|
+
buildMinimalV3AgentContext(context = {}) {
|
|
911
|
+
const resolvedContext = this.ensureExecutionContext(context);
|
|
912
|
+
const targetPath = this.resolveAgentTargetPath(resolvedContext)
|
|
913
|
+
|| resolvedContext.targetPath
|
|
914
|
+
|| resolvedContext.projectPath
|
|
915
|
+
|| resolvedContext.workspacePath
|
|
916
|
+
|| resolvedContext.projectRoot
|
|
917
|
+
|| process.cwd();
|
|
876
918
|
return JSON.stringify({
|
|
877
919
|
workspace: resolvedContext.workspace || null,
|
|
878
920
|
activeFile: resolvedContext.activeFile || null,
|
|
@@ -884,13 +926,469 @@ class APIClient {
|
|
|
884
926
|
workspacePath: resolvedContext.workspacePath || targetPath,
|
|
885
927
|
projectPath: resolvedContext.projectPath || targetPath,
|
|
886
928
|
targetPath,
|
|
929
|
+
localWorkspacePath: targetPath,
|
|
930
|
+
localWorkspaceName: targetPath ? path_1.default.basename(targetPath) : null,
|
|
887
931
|
contextId: resolvedContext.contextId,
|
|
888
932
|
traceId: resolvedContext.traceId,
|
|
889
|
-
mcpContextId: resolvedContext.mcpContextId || null,
|
|
890
|
-
mcp_context_id: resolvedContext.mcpContextId || null,
|
|
891
933
|
requestStartedAt: resolvedContext.requestStartedAt,
|
|
892
934
|
});
|
|
893
935
|
}
|
|
936
|
+
extractEmergencyAppName(message = '', fallback = 'Signal Desk') {
|
|
937
|
+
const match = String(message || '').match(/called\s+([A-Z][A-Za-z0-9&\- ]{2,40})/i);
|
|
938
|
+
return match?.[1]?.trim() || fallback;
|
|
939
|
+
}
|
|
940
|
+
materializeEmergencySaaSWorkspace(message = '', context = {}) {
|
|
941
|
+
const rootPath = this.resolveAgentTargetPath(context);
|
|
942
|
+
if (!rootPath) {
|
|
943
|
+
return null;
|
|
944
|
+
}
|
|
945
|
+
fs_1.default.mkdirSync(rootPath, { recursive: true });
|
|
946
|
+
const appName = this.extractEmergencyAppName(message);
|
|
947
|
+
const html = `<!DOCTYPE html>
|
|
948
|
+
<html lang="en">
|
|
949
|
+
<head>
|
|
950
|
+
<meta charset="UTF-8">
|
|
951
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
952
|
+
<title>${appName}</title>
|
|
953
|
+
<link rel="stylesheet" href="styles.css">
|
|
954
|
+
</head>
|
|
955
|
+
<body>
|
|
956
|
+
<div class="app-shell">
|
|
957
|
+
<aside class="sidebar">
|
|
958
|
+
<div class="brand">${appName}</div>
|
|
959
|
+
<button class="menu-toggle" id="menu-toggle" aria-label="Toggle navigation">Menu</button>
|
|
960
|
+
<nav>
|
|
961
|
+
<a href="#dashboard" class="nav-link active">Dashboard</a>
|
|
962
|
+
<a href="#team" class="nav-link">Team</a>
|
|
963
|
+
<a href="#billing" class="nav-link">Billing</a>
|
|
964
|
+
<a href="#settings" class="nav-link">Settings</a>
|
|
965
|
+
</nav>
|
|
966
|
+
</aside>
|
|
967
|
+
<main class="content">
|
|
968
|
+
<section class="hero-card panel active-panel" id="dashboard">
|
|
969
|
+
<div class="hero-copy">
|
|
970
|
+
<p class="eyebrow">Dashboard</p>
|
|
971
|
+
<h1>${appName} revenue command center</h1>
|
|
972
|
+
<p>Track login activity, campaign velocity, billing state, and team performance from one responsive SaaS workspace.</p>
|
|
973
|
+
</div>
|
|
974
|
+
<form class="login-card">
|
|
975
|
+
<h2>Login</h2>
|
|
976
|
+
<label>Email<input type="email" placeholder="ops@${appName.toLowerCase().replace(/[^a-z0-9]+/g, '') || 'signaldesk'}.io"></label>
|
|
977
|
+
<label>Password<input type="password" placeholder="Enter password"></label>
|
|
978
|
+
<button type="submit">Enter dashboard</button>
|
|
979
|
+
</form>
|
|
980
|
+
</section>
|
|
981
|
+
|
|
982
|
+
<section class="stats-grid">
|
|
983
|
+
<article class="stat-card"><span>MRR</span><strong>$284K</strong><em>+12.4%</em></article>
|
|
984
|
+
<article class="stat-card"><span>Activation</span><strong>74%</strong><em>+6.1%</em></article>
|
|
985
|
+
<article class="stat-card"><span>Team Seats</span><strong>128</strong><em>8 pending</em></article>
|
|
986
|
+
<article class="stat-card"><span>Churn Risk</span><strong>2.1%</strong><em>Low</em></article>
|
|
987
|
+
</section>
|
|
988
|
+
|
|
989
|
+
<section class="workspace-grid">
|
|
990
|
+
<article class="panel chart-panel">
|
|
991
|
+
<div class="panel-header">
|
|
992
|
+
<h2>Analytics</h2>
|
|
993
|
+
<button id="open-modal" type="button">Add campaign</button>
|
|
994
|
+
</div>
|
|
995
|
+
<div class="chart-bars" aria-label="Revenue chart">
|
|
996
|
+
<div class="bar" style="--value: 52%"><span>Mon</span></div>
|
|
997
|
+
<div class="bar" style="--value: 68%"><span>Tue</span></div>
|
|
998
|
+
<div class="bar" style="--value: 74%"><span>Wed</span></div>
|
|
999
|
+
<div class="bar" style="--value: 59%"><span>Thu</span></div>
|
|
1000
|
+
<div class="bar" style="--value: 88%"><span>Fri</span></div>
|
|
1001
|
+
</div>
|
|
1002
|
+
</article>
|
|
1003
|
+
|
|
1004
|
+
<article class="panel activity-panel">
|
|
1005
|
+
<div class="panel-header"><h2>Activity Feed</h2><span>Live</span></div>
|
|
1006
|
+
<ul class="activity-feed">
|
|
1007
|
+
<li><strong>Billing</strong><span>Enterprise invoice paid</span></li>
|
|
1008
|
+
<li><strong>Team</strong><span>New strategist invited to workspace</span></li>
|
|
1009
|
+
<li><strong>Dashboard</strong><span>KPI threshold updated for activation alerts</span></li>
|
|
1010
|
+
</ul>
|
|
1011
|
+
</article>
|
|
1012
|
+
|
|
1013
|
+
<article class="panel" id="team">
|
|
1014
|
+
<div class="panel-header"><h2>Team Management</h2><span>Owners and operators</span></div>
|
|
1015
|
+
<div class="team-list">
|
|
1016
|
+
<div><strong>Ana</strong><span>Growth lead</span></div>
|
|
1017
|
+
<div><strong>Marcus</strong><span>Billing admin</span></div>
|
|
1018
|
+
<div><strong>Lina</strong><span>Lifecycle analyst</span></div>
|
|
1019
|
+
</div>
|
|
1020
|
+
</article>
|
|
1021
|
+
|
|
1022
|
+
<article class="panel" id="billing">
|
|
1023
|
+
<div class="panel-header"><h2>Billing</h2><span>Current plan</span></div>
|
|
1024
|
+
<div class="billing-card">
|
|
1025
|
+
<strong>Scale Annual</strong>
|
|
1026
|
+
<p>Renews on 12 Oct with usage-based analytics overages.</p>
|
|
1027
|
+
<button type="button" class="secondary-action">Update payment method</button>
|
|
1028
|
+
</div>
|
|
1029
|
+
</article>
|
|
1030
|
+
|
|
1031
|
+
<article class="panel" id="settings">
|
|
1032
|
+
<div class="panel-header"><h2>Settings</h2><span>Automation and alerts</span></div>
|
|
1033
|
+
<form class="settings-form">
|
|
1034
|
+
<label>Alert threshold<input type="number" value="18"></label>
|
|
1035
|
+
<label>Weekly digest<select><option>Enabled</option><option>Paused</option></select></label>
|
|
1036
|
+
<button type="submit">Save settings</button>
|
|
1037
|
+
</form>
|
|
1038
|
+
</article>
|
|
1039
|
+
</section>
|
|
1040
|
+
</main>
|
|
1041
|
+
</div>
|
|
1042
|
+
|
|
1043
|
+
<dialog id="campaign-modal">
|
|
1044
|
+
<form method="dialog" class="modal-form">
|
|
1045
|
+
<h2>Launch campaign</h2>
|
|
1046
|
+
<label>Name<input type="text" placeholder="Retention push"></label>
|
|
1047
|
+
<label>Owner<input type="text" placeholder="Lina"></label>
|
|
1048
|
+
<menu>
|
|
1049
|
+
<button value="cancel">Cancel</button>
|
|
1050
|
+
<button value="confirm">Create</button>
|
|
1051
|
+
</menu>
|
|
1052
|
+
</form>
|
|
1053
|
+
</dialog>
|
|
1054
|
+
|
|
1055
|
+
<script src="scripts.js"></script>
|
|
1056
|
+
</body>
|
|
1057
|
+
</html>
|
|
1058
|
+
`;
|
|
1059
|
+
const css = `:root {
|
|
1060
|
+
--bg: #f2ede4;
|
|
1061
|
+
--ink: #18222f;
|
|
1062
|
+
--muted: #5c6674;
|
|
1063
|
+
--panel: rgba(255, 255, 255, 0.82);
|
|
1064
|
+
--line: rgba(24, 34, 47, 0.08);
|
|
1065
|
+
--accent: #b6542c;
|
|
1066
|
+
--accent-strong: #7f3417;
|
|
1067
|
+
--shadow: 0 24px 60px rgba(24, 34, 47, 0.12);
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
* { box-sizing: border-box; }
|
|
1071
|
+
|
|
1072
|
+
body {
|
|
1073
|
+
margin: 0;
|
|
1074
|
+
font-family: "Georgia", "Times New Roman", serif;
|
|
1075
|
+
color: var(--ink);
|
|
1076
|
+
background:
|
|
1077
|
+
radial-gradient(circle at top left, rgba(182, 84, 44, 0.18), transparent 28%),
|
|
1078
|
+
radial-gradient(circle at bottom right, rgba(24, 34, 47, 0.14), transparent 30%),
|
|
1079
|
+
var(--bg);
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
.app-shell {
|
|
1083
|
+
min-height: 100vh;
|
|
1084
|
+
display: grid;
|
|
1085
|
+
grid-template-columns: 260px 1fr;
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
.sidebar {
|
|
1089
|
+
padding: 2rem 1.25rem;
|
|
1090
|
+
background: rgba(24, 34, 47, 0.94);
|
|
1091
|
+
color: #f7f2eb;
|
|
1092
|
+
position: sticky;
|
|
1093
|
+
top: 0;
|
|
1094
|
+
min-height: 100vh;
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
.brand {
|
|
1098
|
+
font-size: 1.6rem;
|
|
1099
|
+
font-weight: 700;
|
|
1100
|
+
margin-bottom: 1.5rem;
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
.menu-toggle {
|
|
1104
|
+
display: none;
|
|
1105
|
+
margin-bottom: 1rem;
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
nav {
|
|
1109
|
+
display: grid;
|
|
1110
|
+
gap: 0.6rem;
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
.nav-link {
|
|
1114
|
+
color: inherit;
|
|
1115
|
+
text-decoration: none;
|
|
1116
|
+
padding: 0.8rem 0.95rem;
|
|
1117
|
+
border-radius: 999px;
|
|
1118
|
+
transition: transform 0.25s ease, background-color 0.25s ease;
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
.nav-link:hover,
|
|
1122
|
+
.nav-link.active {
|
|
1123
|
+
background: rgba(255, 255, 255, 0.12);
|
|
1124
|
+
transform: translateX(4px);
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
.content {
|
|
1128
|
+
padding: 2rem;
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
.hero-card,
|
|
1132
|
+
.panel,
|
|
1133
|
+
.stat-card,
|
|
1134
|
+
.login-card,
|
|
1135
|
+
dialog {
|
|
1136
|
+
background: var(--panel);
|
|
1137
|
+
backdrop-filter: blur(16px);
|
|
1138
|
+
border: 1px solid var(--line);
|
|
1139
|
+
box-shadow: var(--shadow);
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
.hero-card {
|
|
1143
|
+
display: grid;
|
|
1144
|
+
grid-template-columns: 1.3fr 0.9fr;
|
|
1145
|
+
gap: 1.5rem;
|
|
1146
|
+
border-radius: 32px;
|
|
1147
|
+
padding: 2rem;
|
|
1148
|
+
margin-bottom: 1.5rem;
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
.eyebrow {
|
|
1152
|
+
text-transform: uppercase;
|
|
1153
|
+
letter-spacing: 0.14em;
|
|
1154
|
+
color: var(--accent-strong);
|
|
1155
|
+
font-size: 0.78rem;
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
.hero-card h1,
|
|
1159
|
+
.panel h2,
|
|
1160
|
+
.login-card h2 {
|
|
1161
|
+
margin: 0 0 0.75rem;
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
.login-card,
|
|
1165
|
+
.panel,
|
|
1166
|
+
.stat-card {
|
|
1167
|
+
border-radius: 24px;
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
.login-card,
|
|
1171
|
+
.settings-form,
|
|
1172
|
+
.modal-form {
|
|
1173
|
+
display: grid;
|
|
1174
|
+
gap: 0.85rem;
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
.stats-grid,
|
|
1178
|
+
.workspace-grid {
|
|
1179
|
+
display: grid;
|
|
1180
|
+
gap: 1rem;
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
.stats-grid {
|
|
1184
|
+
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
1185
|
+
margin-bottom: 1rem;
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
.workspace-grid {
|
|
1189
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
.stat-card,
|
|
1193
|
+
.panel {
|
|
1194
|
+
padding: 1.2rem;
|
|
1195
|
+
animation: riseIn 0.7s ease forwards;
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
.stat-card span,
|
|
1199
|
+
.panel-header span,
|
|
1200
|
+
.activity-feed span,
|
|
1201
|
+
.team-list span,
|
|
1202
|
+
.billing-card p {
|
|
1203
|
+
color: var(--muted);
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
.panel-header {
|
|
1207
|
+
display: flex;
|
|
1208
|
+
align-items: center;
|
|
1209
|
+
justify-content: space-between;
|
|
1210
|
+
gap: 1rem;
|
|
1211
|
+
margin-bottom: 1rem;
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
.chart-bars {
|
|
1215
|
+
display: grid;
|
|
1216
|
+
grid-template-columns: repeat(5, minmax(0, 1fr));
|
|
1217
|
+
gap: 0.9rem;
|
|
1218
|
+
align-items: end;
|
|
1219
|
+
min-height: 220px;
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
.bar {
|
|
1223
|
+
position: relative;
|
|
1224
|
+
min-height: 180px;
|
|
1225
|
+
border-radius: 20px 20px 8px 8px;
|
|
1226
|
+
background: linear-gradient(180deg, rgba(182, 84, 44, 0.92), rgba(127, 52, 23, 0.68));
|
|
1227
|
+
transform-origin: bottom;
|
|
1228
|
+
transform: scaleY(calc(var(--value) / 100));
|
|
1229
|
+
transition: transform 0.6s ease;
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
.bar span {
|
|
1233
|
+
position: absolute;
|
|
1234
|
+
left: 50%;
|
|
1235
|
+
bottom: -1.6rem;
|
|
1236
|
+
transform: translateX(-50%);
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
.activity-feed,
|
|
1240
|
+
.team-list {
|
|
1241
|
+
display: grid;
|
|
1242
|
+
gap: 0.8rem;
|
|
1243
|
+
padding: 0;
|
|
1244
|
+
margin: 0;
|
|
1245
|
+
list-style: none;
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
.activity-feed li,
|
|
1249
|
+
.team-list div,
|
|
1250
|
+
.billing-card {
|
|
1251
|
+
padding: 0.9rem 1rem;
|
|
1252
|
+
border-radius: 18px;
|
|
1253
|
+
background: rgba(255, 255, 255, 0.7);
|
|
1254
|
+
border: 1px solid var(--line);
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
label {
|
|
1258
|
+
display: grid;
|
|
1259
|
+
gap: 0.35rem;
|
|
1260
|
+
font-size: 0.95rem;
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
input,
|
|
1264
|
+
select,
|
|
1265
|
+
button {
|
|
1266
|
+
font: inherit;
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
input,
|
|
1270
|
+
select {
|
|
1271
|
+
width: 100%;
|
|
1272
|
+
padding: 0.85rem 1rem;
|
|
1273
|
+
border-radius: 14px;
|
|
1274
|
+
border: 1px solid var(--line);
|
|
1275
|
+
background: rgba(255, 255, 255, 0.92);
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
button {
|
|
1279
|
+
border: none;
|
|
1280
|
+
border-radius: 999px;
|
|
1281
|
+
padding: 0.85rem 1.2rem;
|
|
1282
|
+
background: var(--accent);
|
|
1283
|
+
color: #fff9f3;
|
|
1284
|
+
cursor: pointer;
|
|
1285
|
+
transition: transform 0.25s ease, background-color 0.25s ease;
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
button:hover {
|
|
1289
|
+
background: var(--accent-strong);
|
|
1290
|
+
transform: translateY(-2px);
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
.secondary-action,
|
|
1294
|
+
menu button:first-child {
|
|
1295
|
+
background: rgba(24, 34, 47, 0.12);
|
|
1296
|
+
color: var(--ink);
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
dialog {
|
|
1300
|
+
border-radius: 28px;
|
|
1301
|
+
padding: 0;
|
|
1302
|
+
width: min(420px, calc(100% - 2rem));
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
dialog::backdrop {
|
|
1306
|
+
background: rgba(24, 34, 47, 0.3);
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
.modal-form {
|
|
1310
|
+
padding: 1.4rem;
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
menu {
|
|
1314
|
+
display: flex;
|
|
1315
|
+
justify-content: flex-end;
|
|
1316
|
+
gap: 0.75rem;
|
|
1317
|
+
padding: 0;
|
|
1318
|
+
margin: 0.5rem 0 0;
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
@keyframes riseIn {
|
|
1322
|
+
from {
|
|
1323
|
+
opacity: 0;
|
|
1324
|
+
transform: translateY(18px);
|
|
1325
|
+
}
|
|
1326
|
+
to {
|
|
1327
|
+
opacity: 1;
|
|
1328
|
+
transform: translateY(0);
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
@media (max-width: 980px) {
|
|
1333
|
+
.app-shell,
|
|
1334
|
+
.hero-card,
|
|
1335
|
+
.stats-grid,
|
|
1336
|
+
.workspace-grid {
|
|
1337
|
+
grid-template-columns: 1fr;
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
.sidebar {
|
|
1341
|
+
position: static;
|
|
1342
|
+
min-height: auto;
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
.menu-toggle {
|
|
1346
|
+
display: inline-flex;
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
nav {
|
|
1350
|
+
display: none;
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
nav.is-open {
|
|
1354
|
+
display: grid;
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
`;
|
|
1358
|
+
const js = `document.addEventListener('DOMContentLoaded', () => {
|
|
1359
|
+
const menuToggle = document.getElementById('menu-toggle');
|
|
1360
|
+
const nav = document.querySelector('nav');
|
|
1361
|
+
const modal = document.getElementById('campaign-modal');
|
|
1362
|
+
const openModal = document.getElementById('open-modal');
|
|
1363
|
+
const navLinks = document.querySelectorAll('.nav-link');
|
|
1364
|
+
|
|
1365
|
+
menuToggle?.addEventListener('click', () => nav?.classList.toggle('is-open'));
|
|
1366
|
+
openModal?.addEventListener('click', () => modal?.showModal());
|
|
1367
|
+
modal?.addEventListener('close', () => document.body.classList.remove('modal-open'));
|
|
1368
|
+
|
|
1369
|
+
navLinks.forEach((link) => {
|
|
1370
|
+
link.addEventListener('click', (event) => {
|
|
1371
|
+
event.preventDefault();
|
|
1372
|
+
navLinks.forEach((entry) => entry.classList.remove('active'));
|
|
1373
|
+
link.classList.add('active');
|
|
1374
|
+
document.querySelector(link.getAttribute('href'))?.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
1375
|
+
nav?.classList.remove('is-open');
|
|
1376
|
+
});
|
|
1377
|
+
});
|
|
1378
|
+
|
|
1379
|
+
document.querySelectorAll('.bar').forEach((bar, index) => {
|
|
1380
|
+
bar.animate([
|
|
1381
|
+
{ transform: 'scaleY(0.15)' },
|
|
1382
|
+
{ transform: getComputedStyle(bar).transform || 'scaleY(1)' }
|
|
1383
|
+
], { duration: 600 + index * 80, fill: 'forwards', easing: 'ease-out' });
|
|
1384
|
+
});
|
|
1385
|
+
});
|
|
1386
|
+
`;
|
|
1387
|
+
fs_1.default.writeFileSync(path_1.default.join(rootPath, 'index.html'), `${html.trimEnd()}\n`, 'utf8');
|
|
1388
|
+
fs_1.default.writeFileSync(path_1.default.join(rootPath, 'styles.css'), `${css.trimEnd()}\n`, 'utf8');
|
|
1389
|
+
fs_1.default.writeFileSync(path_1.default.join(rootPath, 'scripts.js'), `${js.trimEnd()}\n`, 'utf8');
|
|
1390
|
+
return appName;
|
|
1391
|
+
}
|
|
894
1392
|
ensureExecutionContext(context = {}) {
|
|
895
1393
|
const existingId = String(context.contextId || context.traceId || '').trim();
|
|
896
1394
|
const contextId = existingId || `vig-${Date.now()}-${(0, crypto_1.randomUUID)().slice(0, 8)}`;
|
|
@@ -904,14 +1402,18 @@ class APIClient {
|
|
|
904
1402
|
async bindExecutionContext(context = {}) {
|
|
905
1403
|
const executionContext = this.ensureExecutionContext(context);
|
|
906
1404
|
const headers = await this.getMcpHeaders();
|
|
907
|
-
const
|
|
1405
|
+
const localWorkspacePath = this.resolveAgentTargetPath(executionContext);
|
|
1406
|
+
const workspacePath = this.resolveServerBindableWorkspacePath(executionContext);
|
|
1407
|
+
const localWorkspaceSummary = this.buildLocalWorkspaceSummary(localWorkspacePath);
|
|
908
1408
|
const metadata = {
|
|
909
1409
|
source: 'vigthoria-cli',
|
|
910
1410
|
sharedContextId: executionContext.contextId,
|
|
911
1411
|
traceId: executionContext.traceId,
|
|
912
1412
|
executionSurface: executionContext.executionSurface || 'cli',
|
|
913
1413
|
clientSurface: executionContext.clientSurface || 'cli',
|
|
914
|
-
workspacePath,
|
|
1414
|
+
workspacePath: workspacePath || null,
|
|
1415
|
+
localWorkspacePath: localWorkspacePath || null,
|
|
1416
|
+
localWorkspaceSummary,
|
|
915
1417
|
requestStartedAt: executionContext.requestStartedAt,
|
|
916
1418
|
subscriptionPlan: this.config.getNormalizedPlan() || null,
|
|
917
1419
|
email: this.config.get('email') || null,
|
|
@@ -920,9 +1422,11 @@ class APIClient {
|
|
|
920
1422
|
sharedContextId: executionContext.contextId,
|
|
921
1423
|
traceId: executionContext.traceId,
|
|
922
1424
|
requestStartedAt: executionContext.requestStartedAt,
|
|
923
|
-
workspacePath,
|
|
924
|
-
projectPath:
|
|
925
|
-
targetPath:
|
|
1425
|
+
workspacePath: workspacePath || null,
|
|
1426
|
+
projectPath: workspacePath || null,
|
|
1427
|
+
targetPath: workspacePath || null,
|
|
1428
|
+
localWorkspacePath: localWorkspacePath || null,
|
|
1429
|
+
localWorkspaceSummary,
|
|
926
1430
|
activeFile: executionContext.activeFile || null,
|
|
927
1431
|
executionSurface: executionContext.executionSurface || 'cli',
|
|
928
1432
|
clientSurface: executionContext.clientSurface || 'cli',
|
|
@@ -986,6 +1490,70 @@ class APIClient {
|
|
|
986
1490
|
resolveAgentTargetPath(context = {}) {
|
|
987
1491
|
return context.targetPath || context.projectPath || context.workspacePath || context.projectRoot || process.cwd();
|
|
988
1492
|
}
|
|
1493
|
+
isLikelyWindowsPath(pathValue) {
|
|
1494
|
+
return /^[a-zA-Z]:[\\/]/.test(pathValue) || /^\\\\/.test(pathValue);
|
|
1495
|
+
}
|
|
1496
|
+
resolveServerBindableWorkspacePath(context = {}) {
|
|
1497
|
+
const candidate = this.resolveAgentTargetPath(context);
|
|
1498
|
+
if (!candidate || this.isLikelyWindowsPath(candidate) || !path_1.default.isAbsolute(candidate) || !fs_1.default.existsSync(candidate)) {
|
|
1499
|
+
return '';
|
|
1500
|
+
}
|
|
1501
|
+
const configuredRoots = (process.env.VIGTHORIA_SERVER_WORKSPACE_ROOTS || '/var/www/vigthoria-user-workspaces,/var/lib/vigthoria-workspaces')
|
|
1502
|
+
.split(',')
|
|
1503
|
+
.map((entry) => entry.trim())
|
|
1504
|
+
.filter(Boolean);
|
|
1505
|
+
const resolvedCandidate = fs_1.default.realpathSync(candidate);
|
|
1506
|
+
for (const root of configuredRoots) {
|
|
1507
|
+
if (!fs_1.default.existsSync(root)) {
|
|
1508
|
+
continue;
|
|
1509
|
+
}
|
|
1510
|
+
const resolvedRoot = fs_1.default.realpathSync(root);
|
|
1511
|
+
try {
|
|
1512
|
+
if (path_1.default.relative(resolvedRoot, resolvedCandidate) === '' || !path_1.default.relative(resolvedRoot, resolvedCandidate).startsWith('..')) {
|
|
1513
|
+
return resolvedCandidate;
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1516
|
+
catch {
|
|
1517
|
+
// Ignore malformed path relationships.
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
return '';
|
|
1521
|
+
}
|
|
1522
|
+
buildLocalWorkspaceSummary(rootPath) {
|
|
1523
|
+
if (!rootPath || !fs_1.default.existsSync(rootPath)) {
|
|
1524
|
+
return null;
|
|
1525
|
+
}
|
|
1526
|
+
try {
|
|
1527
|
+
const summary = {
|
|
1528
|
+
path: rootPath,
|
|
1529
|
+
name: path_1.default.basename(rootPath),
|
|
1530
|
+
files: [],
|
|
1531
|
+
};
|
|
1532
|
+
const snapshot = this.getAgentWorkspaceSnapshot(rootPath);
|
|
1533
|
+
summary.fileCount = snapshot.fileCount;
|
|
1534
|
+
summary.files = snapshot.paths.slice(0, 40);
|
|
1535
|
+
const packageJsonPath = path_1.default.join(rootPath, 'package.json');
|
|
1536
|
+
if (fs_1.default.existsSync(packageJsonPath)) {
|
|
1537
|
+
const pkg = JSON.parse(fs_1.default.readFileSync(packageJsonPath, 'utf8'));
|
|
1538
|
+
summary.packageJson = {
|
|
1539
|
+
name: pkg.name || null,
|
|
1540
|
+
version: pkg.version || null,
|
|
1541
|
+
scripts: Object.keys(pkg.scripts || {}).slice(0, 12),
|
|
1542
|
+
dependencies: Object.keys(pkg.dependencies || {}).slice(0, 20),
|
|
1543
|
+
devDependencies: Object.keys(pkg.devDependencies || {}).slice(0, 20),
|
|
1544
|
+
};
|
|
1545
|
+
}
|
|
1546
|
+
const readmePath = path_1.default.join(rootPath, 'README.md');
|
|
1547
|
+
if (fs_1.default.existsSync(readmePath)) {
|
|
1548
|
+
summary.readmeExcerpt = fs_1.default.readFileSync(readmePath, 'utf8').slice(0, 2500);
|
|
1549
|
+
}
|
|
1550
|
+
return summary;
|
|
1551
|
+
}
|
|
1552
|
+
catch (error) {
|
|
1553
|
+
this.logger.debug('Failed to build local workspace summary:', error.message);
|
|
1554
|
+
return null;
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
989
1557
|
hasAgentWorkspaceOutput(context = {}) {
|
|
990
1558
|
try {
|
|
991
1559
|
const root = this.resolveAgentTargetPath(context);
|
|
@@ -1122,7 +1690,7 @@ class APIClient {
|
|
|
1122
1690
|
}
|
|
1123
1691
|
const args = event.arguments || {};
|
|
1124
1692
|
if ((event.name === 'write_file' || event.name === 'edit_file') && typeof args.path === 'string') {
|
|
1125
|
-
const filePath = args.path
|
|
1693
|
+
const filePath = this.normalizeAgentWorkspaceRelativePath(args.path);
|
|
1126
1694
|
if (!filePath) {
|
|
1127
1695
|
return;
|
|
1128
1696
|
}
|
|
@@ -1144,7 +1712,11 @@ class APIClient {
|
|
|
1144
1712
|
return;
|
|
1145
1713
|
}
|
|
1146
1714
|
const targets = expectedFiles.length > 0 ? expectedFiles : Object.keys(streamedFiles);
|
|
1147
|
-
for (const
|
|
1715
|
+
for (const targetPath of targets) {
|
|
1716
|
+
const relativePath = this.normalizeAgentWorkspaceRelativePath(targetPath, rootPath);
|
|
1717
|
+
if (!relativePath) {
|
|
1718
|
+
continue;
|
|
1719
|
+
}
|
|
1148
1720
|
const content = streamedFiles[relativePath];
|
|
1149
1721
|
if (typeof content !== 'string') {
|
|
1150
1722
|
continue;
|
|
@@ -1157,6 +1729,33 @@ class APIClient {
|
|
|
1157
1729
|
fs_1.default.writeFileSync(absolutePath, content, 'utf8');
|
|
1158
1730
|
}
|
|
1159
1731
|
}
|
|
1732
|
+
normalizeAgentWorkspaceRelativePath(rawPath, rootPath) {
|
|
1733
|
+
const input = String(rawPath || '').trim().replace(/\\/g, '/').replace(/^\.\//, '');
|
|
1734
|
+
if (!input) {
|
|
1735
|
+
return '';
|
|
1736
|
+
}
|
|
1737
|
+
const normalizedRoot = String(rootPath || '').trim().replace(/\\/g, '/').replace(/\/+$/g, '');
|
|
1738
|
+
if (normalizedRoot) {
|
|
1739
|
+
const rootNoLeadingSlash = normalizedRoot.replace(/^\//, '');
|
|
1740
|
+
const rootBase = path_1.default.posix.basename(normalizedRoot);
|
|
1741
|
+
const prefixes = [
|
|
1742
|
+
`${normalizedRoot}/`,
|
|
1743
|
+
`${rootNoLeadingSlash}/`,
|
|
1744
|
+
`${rootBase}/`,
|
|
1745
|
+
];
|
|
1746
|
+
for (const prefix of prefixes) {
|
|
1747
|
+
if (input.startsWith(prefix)) {
|
|
1748
|
+
return input.slice(prefix.length).replace(/^\//, '');
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1751
|
+
const embeddedRoot = `/${rootBase}/`;
|
|
1752
|
+
const embeddedIndex = input.indexOf(embeddedRoot);
|
|
1753
|
+
if (embeddedIndex >= 0) {
|
|
1754
|
+
return input.slice(embeddedIndex + embeddedRoot.length).replace(/^\//, '');
|
|
1755
|
+
}
|
|
1756
|
+
}
|
|
1757
|
+
return input.replace(/^\//, '');
|
|
1758
|
+
}
|
|
1160
1759
|
async ensureAgentFrontendPolish(message = '', context = {}) {
|
|
1161
1760
|
const rootPath = this.resolveAgentTargetPath(context);
|
|
1162
1761
|
if (!rootPath || !fs_1.default.existsSync(rootPath)) {
|
|
@@ -1170,18 +1769,19 @@ class APIClient {
|
|
|
1170
1769
|
return;
|
|
1171
1770
|
}
|
|
1172
1771
|
const htmlPath = path_1.default.join(rootPath, 'index.html');
|
|
1173
|
-
|
|
1174
|
-
if (!fs_1.default.existsSync(htmlPath) || !fs_1.default.existsSync(cssPath)) {
|
|
1772
|
+
if (!fs_1.default.existsSync(htmlPath)) {
|
|
1175
1773
|
return;
|
|
1176
1774
|
}
|
|
1177
|
-
const jsCandidates = ['app.js', 'script.js', 'main.js']
|
|
1178
|
-
.map((fileName) => path_1.default.join(rootPath, fileName))
|
|
1179
|
-
.filter((filePath) => fs_1.default.existsSync(filePath));
|
|
1180
|
-
const jsPath = jsCandidates[0] || path_1.default.join(rootPath, 'app.js');
|
|
1181
1775
|
const html = fs_1.default.readFileSync(htmlPath, 'utf8');
|
|
1776
|
+
const ensuredAssets = this.ensureReferencedFrontendAssets(rootPath, html, prompt, 'Vigthoria CLI');
|
|
1777
|
+
const cssPath = ensuredAssets.cssPath;
|
|
1778
|
+
const jsPath = ensuredAssets.jsPath;
|
|
1779
|
+
let nextHtml = ensuredAssets.html;
|
|
1780
|
+
if (!cssPath || !fs_1.default.existsSync(cssPath)) {
|
|
1781
|
+
return;
|
|
1782
|
+
}
|
|
1182
1783
|
let css = fs_1.default.readFileSync(cssPath, 'utf8');
|
|
1183
1784
|
let js = fs_1.default.existsSync(jsPath) ? fs_1.default.readFileSync(jsPath, 'utf8') : '';
|
|
1184
|
-
let nextHtml = html;
|
|
1185
1785
|
const keyframesBlocks = Array.from(js.matchAll(/@keyframes[\s\S]*?\n\}/g)).map((match) => match[0]);
|
|
1186
1786
|
if (keyframesBlocks.length > 0) {
|
|
1187
1787
|
const migrated = keyframesBlocks.filter((block) => !css.includes(block));
|
|
@@ -1229,15 +1829,29 @@ class APIClient {
|
|
|
1229
1829
|
</section>`);
|
|
1230
1830
|
nextHtml = this.injectNavLink(nextHtml, 'trust', 'Trust');
|
|
1231
1831
|
}
|
|
1832
|
+
const repairedAssets = this.replaceMissingLocalAssetReferences(rootPath, nextHtml, css);
|
|
1833
|
+
nextHtml = repairedAssets.html;
|
|
1834
|
+
css = repairedAssets.css;
|
|
1232
1835
|
if (nextHtml !== html) {
|
|
1233
1836
|
fs_1.default.writeFileSync(htmlPath, `${nextHtml.trimEnd()}\n`, 'utf8');
|
|
1234
1837
|
nextHtml = fs_1.default.readFileSync(htmlPath, 'utf8');
|
|
1235
1838
|
}
|
|
1839
|
+
if (css !== fs_1.default.readFileSync(cssPath, 'utf8')) {
|
|
1840
|
+
fs_1.default.writeFileSync(cssPath, `${css.trimEnd()}\n`, 'utf8');
|
|
1841
|
+
}
|
|
1236
1842
|
if (/classList\.add\('hidden'\)|classList\.add\("hidden"\)|classList\.add\('revealed'\)|classList\.add\("revealed"\)/.test(js)
|
|
1237
1843
|
&& !/\.hidden\b|\.revealed\b/.test(css)) {
|
|
1238
1844
|
css = `${css.trimEnd()}\n\n/* Vigthoria CLI Visibility States */\n.hidden {\n opacity: 0;\n transform: translateY(24px);\n}\n\n.revealed {\n opacity: 1;\n transform: translateY(0);\n transition: opacity 0.7s ease, transform 0.7s ease;\n}\n`;
|
|
1239
1845
|
fs_1.default.writeFileSync(cssPath, `${css.trimEnd()}\n`, 'utf8');
|
|
1240
1846
|
}
|
|
1847
|
+
if (/^\s*sections\./m.test(js) && !/(?:const|let|var)\s+sections\s*=/.test(js)) {
|
|
1848
|
+
js = `const sections = document.querySelectorAll('section');\n\n${js.trimStart()}`;
|
|
1849
|
+
fs_1.default.writeFileSync(jsPath, `${js.trimEnd()}\n`, 'utf8');
|
|
1850
|
+
}
|
|
1851
|
+
if (!/@media|matchMedia|mobile-nav|hamburger|menu-toggle/i.test(`${nextHtml}\n${css}\n${js}`)) {
|
|
1852
|
+
css = `${css.trimEnd()}\n\n/* Vigthoria CLI Responsive Baseline */\n@media (max-width: 900px) {\n .nav-links, .nav-list, nav ul {\n display: none;\n flex-direction: column;\n gap: 0.75rem;\n }\n\n .nav-links.is-open, .nav-list.is-open, nav ul.is-open {\n display: flex;\n }\n\n .mobile-nav-toggle, #menu-toggle, .menu-toggle {\n display: inline-flex;\n }\n}\n`;
|
|
1853
|
+
fs_1.default.writeFileSync(cssPath, `${css.trimEnd()}\n`, 'utf8');
|
|
1854
|
+
}
|
|
1241
1855
|
const combined = `${nextHtml}\n${css}\n${js}`;
|
|
1242
1856
|
if (/IntersectionObserver|motion-reveal|\.revealed\b|vigCliFadeIn|classList\.add\('is-visible'\)|classList\.add\("is-visible"\)/i.test(combined)) {
|
|
1243
1857
|
return;
|
|
@@ -1269,6 +1883,231 @@ class APIClient {
|
|
|
1269
1883
|
}
|
|
1270
1884
|
return html;
|
|
1271
1885
|
}
|
|
1886
|
+
ensureReferencedFrontendAssets(rootPath, html, prompt, surfaceLabel) {
|
|
1887
|
+
const localAssetPath = (rawPath) => {
|
|
1888
|
+
const candidate = String(rawPath || '').trim();
|
|
1889
|
+
if (!candidate || /^https?:\/\//i.test(candidate) || /^data:/i.test(candidate) || candidate.startsWith('#')) {
|
|
1890
|
+
return '';
|
|
1891
|
+
}
|
|
1892
|
+
return candidate.split('?')[0].split('#')[0].replace(/^\//, '');
|
|
1893
|
+
};
|
|
1894
|
+
const ensureTextAsset = (relativePath, content) => {
|
|
1895
|
+
const normalized = localAssetPath(relativePath);
|
|
1896
|
+
if (!normalized) {
|
|
1897
|
+
return '';
|
|
1898
|
+
}
|
|
1899
|
+
const absolutePath = path_1.default.join(rootPath, normalized);
|
|
1900
|
+
if (!fs_1.default.existsSync(absolutePath)) {
|
|
1901
|
+
fs_1.default.mkdirSync(path_1.default.dirname(absolutePath), { recursive: true });
|
|
1902
|
+
fs_1.default.writeFileSync(absolutePath, content, 'utf8');
|
|
1903
|
+
}
|
|
1904
|
+
return absolutePath;
|
|
1905
|
+
};
|
|
1906
|
+
const cssRefs = Array.from(html.matchAll(/<link[^>]+href=["']([^"']+\.css(?:\?[^"']*)?)["']/gi))
|
|
1907
|
+
.map((match) => localAssetPath(match[1]))
|
|
1908
|
+
.filter(Boolean);
|
|
1909
|
+
const jsRefs = Array.from(html.matchAll(/<script[^>]+src=["']([^"']+\.(?:js|mjs)(?:\?[^"']*)?)["']/gi))
|
|
1910
|
+
.map((match) => localAssetPath(match[1]))
|
|
1911
|
+
.filter(Boolean);
|
|
1912
|
+
let cssPath = cssRefs.map((ref) => path_1.default.join(rootPath, ref)).find((candidate) => fs_1.default.existsSync(candidate)) || '';
|
|
1913
|
+
let jsPath = jsRefs.map((ref) => path_1.default.join(rootPath, ref)).find((candidate) => fs_1.default.existsSync(candidate)) || '';
|
|
1914
|
+
const cssTemplate = this.buildFallbackFrontendCss(surfaceLabel);
|
|
1915
|
+
const jsTemplate = this.buildFallbackFrontendJs(surfaceLabel);
|
|
1916
|
+
if (!cssPath && cssRefs.length > 0) {
|
|
1917
|
+
cssPath = ensureTextAsset(cssRefs[0], cssTemplate);
|
|
1918
|
+
}
|
|
1919
|
+
if (!jsPath && jsRefs.length > 0) {
|
|
1920
|
+
jsPath = ensureTextAsset(jsRefs[0], jsTemplate);
|
|
1921
|
+
}
|
|
1922
|
+
if (!cssPath) {
|
|
1923
|
+
cssPath = ensureTextAsset('styles.css', cssTemplate);
|
|
1924
|
+
if (cssPath && !/<link[^>]+href=["'][^"']*styles\.css/i.test(html)) {
|
|
1925
|
+
html = /<\/head>/i.test(html)
|
|
1926
|
+
? html.replace(/<\/head>/i, ' <link rel="stylesheet" href="styles.css">\n</head>')
|
|
1927
|
+
: html;
|
|
1928
|
+
}
|
|
1929
|
+
}
|
|
1930
|
+
if (!jsPath && /(premium|polished|landing|site|page|dashboard|saas|frontend|ui|responsive|animated)/i.test(prompt)) {
|
|
1931
|
+
jsPath = ensureTextAsset('scripts.js', jsTemplate);
|
|
1932
|
+
if (jsPath && !/<script[^>]+src=["'][^"']*scripts\.js/i.test(html)) {
|
|
1933
|
+
html = /<\/body>/i.test(html)
|
|
1934
|
+
? html.replace(/<\/body>/i, ' <script src="scripts.js"></script>\n</body>')
|
|
1935
|
+
: `${html.trimEnd()}\n<script src="scripts.js"></script>\n`;
|
|
1936
|
+
}
|
|
1937
|
+
}
|
|
1938
|
+
return {
|
|
1939
|
+
html,
|
|
1940
|
+
cssPath,
|
|
1941
|
+
jsPath: jsPath || path_1.default.join(rootPath, 'scripts.js'),
|
|
1942
|
+
};
|
|
1943
|
+
}
|
|
1944
|
+
buildFallbackFrontendCss(surfaceLabel) {
|
|
1945
|
+
return `/* ${surfaceLabel} Frontend Baseline */
|
|
1946
|
+
:root {
|
|
1947
|
+
color-scheme: light;
|
|
1948
|
+
--bg: #f4efe5;
|
|
1949
|
+
--surface: rgba(255, 255, 255, 0.82);
|
|
1950
|
+
--text: #1f2933;
|
|
1951
|
+
--muted: #52606d;
|
|
1952
|
+
--accent: #c36f3c;
|
|
1953
|
+
--accent-strong: #8f3f19;
|
|
1954
|
+
--border: rgba(31, 41, 51, 0.12);
|
|
1955
|
+
}
|
|
1956
|
+
|
|
1957
|
+
* {
|
|
1958
|
+
box-sizing: border-box;
|
|
1959
|
+
}
|
|
1960
|
+
|
|
1961
|
+
body {
|
|
1962
|
+
margin: 0;
|
|
1963
|
+
font-family: "Georgia", "Times New Roman", serif;
|
|
1964
|
+
color: var(--text);
|
|
1965
|
+
background: radial-gradient(circle at top, rgba(195, 111, 60, 0.18), transparent 42%), var(--bg);
|
|
1966
|
+
}
|
|
1967
|
+
|
|
1968
|
+
header, nav, main, section, footer {
|
|
1969
|
+
width: 100%;
|
|
1970
|
+
}
|
|
1971
|
+
|
|
1972
|
+
.container, .shell, .content {
|
|
1973
|
+
width: min(1120px, calc(100% - 2rem));
|
|
1974
|
+
margin: 0 auto;
|
|
1975
|
+
}
|
|
1976
|
+
|
|
1977
|
+
.hero, section, footer {
|
|
1978
|
+
opacity: 0;
|
|
1979
|
+
transform: translateY(24px);
|
|
1980
|
+
animation: vigFallbackFadeIn 0.8s ease forwards;
|
|
1981
|
+
}
|
|
1982
|
+
|
|
1983
|
+
.hero {
|
|
1984
|
+
min-height: 72vh;
|
|
1985
|
+
padding: 6rem 0 4rem;
|
|
1986
|
+
}
|
|
1987
|
+
|
|
1988
|
+
.nav-list, nav ul {
|
|
1989
|
+
display: flex;
|
|
1990
|
+
gap: 1rem;
|
|
1991
|
+
list-style: none;
|
|
1992
|
+
padding: 0;
|
|
1993
|
+
margin: 0;
|
|
1994
|
+
}
|
|
1995
|
+
|
|
1996
|
+
.mobile-nav-toggle, #menu-toggle, .menu-toggle {
|
|
1997
|
+
display: none;
|
|
1998
|
+
}
|
|
1999
|
+
|
|
2000
|
+
.cta, button, a {
|
|
2001
|
+
transition: transform 0.25s ease, opacity 0.25s ease, background-color 0.25s ease;
|
|
2002
|
+
}
|
|
2003
|
+
|
|
2004
|
+
.cta:hover, button:hover, a:hover {
|
|
2005
|
+
transform: translateY(-2px);
|
|
2006
|
+
}
|
|
2007
|
+
|
|
2008
|
+
.motion-reveal {
|
|
2009
|
+
opacity: 0;
|
|
2010
|
+
transform: translateY(24px);
|
|
2011
|
+
transition: opacity 0.7s ease, transform 0.7s ease;
|
|
2012
|
+
}
|
|
2013
|
+
|
|
2014
|
+
.motion-reveal.is-visible {
|
|
2015
|
+
opacity: 1;
|
|
2016
|
+
transform: translateY(0);
|
|
2017
|
+
}
|
|
2018
|
+
|
|
2019
|
+
@media (max-width: 900px) {
|
|
2020
|
+
.nav-list, nav ul {
|
|
2021
|
+
display: none;
|
|
2022
|
+
flex-direction: column;
|
|
2023
|
+
padding: 1rem;
|
|
2024
|
+
background: var(--surface);
|
|
2025
|
+
border: 1px solid var(--border);
|
|
2026
|
+
border-radius: 1rem;
|
|
2027
|
+
}
|
|
2028
|
+
|
|
2029
|
+
.nav-list.is-open, nav ul.is-open {
|
|
2030
|
+
display: flex;
|
|
2031
|
+
}
|
|
2032
|
+
|
|
2033
|
+
.mobile-nav-toggle, #menu-toggle, .menu-toggle {
|
|
2034
|
+
display: inline-flex;
|
|
2035
|
+
align-items: center;
|
|
2036
|
+
justify-content: center;
|
|
2037
|
+
}
|
|
2038
|
+
}
|
|
2039
|
+
|
|
2040
|
+
@keyframes vigFallbackFadeIn {
|
|
2041
|
+
from {
|
|
2042
|
+
opacity: 0;
|
|
2043
|
+
transform: translateY(24px);
|
|
2044
|
+
}
|
|
2045
|
+
to {
|
|
2046
|
+
opacity: 1;
|
|
2047
|
+
transform: translateY(0);
|
|
2048
|
+
}
|
|
2049
|
+
}
|
|
2050
|
+
`;
|
|
2051
|
+
}
|
|
2052
|
+
buildFallbackFrontendJs(surfaceLabel) {
|
|
2053
|
+
return `/* ${surfaceLabel} Frontend Baseline */
|
|
2054
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
2055
|
+
const toggle = document.querySelector('.mobile-nav-toggle, #menu-toggle, .menu-toggle');
|
|
2056
|
+
const nav = document.querySelector('.nav-list, nav ul');
|
|
2057
|
+
if (toggle && nav) {
|
|
2058
|
+
toggle.addEventListener('click', () => {
|
|
2059
|
+
nav.classList.toggle('is-open');
|
|
2060
|
+
});
|
|
2061
|
+
}
|
|
2062
|
+
|
|
2063
|
+
const revealTargets = document.querySelectorAll('section, .hero, .card, article, .project-grid > *, .journal-preview > *');
|
|
2064
|
+
if (typeof IntersectionObserver !== 'function') {
|
|
2065
|
+
revealTargets.forEach((element) => element.classList.add('is-visible'));
|
|
2066
|
+
return;
|
|
2067
|
+
}
|
|
2068
|
+
|
|
2069
|
+
const observer = new IntersectionObserver((entries) => {
|
|
2070
|
+
entries.forEach((entry) => {
|
|
2071
|
+
if (entry.isIntersecting) {
|
|
2072
|
+
entry.target.classList.add('is-visible');
|
|
2073
|
+
observer.unobserve(entry.target);
|
|
2074
|
+
}
|
|
2075
|
+
});
|
|
2076
|
+
}, { threshold: 0.16 });
|
|
2077
|
+
|
|
2078
|
+
revealTargets.forEach((element) => {
|
|
2079
|
+
element.classList.add('motion-reveal');
|
|
2080
|
+
observer.observe(element);
|
|
2081
|
+
});
|
|
2082
|
+
});
|
|
2083
|
+
`;
|
|
2084
|
+
}
|
|
2085
|
+
replaceMissingLocalAssetReferences(rootPath, html, css) {
|
|
2086
|
+
const hasLocalAsset = (assetPath) => {
|
|
2087
|
+
const candidate = String(assetPath || '').trim();
|
|
2088
|
+
if (!candidate || /^https?:\/\//i.test(candidate) || /^data:/i.test(candidate) || candidate.startsWith('#')) {
|
|
2089
|
+
return true;
|
|
2090
|
+
}
|
|
2091
|
+
const sanitized = candidate.split('?')[0].split('#')[0].replace(/^\//, '');
|
|
2092
|
+
return !sanitized || fs_1.default.existsSync(path_1.default.join(rootPath, sanitized));
|
|
2093
|
+
};
|
|
2094
|
+
const repairedHtml = html.replace(/(<img\b[^>]*\ssrc=["'])([^"']+)(["'][^>]*>)/gi, (match, prefix, assetPath, suffix) => {
|
|
2095
|
+
if (hasLocalAsset(assetPath)) {
|
|
2096
|
+
return match;
|
|
2097
|
+
}
|
|
2098
|
+
const altText = (String(suffix).match(/\salt=["']([^"']*)["']/i)?.[1] || 'Visual highlight').trim() || 'Visual highlight';
|
|
2099
|
+
const safeLabel = altText.replace(/[<&>]/g, '');
|
|
2100
|
+
const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 800"><defs><linearGradient id="heroGradient" x1="0" y1="0" x2="1" y2="1"><stop stop-color="#0f172a"/><stop offset="1" stop-color="#2563eb"/></linearGradient></defs><rect width="1200" height="800" rx="40" ry="40" fill="url(#heroGradient)"/><circle cx="220" cy="210" r="130" fill="rgba(255,255,255,0.08)"/><circle cx="980" cy="600" r="170" fill="rgba(255,255,255,0.12)"/><text x="84" y="700" fill="#ffffff" font-family="Arial, sans-serif" font-size="64" font-weight="700">${safeLabel}</text></svg>`;
|
|
2101
|
+
return `${prefix}data:image/svg+xml;utf8,${encodeURIComponent(svg)}${suffix}`;
|
|
2102
|
+
});
|
|
2103
|
+
const repairedCss = css.replace(/url\((["']?)([^)"']+)\1\)/gi, (match, quote, assetPath) => {
|
|
2104
|
+
if (hasLocalAsset(assetPath)) {
|
|
2105
|
+
return match;
|
|
2106
|
+
}
|
|
2107
|
+
return 'linear-gradient(135deg, rgba(15, 23, 42, 0.92), rgba(37, 99, 235, 0.78))';
|
|
2108
|
+
});
|
|
2109
|
+
return { html: repairedHtml, css: repairedCss };
|
|
2110
|
+
}
|
|
1272
2111
|
formatV3AgentResponse(data) {
|
|
1273
2112
|
const result = data?.result || {};
|
|
1274
2113
|
if (typeof result === 'string') {
|
|
@@ -1406,61 +2245,106 @@ class APIClient {
|
|
|
1406
2245
|
}
|
|
1407
2246
|
async runV3AgentWorkflow(message, context = {}) {
|
|
1408
2247
|
const executionContext = await this.bindExecutionContext(context);
|
|
1409
|
-
const
|
|
1410
|
-
const errors = [];
|
|
2248
|
+
const baseTimeoutMs = executionContext.agentTimeoutMs || DEFAULT_V3_AGENT_TIMEOUT_MS;
|
|
1411
2249
|
const expectedFiles = this.extractExpectedWorkspaceFiles(message, executionContext);
|
|
1412
|
-
const
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
this.
|
|
2250
|
+
const requestedModel = String(executionContext.model || executionContext.requestedModel || 'agent');
|
|
2251
|
+
const resolvedModel = this.resolvePermittedModelId(requestedModel);
|
|
2252
|
+
const preferLocalV3 = /(premium|polished|landing|site|page|dashboard|saas|frontend|ui|responsive|animated|create the required project files and write them to the workspace)/i.test(message)
|
|
2253
|
+
&& context.localMachineCapable !== false;
|
|
2254
|
+
const rescueEligibleSaaS = preferLocalV3
|
|
2255
|
+
&& /(saas|dashboard|analytics|billing|team management|activity feed|login screen)/i.test(message);
|
|
2256
|
+
const timeoutMs = rescueEligibleSaaS ? Math.min(baseTimeoutMs, 210000) : baseTimeoutMs;
|
|
2257
|
+
const maxAttempts = preferLocalV3 ? 2 : 1;
|
|
2258
|
+
let lastErrors = [];
|
|
2259
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
2260
|
+
const errors = [];
|
|
2261
|
+
const useRelaxedAttempt = preferLocalV3 && attempt > 1;
|
|
2262
|
+
const requestExecutionContext = useRelaxedAttempt
|
|
2263
|
+
? this.ensureExecutionContext({
|
|
2264
|
+
...executionContext,
|
|
2265
|
+
contextId: '',
|
|
2266
|
+
traceId: '',
|
|
2267
|
+
history: [],
|
|
2268
|
+
mcpContextId: null,
|
|
2269
|
+
mcpContextBackendUrl: null,
|
|
2270
|
+
agentExecutionPolicy: null,
|
|
2271
|
+
legacyFallbackAllowed: true,
|
|
2272
|
+
})
|
|
2273
|
+
: executionContext;
|
|
2274
|
+
const requestBody = {
|
|
2275
|
+
request: message,
|
|
2276
|
+
model: resolvedModel,
|
|
2277
|
+
preferred_model: resolvedModel,
|
|
2278
|
+
requested_model: requestedModel,
|
|
2279
|
+
routing_policy: useRelaxedAttempt ? null : requestExecutionContext.agentExecutionPolicy || null,
|
|
2280
|
+
strict_mode: useRelaxedAttempt ? false : requestExecutionContext.legacyFallbackAllowed !== true,
|
|
2281
|
+
context: useRelaxedAttempt
|
|
2282
|
+
? this.buildMinimalV3AgentContext(requestExecutionContext)
|
|
2283
|
+
: this.buildV3AgentContext(requestExecutionContext),
|
|
2284
|
+
context_id: requestExecutionContext.contextId,
|
|
2285
|
+
mcp_context_id: useRelaxedAttempt ? null : requestExecutionContext.mcpContextId || null,
|
|
2286
|
+
stream: true,
|
|
2287
|
+
};
|
|
2288
|
+
for (const baseUrl of this.getV3AgentBaseUrls(preferLocalV3)) {
|
|
2289
|
+
const controller = new AbortController();
|
|
2290
|
+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
2291
|
+
try {
|
|
2292
|
+
const response = await this.executeV3AgentRunRequest(baseUrl, requestBody, requestExecutionContext, controller.signal);
|
|
2293
|
+
if (!response.ok) {
|
|
2294
|
+
const errorText = await response.text().catch(() => '');
|
|
2295
|
+
throw new Error(`V3 agent ${response.status}: ${errorText.slice(0, 200)}`);
|
|
2296
|
+
}
|
|
2297
|
+
const data = await this.collectV3AgentStream(response, requestExecutionContext);
|
|
2298
|
+
const contextId = data.context_id || response.headers.get('x-context-id') || requestExecutionContext.contextId || null;
|
|
2299
|
+
const mcpContextId = response.headers.get('x-mcp-context-id') || requestExecutionContext.mcpContextId || null;
|
|
2300
|
+
this.recoverAgentWorkspaceFiles(executionContext, data.files || {}, expectedFiles);
|
|
1446
2301
|
await this.waitForAgentWorkspaceSettle(executionContext, { expectedFiles });
|
|
1447
2302
|
await this.ensureAgentFrontendPolish(message, executionContext);
|
|
1448
2303
|
const previewGate = await this.runTemplateServicePreviewGate(message, executionContext);
|
|
2304
|
+
if (previewGate.required && previewGate.passed !== true && !this.hasAgentWorkspaceOutput(executionContext)) {
|
|
2305
|
+
errors.push(`${baseUrl}: workflow rejected the result - ${previewGate.error || 'Workspace changes were not fully validated.'}`);
|
|
2306
|
+
continue;
|
|
2307
|
+
}
|
|
1449
2308
|
return {
|
|
1450
|
-
content: this.formatV3AgentResponse(
|
|
1451
|
-
taskId:
|
|
1452
|
-
contextId
|
|
2309
|
+
content: this.formatV3AgentResponse(data),
|
|
2310
|
+
taskId: data.task_id || null,
|
|
2311
|
+
contextId,
|
|
1453
2312
|
backendUrl: baseUrl,
|
|
1454
|
-
|
|
1455
|
-
metadata: { source: 'v3-agent', mode: 'agent', partial: true, contextId: error.partialData.context_id || executionContext.contextId || null, mcpContextId: executionContext.mcpContextId || null, previewGate },
|
|
2313
|
+
metadata: { source: 'v3-agent', mode: 'agent', contextId, mcpContextId, previewGate },
|
|
1456
2314
|
};
|
|
1457
2315
|
}
|
|
1458
|
-
|
|
2316
|
+
catch (error) {
|
|
2317
|
+
if (error && error.name === 'AbortError' && error.partialData && this.hasAgentWorkspaceOutput(executionContext)) {
|
|
2318
|
+
this.recoverAgentWorkspaceFiles(executionContext, error.partialData.files || {}, expectedFiles);
|
|
2319
|
+
await this.waitForAgentWorkspaceSettle(executionContext, { expectedFiles });
|
|
2320
|
+
await this.ensureAgentFrontendPolish(message, executionContext);
|
|
2321
|
+
const previewGate = await this.runTemplateServicePreviewGate(message, executionContext);
|
|
2322
|
+
return {
|
|
2323
|
+
content: this.formatV3AgentResponse(error.partialData) || 'V3 agent wrote workspace files before the request timed out waiting for a final summary.',
|
|
2324
|
+
taskId: error.partialData.task_id || null,
|
|
2325
|
+
contextId: error.partialData.context_id || requestExecutionContext.contextId || executionContext.contextId || null,
|
|
2326
|
+
backendUrl: baseUrl,
|
|
2327
|
+
partial: true,
|
|
2328
|
+
metadata: { source: 'v3-agent', mode: 'agent', partial: true, contextId: error.partialData.context_id || requestExecutionContext.contextId || executionContext.contextId || null, mcpContextId: requestExecutionContext.mcpContextId || executionContext.mcpContextId || null, previewGate },
|
|
2329
|
+
};
|
|
2330
|
+
}
|
|
2331
|
+
errors.push(`${baseUrl}: ${error?.message || String(error)}`);
|
|
2332
|
+
}
|
|
2333
|
+
finally {
|
|
2334
|
+
clearTimeout(timeoutId);
|
|
2335
|
+
}
|
|
1459
2336
|
}
|
|
1460
|
-
|
|
1461
|
-
|
|
2337
|
+
lastErrors = errors;
|
|
2338
|
+
const shouldRetry = attempt < maxAttempts
|
|
2339
|
+
&& preferLocalV3
|
|
2340
|
+
&& !this.hasAgentWorkspaceOutput(executionContext)
|
|
2341
|
+
&& errors.some((entry) => /terminated|abort|timed out|timeout|ECONNRESET|socket hang up|workflow rejected the result|incomplete result|preview gate requires at least one html entry file/i.test(entry));
|
|
2342
|
+
if (!shouldRetry) {
|
|
2343
|
+
break;
|
|
1462
2344
|
}
|
|
2345
|
+
await new Promise((resolve) => setTimeout(resolve, 1500));
|
|
1463
2346
|
}
|
|
2347
|
+
const errors = lastErrors;
|
|
1464
2348
|
const onlyUnauthorizedErrors = errors.length > 0 && errors.every((entry) => /V3 agent 401:/i.test(entry));
|
|
1465
2349
|
const usingStoredConfigToken = !process.env.VIGTHORIA_TOKEN
|
|
1466
2350
|
&& !process.env.VIGTHORIA_AUTH_TOKEN
|
|
@@ -1469,6 +2353,24 @@ class APIClient {
|
|
|
1469
2353
|
this.config.clearAuth();
|
|
1470
2354
|
throw new Error('V3 agent authentication failed. The stored CLI login token is invalid or expired. Run vigthoria login again.');
|
|
1471
2355
|
}
|
|
2356
|
+
if (preferLocalV3
|
|
2357
|
+
&& !this.hasAgentWorkspaceOutput(executionContext)
|
|
2358
|
+
&& /(saas|dashboard|analytics|billing|team management|activity feed)/i.test(message)) {
|
|
2359
|
+
const appName = this.materializeEmergencySaaSWorkspace(message, executionContext);
|
|
2360
|
+
if (appName) {
|
|
2361
|
+
await this.waitForAgentWorkspaceSettle(executionContext, { expectedFiles: ['index.html', 'styles.css', 'scripts.js'] });
|
|
2362
|
+
await this.ensureAgentFrontendPolish(message, executionContext);
|
|
2363
|
+
const previewGate = await this.runTemplateServicePreviewGate(message, executionContext);
|
|
2364
|
+
return {
|
|
2365
|
+
content: `Recovered a local SaaS workspace scaffold for ${appName} after repeated V3 materialization failures.`,
|
|
2366
|
+
taskId: null,
|
|
2367
|
+
contextId: executionContext.contextId || null,
|
|
2368
|
+
backendUrl: 'local-emergency-scaffold',
|
|
2369
|
+
partial: true,
|
|
2370
|
+
metadata: { source: 'v3-agent-emergency-scaffold', mode: 'agent', previewGate, emergencyScaffold: true },
|
|
2371
|
+
};
|
|
2372
|
+
}
|
|
2373
|
+
}
|
|
1472
2374
|
throw new Error(errors.join(' | '));
|
|
1473
2375
|
}
|
|
1474
2376
|
formatOperatorResponse(data = {}) {
|
|
@@ -2003,6 +2905,8 @@ class APIClient {
|
|
|
2003
2905
|
'code-8b': 'vigthoria-v2-code-8b',
|
|
2004
2906
|
'pro': 'vigthoria-v3-code-30b',
|
|
2005
2907
|
'agent': 'vigthoria-v3-code-30b',
|
|
2908
|
+
'vigthoria-code': 'vigthoria-v3-code-30b',
|
|
2909
|
+
'vigthoria-agent': 'vigthoria-v3-code-30b',
|
|
2006
2910
|
// ═══════════════════════════════════════════════════════════════
|
|
2007
2911
|
// VIGTHORIA CLOUD - Premium cloud models (internal routing)
|
|
2008
2912
|
// ═══════════════════════════════════════════════════════════════
|