vigthoria-cli 1.13.26 → 1.13.30
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 +12 -0
- package/completions/_vigthoria +1 -0
- package/completions/vigthoria.bash +1 -1
- package/completions/vigthoria.fish +1 -0
- package/dist/commands/chat.js +73 -26
- package/dist/commands/config.js +4 -4
- package/dist/commands/creative-registration.d.ts +13 -0
- package/dist/commands/creative-registration.js +88 -0
- package/dist/commands/fork.d.ts +3 -2
- package/dist/commands/fork.js +124 -123
- package/dist/commands/game.d.ts +8 -0
- package/dist/commands/game.js +113 -9
- package/dist/commands/history.d.ts +0 -1
- package/dist/commands/history.js +8 -22
- package/dist/commands/hub.d.ts +20 -0
- package/dist/commands/hub.js +17 -3
- package/dist/commands/preview.js +7 -2
- package/dist/commands/product-run-registration.js +1 -1
- package/dist/commands/replay.d.ts +0 -1
- package/dist/commands/replay.js +10 -19
- package/dist/commands/repo.js +16 -4
- package/dist/commands/update-registration.js +2 -2
- package/dist/commands/workflow.d.ts +4 -0
- package/dist/commands/workflow.js +27 -0
- package/dist/index.js +8 -4
- package/dist/utils/agentRunOutcome.d.ts +7 -0
- package/dist/utils/agentRunOutcome.js +13 -0
- package/dist/utils/api.d.ts +20 -5
- package/dist/utils/api.js +428 -43
- package/dist/utils/command-policy.js +3 -1
- package/dist/utils/config.d.ts +2 -0
- package/dist/utils/config.js +22 -9
- package/dist/utils/frontend-preview-service.d.ts +1 -0
- package/dist/utils/frontend-preview-service.js +54 -5
- package/dist/utils/model-governance.js +23 -14
- package/dist/utils/model-transport-service.js +1 -1
- package/dist/utils/network-policy.js +15 -3
- package/dist/utils/operator-client.js +23 -4
- package/dist/utils/post-write-validator.js +7 -3
- package/dist/utils/preview-screenshot-adapter.d.ts +16 -41
- package/dist/utils/preview-screenshot-adapter.js +273 -64
- package/dist/utils/runtime-capability.d.ts +7 -0
- package/dist/utils/runtime-capability.js +11 -0
- package/dist/utils/runtime-temp.d.ts +5 -2
- package/dist/utils/runtime-temp.js +131 -30
- package/dist/utils/tools.js +1 -1
- package/dist/utils/v3-stream-events.js +10 -2
- package/dist/utils/v3-workspace-service.d.ts +1 -0
- package/dist/utils/v3-workspace-service.js +38 -1
- package/dist/utils/vigflow-client.d.ts +9 -0
- package/dist/utils/vigflow-client.js +48 -2
- package/dist/utils/workspace-reference.d.ts +8 -0
- package/dist/utils/workspace-reference.js +21 -0
- package/install.ps1 +2 -2
- package/install.sh +2 -2
- package/package.json +4 -6
- package/scripts/release/LOCAL_MACHINE_USER_VERIFICATION.md +2 -2
- package/scripts/release/validate-live-service-gates.sh +3 -3
- package/scripts/release/validate-no-go-gates.sh +2 -0
package/dist/commands/fork.js
CHANGED
|
@@ -3,14 +3,17 @@ import { hasLocalV3AgentCapability } from '../utils/runtime-capability.js';
|
|
|
3
3
|
* fork.ts — Fork from an existing V3 agent run and stream the result.
|
|
4
4
|
*/
|
|
5
5
|
import chalk from 'chalk';
|
|
6
|
-
import { createRequire } from 'node:module';
|
|
7
6
|
import { createSpinner, CH } from '../utils/logger.js';
|
|
8
7
|
import { CliCommandError, commandFailure, formatSuccessJson } from '../utils/command-contract.js';
|
|
9
|
-
|
|
8
|
+
import { createAPIClient } from '../utils/api-client-factory.js';
|
|
9
|
+
import { buildLocalWorkspaceReference } from '../utils/workspace-reference.js';
|
|
10
|
+
import { guardedFetch } from '../utils/network-policy.js';
|
|
10
11
|
export class ForkCommand {
|
|
11
12
|
config;
|
|
12
|
-
|
|
13
|
+
logger;
|
|
14
|
+
constructor(config, logger) {
|
|
13
15
|
this.config = config;
|
|
16
|
+
this.logger = logger;
|
|
14
17
|
}
|
|
15
18
|
getHeaders() {
|
|
16
19
|
const headers = { 'Content-Type': 'application/json' };
|
|
@@ -34,50 +37,69 @@ export class ForkCommand {
|
|
|
34
37
|
(allowLocal ? 'http://127.0.0.1:8030' : null) ||
|
|
35
38
|
configuredApiUrl);
|
|
36
39
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
40
|
+
buildForkHistory(events, eventIndex) {
|
|
41
|
+
const selected = eventIndex > 0 ? events.slice(0, eventIndex) : events;
|
|
42
|
+
const lines = [
|
|
43
|
+
`Fork point: ${eventIndex > 0 ? eventIndex : events.length} of ${events.length} recorded events.`,
|
|
44
|
+
'Prior run evidence (metadata only; re-read current files through client tools):',
|
|
45
|
+
];
|
|
46
|
+
let toolCalls = 0;
|
|
47
|
+
for (const event of selected.slice(-120)) {
|
|
48
|
+
const type = String(event?.type || 'unknown');
|
|
49
|
+
if (type === 'plan') {
|
|
50
|
+
const count = Array.isArray(event.tasks) ? event.tasks.length : 0;
|
|
51
|
+
lines.push(`- plan: ${count} task(s)`);
|
|
45
52
|
}
|
|
46
|
-
|
|
53
|
+
else if (type === 'tool_call') {
|
|
54
|
+
toolCalls += 1;
|
|
55
|
+
lines.push(`- tool_call ${toolCalls}: ${String(event.name || 'unknown').slice(0, 80)}`);
|
|
56
|
+
}
|
|
57
|
+
else if (type === 'tool_result') {
|
|
58
|
+
lines.push(`- tool_result: ${String(event.name || 'unknown').slice(0, 80)} success=${event.success !== false}`);
|
|
59
|
+
}
|
|
60
|
+
else if (type === 'file_mutation') {
|
|
61
|
+
lines.push(`- file_mutation: ${String(event.kind || 'change').slice(0, 32)} (path intentionally omitted)`);
|
|
62
|
+
}
|
|
63
|
+
else if (type === 'complete') {
|
|
64
|
+
lines.push('- prior run reached its completion event');
|
|
65
|
+
}
|
|
66
|
+
else if (type === 'error') {
|
|
67
|
+
lines.push(`- prior run error code: ${String(event.code || event.error_code || 'unspecified').slice(0, 80)}`);
|
|
68
|
+
}
|
|
69
|
+
if (lines.join('\n').length > 12_000)
|
|
70
|
+
break;
|
|
47
71
|
}
|
|
48
|
-
return
|
|
72
|
+
return lines.join('\n').slice(0, 12_000);
|
|
49
73
|
}
|
|
50
74
|
async run(runId, message, options) {
|
|
51
75
|
const project = options.project || process.cwd();
|
|
52
|
-
const
|
|
76
|
+
const workspaceRef = buildLocalWorkspaceReference(project, String(this.config.get('userId') || this.config.get('email') || ''));
|
|
53
77
|
const eventIndex = options.eventIndex || 0;
|
|
54
|
-
const spinner = createSpinner(`
|
|
78
|
+
const spinner = createSpinner(`Loading run ${runId} for a local fork...`).start();
|
|
55
79
|
const streamController = new AbortController();
|
|
80
|
+
let api = null;
|
|
56
81
|
let interrupted = false;
|
|
57
82
|
const onInterrupt = () => {
|
|
58
83
|
interrupted = true;
|
|
59
84
|
streamController.abort();
|
|
85
|
+
api?.destroy();
|
|
60
86
|
};
|
|
61
87
|
process.once('SIGINT', onInterrupt);
|
|
62
88
|
try {
|
|
63
89
|
const baseUrl = this.getBaseUrl();
|
|
64
|
-
const
|
|
65
|
-
workspace_root:
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
};
|
|
71
|
-
const resp = await fetch(`${baseUrl}/api/runs/${encodeURIComponent(runId)}/fork`, {
|
|
72
|
-
method: 'POST',
|
|
90
|
+
const params = new URLSearchParams({
|
|
91
|
+
workspace_root: '',
|
|
92
|
+
local_workspace_path: workspaceRef,
|
|
93
|
+
project_path: workspaceRef,
|
|
94
|
+
});
|
|
95
|
+
const resp = await guardedFetch(`${baseUrl}/api/runs/${encodeURIComponent(runId)}/events?${params}`, {
|
|
73
96
|
headers: this.getHeaders(),
|
|
74
|
-
body: JSON.stringify(body),
|
|
75
97
|
signal: streamController.signal,
|
|
76
|
-
});
|
|
98
|
+
}, { audience: 'v3' });
|
|
77
99
|
if (!resp.ok) {
|
|
78
100
|
spinner.stop();
|
|
79
101
|
if (resp.status === 404) {
|
|
80
|
-
throw new CliCommandError(`Run ${runId} not found or has no event log.`, {
|
|
102
|
+
throw new CliCommandError(`Run ${runId} was not found or has no event log.`, {
|
|
81
103
|
code: 'RUN_NOT_FOUND', status: resp.status,
|
|
82
104
|
});
|
|
83
105
|
}
|
|
@@ -87,116 +109,94 @@ export class ForkCommand {
|
|
|
87
109
|
});
|
|
88
110
|
}
|
|
89
111
|
else {
|
|
90
|
-
throw new CliCommandError(`
|
|
112
|
+
throw new CliCommandError(`Could not load the fork source: ${resp.status} ${resp.statusText}`, {
|
|
91
113
|
code: 'RUN_FORK_FAILED', status: resp.status,
|
|
92
114
|
});
|
|
93
115
|
}
|
|
94
116
|
}
|
|
117
|
+
const source = (await resp.json());
|
|
118
|
+
const sourceEvents = Array.isArray(source.events) ? source.events : [];
|
|
119
|
+
if (eventIndex < 0 || eventIndex > sourceEvents.length) {
|
|
120
|
+
throw new CliCommandError(`Fork event index ${eventIndex} is outside 0..${sourceEvents.length}.`, {
|
|
121
|
+
code: 'RUN_FORK_EVENT_INDEX_INVALID',
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
const forkHistory = this.buildForkHistory(sourceEvents, eventIndex);
|
|
95
125
|
spinner.stop();
|
|
96
|
-
if (!options.json)
|
|
97
|
-
console.log(chalk.bold(`\n${CH.success}
|
|
98
|
-
if (!resp.body) {
|
|
99
|
-
throw new CliCommandError('Fork response did not contain an event stream', { code: 'RUN_FORK_STREAM_MISSING' });
|
|
126
|
+
if (!options.json) {
|
|
127
|
+
console.log(chalk.bold(`\n${CH.success} Starting local fork from ${chalk.cyan(runId)} at event ${eventIndex || sourceEvents.length}\n`));
|
|
100
128
|
}
|
|
101
|
-
|
|
102
|
-
const reader = resp.body.getReader();
|
|
103
|
-
const decoder = new TextDecoder();
|
|
104
|
-
let buffer = '';
|
|
129
|
+
const streamEvents = [];
|
|
105
130
|
let toolCallNum = 0;
|
|
106
|
-
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
131
|
+
api = createAPIClient(this.config, this.logger);
|
|
132
|
+
const request = [
|
|
133
|
+
message.trim() || 'Continue the previous run from the selected fork point.',
|
|
134
|
+
'',
|
|
135
|
+
forkHistory,
|
|
136
|
+
'',
|
|
137
|
+
'Use the current client-authoritative workspace as truth. Re-read every needed file through client tools; do not assume prior file contents.',
|
|
138
|
+
].join('\n');
|
|
139
|
+
const result = await api.runV3AgentWorkflow(request, {
|
|
140
|
+
projectPath: project,
|
|
141
|
+
targetPath: project,
|
|
142
|
+
workspacePath: project,
|
|
143
|
+
localWorkspacePath: project,
|
|
144
|
+
localMachineCapable: true,
|
|
145
|
+
clientToolExecution: true,
|
|
146
|
+
executionSurface: 'fork',
|
|
147
|
+
clientSurface: 'cli',
|
|
148
|
+
rawPrompt: message || request,
|
|
149
|
+
contextualPrompt: forkHistory,
|
|
150
|
+
forkedFrom: runId,
|
|
151
|
+
forkEventIndex: eventIndex,
|
|
152
|
+
onStreamEvent: (event) => {
|
|
153
|
+
streamEvents.push({
|
|
154
|
+
type: String(event.type || 'unknown'),
|
|
155
|
+
name: typeof event.name === 'string' ? event.name : undefined,
|
|
156
|
+
success: typeof event.success === 'boolean' ? event.success : undefined,
|
|
157
|
+
code: typeof event.code === 'string' ? event.code : undefined,
|
|
158
|
+
});
|
|
159
|
+
if (options.json)
|
|
160
|
+
return;
|
|
161
|
+
const type = String(event.type || 'unknown');
|
|
162
|
+
if (type === 'plan') {
|
|
163
|
+
console.log(chalk.magenta(` 📋 PLAN`) + ` ${event.tasks?.length || 0} tasks`);
|
|
138
164
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
events.push(evt);
|
|
143
|
-
if (options.json) {
|
|
144
|
-
continue;
|
|
145
|
-
}
|
|
146
|
-
switch (type) {
|
|
147
|
-
case 'context':
|
|
148
|
-
console.log(chalk.blue(` context: ${evt.context_id || '?'}`) +
|
|
149
|
-
(evt.forked_from ? chalk.dim(` (forked from ${evt.forked_from})`) : ''));
|
|
150
|
-
break;
|
|
151
|
-
case 'start':
|
|
152
|
-
console.log(chalk.green(` ▶ START`) + ` task=${evt.task_id || '?'}` +
|
|
153
|
-
(evt.forked_from ? chalk.dim(` forked_from=${evt.forked_from}`) : ''));
|
|
154
|
-
break;
|
|
155
|
-
case 'plan':
|
|
156
|
-
console.log(chalk.magenta(` 📋 PLAN`) + ` ${evt.tasks?.length || 0} tasks`);
|
|
157
|
-
break;
|
|
158
|
-
case 'tool_call':
|
|
159
|
-
toolCallNum++;
|
|
160
|
-
console.log(chalk.yellow(` 🔧 #${toolCallNum}`) + ` ${evt.name || '?'}(${JSON.stringify(evt.arguments || {}).substring(0, 60)})`);
|
|
161
|
-
break;
|
|
162
|
-
case 'tool_result': {
|
|
163
|
-
const icon = evt.success !== false ? chalk.green('✓') : chalk.red('✗');
|
|
164
|
-
console.log(` ${icon} ${evt.name || '?'}: ${chalk.dim((evt.output || '').substring(0, 100))}`);
|
|
165
|
-
break;
|
|
166
|
-
}
|
|
167
|
-
case 'message':
|
|
168
|
-
console.log(chalk.cyan(` 💬 `) + (evt.content || '').substring(0, 150));
|
|
169
|
-
break;
|
|
170
|
-
case 'complete': {
|
|
171
|
-
const seal = evt.seal_score ? `[${evt.seal_score.tier} ${evt.seal_score.overall}]` : '';
|
|
172
|
-
console.log(chalk.green(` ✅ COMPLETE `) + chalk.yellow(seal) +
|
|
173
|
-
` ${evt.iterations || '?'} iterations, ${evt.tool_calls || '?'} tool calls`);
|
|
174
|
-
break;
|
|
175
|
-
}
|
|
176
|
-
case 'error':
|
|
177
|
-
throw new CliCommandError(String(evt.message || 'Fork stream reported an error.'), {
|
|
178
|
-
code: 'RUN_FORK_STREAM_ERROR', details: evt,
|
|
179
|
-
});
|
|
180
|
-
default:
|
|
181
|
-
console.log(chalk.dim(` ${type}: ${JSON.stringify(evt).substring(0, 80)}`));
|
|
182
|
-
}
|
|
165
|
+
else if (type === 'tool_call') {
|
|
166
|
+
toolCallNum += 1;
|
|
167
|
+
console.log(chalk.yellow(` 🔧 #${toolCallNum}`) + ` ${event.name || '?'}`);
|
|
183
168
|
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
throw new CliCommandError('Fork event stream contained malformed JSON.', {
|
|
188
|
-
code: 'RUN_FORK_STREAM_MALFORMED', details: { payload: payload.slice(0, 200) }, cause: error,
|
|
189
|
-
});
|
|
169
|
+
else if (type === 'tool_result') {
|
|
170
|
+
const icon = event.success !== false ? chalk.green('✓') : chalk.red('✗');
|
|
171
|
+
console.log(` ${icon} ${event.name || '?'}`);
|
|
190
172
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
173
|
+
else if (type === 'message' && event.content) {
|
|
174
|
+
console.log(chalk.cyan(' 💬 ') + String(event.content).slice(0, 180));
|
|
175
|
+
}
|
|
176
|
+
else if (type === 'complete') {
|
|
177
|
+
console.log(chalk.green(' ✅ COMPLETE'));
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
});
|
|
181
|
+
if (result.partial) {
|
|
182
|
+
throw new CliCommandError('Fork execution ended with a partial result.', {
|
|
183
|
+
code: 'RUN_FORK_PARTIAL', details: { contextId: result.contextId, taskId: result.taskId },
|
|
184
|
+
});
|
|
195
185
|
}
|
|
196
186
|
if (options.json) {
|
|
197
|
-
console.log(formatSuccessJson('fork', {
|
|
187
|
+
console.log(formatSuccessJson('fork', {
|
|
188
|
+
runId,
|
|
189
|
+
eventIndex: eventIndex || sourceEvents.length,
|
|
190
|
+
taskId: result.taskId,
|
|
191
|
+
contextId: result.contextId,
|
|
192
|
+
content: result.content,
|
|
193
|
+
changedFiles: Object.keys(result.changedFiles || {}),
|
|
194
|
+
events: streamEvents,
|
|
195
|
+
}, { stream: true, eventCount: streamEvents.length, workspaceAuthority: 'client-tool-bridge' }));
|
|
198
196
|
}
|
|
199
197
|
else {
|
|
198
|
+
if (result.content)
|
|
199
|
+
console.log(result.content);
|
|
200
200
|
console.log(chalk.bold(`\n${CH.success} Fork run complete\n`));
|
|
201
201
|
}
|
|
202
202
|
}
|
|
@@ -208,6 +208,7 @@ export class ForkCommand {
|
|
|
208
208
|
throw commandFailure(err, { code: 'RUN_FORK_FAILED' });
|
|
209
209
|
}
|
|
210
210
|
finally {
|
|
211
|
+
api?.destroy();
|
|
211
212
|
process.removeListener('SIGINT', onInterrupt);
|
|
212
213
|
}
|
|
213
214
|
}
|
package/dist/commands/game.d.ts
CHANGED
|
@@ -8,6 +8,14 @@ export declare function gameProcessInvocation(pm: Manager, args: readonly string
|
|
|
8
8
|
executable: string;
|
|
9
9
|
args: string[];
|
|
10
10
|
};
|
|
11
|
+
export declare function windowsProcessTreeTerminationInvocation(pid: number, systemRoot?: string): {
|
|
12
|
+
executable: string;
|
|
13
|
+
args: string[];
|
|
14
|
+
};
|
|
15
|
+
export declare const runGameProcess: (pm: Manager, args: string[], cwd: string, timeoutMs?: number, outputMode?: "inherit" | "capture") => Promise<{
|
|
16
|
+
stdout: string;
|
|
17
|
+
stderr: string;
|
|
18
|
+
}>;
|
|
11
19
|
export declare class GameCommand {
|
|
12
20
|
private logger;
|
|
13
21
|
constructor(logger: Logger);
|
package/dist/commands/game.js
CHANGED
|
@@ -46,11 +46,114 @@ export function gameProcessInvocation(pm, args, platform = process.platform, com
|
|
|
46
46
|
const command = [executable, ...args].join(' ');
|
|
47
47
|
return { executable: comSpec, args: ['/d', '/s', '/c', command] };
|
|
48
48
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
49
|
+
export function windowsProcessTreeTerminationInvocation(pid, systemRoot = process.env.SystemRoot || 'C:\\Windows') {
|
|
50
|
+
if (!Number.isSafeInteger(pid) || pid <= 0) {
|
|
51
|
+
throw new CliCommandError('Cannot terminate a game process without a valid owned PID.', {
|
|
52
|
+
code: 'GAME_PROCESS_ID_INVALID',
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
executable: path.win32.join(systemRoot, 'System32', 'taskkill.exe'),
|
|
57
|
+
args: ['/PID', String(pid), '/T', '/F'],
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
async function terminateOwnedProcessTree(child, platform) {
|
|
61
|
+
const pid = child.pid;
|
|
62
|
+
if (!pid)
|
|
63
|
+
return;
|
|
64
|
+
if (platform === 'win32') {
|
|
65
|
+
const invocation = windowsProcessTreeTerminationInvocation(pid);
|
|
66
|
+
await new Promise((resolve) => {
|
|
67
|
+
const killer = spawn(invocation.executable, invocation.args, {
|
|
68
|
+
stdio: 'ignore',
|
|
69
|
+
windowsHide: true,
|
|
70
|
+
env: safeChildProcessEnv(),
|
|
71
|
+
});
|
|
72
|
+
const timer = setTimeout(() => { try {
|
|
73
|
+
killer.kill();
|
|
74
|
+
}
|
|
75
|
+
catch { /* exact helper only */ } resolve(); }, 5_000);
|
|
76
|
+
killer.once('error', () => { clearTimeout(timer); try {
|
|
77
|
+
child.kill();
|
|
78
|
+
}
|
|
79
|
+
catch { /* already gone */ } resolve(); });
|
|
80
|
+
killer.once('exit', () => { clearTimeout(timer); resolve(); });
|
|
81
|
+
});
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
try {
|
|
85
|
+
process.kill(-pid, 'SIGTERM');
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
try {
|
|
89
|
+
child.kill('SIGTERM');
|
|
90
|
+
}
|
|
91
|
+
catch { /* already gone */ }
|
|
92
|
+
}
|
|
93
|
+
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
94
|
+
try {
|
|
95
|
+
process.kill(-pid, 0);
|
|
96
|
+
process.kill(-pid, 'SIGKILL');
|
|
97
|
+
}
|
|
98
|
+
catch { /* process group exited */ }
|
|
99
|
+
}
|
|
100
|
+
export const runGameProcess = (pm, args, cwd, timeoutMs = 10 * 60_000, outputMode = 'inherit') => new Promise((resolve, reject) => {
|
|
101
|
+
const invocation = gameProcessInvocation(pm, args);
|
|
102
|
+
const platform = process.platform;
|
|
103
|
+
const child = spawn(invocation.executable, invocation.args, {
|
|
104
|
+
cwd,
|
|
105
|
+
stdio: outputMode === 'capture' ? ['ignore', 'pipe', 'pipe'] : 'inherit',
|
|
106
|
+
windowsHide: true,
|
|
107
|
+
env: safeChildProcessEnv(),
|
|
108
|
+
detached: platform !== 'win32',
|
|
109
|
+
});
|
|
110
|
+
let interrupted = false;
|
|
111
|
+
let timedOut = false;
|
|
112
|
+
let settled = false;
|
|
113
|
+
let stdout = '';
|
|
114
|
+
let stderr = '';
|
|
115
|
+
const appendBounded = (current, chunk) => `${current}${String(chunk)}`.slice(-64 * 1024);
|
|
116
|
+
if (outputMode === 'capture') {
|
|
117
|
+
child.stdout?.setEncoding('utf8');
|
|
118
|
+
child.stderr?.setEncoding('utf8');
|
|
119
|
+
child.stdout?.on('data', (chunk) => { stdout = appendBounded(stdout, chunk); });
|
|
120
|
+
child.stderr?.on('data', (chunk) => { stderr = appendBounded(stderr, chunk); });
|
|
121
|
+
}
|
|
122
|
+
const settle = (error) => {
|
|
123
|
+
if (settled)
|
|
124
|
+
return;
|
|
125
|
+
settled = true;
|
|
126
|
+
if (timer)
|
|
127
|
+
clearTimeout(timer);
|
|
128
|
+
process.removeListener('SIGINT', onInterrupt);
|
|
129
|
+
error ? reject(error) : resolve({ stdout, stderr });
|
|
130
|
+
};
|
|
131
|
+
const onInterrupt = () => {
|
|
132
|
+
interrupted = true;
|
|
133
|
+
void terminateOwnedProcessTree(child, platform);
|
|
134
|
+
};
|
|
135
|
+
process.once('SIGINT', onInterrupt);
|
|
136
|
+
const timer = timeoutMs > 0 ? setTimeout(() => {
|
|
137
|
+
timedOut = true;
|
|
138
|
+
void terminateOwnedProcessTree(child, platform).finally(() => settle(new CliCommandError(`${pm} command timed out after ${timeoutMs}ms`, { code: 'GAME_PROCESS_TIMEOUT' })));
|
|
139
|
+
}, timeoutMs) : null;
|
|
140
|
+
child.once('error', (error) => settle(error));
|
|
141
|
+
child.once('exit', (code, signal) => {
|
|
142
|
+
if (timedOut) {
|
|
143
|
+
settle(new CliCommandError(`${pm} command timed out after ${timeoutMs}ms`, { code: 'GAME_PROCESS_TIMEOUT' }));
|
|
144
|
+
}
|
|
145
|
+
else if (interrupted) {
|
|
146
|
+
settle(new CliCommandError(`${pm} command cancelled by user`, { code: 'GAME_PROCESS_CANCELLED', category: 'cancelled' }));
|
|
147
|
+
}
|
|
148
|
+
else if (code === 0) {
|
|
149
|
+
settle();
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
const detail = outputMode === 'capture' ? `: ${(stderr || stdout).trim().slice(-4_000)}` : '';
|
|
153
|
+
settle(new Error(`${pm} exited with ${signal || code}${detail}`));
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
});
|
|
54
157
|
const html = `<!doctype html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>VGE Game</title></head><body><canvas id="game"></canvas><div id="hud"><strong>Vigthoria Gaming Engine</strong><span>Ready</span></div><script type="module" src="/src/main.ts"></script></body></html>\n`;
|
|
55
158
|
const style = `*{box-sizing:border-box}html,body,#game{width:100%;height:100%;margin:0;overflow:hidden}body{background:#030510;color:#e8f7ff;font-family:Inter,system-ui,sans-serif}#game{display:block;touch-action:none}#hud{position:fixed;left:24px;top:24px;display:flex;gap:16px;padding:12px 16px;border:1px solid #00d4ff66;border-radius:12px;background:#070a18cc;backdrop-filter:blur(12px);box-shadow:0 0 32px #7c3aed33}#hud span{color:#5eead4}\n`;
|
|
56
159
|
const main = `import { ArcRotateCamera, Color3, Color4, DirectionalLight, Engine, HemisphericLight, MeshBuilder, PBRMaterial, Scene, ShadowGenerator, Vector3 } from '@babylonjs/core';
|
|
@@ -80,21 +183,22 @@ export class GameCommand {
|
|
|
80
183
|
if (fs.existsSync(root) && fs.readdirSync(root).length)
|
|
81
184
|
throw new Error(`Target directory is not empty: ${root}`);
|
|
82
185
|
fs.mkdirSync(path.join(root, 'src'), { recursive: true });
|
|
83
|
-
const pkg = { name: slug, version: '0.1.0', private: true, type: 'module', vigthoria: { kind: 'game', engine: 'babylon', renderTransport: 'client-state', schemaVersion: 1 }, scripts: { dev: 'vite', build: 'tsc --noEmit && vite build', preview: 'vite preview' }, dependencies: { '@babylonjs/core': '
|
|
186
|
+
const pkg = { name: slug, version: '0.1.0', private: true, type: 'module', engines: { node: '>=20.19.0' }, vigthoria: { kind: 'game', engine: 'babylon', renderTransport: 'client-state', schemaVersion: 1 }, scripts: { dev: 'node ./node_modules/vite/bin/vite.js', build: 'node ./node_modules/typescript/bin/tsc --noEmit && node ./node_modules/vite/bin/vite.js build', preview: 'node ./node_modules/vite/bin/vite.js preview' }, dependencies: { '@babylonjs/core': '9.20.0', '@babylonjs/loaders': '9.20.0' }, devDependencies: { typescript: '5.9.3', vite: '7.3.6' } };
|
|
84
187
|
fs.writeFileSync(path.join(root, 'package.json'), JSON.stringify(pkg, null, 2) + '\n');
|
|
85
188
|
fs.writeFileSync(path.join(root, 'index.html'), html);
|
|
86
189
|
fs.writeFileSync(path.join(root, 'src/main.ts'), main);
|
|
87
190
|
fs.writeFileSync(path.join(root, 'src/style.css'), style);
|
|
191
|
+
fs.writeFileSync(path.join(root, 'src/vite-env.d.ts'), '/// <reference types="vite/client" />\n');
|
|
88
192
|
fs.writeFileSync(path.join(root, '.gitignore'), 'node_modules\ndist\n.vigthoria\n');
|
|
89
193
|
fs.writeFileSync(path.join(root, 'tsconfig.json'), JSON.stringify({ compilerOptions: { target: 'ES2022', module: 'ESNext', lib: ['ES2022', 'DOM'], skipLibCheck: true, moduleResolution: 'Bundler', isolatedModules: true, noEmit: true, strict: true }, include: ['src'] }, null, 2) + '\n');
|
|
90
194
|
if (o.install !== false)
|
|
91
|
-
await
|
|
195
|
+
await runGameProcess(manager(root, o.packageManager), ['install'], root);
|
|
92
196
|
this.logger.success(`Babylon/VGE game created: ${root}`);
|
|
93
197
|
this.logger.info(`Next: cd ${slug} && vigthoria game run`);
|
|
94
198
|
}
|
|
95
199
|
async run(o) { const root = path.resolve(o.project || process.cwd()); this.project(root); const args = ['run', 'dev', '--', '--host', '127.0.0.1']; if (o.port)
|
|
96
200
|
args.push('--port', String(o.port)); if (o.open !== false)
|
|
97
|
-
args.push('--open'); await
|
|
201
|
+
args.push('--open'); await runGameProcess(manager(root, o.packageManager), args, root, 0); }
|
|
98
202
|
async validate(o) {
|
|
99
203
|
const root = path.resolve(o.project || process.cwd());
|
|
100
204
|
const checks = [];
|
|
@@ -110,7 +214,7 @@ export class GameCommand {
|
|
|
110
214
|
const source = this.sources(root);
|
|
111
215
|
checks.push({ check: 'Babylon client-state metadata', passed: pkg.vigthoria?.engine === 'babylon' && pkg.vigthoria?.renderTransport === 'client-state' }, { check: 'Pinned local Babylon dependencies', passed: /^\d/.test(pkg.dependencies?.['@babylonjs/core'] || '') && /^\d/.test(pkg.dependencies?.['@babylonjs/loaders'] || '') }, { check: 'No CDN engine imports', passed: !/https?:\/\/(cdn|unpkg|jsdelivr)[^\s"']*(babylon|three)/i.test(source) }, { check: 'Runtime diagnostics', passed: source.includes('__vgeDiagnostics') }, { check: 'Client source entry', passed: fs.existsSync(path.join(root, 'src/main.ts')) || fs.existsSync(path.join(root, 'src/main.js')) });
|
|
112
216
|
try {
|
|
113
|
-
await
|
|
217
|
+
await runGameProcess(manager(root, o.packageManager), ['run', 'build'], root, 10 * 60_000, o.json ? 'capture' : 'inherit');
|
|
114
218
|
checks.push({ check: 'Production build', passed: true });
|
|
115
219
|
}
|
|
116
220
|
catch (e) {
|
package/dist/commands/history.js
CHANGED
|
@@ -3,10 +3,10 @@ import { hasLocalV3AgentCapability } from '../utils/runtime-capability.js';
|
|
|
3
3
|
* history.ts — List recent V3 agent runs with summaries.
|
|
4
4
|
*/
|
|
5
5
|
import chalk from 'chalk';
|
|
6
|
-
import { createRequire } from 'node:module';
|
|
7
6
|
import { createSpinner, CH } from '../utils/logger.js';
|
|
8
7
|
import { CliCommandError, commandFailure, formatSuccessJson } from '../utils/command-contract.js';
|
|
9
|
-
|
|
8
|
+
import { buildLocalWorkspaceReference } from '../utils/workspace-reference.js';
|
|
9
|
+
import { guardedFetch } from '../utils/network-policy.js';
|
|
10
10
|
export class HistoryCommand {
|
|
11
11
|
config;
|
|
12
12
|
logger;
|
|
@@ -36,36 +36,22 @@ export class HistoryCommand {
|
|
|
36
36
|
(allowLocal ? 'http://127.0.0.1:8030' : null) ||
|
|
37
37
|
configuredApiUrl);
|
|
38
38
|
}
|
|
39
|
-
resolveWorkspaceRoot(project) {
|
|
40
|
-
// On Windows or non-absolute paths, send empty so the server uses its fallback root
|
|
41
|
-
if (/^[a-zA-Z]:[\\/]/.test(project) || /^\\\\/.test(project))
|
|
42
|
-
return '';
|
|
43
|
-
if (typeof require !== 'undefined') {
|
|
44
|
-
try {
|
|
45
|
-
const path = require('path');
|
|
46
|
-
if (!path.isAbsolute(project))
|
|
47
|
-
return '';
|
|
48
|
-
}
|
|
49
|
-
catch { }
|
|
50
|
-
}
|
|
51
|
-
return project;
|
|
52
|
-
}
|
|
53
39
|
async run(options) {
|
|
54
40
|
const limit = options.limit || 20;
|
|
55
41
|
const project = options.project || process.cwd();
|
|
56
|
-
const
|
|
42
|
+
const workspaceRef = buildLocalWorkspaceReference(project, String(this.config.get('userId') || this.config.get('email') || ''));
|
|
57
43
|
const spinner = createSpinner('Loading run history...').start();
|
|
58
44
|
try {
|
|
59
45
|
const baseUrl = this.getBaseUrl();
|
|
60
46
|
const params = new URLSearchParams({
|
|
61
47
|
limit: String(limit),
|
|
62
|
-
workspace_root:
|
|
63
|
-
local_workspace_path:
|
|
64
|
-
project_path:
|
|
48
|
+
workspace_root: '',
|
|
49
|
+
local_workspace_path: workspaceRef,
|
|
50
|
+
project_path: workspaceRef,
|
|
65
51
|
});
|
|
66
|
-
const resp = await
|
|
52
|
+
const resp = await guardedFetch(`${baseUrl}/api/runs?${params}`, {
|
|
67
53
|
headers: this.getHeaders(),
|
|
68
|
-
});
|
|
54
|
+
}, { audience: 'v3' });
|
|
69
55
|
if (!resp.ok) {
|
|
70
56
|
spinner.stop();
|
|
71
57
|
if (resp.status === 404 || resp.status === 502 || resp.status === 503) {
|
package/dist/commands/hub.d.ts
CHANGED
|
@@ -6,6 +6,25 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { Config } from '../utils/config.js';
|
|
8
8
|
import { Logger } from '../utils/logger.js';
|
|
9
|
+
interface Module {
|
|
10
|
+
id: string;
|
|
11
|
+
name: string;
|
|
12
|
+
description: string;
|
|
13
|
+
endpoint: string;
|
|
14
|
+
documentation: string;
|
|
15
|
+
pricing: {
|
|
16
|
+
unit: string;
|
|
17
|
+
cost: number;
|
|
18
|
+
example: string;
|
|
19
|
+
};
|
|
20
|
+
status: string;
|
|
21
|
+
category: string;
|
|
22
|
+
tags: string[];
|
|
23
|
+
unit?: string;
|
|
24
|
+
creditsPerUnit?: number | string;
|
|
25
|
+
euroPerUnit?: number | string;
|
|
26
|
+
}
|
|
27
|
+
export declare function moduleMatchesIdentifier(module: Pick<Module, 'id' | 'name'>, requested: string): boolean;
|
|
9
28
|
export declare class HubCommand {
|
|
10
29
|
private config;
|
|
11
30
|
constructor(config: Config, _logger: Logger);
|
|
@@ -39,3 +58,4 @@ export declare class HubCommand {
|
|
|
39
58
|
*/
|
|
40
59
|
discover(): Promise<void>;
|
|
41
60
|
}
|
|
61
|
+
export {};
|
package/dist/commands/hub.js
CHANGED
|
@@ -8,6 +8,19 @@ import chalk from 'chalk';
|
|
|
8
8
|
import { guardedFetch } from '../utils/network-policy.js';
|
|
9
9
|
import { CliCommandError, commandFailure } from '../utils/command-contract.js';
|
|
10
10
|
const API_BASE = 'https://hub.vigthoria.io';
|
|
11
|
+
function normalizeModuleIdentifier(value) {
|
|
12
|
+
return String(value || '').trim().toLowerCase().replace(/[\s_]+/g, '-');
|
|
13
|
+
}
|
|
14
|
+
export function moduleMatchesIdentifier(module, requested) {
|
|
15
|
+
const needle = normalizeModuleIdentifier(requested);
|
|
16
|
+
if (!needle)
|
|
17
|
+
return false;
|
|
18
|
+
const id = normalizeModuleIdentifier(module.id);
|
|
19
|
+
const name = normalizeModuleIdentifier(module.name);
|
|
20
|
+
return needle === id
|
|
21
|
+
|| needle === name
|
|
22
|
+
|| needle === id.replace(/-api$/, '');
|
|
23
|
+
}
|
|
11
24
|
export class HubCommand {
|
|
12
25
|
config;
|
|
13
26
|
constructor(config, _logger) {
|
|
@@ -76,6 +89,7 @@ export class HubCommand {
|
|
|
76
89
|
data.results.forEach((module, index) => {
|
|
77
90
|
const statusColor = module.status === 'available' ? chalk.green : chalk.gray;
|
|
78
91
|
console.log(chalk.bold.white(` ${index + 1}. ${module.name}`));
|
|
92
|
+
console.log(chalk.gray(` Module ID: ${module.id}`));
|
|
79
93
|
console.log(chalk.gray(` ${module.description.substring(0, 80)}...`));
|
|
80
94
|
console.log(chalk.cyan(` 💰 ${module.pricing.example}`));
|
|
81
95
|
console.log(statusColor(` 📊 Status: ${module.status.toUpperCase()}`));
|
|
@@ -86,7 +100,7 @@ export class HubCommand {
|
|
|
86
100
|
console.log(chalk.cyan(`💡 ${data.suggestion}`));
|
|
87
101
|
}
|
|
88
102
|
console.log(chalk.gray('\nTo activate a module: vigthoria hub activate <module-name>'));
|
|
89
|
-
console.log(chalk.gray(
|
|
103
|
+
console.log(chalk.gray(`Example: vigthoria hub activate ${data.results[0].id}\n`));
|
|
90
104
|
}
|
|
91
105
|
catch (error) {
|
|
92
106
|
throw commandFailure(error, { code: 'HUB_SEARCH_FAILED' });
|
|
@@ -144,7 +158,7 @@ export class HubCommand {
|
|
|
144
158
|
this.getAuthToken();
|
|
145
159
|
console.log(chalk.cyan(`\n🔌 Activating module: ${moduleId}...\n`));
|
|
146
160
|
try {
|
|
147
|
-
const module = (await this.moduleCatalog()).find((entry) => entry
|
|
161
|
+
const module = (await this.moduleCatalog()).find((entry) => moduleMatchesIdentifier(entry, moduleId));
|
|
148
162
|
if (!module)
|
|
149
163
|
throw new CliCommandError(`Unknown Hub module: ${moduleId}`, { code: 'HUB_MODULE_NOT_FOUND', category: 'usage' });
|
|
150
164
|
console.log(chalk.green(`✅ ${module.name} is published in the Vigthoria module catalog.`));
|
|
@@ -186,7 +200,7 @@ export class HubCommand {
|
|
|
186
200
|
async info(moduleId) {
|
|
187
201
|
console.log(chalk.cyan(`\n📖 Module Info: ${moduleId}\n`));
|
|
188
202
|
try {
|
|
189
|
-
const module = (await this.moduleCatalog()).find((entry) => entry
|
|
203
|
+
const module = (await this.moduleCatalog()).find((entry) => moduleMatchesIdentifier(entry, moduleId));
|
|
190
204
|
if (!module)
|
|
191
205
|
throw new CliCommandError(`Unknown Hub module: ${moduleId}`, { code: 'HUB_MODULE_NOT_FOUND', category: 'usage' });
|
|
192
206
|
console.log(chalk.bold.white(`${module.name}`));
|