vigthoria-cli 1.11.19 → 1.11.25
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.d.ts +1 -0
- package/dist/commands/chat.js +10 -3
- package/dist/commands/index.js +6 -0
- package/dist/commands/music.d.ts +31 -0
- package/dist/commands/music.js +137 -0
- package/dist/index.js +33 -2
- package/dist/utils/agentRunOutcome.d.ts +1 -0
- package/dist/utils/agentRunOutcome.js +23 -0
- package/dist/utils/api.d.ts +1 -0
- package/dist/utils/api.js +62 -20
- package/dist/utils/config.d.ts +1 -0
- package/dist/utils/config.js +2 -0
- package/dist/utils/requestIntent.js +3 -3
- package/dist/utils/templateInstantPath.js +89 -11
- package/dist/utils/tools.js +1 -0
- package/dist/utils/v3-workspace-path.js +14 -0
- package/install.ps1 +17 -9
- package/install.sh +45 -2
- package/package.json +1 -1
- package/scripts/release/publish-cli-release.sh +1 -1
package/dist/commands/chat.d.ts
CHANGED
package/dist/commands/chat.js
CHANGED
|
@@ -77,6 +77,7 @@ export class ChatCommand {
|
|
|
77
77
|
v3StreamedAnswerDisplayed = false;
|
|
78
78
|
v3SeenToolCalls = new Set();
|
|
79
79
|
v3SeenToolResults = new Set();
|
|
80
|
+
v3StartSeen = false;
|
|
80
81
|
lastAgentRoute = null;
|
|
81
82
|
// Last completed Agent run — used by /retry, /continue, and the final summary block.
|
|
82
83
|
lastAgentRunOutcome = null;
|
|
@@ -299,7 +300,7 @@ export class ChatCommand {
|
|
|
299
300
|
resolveAgentExecutionPolicy(prompt) {
|
|
300
301
|
const explicitModel = this.modelExplicitlySelected;
|
|
301
302
|
const heavyTask = this.config.isComplexTask(prompt);
|
|
302
|
-
const cloudEligible = this.config.hasCloudAccess()
|
|
303
|
+
const cloudEligible = this.config.hasCloudAccess();
|
|
303
304
|
const requiresV3Workflow = this.shouldRequireV3AgentWorkflow(prompt);
|
|
304
305
|
if (explicitModel) {
|
|
305
306
|
return {
|
|
@@ -1282,8 +1283,11 @@ export class ChatCommand {
|
|
|
1282
1283
|
return;
|
|
1283
1284
|
}
|
|
1284
1285
|
if (event.type === 'start') {
|
|
1285
|
-
this.
|
|
1286
|
-
|
|
1286
|
+
if (this.v3StartSeen) {
|
|
1287
|
+
spinner.text = 'Working...';
|
|
1288
|
+
return;
|
|
1289
|
+
}
|
|
1290
|
+
this.v3StartSeen = true;
|
|
1287
1291
|
if (spinner.isSpinning)
|
|
1288
1292
|
spinner.stop();
|
|
1289
1293
|
process.stderr.write(chalk.cyan(' [Start] ') + 'Agent initialized\n');
|
|
@@ -2793,6 +2797,7 @@ export class ChatCommand {
|
|
|
2793
2797
|
console.log(chalk.gray('━━━ ROUTING DECISION ━━━'));
|
|
2794
2798
|
console.log(chalk.gray(`Reason: ${routingPolicy.routeReason}`));
|
|
2795
2799
|
console.log(chalk.gray(`Model: ${routingPolicy.selectedModel}`));
|
|
2800
|
+
console.log(chalk.gray('Remote Engine Session: true'));
|
|
2796
2801
|
console.log(chalk.gray(`Cloud Eligible: ${routingPolicy.cloudEligible}`));
|
|
2797
2802
|
console.log(chalk.gray(`Cloud Selected: ${routingPolicy.cloudSelected}`));
|
|
2798
2803
|
if (routingPolicy.heavyTask) {
|
|
@@ -2808,6 +2813,7 @@ export class ChatCommand {
|
|
|
2808
2813
|
this.v3StreamingStarted = false;
|
|
2809
2814
|
this.v3StreamedAnswerBuffer = '';
|
|
2810
2815
|
this.v3StreamedAnswerDisplayed = false;
|
|
2816
|
+
this.v3StartSeen = false;
|
|
2811
2817
|
this.v3SeenToolCalls.clear();
|
|
2812
2818
|
this.v3SeenToolResults.clear();
|
|
2813
2819
|
emitDeckEvent({ type: 'agent_start' });
|
|
@@ -3363,6 +3369,7 @@ export class ChatCommand {
|
|
|
3363
3369
|
this.v3ToolCallCount = 0;
|
|
3364
3370
|
this.v3LastActivity = Date.now();
|
|
3365
3371
|
this.v3StreamingStarted = false;
|
|
3372
|
+
this.v3StartSeen = false;
|
|
3366
3373
|
try {
|
|
3367
3374
|
const retryTaskType = this.inferAgentTaskType(rawPrompt);
|
|
3368
3375
|
const retryResponse = await this.api.runV3AgentWorkflow(executionPrompt, {
|
package/dist/commands/index.js
CHANGED
|
@@ -11,6 +11,7 @@ import { GenerateCommand } from './generate.js';
|
|
|
11
11
|
import { HistoryCommand } from './history.js';
|
|
12
12
|
import { HubCommand } from './hub.js';
|
|
13
13
|
import { LegionCommand } from './legion.js';
|
|
14
|
+
import { MusicCommand } from './music.js';
|
|
14
15
|
import { PreviewCommand } from './preview.js';
|
|
15
16
|
import { ReplayCommand } from './replay.js';
|
|
16
17
|
import { RepoCommand } from './repo.js';
|
|
@@ -96,6 +97,11 @@ const commandRegistry = [
|
|
|
96
97
|
name: 'hub',
|
|
97
98
|
handler: createClassRegistrar(HubCommand),
|
|
98
99
|
},
|
|
100
|
+
{
|
|
101
|
+
name: 'music',
|
|
102
|
+
aliases: ['audio'],
|
|
103
|
+
handler: createClassRegistrar(MusicCommand),
|
|
104
|
+
},
|
|
99
105
|
{
|
|
100
106
|
name: 'repo',
|
|
101
107
|
handler: createClassRegistrar(RepoCommand),
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Config } from '../utils/config.js';
|
|
2
|
+
import { Logger } from '../utils/logger.js';
|
|
3
|
+
type MusicGenerateOptions = {
|
|
4
|
+
prompt?: string;
|
|
5
|
+
lyrics?: string;
|
|
6
|
+
duration?: string | number;
|
|
7
|
+
genre?: string;
|
|
8
|
+
language?: string;
|
|
9
|
+
voiceType?: string;
|
|
10
|
+
styleTags?: string;
|
|
11
|
+
voice?: string;
|
|
12
|
+
wait?: boolean;
|
|
13
|
+
pollInterval?: string | number;
|
|
14
|
+
json?: boolean;
|
|
15
|
+
};
|
|
16
|
+
type MusicStatusOptions = {
|
|
17
|
+
json?: boolean;
|
|
18
|
+
};
|
|
19
|
+
export declare class MusicCommand {
|
|
20
|
+
private config;
|
|
21
|
+
private logger;
|
|
22
|
+
constructor(config: Config, logger: Logger);
|
|
23
|
+
private apiBase;
|
|
24
|
+
private authHeaders;
|
|
25
|
+
private request;
|
|
26
|
+
private uploadVoiceReference;
|
|
27
|
+
generate(promptArg: string | undefined, options: MusicGenerateOptions): Promise<void>;
|
|
28
|
+
status(taskId: string, options?: MusicStatusOptions): Promise<void>;
|
|
29
|
+
private waitForStatus;
|
|
30
|
+
}
|
|
31
|
+
export {};
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
export class MusicCommand {
|
|
5
|
+
config;
|
|
6
|
+
logger;
|
|
7
|
+
constructor(config, logger) {
|
|
8
|
+
this.config = config;
|
|
9
|
+
this.logger = logger;
|
|
10
|
+
}
|
|
11
|
+
apiBase() {
|
|
12
|
+
const configured = String(this.config.getAll().musicApiUrl || '').trim();
|
|
13
|
+
return (process.env.VIGTHORIA_MUSIC_API_URL || configured || 'https://music.vigthoria.io').replace(/\/+$/, '');
|
|
14
|
+
}
|
|
15
|
+
authHeaders() {
|
|
16
|
+
const token = this.config.get('authToken');
|
|
17
|
+
return token ? { Authorization: `Bearer ${token}` } : {};
|
|
18
|
+
}
|
|
19
|
+
async request(endpoint, init = {}) {
|
|
20
|
+
const response = await fetch(`${this.apiBase()}${endpoint}`, {
|
|
21
|
+
...init,
|
|
22
|
+
headers: {
|
|
23
|
+
...this.authHeaders(),
|
|
24
|
+
...init.headers,
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
const text = await response.text();
|
|
28
|
+
let data = {};
|
|
29
|
+
try {
|
|
30
|
+
data = text ? JSON.parse(text) : {};
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
data = { message: text };
|
|
34
|
+
}
|
|
35
|
+
if (!response.ok) {
|
|
36
|
+
throw new Error(String(data.error || data.message || `Music API request failed: ${response.status}`));
|
|
37
|
+
}
|
|
38
|
+
return data;
|
|
39
|
+
}
|
|
40
|
+
async uploadVoiceReference(filePath) {
|
|
41
|
+
const absolutePath = path.resolve(filePath);
|
|
42
|
+
if (!fs.existsSync(absolutePath) || !fs.statSync(absolutePath).isFile()) {
|
|
43
|
+
throw new Error(`Voice reference file not found: ${filePath}`);
|
|
44
|
+
}
|
|
45
|
+
const form = new FormData();
|
|
46
|
+
const blob = new Blob([fs.readFileSync(absolutePath)]);
|
|
47
|
+
form.append('audio', blob, path.basename(absolutePath));
|
|
48
|
+
const data = await this.request('/api/voice/upload', {
|
|
49
|
+
method: 'POST',
|
|
50
|
+
body: form,
|
|
51
|
+
});
|
|
52
|
+
const referencePath = String(data.reference_audio_path || data.path || data.file_path || '').trim();
|
|
53
|
+
if (!referencePath) {
|
|
54
|
+
throw new Error('Voice upload succeeded but did not return a reference_audio_path.');
|
|
55
|
+
}
|
|
56
|
+
return referencePath;
|
|
57
|
+
}
|
|
58
|
+
async generate(promptArg, options) {
|
|
59
|
+
const prompt = String(promptArg || options.prompt || '').trim();
|
|
60
|
+
if (!prompt) {
|
|
61
|
+
throw new Error('Music generation requires a prompt.');
|
|
62
|
+
}
|
|
63
|
+
let referenceAudioPath = '';
|
|
64
|
+
if (options.voice) {
|
|
65
|
+
referenceAudioPath = await this.uploadVoiceReference(options.voice);
|
|
66
|
+
}
|
|
67
|
+
const duration = Math.max(1, Number(options.duration || 120));
|
|
68
|
+
const payload = {
|
|
69
|
+
prompt,
|
|
70
|
+
language: options.language || 'en',
|
|
71
|
+
voice_type: options.voiceType || 'female',
|
|
72
|
+
genre: options.genre || 'pop',
|
|
73
|
+
style_tags: options.styleTags || '',
|
|
74
|
+
lyrics: options.lyrics || undefined,
|
|
75
|
+
duration,
|
|
76
|
+
engine: 'v4',
|
|
77
|
+
is_preview: false,
|
|
78
|
+
};
|
|
79
|
+
if (referenceAudioPath) {
|
|
80
|
+
payload.voice_cloning = {
|
|
81
|
+
enabled: true,
|
|
82
|
+
reference_audio_path: referenceAudioPath,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
const data = await this.request('/api/quick/generate', {
|
|
86
|
+
method: 'POST',
|
|
87
|
+
headers: { 'Content-Type': 'application/json' },
|
|
88
|
+
body: JSON.stringify(payload),
|
|
89
|
+
});
|
|
90
|
+
const taskId = String(data.task_id || data.session_id || '').trim();
|
|
91
|
+
if (options.json) {
|
|
92
|
+
console.log(JSON.stringify({ ...data, task_id: taskId || data.task_id }, null, 2));
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
console.log(chalk.green('Music generation queued.'));
|
|
96
|
+
console.log(chalk.cyan('Task ID: ') + (taskId || '(pending)'));
|
|
97
|
+
if (data.queue_position)
|
|
98
|
+
console.log(chalk.gray(`Queue position: ${data.queue_position}`));
|
|
99
|
+
if (data.estimated_wait_seconds)
|
|
100
|
+
console.log(chalk.gray(`Estimated wait: ${data.estimated_wait_seconds}s`));
|
|
101
|
+
}
|
|
102
|
+
if (options.wait && taskId) {
|
|
103
|
+
await this.waitForStatus(taskId, Number(options.pollInterval || 5), Boolean(options.json));
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
async status(taskId, options = {}) {
|
|
107
|
+
const data = await this.request(`/api/status/${encodeURIComponent(taskId)}`);
|
|
108
|
+
if (options.json) {
|
|
109
|
+
console.log(JSON.stringify(data, null, 2));
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
console.log(chalk.cyan(`Music task ${taskId}`));
|
|
113
|
+
console.log(`Status: ${data.status || 'unknown'}`);
|
|
114
|
+
if (data.progress !== undefined)
|
|
115
|
+
console.log(`Progress: ${data.progress}%`);
|
|
116
|
+
const downloadUrl = data.download_url || data.audio_url || data.file_url || data.result_url;
|
|
117
|
+
if (downloadUrl)
|
|
118
|
+
console.log(`Download: ${downloadUrl}`);
|
|
119
|
+
}
|
|
120
|
+
async waitForStatus(taskId, intervalSeconds, json) {
|
|
121
|
+
const delay = Math.max(1, intervalSeconds) * 1000;
|
|
122
|
+
for (;;) {
|
|
123
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
124
|
+
const data = await this.request(`/api/status/${encodeURIComponent(taskId)}`);
|
|
125
|
+
const status = String(data.status || '').toLowerCase();
|
|
126
|
+
if (!json) {
|
|
127
|
+
const progress = data.progress !== undefined ? ` (${data.progress}%)` : '';
|
|
128
|
+
console.log(chalk.gray(`Status: ${status || 'unknown'}${progress}`));
|
|
129
|
+
}
|
|
130
|
+
if (['completed', 'complete', 'finished', 'failed', 'error', 'cancelled'].includes(status)) {
|
|
131
|
+
if (json)
|
|
132
|
+
console.log(JSON.stringify(data, null, 2));
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -24,6 +24,7 @@ import { handleLogin, handleLogout, statusAction } from './commands/auth.js';
|
|
|
24
24
|
import { ConfigCommand } from './commands/config.js';
|
|
25
25
|
import { ReviewCommand } from './commands/review.js';
|
|
26
26
|
import { HubCommand } from './commands/hub.js';
|
|
27
|
+
import { MusicCommand } from './commands/music.js';
|
|
27
28
|
import { RepoCommand } from './commands/repo.js';
|
|
28
29
|
import { DeployCommand } from './commands/deploy.js';
|
|
29
30
|
import { BridgeCommand } from './commands/bridge.js';
|
|
@@ -207,7 +208,7 @@ async function fetchNpmLatestVersion() {
|
|
|
207
208
|
}
|
|
208
209
|
}
|
|
209
210
|
const VERSION = getVersion();
|
|
210
|
-
const VIGTHORIA_DEFAULT_MANIFEST_URL = process.env.VIGTHORIA_UPDATE_MANIFEST_URL || "https://
|
|
211
|
+
const VIGTHORIA_DEFAULT_MANIFEST_URL = process.env.VIGTHORIA_UPDATE_MANIFEST_URL || "https://extension.vigthoria.io/downloads/manifest.json";
|
|
211
212
|
function resolveManifestEntry(manifest, channel) {
|
|
212
213
|
const normalized = String(channel || 'stable').trim() || 'stable';
|
|
213
214
|
if (manifest.channels && manifest.channels[normalized]) {
|
|
@@ -1043,6 +1044,36 @@ export async function main(args) {
|
|
|
1043
1044
|
const hub = new HubCommand(config, logger);
|
|
1044
1045
|
await hub.discover();
|
|
1045
1046
|
});
|
|
1047
|
+
const musicCommand = program
|
|
1048
|
+
.command('music')
|
|
1049
|
+
.alias('audio')
|
|
1050
|
+
.description('Generate songs with Vigthoria Music AI / ACE-Step');
|
|
1051
|
+
musicCommand
|
|
1052
|
+
.command('generate [prompt]')
|
|
1053
|
+
.alias('g')
|
|
1054
|
+
.description('Queue full-song generation and return a task id')
|
|
1055
|
+
.option('--lyrics <text>', 'Provide fixed lyrics instead of auto-writing lyrics')
|
|
1056
|
+
.option('--duration <seconds>', 'Song duration in seconds', '120')
|
|
1057
|
+
.option('--genre <genre>', 'Music genre', 'pop')
|
|
1058
|
+
.option('--language <code>', 'Vocal language', 'en')
|
|
1059
|
+
.option('--voice-type <type>', 'Voice type (female, male, mixed)', 'female')
|
|
1060
|
+
.option('--style-tags <tags>', 'Additional style tags')
|
|
1061
|
+
.option('--voice <path>', 'Upload a local voice reference for cloning')
|
|
1062
|
+
.option('--wait', 'Poll until the task reaches a terminal state', false)
|
|
1063
|
+
.option('--poll-interval <seconds>', 'Polling interval when --wait is used', '5')
|
|
1064
|
+
.option('--json', 'Emit machine-readable JSON output', false)
|
|
1065
|
+
.action(async (prompt, options) => {
|
|
1066
|
+
const music = new MusicCommand(config, logger);
|
|
1067
|
+
await music.generate(prompt, options);
|
|
1068
|
+
});
|
|
1069
|
+
musicCommand
|
|
1070
|
+
.command('status <taskId>')
|
|
1071
|
+
.description('Check a queued music generation task')
|
|
1072
|
+
.option('--json', 'Emit machine-readable JSON output', false)
|
|
1073
|
+
.action(async (taskId, options) => {
|
|
1074
|
+
const music = new MusicCommand(config, logger);
|
|
1075
|
+
await music.status(taskId, options);
|
|
1076
|
+
});
|
|
1046
1077
|
// ==================== REPO COMMANDS ====================
|
|
1047
1078
|
// Repo command - Push/Pull projects to/from Vigthoria Repository
|
|
1048
1079
|
const repoCommand = program
|
|
@@ -1514,7 +1545,7 @@ Examples:
|
|
|
1514
1545
|
program
|
|
1515
1546
|
.command('update')
|
|
1516
1547
|
.alias('upgrade')
|
|
1517
|
-
.description('Check for updates and upgrade Vigthoria CLI. Default manifest: https://
|
|
1548
|
+
.description('Check for updates and upgrade Vigthoria CLI. Default manifest: https://extension.vigthoria.io/downloads/manifest.json')
|
|
1518
1549
|
.option('-c, --check', 'Only check for updates, don\'t install')
|
|
1519
1550
|
.option('-f, --from <target>', 'Install update from npm spec, local .tgz path, or URL')
|
|
1520
1551
|
.option('-m, --manifest <url>', 'Update manifest URL for server-driven releases')
|
|
@@ -33,6 +33,7 @@ export interface RunEvaluation {
|
|
|
33
33
|
uiTheme: 'success' | 'warning' | 'error';
|
|
34
34
|
}
|
|
35
35
|
export declare function createLiveOutcome(): LiveOutcome;
|
|
36
|
+
export declare function isExecutorTimeoutFailure(liveOutcome: LiveOutcome): boolean;
|
|
36
37
|
/** Tool-trace stubs like `[read_file] "index.html"` or `<read_file>…</read_file>` — not a user-facing answer. */
|
|
37
38
|
export declare function isToolEvidenceStubAnswer(text: string): boolean;
|
|
38
39
|
/** True when text looks like a real answer, not an executor/system placeholder. */
|
|
@@ -30,6 +30,10 @@ const LIST_ONLY_DISCOVERY_TOOLS = new Set([
|
|
|
30
30
|
'dir',
|
|
31
31
|
]);
|
|
32
32
|
const GENERIC_SUMMARY_RE = /^(completed the requested analysis|reviewed the workspace without writing changes|task completed|done|finished|analysis completed)([.\s]|$)/i;
|
|
33
|
+
export function isExecutorTimeoutFailure(liveOutcome) {
|
|
34
|
+
const errorText = String(liveOutcome.executorError || '').trim();
|
|
35
|
+
return /executor llm call timed out|fast fallback also failed|executor model timed out/i.test(errorText);
|
|
36
|
+
}
|
|
33
37
|
function looksLikeRawV3EventPayload(text) {
|
|
34
38
|
const trimmed = String(text || '').trim();
|
|
35
39
|
if (!/^[{[]/.test(trimmed))
|
|
@@ -164,6 +168,15 @@ export function evaluateExecutorSuccess(liveOutcome) {
|
|
|
164
168
|
|| Boolean(liveOutcome.streamAborted);
|
|
165
169
|
if (liveOutcome.streamAborted && liveOutcome.tasksTotal > 0) {
|
|
166
170
|
const partial = liveOutcome.tasksSucceeded > 0;
|
|
171
|
+
if (isExecutorTimeoutFailure(liveOutcome)) {
|
|
172
|
+
return {
|
|
173
|
+
executorSucceeded: false,
|
|
174
|
+
statusHeadline: partial
|
|
175
|
+
? `⚠ ${liveOutcome.tasksSucceeded}/${liveOutcome.tasksTotal} tasks completed — executor model timed out`
|
|
176
|
+
: `✕ Executor model timed out — 0/${liveOutcome.tasksTotal} tasks completed`,
|
|
177
|
+
uiTheme: 'warning',
|
|
178
|
+
};
|
|
179
|
+
}
|
|
167
180
|
return {
|
|
168
181
|
executorSucceeded: false,
|
|
169
182
|
statusHeadline: partial
|
|
@@ -172,6 +185,16 @@ export function evaluateExecutorSuccess(liveOutcome) {
|
|
|
172
185
|
uiTheme: 'warning',
|
|
173
186
|
};
|
|
174
187
|
}
|
|
188
|
+
if (liveOutcome.tasksTotal > 0 && isExecutorTimeoutFailure(liveOutcome)) {
|
|
189
|
+
const partial = liveOutcome.tasksSucceeded > 0;
|
|
190
|
+
return {
|
|
191
|
+
executorSucceeded: false,
|
|
192
|
+
statusHeadline: partial
|
|
193
|
+
? `⚠ ${liveOutcome.tasksSucceeded}/${liveOutcome.tasksTotal} tasks completed — executor model timed out`
|
|
194
|
+
: `✕ Executor model timed out — 0/${liveOutcome.tasksTotal} tasks completed`,
|
|
195
|
+
uiTheme: 'error',
|
|
196
|
+
};
|
|
197
|
+
}
|
|
175
198
|
if (hasFatalError && liveOutcome.tasksTotal === 0) {
|
|
176
199
|
return {
|
|
177
200
|
executorSucceeded: false,
|
package/dist/utils/api.d.ts
CHANGED
|
@@ -342,6 +342,7 @@ export declare class APIClient {
|
|
|
342
342
|
private materializeEmergencySaaSWorkspace;
|
|
343
343
|
private ensureExecutionContext;
|
|
344
344
|
bindExecutionContext(context?: Record<string, any>): Promise<Record<string, any>>;
|
|
345
|
+
private isServerManagedTempWorkspacePath;
|
|
345
346
|
private resolveAgentTargetPath;
|
|
346
347
|
private isLikelyWindowsPath;
|
|
347
348
|
private resolveServerBindableWorkspacePath;
|
package/dist/utils/api.js
CHANGED
|
@@ -2236,6 +2236,10 @@ menu {
|
|
|
2236
2236
|
}
|
|
2237
2237
|
return {
|
|
2238
2238
|
...executionContext,
|
|
2239
|
+
localWorkspacePath: localWorkspacePath || executionContext.localWorkspacePath || null,
|
|
2240
|
+
targetPath: localWorkspacePath || executionContext.targetPath || executionContext.projectPath || executionContext.workspacePath || null,
|
|
2241
|
+
projectPath: localWorkspacePath || executionContext.projectPath || executionContext.targetPath || executionContext.workspacePath || null,
|
|
2242
|
+
workspacePath: localWorkspacePath || executionContext.workspacePath || executionContext.projectPath || executionContext.targetPath || null,
|
|
2239
2243
|
mcpContextId: binding.mcpContextId,
|
|
2240
2244
|
mcpContextBackendUrl: binding.backendUrl,
|
|
2241
2245
|
};
|
|
@@ -2276,10 +2280,38 @@ menu {
|
|
|
2276
2280
|
this.logger.debug(`Failed to bind MCP context via ${baseUrl}:`, error.message);
|
|
2277
2281
|
}
|
|
2278
2282
|
}
|
|
2279
|
-
return
|
|
2283
|
+
return {
|
|
2284
|
+
...executionContext,
|
|
2285
|
+
localWorkspacePath: localWorkspacePath || executionContext.localWorkspacePath || null,
|
|
2286
|
+
targetPath: localWorkspacePath || executionContext.targetPath || executionContext.projectPath || executionContext.workspacePath || null,
|
|
2287
|
+
projectPath: localWorkspacePath || executionContext.projectPath || executionContext.targetPath || executionContext.workspacePath || null,
|
|
2288
|
+
workspacePath: localWorkspacePath || executionContext.workspacePath || executionContext.projectPath || executionContext.targetPath || null,
|
|
2289
|
+
};
|
|
2290
|
+
}
|
|
2291
|
+
isServerManagedTempWorkspacePath(pathValue) {
|
|
2292
|
+
const normalized = String(pathValue || '').replace(/\\/g, '/');
|
|
2293
|
+
return /(?:^|\/)v3-temp\/vig-remote-/i.test(normalized)
|
|
2294
|
+
|| /(?:^|\/)tmp\/vig-remote(?:-server)?-/i.test(normalized)
|
|
2295
|
+
|| /(?:^|\/)var\/tmp\/vig-remote(?:-server)?-/i.test(normalized);
|
|
2280
2296
|
}
|
|
2281
2297
|
resolveAgentTargetPath(context = {}) {
|
|
2282
|
-
|
|
2298
|
+
const explicitLocal = String(context.localWorkspacePath || '').trim();
|
|
2299
|
+
if (explicitLocal
|
|
2300
|
+
&& !explicitLocal.startsWith('vigthoria://')
|
|
2301
|
+
&& (this.isLikelyWindowsPath(explicitLocal) || path.isAbsolute(explicitLocal))) {
|
|
2302
|
+
return explicitLocal;
|
|
2303
|
+
}
|
|
2304
|
+
const candidate = context.targetPath || context.projectPath || context.workspacePath || context.projectRoot || process.cwd();
|
|
2305
|
+
if (this.isServerManagedTempWorkspacePath(candidate) && explicitLocal && !explicitLocal.startsWith('vigthoria://')) {
|
|
2306
|
+
return explicitLocal;
|
|
2307
|
+
}
|
|
2308
|
+
if (this.isServerManagedTempWorkspacePath(candidate)) {
|
|
2309
|
+
const preserved = String(context.__localWorkspacePath || context.clientWorkspacePath || '').trim();
|
|
2310
|
+
if (preserved && !preserved.startsWith('vigthoria://')) {
|
|
2311
|
+
return preserved;
|
|
2312
|
+
}
|
|
2313
|
+
}
|
|
2314
|
+
return candidate;
|
|
2283
2315
|
}
|
|
2284
2316
|
isLikelyWindowsPath(pathValue) {
|
|
2285
2317
|
return /^[a-zA-Z]:[\\/]/.test(pathValue) || /^\\\\/.test(pathValue);
|
|
@@ -2955,7 +2987,9 @@ menu {
|
|
|
2955
2987
|
return;
|
|
2956
2988
|
}
|
|
2957
2989
|
}
|
|
2958
|
-
const targets = expectedFiles.length > 0
|
|
2990
|
+
const targets = expectedFiles.length > 0
|
|
2991
|
+
? Array.from(new Set([...expectedFiles, ...Object.keys(streamedFiles)]))
|
|
2992
|
+
: Object.keys(streamedFiles);
|
|
2959
2993
|
for (const targetPath of targets) {
|
|
2960
2994
|
const relativePath = this.normalizeAgentWorkspaceRelativePath(targetPath, rootPath);
|
|
2961
2995
|
if (!relativePath) {
|
|
@@ -3521,6 +3555,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
3521
3555
|
let buffer = '';
|
|
3522
3556
|
const events = [];
|
|
3523
3557
|
let final = null;
|
|
3558
|
+
let streamDone = false;
|
|
3524
3559
|
let contextId = response.headers.get('x-context-id') || String(context.contextId || '').trim() || null;
|
|
3525
3560
|
let serverWorkspaceRoot = null;
|
|
3526
3561
|
const streamedFiles = {};
|
|
@@ -3609,9 +3644,13 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
3609
3644
|
}
|
|
3610
3645
|
}
|
|
3611
3646
|
const payload = dataLines.join('\n').trim();
|
|
3612
|
-
if (!payload
|
|
3647
|
+
if (!payload) {
|
|
3613
3648
|
continue;
|
|
3614
3649
|
}
|
|
3650
|
+
if (payload === '[DONE]') {
|
|
3651
|
+
streamDone = true;
|
|
3652
|
+
break;
|
|
3653
|
+
}
|
|
3615
3654
|
let event;
|
|
3616
3655
|
try {
|
|
3617
3656
|
event = JSON.parse(payload);
|
|
@@ -3702,20 +3741,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
3702
3741
|
if (event.type === 'complete' || event.type === 'message') {
|
|
3703
3742
|
final = event;
|
|
3704
3743
|
}
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
await Promise.allSettled([...this.pendingV3ClientToolTasks]);
|
|
3709
|
-
reader.cancel().catch(() => { });
|
|
3710
|
-
return {
|
|
3711
|
-
task_id: events.find((entry) => entry && entry.task_id)?.task_id || null,
|
|
3712
|
-
context_id: contextId,
|
|
3713
|
-
result: final,
|
|
3714
|
-
events,
|
|
3715
|
-
files: streamedFiles,
|
|
3716
|
-
serverWorkspaceRoot: serverWorkspaceRoot || null,
|
|
3717
|
-
};
|
|
3718
|
-
}
|
|
3744
|
+
}
|
|
3745
|
+
if (streamDone) {
|
|
3746
|
+
break;
|
|
3719
3747
|
}
|
|
3720
3748
|
}
|
|
3721
3749
|
await Promise.allSettled([...this.pendingV3ClientToolTasks]);
|
|
@@ -5991,9 +6019,23 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
5991
6019
|
}
|
|
5992
6020
|
async attemptV3ServiceRecovery(reason = '', _options = {}) {
|
|
5993
6021
|
const safeReason = sanitizeUserFacingErrorText(reason || 'unknown failure');
|
|
6022
|
+
const isTransient = /timed out|aborted|econnreset|fetch failed|network|503|502|504|socket hang up/i.test(safeReason);
|
|
6023
|
+
if (!isTransient) {
|
|
6024
|
+
return {
|
|
6025
|
+
recovered: false,
|
|
6026
|
+
message: safeReason ? `Recovery unavailable: ${safeReason}` : 'Recovery unavailable',
|
|
6027
|
+
};
|
|
6028
|
+
}
|
|
6029
|
+
const health = await this.runV3HealthCheck();
|
|
6030
|
+
if (!health.healthy) {
|
|
6031
|
+
return {
|
|
6032
|
+
recovered: false,
|
|
6033
|
+
message: health.error || 'V3 service is still unreachable',
|
|
6034
|
+
};
|
|
6035
|
+
}
|
|
5994
6036
|
return {
|
|
5995
|
-
recovered:
|
|
5996
|
-
message:
|
|
6037
|
+
recovered: true,
|
|
6038
|
+
message: 'V3 service is reachable again — retrying the workflow once.',
|
|
5997
6039
|
};
|
|
5998
6040
|
}
|
|
5999
6041
|
async getDevtoolsBridgeStatus() {
|
package/dist/utils/config.d.ts
CHANGED
package/dist/utils/config.js
CHANGED
|
@@ -6,6 +6,7 @@ import { secureFileMode } from './cli-state.js';
|
|
|
6
6
|
const defaultConfig = {
|
|
7
7
|
apiUrl: 'https://coder.vigthoria.io',
|
|
8
8
|
modelsApiUrl: 'https://api.vigthoria.io', // Direct AI Models API - no proxying through Coder
|
|
9
|
+
musicApiUrl: 'https://music.vigthoria.io',
|
|
9
10
|
wsUrl: 'wss://coder.vigthoria.io/ws',
|
|
10
11
|
selfHostedModelsApiUrl: null,
|
|
11
12
|
authToken: null,
|
|
@@ -59,6 +60,7 @@ export class Config {
|
|
|
59
60
|
schema: {
|
|
60
61
|
apiUrl: { type: 'string' },
|
|
61
62
|
modelsApiUrl: { type: 'string' },
|
|
63
|
+
musicApiUrl: { type: 'string' },
|
|
62
64
|
wsUrl: { type: 'string' },
|
|
63
65
|
selfHostedModelsApiUrl: { type: ['string', 'null'] },
|
|
64
66
|
authToken: { type: ['string', 'null'] },
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
*/
|
|
5
5
|
const CLI_SHAPING_BLOCK = /(?:^|\n\n)Platform:\s*(?:Windows|macOS|Linux)\.[\s\S]*?(?=\n\n[A-Z][^\n]{0,40}:|\s*$)/i;
|
|
6
6
|
const READONLY_SHAPING_BLOCK = /(?:^|\n\n)(?:Read-only analysis mode is active\.|Diagnostic mode is active\.)[\s\S]*?(?=\n\n[A-Z][^\n]{0,40}:|\s*$)/i;
|
|
7
|
-
const BUILD_VERBS = /\b(build|create|make|implement|complete|fix|repair|edit|modify|write|generate|add|finish|scaffold|develop|update|change|refactor|let's|let us|we need|we should|erstelle|erstellen|schreib|schreibe|bearbeite)\b/i;
|
|
8
|
-
const ARTIFACT_NOUNS = /\b(file|files|project|game|spiel|app|website|html5|frontend|component|feature|code|workspace|repo|clone|replica|remake|page|site|canvas|sprite|level|levels|enemy|enemies|character|npc|world|scene|script|module|api|database|config|landing|dashboard|platformer|arcade|playable|collectible|scoreboard|leaderboard|pitfall|pacman|rogue|html|css|javascript|typescript)\b/i;
|
|
7
|
+
const BUILD_VERBS = /\b(build|create|make|implement|complete|fix|repair|edit|modify|write|generate|add|finish|scaffold|develop|update|change|refactor|want|need|require|looking for|let's|let us|we need|we should|erstelle|erstellen|schreib|schreibe|bearbeite)\b/i;
|
|
8
|
+
const ARTIFACT_NOUNS = /\b(file|files|project|game|spiel|app|website|html5|frontend|component|feature|code|workspace|repo|clone|replica|remake|page|site|screen|button|popup|modal|alert|canvas|sprite|level|levels|enemy|enemies|character|npc|world|scene|script|module|api|database|config|landing|dashboard|platformer|arcade|playable|collectible|scoreboard|leaderboard|pitfall|pacman|rogue|html|css|javascript|typescript)\b/i;
|
|
9
9
|
/** Canvas/playable games only — bare "html5" or "website" must NOT match. */
|
|
10
10
|
const GAME_INTENT = /\b(game|spiel|playable|html5\s+(?:game|canvas)|html5\s+game|canvas\s+game|game\s+canvas|arcade|platformer|side[- ]?scroller|pitfall|pac[- ]?man|tetris|snake|breakout|pong|roguelike|metroidvania|tower\s+defense|clone\s+of\s+(?:a\s+)?(?:game|pitfall|pac|mario|zelda|arcade)|gameplay|collectible|boss\s+fight|level\s+design|sprite\s+sheet|wild\s+(?:boar|pig|forest)|forest\s+pig)\b/i;
|
|
11
11
|
const WEB_PAGE_INTENT = /\b(website|web\s*site|webpage|web\s*page|landing\s+page|home\s*page|index\.html|html\s+page|static\s+page|single\s+page|popup|alert|modal|hello\s+world)\b/i;
|
|
@@ -54,7 +54,7 @@ export function isTrivialHtmlPageRequest(prompt) {
|
|
|
54
54
|
if (isContinueOrRetryPrompt(prompt)) {
|
|
55
55
|
return false;
|
|
56
56
|
}
|
|
57
|
-
if (!hasBuildVerb(prompt) && !/\b(write|create|make|show)\b/i.test(text)) {
|
|
57
|
+
if (!hasBuildVerb(prompt) && !/\b(write|create|make|show|want|need|looking\s+for)\b/i.test(text)) {
|
|
58
58
|
return false;
|
|
59
59
|
}
|
|
60
60
|
if (hasGameIntent(prompt)) {
|
|
@@ -4,22 +4,108 @@
|
|
|
4
4
|
import * as fs from 'fs';
|
|
5
5
|
import * as path from 'path';
|
|
6
6
|
import { stripExecutionShaping } from './requestIntent.js';
|
|
7
|
+
function escapeJsString(value) {
|
|
8
|
+
return String(value || '').replace(/\\/g, '\\\\').replace(/'/g, "\\'");
|
|
9
|
+
}
|
|
10
|
+
function escapeHtml(value) {
|
|
11
|
+
return String(value || '')
|
|
12
|
+
.replace(/&/g, '&')
|
|
13
|
+
.replace(/</g, '<')
|
|
14
|
+
.replace(/>/g, '>')
|
|
15
|
+
.replace(/"/g, '"');
|
|
16
|
+
}
|
|
17
|
+
function extractPopupMessage(vision) {
|
|
18
|
+
const explicitShown = vision.match(/(?:popup\s+is\s+shown|popup\s+shown|shown|shows?|display(?:s|ed)?)\s*:?\s*["']([^"']+)["']/i);
|
|
19
|
+
if (explicitShown?.[1])
|
|
20
|
+
return explicitShown[1];
|
|
21
|
+
const popupPhrases = [...vision.matchAll(/popup[^"']*(?:message|text|says?|shown|with)\s*:?\s*["']([^"']+)["']/gi)];
|
|
22
|
+
if (popupPhrases.length > 0)
|
|
23
|
+
return popupPhrases[popupPhrases.length - 1][1];
|
|
24
|
+
const quoted = vision.match(/(?:alert|message)\s*(?:with|says?|text|:)\s*["']([^"']+)["']/i);
|
|
25
|
+
if (quoted?.[1])
|
|
26
|
+
return quoted[1];
|
|
27
|
+
if (/\bhello\s*world\b/i.test(vision))
|
|
28
|
+
return 'hello world';
|
|
29
|
+
return 'Hello World';
|
|
30
|
+
}
|
|
31
|
+
function extractButtonLabel(vision) {
|
|
32
|
+
const label = vision.match(/\blabel\s+["']([^"']+)["']/i)
|
|
33
|
+
|| vision.match(/\bbutton(?:\s+(?:text|label|called|named))?\s+["']([^"']+)["']/i);
|
|
34
|
+
if (label?.[1])
|
|
35
|
+
return label[1];
|
|
36
|
+
if (/\bclick\b/i.test(vision))
|
|
37
|
+
return 'CLICK';
|
|
38
|
+
return 'Click';
|
|
39
|
+
}
|
|
40
|
+
function isSimplePopupButtonPrompt(vision) {
|
|
41
|
+
const text = String(vision || '').toLowerCase();
|
|
42
|
+
return /\b(popup|alert|message)\b/.test(text)
|
|
43
|
+
&& /\b(button|click|label)\b/.test(text)
|
|
44
|
+
&& /\bhello\s*world\b/.test(text);
|
|
45
|
+
}
|
|
46
|
+
function exactPopupButtonHtml(vision) {
|
|
47
|
+
const popupMessage = extractPopupMessage(vision);
|
|
48
|
+
const buttonLabel = extractButtonLabel(vision);
|
|
49
|
+
const blue = /\bblue\b/i.test(vision) ? '#0000ff' : '#0a0a1a';
|
|
50
|
+
return `<!DOCTYPE html>
|
|
51
|
+
<html lang="en">
|
|
52
|
+
<head>
|
|
53
|
+
<meta charset="UTF-8">
|
|
54
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
55
|
+
<title>${escapeHtml(buttonLabel)} Popup</title>
|
|
56
|
+
<style>
|
|
57
|
+
* { box-sizing: border-box; }
|
|
58
|
+
body {
|
|
59
|
+
min-height: 100vh;
|
|
60
|
+
margin: 0;
|
|
61
|
+
display: grid;
|
|
62
|
+
place-items: center;
|
|
63
|
+
background: ${blue};
|
|
64
|
+
font-family: Arial, sans-serif;
|
|
65
|
+
}
|
|
66
|
+
button {
|
|
67
|
+
padding: 14px 28px;
|
|
68
|
+
border: 0;
|
|
69
|
+
border-radius: 10px;
|
|
70
|
+
background: #ffffff;
|
|
71
|
+
color: #0000aa;
|
|
72
|
+
font-size: 20px;
|
|
73
|
+
font-weight: 700;
|
|
74
|
+
cursor: pointer;
|
|
75
|
+
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.25);
|
|
76
|
+
}
|
|
77
|
+
</style>
|
|
78
|
+
</head>
|
|
79
|
+
<body>
|
|
80
|
+
<button id="vigthoria-popup-button" type="button">${escapeHtml(buttonLabel)}</button>
|
|
81
|
+
<script>
|
|
82
|
+
document.getElementById('vigthoria-popup-button').addEventListener('click', function () {
|
|
83
|
+
alert('${escapeJsString(popupMessage)}');
|
|
84
|
+
});
|
|
85
|
+
</script>
|
|
86
|
+
</body>
|
|
87
|
+
</html>
|
|
88
|
+
`;
|
|
89
|
+
}
|
|
7
90
|
function ensurePopupScript(html, vision) {
|
|
8
91
|
const text = String(vision || '').toLowerCase();
|
|
9
92
|
if (!/\b(popup|alert|hello\s*world)\b/.test(text)) {
|
|
10
93
|
return html;
|
|
11
94
|
}
|
|
95
|
+
if (isSimplePopupButtonPrompt(vision)) {
|
|
96
|
+
return exactPopupButtonHtml(vision);
|
|
97
|
+
}
|
|
12
98
|
if (/\balert\s*\(/i.test(html)) {
|
|
13
99
|
return html;
|
|
14
100
|
}
|
|
15
|
-
const script =
|
|
101
|
+
const script = `<script>document.addEventListener('DOMContentLoaded',()=>{const b=document.querySelector('button,a.cta,.cta');if(b)b.addEventListener('click',()=>alert('${escapeJsString(extractPopupMessage(vision))}'));});</script>`;
|
|
16
102
|
if (/<\/body>/i.test(html)) {
|
|
17
103
|
return html.replace(/<\/body>/i, `${script}\n</body>`);
|
|
18
104
|
}
|
|
19
105
|
return `${html}\n${script}`;
|
|
20
106
|
}
|
|
21
107
|
function minimalHelloWorldHtml(popupMessage = 'Hello World') {
|
|
22
|
-
const safe =
|
|
108
|
+
const safe = escapeJsString(popupMessage || 'Hello World');
|
|
23
109
|
return `<!DOCTYPE html>
|
|
24
110
|
<html lang="en">
|
|
25
111
|
<head>
|
|
@@ -33,7 +119,7 @@ function minimalHelloWorldHtml(popupMessage = 'Hello World') {
|
|
|
33
119
|
</head>
|
|
34
120
|
<body>
|
|
35
121
|
<h1>Hello World</h1>
|
|
36
|
-
<
|
|
122
|
+
<button type="button" onclick="alert('${safe}')">CLICK</button>
|
|
37
123
|
</body>
|
|
38
124
|
</html>
|
|
39
125
|
`;
|
|
@@ -79,14 +165,6 @@ async function fetchTemplateMatch(api, vision, timeoutMs = 15000) {
|
|
|
79
165
|
}
|
|
80
166
|
return { error: lastError };
|
|
81
167
|
}
|
|
82
|
-
function extractPopupMessage(vision) {
|
|
83
|
-
const quoted = vision.match(/popup\s+with\s+["']([^"']+)["']/i);
|
|
84
|
-
if (quoted?.[1])
|
|
85
|
-
return quoted[1];
|
|
86
|
-
if (/\bhello\s*world\b/i.test(vision))
|
|
87
|
-
return 'Hello World';
|
|
88
|
-
return 'Hello World';
|
|
89
|
-
}
|
|
90
168
|
export async function runTemplateInstantPath(api, vision, workspacePath, options = {}) {
|
|
91
169
|
const entryPath = options.entryPath || 'index.html';
|
|
92
170
|
const target = path.join(workspacePath, entryPath);
|
package/dist/utils/tools.js
CHANGED
|
@@ -25,6 +25,16 @@ export function normalizeV3WorkspaceRelativePath(rawPath, rootPath) {
|
|
|
25
25
|
}
|
|
26
26
|
return normalized;
|
|
27
27
|
};
|
|
28
|
+
const stripWorkspaceAlias = (value) => {
|
|
29
|
+
const candidate = String(value || '').replace(/^\/+/, '');
|
|
30
|
+
if (!candidate || candidate === '.' || /^workspace\/?$/i.test(candidate)) {
|
|
31
|
+
return '';
|
|
32
|
+
}
|
|
33
|
+
if (/^workspace\//i.test(candidate)) {
|
|
34
|
+
return safeRelative(candidate.replace(/^workspace\/+/i, ''));
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
};
|
|
28
38
|
const decodeBoundaryUri = (value) => {
|
|
29
39
|
const match = value.match(/^vigthoria:\/\/(?:workspace|server-internal)\/?(.*)$/i);
|
|
30
40
|
if (match) {
|
|
@@ -39,6 +49,10 @@ export function normalizeV3WorkspaceRelativePath(rawPath, rootPath) {
|
|
|
39
49
|
if (/^vigthoria:\/\//i.test(input)) {
|
|
40
50
|
return '';
|
|
41
51
|
}
|
|
52
|
+
const workspaceAlias = stripWorkspaceAlias(input);
|
|
53
|
+
if (workspaceAlias !== null) {
|
|
54
|
+
return workspaceAlias;
|
|
55
|
+
}
|
|
42
56
|
const internalWorkspacePatterns = [
|
|
43
57
|
/^\/?var\/www\/\.vigthoria\/v3-temp\/vig-remote-[^/]+\/(.+)$/i,
|
|
44
58
|
/^\/?var\/www\/vigthoria:\/\/?server-internal\/?(.+)$/i,
|
package/install.ps1
CHANGED
|
@@ -5,25 +5,33 @@
|
|
|
5
5
|
$ErrorActionPreference = "Stop"
|
|
6
6
|
|
|
7
7
|
# Configuration
|
|
8
|
-
$CLI_VERSION = "1.
|
|
8
|
+
$CLI_VERSION = "1.11.25"
|
|
9
9
|
$INSTALL_DIR = "$env:USERPROFILE\.vigthoria"
|
|
10
10
|
$NPM_PACKAGE = "vigthoria-cli"
|
|
11
11
|
$GIT_PACKAGE_URL = "git+https://market.vigthoria.io/vigthoria/vigthoria-cli.git"
|
|
12
|
-
$
|
|
12
|
+
$MANIFEST_URL = "https://extension.vigthoria.io/downloads/manifest.json"
|
|
13
|
+
$HOSTED_TARBALL_URL = "https://extension.vigthoria.io/downloads/vigthoria-cli-$CLI_VERSION.tgz"
|
|
13
14
|
|
|
14
|
-
function Resolve-
|
|
15
|
+
function Resolve-ReleaseManifest {
|
|
15
16
|
try {
|
|
16
|
-
$
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
$manifest = Invoke-WebRequest -Uri $MANIFEST_URL -UseBasicParsing -TimeoutSec 10 -ErrorAction Stop |
|
|
18
|
+
Select-Object -ExpandProperty Content |
|
|
19
|
+
ConvertFrom-Json
|
|
20
|
+
$stable = $manifest.channels.stable
|
|
21
|
+
if ($stable.version) {
|
|
22
|
+
$script:CLI_VERSION = [string]$stable.version
|
|
23
|
+
}
|
|
24
|
+
if ($stable.url) {
|
|
25
|
+
$script:HOSTED_TARBALL_URL = [string]$stable.url
|
|
26
|
+
} elseif ($stable.version) {
|
|
27
|
+
$script:HOSTED_TARBALL_URL = "https://extension.vigthoria.io/downloads/vigthoria-cli-$($stable.version).tgz"
|
|
20
28
|
}
|
|
21
29
|
} catch {
|
|
22
|
-
Write-Host "Release
|
|
30
|
+
Write-Host "Release manifest unavailable, using bundled fallback $CLI_VERSION" -ForegroundColor Yellow
|
|
23
31
|
}
|
|
24
32
|
}
|
|
25
33
|
|
|
26
|
-
Resolve-
|
|
34
|
+
Resolve-ReleaseManifest
|
|
27
35
|
|
|
28
36
|
function Write-Banner {
|
|
29
37
|
Write-Host ""
|
package/install.sh
CHANGED
|
@@ -26,11 +26,54 @@ else
|
|
|
26
26
|
fi
|
|
27
27
|
|
|
28
28
|
# Configuration
|
|
29
|
-
CLI_VERSION="1.
|
|
29
|
+
CLI_VERSION="1.11.25"
|
|
30
30
|
INSTALL_DIR="$HOME/.vigthoria"
|
|
31
31
|
REPO_URL="https://market.vigthoria.io/vigthoria/vigthoria-cli"
|
|
32
32
|
GIT_PACKAGE_URL="git+https://market.vigthoria.io/vigthoria/vigthoria-cli.git"
|
|
33
|
-
|
|
33
|
+
MANIFEST_URL="${VIGTHORIA_UPDATE_MANIFEST_URL:-https://extension.vigthoria.io/downloads/manifest.json}"
|
|
34
|
+
HOSTED_TARBALL_URL="https://extension.vigthoria.io/downloads/vigthoria-cli-${CLI_VERSION}.tgz"
|
|
35
|
+
|
|
36
|
+
resolve_release_manifest() {
|
|
37
|
+
if ! command -v curl >/dev/null 2>&1 || ! command -v python3 >/dev/null 2>&1; then
|
|
38
|
+
return 0
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
local manifest_json
|
|
42
|
+
manifest_json="$(curl -fsSL "$MANIFEST_URL" 2>/dev/null || true)"
|
|
43
|
+
if [[ -z "$manifest_json" ]]; then
|
|
44
|
+
return 0
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
local resolved
|
|
48
|
+
resolved="$(printf '%s' "$manifest_json" | python3 -c "
|
|
49
|
+
import json, sys
|
|
50
|
+
try:
|
|
51
|
+
data = json.load(sys.stdin)
|
|
52
|
+
stable = (data.get('channels') or {}).get('stable') or {}
|
|
53
|
+
version = str(stable.get('version') or '').strip()
|
|
54
|
+
url = str(stable.get('url') or '').strip()
|
|
55
|
+
if version:
|
|
56
|
+
print(version)
|
|
57
|
+
if url:
|
|
58
|
+
print(url)
|
|
59
|
+
except Exception:
|
|
60
|
+
pass
|
|
61
|
+
" 2>/dev/null || true)"
|
|
62
|
+
|
|
63
|
+
if [[ -n "$resolved" ]]; then
|
|
64
|
+
mapfile -t _manifest_lines <<< "$resolved"
|
|
65
|
+
if [[ -n "${_manifest_lines[0]:-}" ]]; then
|
|
66
|
+
CLI_VERSION="${_manifest_lines[0]}"
|
|
67
|
+
fi
|
|
68
|
+
if [[ -n "${_manifest_lines[1]:-}" ]]; then
|
|
69
|
+
HOSTED_TARBALL_URL="${_manifest_lines[1]}"
|
|
70
|
+
elif [[ -n "${_manifest_lines[0]:-}" ]]; then
|
|
71
|
+
HOSTED_TARBALL_URL="https://extension.vigthoria.io/downloads/vigthoria-cli-${CLI_VERSION}.tgz"
|
|
72
|
+
fi
|
|
73
|
+
fi
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
resolve_release_manifest
|
|
34
77
|
|
|
35
78
|
# Detect platform and set appropriate bin directory
|
|
36
79
|
detect_platform() {
|
package/package.json
CHANGED
|
@@ -17,7 +17,7 @@ REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
|
17
17
|
PKG_FILE="$REPO_ROOT/vigthoria-cli-${VERSION}.tgz"
|
|
18
18
|
HOST_DOWNLOADS_DIR="${HOST_DOWNLOADS_DIR:-}"
|
|
19
19
|
HOST_MANIFEST_FILE="${HOST_MANIFEST_FILE:-}"
|
|
20
|
-
PUBLIC_BASE_URL="${PUBLIC_BASE_URL:-https://
|
|
20
|
+
PUBLIC_BASE_URL="${PUBLIC_BASE_URL:-https://extension.vigthoria.io/downloads}"
|
|
21
21
|
CHANNEL="${CHANNEL:-stable}"
|
|
22
22
|
|
|
23
23
|
if [[ ! -f "$PKG_FILE" ]]; then
|