vigthoria-cli 1.13.25 → 1.13.29
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/dist/commands/chat.js +83 -27
- package/dist/commands/config.js +4 -4
- 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 +118 -10
- 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.d.ts +1 -0
- package/dist/commands/preview.js +36 -15
- 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.d.ts +35 -2
- package/dist/commands/repo.js +76 -34
- package/dist/commands/update-registration.js +4 -3
- package/dist/commands/workflow.d.ts +4 -0
- package/dist/commands/workflow.js +27 -0
- package/dist/index.js +6 -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 +440 -54
- package/dist/utils/command-policy.js +1 -1
- package/dist/utils/config.d.ts +2 -0
- package/dist/utils/config.js +8 -3
- 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 +16 -4
- 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 +19 -3
- package/dist/utils/preview-screenshot-adapter.js +289 -28
- package/dist/utils/runtime-capability.d.ts +7 -0
- package/dist/utils/runtime-capability.js +11 -0
- package/dist/utils/runtime-temp.d.ts +6 -2
- package/dist/utils/runtime-temp.js +143 -27
- package/dist/utils/tools.js +1 -1
- package/dist/utils/v3-stream-events.d.ts +8 -0
- package/dist/utils/v3-stream-events.js +70 -0
- 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/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/game.js
CHANGED
|
@@ -39,14 +39,121 @@ export function gameProcessInvocation(pm, args, platform = process.platform, com
|
|
|
39
39
|
category: 'usage',
|
|
40
40
|
});
|
|
41
41
|
}
|
|
42
|
-
|
|
42
|
+
// Every token is already restricted to an inert alphabet above. Passing the
|
|
43
|
+
// tokens without embedded quotes is important: on Windows, cmd.exe can hand
|
|
44
|
+
// the quote characters to the .cmd shim as part of argv[0], producing the
|
|
45
|
+
// observed literal `"npm.cmd"` launch failure.
|
|
46
|
+
const command = [executable, ...args].join(' ');
|
|
43
47
|
return { executable: comSpec, args: ['/d', '/s', '/c', command] };
|
|
44
48
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
+
});
|
|
50
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`;
|
|
51
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`;
|
|
52
159
|
const main = `import { ArcRotateCamera, Color3, Color4, DirectionalLight, Engine, HemisphericLight, MeshBuilder, PBRMaterial, Scene, ShadowGenerator, Vector3 } from '@babylonjs/core';
|
|
@@ -76,21 +183,22 @@ export class GameCommand {
|
|
|
76
183
|
if (fs.existsSync(root) && fs.readdirSync(root).length)
|
|
77
184
|
throw new Error(`Target directory is not empty: ${root}`);
|
|
78
185
|
fs.mkdirSync(path.join(root, 'src'), { recursive: true });
|
|
79
|
-
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' } };
|
|
80
187
|
fs.writeFileSync(path.join(root, 'package.json'), JSON.stringify(pkg, null, 2) + '\n');
|
|
81
188
|
fs.writeFileSync(path.join(root, 'index.html'), html);
|
|
82
189
|
fs.writeFileSync(path.join(root, 'src/main.ts'), main);
|
|
83
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');
|
|
84
192
|
fs.writeFileSync(path.join(root, '.gitignore'), 'node_modules\ndist\n.vigthoria\n');
|
|
85
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');
|
|
86
194
|
if (o.install !== false)
|
|
87
|
-
await
|
|
195
|
+
await runGameProcess(manager(root, o.packageManager), ['install'], root);
|
|
88
196
|
this.logger.success(`Babylon/VGE game created: ${root}`);
|
|
89
197
|
this.logger.info(`Next: cd ${slug} && vigthoria game run`);
|
|
90
198
|
}
|
|
91
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)
|
|
92
200
|
args.push('--port', String(o.port)); if (o.open !== false)
|
|
93
|
-
args.push('--open'); await
|
|
201
|
+
args.push('--open'); await runGameProcess(manager(root, o.packageManager), args, root, 0); }
|
|
94
202
|
async validate(o) {
|
|
95
203
|
const root = path.resolve(o.project || process.cwd());
|
|
96
204
|
const checks = [];
|
|
@@ -106,7 +214,7 @@ export class GameCommand {
|
|
|
106
214
|
const source = this.sources(root);
|
|
107
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')) });
|
|
108
216
|
try {
|
|
109
|
-
await
|
|
217
|
+
await runGameProcess(manager(root, o.packageManager), ['run', 'build'], root, 10 * 60_000, o.json ? 'capture' : 'inherit');
|
|
110
218
|
checks.push({ check: 'Production build', passed: true });
|
|
111
219
|
}
|
|
112
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}`));
|
|
@@ -20,6 +20,7 @@ interface PreviewOptions {
|
|
|
20
20
|
proof?: boolean;
|
|
21
21
|
screenshot?: boolean;
|
|
22
22
|
}
|
|
23
|
+
export declare function isPreviewProofRequested(options: Pick<PreviewOptions, 'proof' | 'screenshot'>): boolean;
|
|
23
24
|
export declare class PreviewCommand {
|
|
24
25
|
private logger;
|
|
25
26
|
private api;
|
package/dist/commands/preview.js
CHANGED
|
@@ -51,6 +51,9 @@ const MIME_TYPES = {
|
|
|
51
51
|
'.txt': 'text/plain',
|
|
52
52
|
'.map': 'application/json',
|
|
53
53
|
};
|
|
54
|
+
export function isPreviewProofRequested(options) {
|
|
55
|
+
return options.proof === true || options.screenshot === true;
|
|
56
|
+
}
|
|
54
57
|
export class PreviewCommand {
|
|
55
58
|
logger;
|
|
56
59
|
api;
|
|
@@ -76,11 +79,17 @@ export class PreviewCommand {
|
|
|
76
79
|
await this.showConsolidatedDiff(projectPath);
|
|
77
80
|
}
|
|
78
81
|
// Run Template Service preview proof gate
|
|
79
|
-
if (options
|
|
80
|
-
|
|
82
|
+
if (isPreviewProofRequested(options)) {
|
|
83
|
+
try {
|
|
84
|
+
await this.runProofGate(projectPath, options.screenshot, options.screenshot === true && options.proof !== true);
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
this.api.destroy();
|
|
88
|
+
throw error;
|
|
89
|
+
}
|
|
81
90
|
}
|
|
82
91
|
// If only --diff or --proof was requested (no explicit entry/port), just exit
|
|
83
|
-
if (options.diff || options
|
|
92
|
+
if (options.diff || isPreviewProofRequested(options)) {
|
|
84
93
|
if (!options.entry && !options.port) {
|
|
85
94
|
this.api.destroy();
|
|
86
95
|
return;
|
|
@@ -90,7 +99,7 @@ export class PreviewCommand {
|
|
|
90
99
|
const entryFile = this.detectEntryFile(projectPath, options.entry);
|
|
91
100
|
if (!entryFile) {
|
|
92
101
|
this.logger.warn('No HTML entry file found. Use --entry <file> to specify one.');
|
|
93
|
-
if (!options.diff && !options.proof) {
|
|
102
|
+
if (!options.diff && !options.proof && !options.screenshot) {
|
|
94
103
|
this.api.destroy();
|
|
95
104
|
throw new CliCommandError('No HTML entry file found. Use --entry <file> to specify one.', {
|
|
96
105
|
code: 'PREVIEW_ENTRY_NOT_FOUND', category: 'configuration',
|
|
@@ -162,7 +171,7 @@ export class PreviewCommand {
|
|
|
162
171
|
* Start a local HTTP server for preview
|
|
163
172
|
*/
|
|
164
173
|
async startServer(projectPath, entryFile, port, autoOpen) {
|
|
165
|
-
return new Promise((resolve) => {
|
|
174
|
+
return new Promise((resolve, reject) => {
|
|
166
175
|
this.server = http.createServer((req, res) => {
|
|
167
176
|
let filePath;
|
|
168
177
|
try {
|
|
@@ -210,6 +219,20 @@ export class PreviewCommand {
|
|
|
210
219
|
res.end('Internal server error');
|
|
211
220
|
}
|
|
212
221
|
});
|
|
222
|
+
const finish = (error) => {
|
|
223
|
+
process.removeListener('SIGINT', shutdown);
|
|
224
|
+
process.removeListener('SIGTERM', shutdown);
|
|
225
|
+
this.api.destroy();
|
|
226
|
+
this.server = null;
|
|
227
|
+
error ? reject(error) : resolve();
|
|
228
|
+
};
|
|
229
|
+
const shutdown = () => {
|
|
230
|
+
console.log(chalk.gray('\n Stopping preview server...'));
|
|
231
|
+
if (!this.server)
|
|
232
|
+
return finish();
|
|
233
|
+
this.server.close((error) => finish(error || undefined));
|
|
234
|
+
};
|
|
235
|
+
this.server.once('error', finish);
|
|
213
236
|
this.server.listen(port, () => {
|
|
214
237
|
const url = `http://localhost:${port}/${entryFile}`;
|
|
215
238
|
console.log(chalk.green(` ${CH.success} Preview server running`));
|
|
@@ -221,15 +244,8 @@ export class PreviewCommand {
|
|
|
221
244
|
this.openBrowser(url);
|
|
222
245
|
}
|
|
223
246
|
});
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
console.log(chalk.gray('\n Stopping preview server...'));
|
|
227
|
-
this.server?.close();
|
|
228
|
-
this.api.destroy();
|
|
229
|
-
resolve();
|
|
230
|
-
};
|
|
231
|
-
process.on('SIGINT', shutdown);
|
|
232
|
-
process.on('SIGTERM', shutdown);
|
|
247
|
+
process.once('SIGINT', shutdown);
|
|
248
|
+
process.once('SIGTERM', shutdown);
|
|
233
249
|
});
|
|
234
250
|
}
|
|
235
251
|
/**
|
|
@@ -419,7 +435,7 @@ export class PreviewCommand {
|
|
|
419
435
|
/**
|
|
420
436
|
* Run Template Service preview gate and persist proof bundle
|
|
421
437
|
*/
|
|
422
|
-
async runProofGate(projectPath, captureScreenshot) {
|
|
438
|
+
async runProofGate(projectPath, captureScreenshot, localScreenshotProof = false) {
|
|
423
439
|
const spinner = createSpinner('Running preview proof gate...').start();
|
|
424
440
|
try {
|
|
425
441
|
const result = await this.api.runTemplateServicePreviewGate('', {
|
|
@@ -428,6 +444,11 @@ export class PreviewCommand {
|
|
|
428
444
|
targetPath: projectPath,
|
|
429
445
|
forceFrontendPreview: true,
|
|
430
446
|
requireScreenshot: captureScreenshot === true,
|
|
447
|
+
// An explicit screenshot-only command is a local operation. Do not
|
|
448
|
+
// upload the user's frontend or require an ecosystem login merely to
|
|
449
|
+
// capture local visual evidence. `--proof` retains the hosted proof
|
|
450
|
+
// contract, including when combined with `--screenshot`.
|
|
451
|
+
localScreenshotProof,
|
|
431
452
|
});
|
|
432
453
|
spinner.stop();
|
|
433
454
|
console.log(chalk.bold.white(` ${CH.hLine.repeat(3)} Preview Proof Gate ${CH.hLine.repeat(39)}`));
|
|
@@ -386,7 +386,7 @@ Examples:
|
|
|
386
386
|
.option('--no-open', 'Do not auto-open browser')
|
|
387
387
|
.option('--diff', 'Show consolidated diff of recent agent changes')
|
|
388
388
|
.option('--proof', 'Run Template Service preview gate and persist proof bundle')
|
|
389
|
-
.option('--screenshot', 'Capture screenshot via
|
|
389
|
+
.option('--screenshot', 'Capture screenshot via an installed system browser')
|
|
390
390
|
.action(async (options) => {
|
|
391
391
|
const preview = new PreviewCommand(config, logger);
|
|
392
392
|
await preview.run({
|
package/dist/commands/replay.js
CHANGED
|
@@ -3,10 +3,10 @@ import { hasLocalV3AgentCapability } from '../utils/runtime-capability.js';
|
|
|
3
3
|
* replay.ts — Replay events from a V3 agent run step-by-step.
|
|
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 ReplayCommand {
|
|
11
11
|
config;
|
|
12
12
|
constructor(config, _logger) {
|
|
@@ -34,33 +34,24 @@ export class ReplayCommand {
|
|
|
34
34
|
(allowLocal ? 'http://127.0.0.1:8030' : null) ||
|
|
35
35
|
configuredApiUrl);
|
|
36
36
|
}
|
|
37
|
-
resolveWorkspaceRoot(project) {
|
|
38
|
-
if (/^[a-zA-Z]:[\\/]/.test(project) || /^\\\\/.test(project))
|
|
39
|
-
return '';
|
|
40
|
-
if (typeof require !== 'undefined') {
|
|
41
|
-
try {
|
|
42
|
-
const path = require('path');
|
|
43
|
-
if (!path.isAbsolute(project))
|
|
44
|
-
return '';
|
|
45
|
-
}
|
|
46
|
-
catch { }
|
|
47
|
-
}
|
|
48
|
-
return project;
|
|
49
|
-
}
|
|
50
37
|
sleep(ms) {
|
|
51
38
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
52
39
|
}
|
|
53
40
|
async run(runId, options) {
|
|
54
41
|
const speed = options.speed || 200;
|
|
55
42
|
const project = options.project || process.cwd();
|
|
56
|
-
const
|
|
43
|
+
const workspaceRef = buildLocalWorkspaceReference(project, String(this.config.get('userId') || this.config.get('email') || ''));
|
|
57
44
|
const spinner = createSpinner(`Loading events for run ${runId}...`).start();
|
|
58
45
|
try {
|
|
59
46
|
const baseUrl = this.getBaseUrl();
|
|
60
|
-
const params = new URLSearchParams({
|
|
61
|
-
|
|
62
|
-
|
|
47
|
+
const params = new URLSearchParams({
|
|
48
|
+
workspace_root: '',
|
|
49
|
+
local_workspace_path: workspaceRef,
|
|
50
|
+
project_path: workspaceRef,
|
|
63
51
|
});
|
|
52
|
+
const resp = await guardedFetch(`${baseUrl}/api/runs/${encodeURIComponent(runId)}/events?${params}`, {
|
|
53
|
+
headers: this.getHeaders(),
|
|
54
|
+
}, { audience: 'v3' });
|
|
64
55
|
if (!resp.ok) {
|
|
65
56
|
spinner.stop();
|
|
66
57
|
if (resp.status === 404) {
|
package/dist/commands/repo.d.ts
CHANGED
|
@@ -14,6 +14,22 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import { Config } from '../utils/config.js';
|
|
16
16
|
import { Logger } from '../utils/logger.js';
|
|
17
|
+
interface Project {
|
|
18
|
+
id: number;
|
|
19
|
+
project_name: string;
|
|
20
|
+
project_path: string;
|
|
21
|
+
description: string;
|
|
22
|
+
tech_stack: string;
|
|
23
|
+
visibility: 'public' | 'private' | 'restricted';
|
|
24
|
+
is_public: boolean;
|
|
25
|
+
demo_url: string | null;
|
|
26
|
+
repo_url: string | null;
|
|
27
|
+
status: string;
|
|
28
|
+
storage_usage_mb: number;
|
|
29
|
+
created_at: string;
|
|
30
|
+
updated_at: string;
|
|
31
|
+
last_synced_at: string | null;
|
|
32
|
+
}
|
|
17
33
|
interface PushOptions {
|
|
18
34
|
path?: string;
|
|
19
35
|
name?: string;
|
|
@@ -34,6 +50,25 @@ interface ListOptions {
|
|
|
34
50
|
interface ShareOptions {
|
|
35
51
|
expires?: string;
|
|
36
52
|
}
|
|
53
|
+
interface RepositoryMaterialization {
|
|
54
|
+
project?: Project;
|
|
55
|
+
projectName?: string;
|
|
56
|
+
description?: string;
|
|
57
|
+
downloadUrl?: string;
|
|
58
|
+
inlineComplete?: boolean;
|
|
59
|
+
files?: Array<{
|
|
60
|
+
path: string;
|
|
61
|
+
content?: string;
|
|
62
|
+
encoding?: 'utf8' | 'base64';
|
|
63
|
+
size?: number;
|
|
64
|
+
binary?: boolean;
|
|
65
|
+
}>;
|
|
66
|
+
}
|
|
67
|
+
export declare function isCompleteRepositoryInlinePayload(data: RepositoryMaterialization): boolean;
|
|
68
|
+
export declare function resolveRepositoryDownloadRoute(rawUrl: string, coderApiBase: string, publicDownload?: boolean): {
|
|
69
|
+
url: string;
|
|
70
|
+
audience: 'coder' | 'community';
|
|
71
|
+
};
|
|
37
72
|
export declare class RepositoryMutationAmbiguousError extends Error {
|
|
38
73
|
readonly operationId: string;
|
|
39
74
|
readonly mutationOutcome = "unknown";
|
|
@@ -48,8 +83,6 @@ export declare class RepoCommand {
|
|
|
48
83
|
private communityToken;
|
|
49
84
|
constructor(config: Config, _logger: Logger);
|
|
50
85
|
private getAuthHeaders;
|
|
51
|
-
private getRepositoryAudienceForUrl;
|
|
52
|
-
private getAuthHeadersForUrl;
|
|
53
86
|
private ensureCommunityAuth;
|
|
54
87
|
private repoFetch;
|
|
55
88
|
private collectProjectFiles;
|