@pixelbyte-software/pixcode 1.48.5 → 1.49.0
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/assets/{index-QNWLsise.js → index-58IIiyST.js} +163 -163
- package/dist/assets/index-BpUexHb8.css +32 -0
- package/dist/index.html +2 -2
- package/dist-server/server/index.js +80 -6
- package/dist-server/server/index.js.map +1 -1
- package/dist-server/server/modules/orchestration/hermes/hermes.routes.js +39 -0
- package/dist-server/server/modules/orchestration/hermes/hermes.routes.js.map +1 -1
- package/package.json +1 -1
- package/scripts/hermes/configure-pixcode-mcp.mjs +87 -0
- package/scripts/hermes/pixcode-mcp-server.mjs +216 -0
- package/scripts/smoke/git-install-update.mjs +133 -54
- package/scripts/smoke/pixcode-workbench-1-48.mjs +15 -1
- package/scripts/smoke/vscode-workbench-polish.mjs +21 -2
- package/scripts/update-git-install.mjs +162 -4
- package/server/index.js +88 -6
- package/server/modules/orchestration/hermes/hermes.routes.ts +55 -0
- package/dist/assets/index-B3lN7dBd.css +0 -32
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
|
|
6
|
+
const hermesHome = process.env.HERMES_HOME || path.join(os.homedir(), '.hermes');
|
|
7
|
+
const configPath = path.join(hermesHome, 'config.yaml');
|
|
8
|
+
const appRoot = process.env.PIXCODE_APP_ROOT || process.cwd();
|
|
9
|
+
const baseUrl = process.env.PIXCODE_BASE_URL;
|
|
10
|
+
const apiKey = process.env.PIXCODE_API_KEY;
|
|
11
|
+
|
|
12
|
+
if (!baseUrl || !apiKey) {
|
|
13
|
+
process.stderr.write('PIXCODE_BASE_URL and PIXCODE_API_KEY are required for Pixcode MCP setup.\n');
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const mcpServerPath = path.join(appRoot, 'scripts', 'hermes', 'pixcode-mcp-server.mjs');
|
|
18
|
+
const block = [
|
|
19
|
+
' pixcode:',
|
|
20
|
+
' command: "node"',
|
|
21
|
+
' args:',
|
|
22
|
+
` - "${mcpServerPath.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`,
|
|
23
|
+
' env:',
|
|
24
|
+
` PIXCODE_BASE_URL: "${baseUrl.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`,
|
|
25
|
+
` PIXCODE_API_KEY: "${apiKey.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`,
|
|
26
|
+
' enabled: true',
|
|
27
|
+
' tools:',
|
|
28
|
+
' include:',
|
|
29
|
+
' - pixcode_list_projects',
|
|
30
|
+
' - pixcode_get_provider_status',
|
|
31
|
+
' - pixcode_open_cli_terminal',
|
|
32
|
+
' resources: false',
|
|
33
|
+
' prompts: false',
|
|
34
|
+
].join('\n');
|
|
35
|
+
|
|
36
|
+
function findRootKeyEnd(lines, startIndex) {
|
|
37
|
+
for (let index = startIndex + 1; index < lines.length; index += 1) {
|
|
38
|
+
if (/^\S[^:]*:\s*(?:#.*)?$/.test(lines[index])) {
|
|
39
|
+
return index;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return lines.length;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function findNestedKeyEnd(lines, startIndex, parentEnd) {
|
|
46
|
+
for (let index = startIndex + 1; index < parentEnd; index += 1) {
|
|
47
|
+
if (/^ \S[^:]*:\s*(?:#.*)?$/.test(lines[index])) {
|
|
48
|
+
return index;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return parentEnd;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function upsertPixcodeMcpConfig(rawConfig) {
|
|
55
|
+
const lines = rawConfig.split(/\r?\n/);
|
|
56
|
+
const mcpIndex = lines.findIndex((line) => /^mcp_servers:\s*(?:#.*)?$/.test(line));
|
|
57
|
+
|
|
58
|
+
if (mcpIndex === -1) {
|
|
59
|
+
const prefix = rawConfig.trim() ? `${rawConfig.replace(/\s*$/, '')}\n\n` : '';
|
|
60
|
+
return `${prefix}mcp_servers:\n${block}\n`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const mcpEnd = findRootKeyEnd(lines, mcpIndex);
|
|
64
|
+
const pixcodeIndex = lines.findIndex((line, index) => (
|
|
65
|
+
index > mcpIndex && index < mcpEnd && /^ pixcode:\s*(?:#.*)?$/.test(line)
|
|
66
|
+
));
|
|
67
|
+
|
|
68
|
+
if (pixcodeIndex === -1) {
|
|
69
|
+
lines.splice(mcpIndex + 1, 0, block);
|
|
70
|
+
return `${lines.join('\n').replace(/\s*$/, '')}\n`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const pixcodeEnd = findNestedKeyEnd(lines, pixcodeIndex, mcpEnd);
|
|
74
|
+
lines.splice(pixcodeIndex, pixcodeEnd - pixcodeIndex, block);
|
|
75
|
+
return `${lines.join('\n').replace(/\s*$/, '')}\n`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
fs.mkdirSync(hermesHome, { recursive: true });
|
|
79
|
+
const previous = fs.existsSync(configPath) ? fs.readFileSync(configPath, 'utf8') : '';
|
|
80
|
+
const next = upsertPixcodeMcpConfig(previous);
|
|
81
|
+
|
|
82
|
+
if (previous !== next) {
|
|
83
|
+
fs.writeFileSync(configPath, next);
|
|
84
|
+
process.stdout.write(`Pixcode MCP configured in ${configPath}\n`);
|
|
85
|
+
} else {
|
|
86
|
+
process.stdout.write(`Pixcode MCP already configured in ${configPath}\n`);
|
|
87
|
+
}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import readline from 'node:readline';
|
|
3
|
+
|
|
4
|
+
const baseUrl = (process.env.PIXCODE_BASE_URL || '').replace(/\/$/, '');
|
|
5
|
+
const apiKey = process.env.PIXCODE_API_KEY || '';
|
|
6
|
+
|
|
7
|
+
const tools = [
|
|
8
|
+
{
|
|
9
|
+
name: 'pixcode_list_projects',
|
|
10
|
+
description: 'List Pixcode workspaces/projects visible to this user, including display name, path, and file count when available.',
|
|
11
|
+
inputSchema: {
|
|
12
|
+
type: 'object',
|
|
13
|
+
properties: {},
|
|
14
|
+
additionalProperties: false,
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
name: 'pixcode_get_provider_status',
|
|
19
|
+
description: 'Get install/auth/version status for one Pixcode CLI provider.',
|
|
20
|
+
inputSchema: {
|
|
21
|
+
type: 'object',
|
|
22
|
+
properties: {
|
|
23
|
+
provider: {
|
|
24
|
+
type: 'string',
|
|
25
|
+
enum: ['claude', 'codex', 'cursor', 'gemini', 'qwen', 'opencode'],
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
required: ['provider'],
|
|
29
|
+
additionalProperties: false,
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: 'pixcode_open_cli_terminal',
|
|
34
|
+
description: 'Ask the open Pixcode workbench to open a visible CLI terminal for a provider in a project.',
|
|
35
|
+
inputSchema: {
|
|
36
|
+
type: 'object',
|
|
37
|
+
properties: {
|
|
38
|
+
provider: {
|
|
39
|
+
type: 'string',
|
|
40
|
+
enum: ['claude', 'codex', 'cursor', 'gemini', 'qwen', 'opencode'],
|
|
41
|
+
},
|
|
42
|
+
projectPath: {
|
|
43
|
+
type: 'string',
|
|
44
|
+
description: 'Absolute project path. Omit to use the currently selected Pixcode project.',
|
|
45
|
+
},
|
|
46
|
+
prompt: {
|
|
47
|
+
type: 'string',
|
|
48
|
+
description: 'Optional short reason shown to Pixcode for audit/display.',
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
required: ['provider'],
|
|
52
|
+
additionalProperties: false,
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
function send(payload) {
|
|
58
|
+
process.stdout.write(`${JSON.stringify(payload)}\n`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function textResult(text) {
|
|
62
|
+
return {
|
|
63
|
+
content: [
|
|
64
|
+
{
|
|
65
|
+
type: 'text',
|
|
66
|
+
text,
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function pixcodeFetch(endpoint, options = {}) {
|
|
73
|
+
if (!baseUrl || !apiKey) {
|
|
74
|
+
throw new Error('Pixcode MCP is missing PIXCODE_BASE_URL or PIXCODE_API_KEY.');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const response = await fetch(`${baseUrl}${endpoint}`, {
|
|
78
|
+
...options,
|
|
79
|
+
headers: {
|
|
80
|
+
Authorization: `Bearer ${apiKey}`,
|
|
81
|
+
'Content-Type': 'application/json',
|
|
82
|
+
...(options.headers || {}),
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
const text = await response.text();
|
|
86
|
+
let body = null;
|
|
87
|
+
try {
|
|
88
|
+
body = text ? JSON.parse(text) : null;
|
|
89
|
+
} catch {
|
|
90
|
+
body = text;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (!response.ok) {
|
|
94
|
+
throw new Error(`Pixcode API ${endpoint} failed with HTTP ${response.status}: ${typeof body === 'string' ? body : JSON.stringify(body)}`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return body;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async function callTool(name, args = {}) {
|
|
101
|
+
if (name === 'pixcode_list_projects') {
|
|
102
|
+
const projects = await pixcodeFetch('/api/projects');
|
|
103
|
+
const normalized = (Array.isArray(projects) ? projects : []).map((project) => ({
|
|
104
|
+
name: project.name,
|
|
105
|
+
displayName: project.displayName,
|
|
106
|
+
path: project.fullPath || project.path,
|
|
107
|
+
fileCount: project.fileCount ?? null,
|
|
108
|
+
}));
|
|
109
|
+
return textResult(JSON.stringify(normalized, null, 2));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (name === 'pixcode_get_provider_status') {
|
|
113
|
+
const provider = String(args.provider || '');
|
|
114
|
+
const body = await pixcodeFetch(`/api/providers/${encodeURIComponent(provider)}/auth/status?refresh=1`);
|
|
115
|
+
return textResult(JSON.stringify(body?.data ?? body, null, 2));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (name === 'pixcode_open_cli_terminal') {
|
|
119
|
+
const body = await pixcodeFetch('/api/orchestration/hermes/terminal-launches', {
|
|
120
|
+
method: 'POST',
|
|
121
|
+
body: JSON.stringify({
|
|
122
|
+
provider: args.provider,
|
|
123
|
+
projectPath: args.projectPath || null,
|
|
124
|
+
prompt: args.prompt || null,
|
|
125
|
+
}),
|
|
126
|
+
});
|
|
127
|
+
return textResult(JSON.stringify(body?.event ?? body, null, 2));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
throw new Error(`Unknown Pixcode MCP tool: ${name}`);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async function handleMessage(message) {
|
|
134
|
+
if (message.method === 'initialize') {
|
|
135
|
+
send({
|
|
136
|
+
jsonrpc: '2.0',
|
|
137
|
+
id: message.id,
|
|
138
|
+
result: {
|
|
139
|
+
protocolVersion: message.params?.protocolVersion || '2024-11-05',
|
|
140
|
+
capabilities: {
|
|
141
|
+
tools: {},
|
|
142
|
+
},
|
|
143
|
+
serverInfo: {
|
|
144
|
+
name: 'pixcode-mcp',
|
|
145
|
+
version: '1.0.0',
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (message.method === 'tools/list') {
|
|
153
|
+
send({
|
|
154
|
+
jsonrpc: '2.0',
|
|
155
|
+
id: message.id,
|
|
156
|
+
result: { tools },
|
|
157
|
+
});
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (message.method === 'tools/call') {
|
|
162
|
+
try {
|
|
163
|
+
const result = await callTool(message.params?.name, message.params?.arguments || {});
|
|
164
|
+
send({
|
|
165
|
+
jsonrpc: '2.0',
|
|
166
|
+
id: message.id,
|
|
167
|
+
result,
|
|
168
|
+
});
|
|
169
|
+
} catch (error) {
|
|
170
|
+
send({
|
|
171
|
+
jsonrpc: '2.0',
|
|
172
|
+
id: message.id,
|
|
173
|
+
error: {
|
|
174
|
+
code: -32000,
|
|
175
|
+
message: error instanceof Error ? error.message : String(error),
|
|
176
|
+
},
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (typeof message.id !== 'undefined') {
|
|
183
|
+
send({
|
|
184
|
+
jsonrpc: '2.0',
|
|
185
|
+
id: message.id,
|
|
186
|
+
error: {
|
|
187
|
+
code: -32601,
|
|
188
|
+
message: `Method not found: ${message.method}`,
|
|
189
|
+
},
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const rl = readline.createInterface({
|
|
195
|
+
input: process.stdin,
|
|
196
|
+
crlfDelay: Number.POSITIVE_INFINITY,
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
rl.on('line', (line) => {
|
|
200
|
+
if (!line.trim()) return;
|
|
201
|
+
|
|
202
|
+
void (async () => {
|
|
203
|
+
try {
|
|
204
|
+
await handleMessage(JSON.parse(line));
|
|
205
|
+
} catch (error) {
|
|
206
|
+
send({
|
|
207
|
+
jsonrpc: '2.0',
|
|
208
|
+
id: null,
|
|
209
|
+
error: {
|
|
210
|
+
code: -32700,
|
|
211
|
+
message: error instanceof Error ? error.message : String(error),
|
|
212
|
+
},
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
})();
|
|
216
|
+
});
|
|
@@ -65,20 +65,51 @@ assert.match(
|
|
|
65
65
|
|
|
66
66
|
assert.match(
|
|
67
67
|
updater,
|
|
68
|
-
/
|
|
69
|
-
'Safe updater should
|
|
68
|
+
/shouldRunNpmInstall/,
|
|
69
|
+
'Safe updater should decide whether dependency reconciliation is needed from changed files.',
|
|
70
70
|
);
|
|
71
71
|
|
|
72
72
|
assert.match(
|
|
73
73
|
updater,
|
|
74
|
-
/npm
|
|
75
|
-
'Safe updater should
|
|
74
|
+
/Dependencies unchanged; skipping npm install\./,
|
|
75
|
+
'Safe updater should skip npm install when package manifests did not change.',
|
|
76
76
|
);
|
|
77
77
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
78
|
+
assert.match(
|
|
79
|
+
updater,
|
|
80
|
+
/shouldRunBuild/,
|
|
81
|
+
'Safe updater should decide whether source rebuild is needed from changed files.',
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
assert.match(
|
|
85
|
+
updater,
|
|
86
|
+
/Build inputs unchanged; skipping build\./,
|
|
87
|
+
'Safe updater should skip build when only non-runtime files changed.',
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
function makeTempRepo(name) {
|
|
91
|
+
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), `pixcode-git-update-${name}-`));
|
|
92
|
+
const origin = path.join(tempRoot, 'origin.git');
|
|
93
|
+
const source = path.join(tempRoot, 'source');
|
|
94
|
+
const install = path.join(tempRoot, 'install');
|
|
95
|
+
|
|
96
|
+
fs.mkdirSync(source, { recursive: true });
|
|
97
|
+
run('git', ['init', '--bare', origin], tempRoot);
|
|
98
|
+
run('git', ['init', '-b', 'main'], source);
|
|
99
|
+
writePackage(source, '1.0.0');
|
|
100
|
+
fs.mkdirSync(path.join(source, 'src'), { recursive: true });
|
|
101
|
+
fs.writeFileSync(path.join(source, 'src', 'app.js'), 'old\n');
|
|
102
|
+
fs.writeFileSync(path.join(source, 'README.md'), 'old docs\n');
|
|
103
|
+
fs.writeFileSync(path.join(source, 'tracked.txt'), 'old\n');
|
|
104
|
+
run('git', ['add', '.'], source);
|
|
105
|
+
run('git', ['commit', '-m', 'initial'], source);
|
|
106
|
+
run('git', ['remote', 'add', 'origin', origin], source);
|
|
107
|
+
run('git', ['push', '-u', 'origin', 'main'], source);
|
|
108
|
+
run('git', ['symbolic-ref', 'HEAD', 'refs/heads/main'], origin);
|
|
109
|
+
run('git', ['clone', origin, install], tempRoot);
|
|
110
|
+
|
|
111
|
+
return { origin, source, install };
|
|
112
|
+
}
|
|
82
113
|
|
|
83
114
|
function run(command, args, cwd) {
|
|
84
115
|
const result = spawnSync(command, args, {
|
|
@@ -99,22 +130,24 @@ function run(command, args, cwd) {
|
|
|
99
130
|
`${command} ${args.join(' ')} failed\nstdout:\n${result.stdout}\nstderr:\n${result.stderr}`,
|
|
100
131
|
);
|
|
101
132
|
|
|
102
|
-
return result.stdout.trim();
|
|
133
|
+
return `${result.stdout}${result.stderr}`.trim();
|
|
103
134
|
}
|
|
104
135
|
|
|
105
|
-
function writePackage(version) {
|
|
136
|
+
function writePackage(root, version, dependencies = {}) {
|
|
106
137
|
fs.writeFileSync(
|
|
107
|
-
path.join(
|
|
138
|
+
path.join(root, 'package.json'),
|
|
108
139
|
JSON.stringify({
|
|
109
140
|
name: 'pixcode-update-smoke',
|
|
110
141
|
version,
|
|
111
142
|
scripts: {
|
|
143
|
+
preinstall: 'node -e "require(\\"node:fs\\").writeFileSync(\\"install-ran.txt\\", \\"install\\")"',
|
|
112
144
|
build: 'node -e "require(\\"node:fs\\").writeFileSync(\\"built.txt\\", \\"built\\")"',
|
|
113
145
|
},
|
|
146
|
+
dependencies,
|
|
114
147
|
}, null, 2),
|
|
115
148
|
);
|
|
116
149
|
fs.writeFileSync(
|
|
117
|
-
path.join(
|
|
150
|
+
path.join(root, 'package-lock.json'),
|
|
118
151
|
JSON.stringify({
|
|
119
152
|
name: 'pixcode-update-smoke',
|
|
120
153
|
version,
|
|
@@ -130,47 +163,93 @@ function writePackage(version) {
|
|
|
130
163
|
);
|
|
131
164
|
}
|
|
132
165
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
fs.writeFileSync(
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
run('git', ['
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
run(
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
fs.
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
166
|
+
{
|
|
167
|
+
const { source, install } = makeTempRepo('deps');
|
|
168
|
+
|
|
169
|
+
fs.mkdirSync(path.join(source, 'local-dep'), { recursive: true });
|
|
170
|
+
fs.writeFileSync(
|
|
171
|
+
path.join(source, 'local-dep', 'package.json'),
|
|
172
|
+
JSON.stringify({ name: 'pixcode-smoke-local-dep', version: '1.0.0' }, null, 2),
|
|
173
|
+
);
|
|
174
|
+
writePackage(source, '1.0.1', { 'pixcode-smoke-local-dep': 'file:./local-dep' });
|
|
175
|
+
fs.writeFileSync(path.join(source, 'tracked.txt'), 'new\n');
|
|
176
|
+
run('git', ['add', '.'], source);
|
|
177
|
+
run('git', ['commit', '-m', 'dependency update'], source);
|
|
178
|
+
run('git', ['push', 'origin', 'main'], source);
|
|
179
|
+
|
|
180
|
+
fs.writeFileSync(path.join(install, 'tracked.txt'), 'local dirty change\n');
|
|
181
|
+
fs.writeFileSync(path.join(install, 'untracked.txt'), 'local untracked change\n');
|
|
182
|
+
run(process.execPath, [updaterPath], install);
|
|
183
|
+
|
|
184
|
+
assert.equal(
|
|
185
|
+
JSON.parse(fs.readFileSync(path.join(install, 'package.json'), 'utf8')).version,
|
|
186
|
+
'1.0.1',
|
|
187
|
+
'Safe updater should fast-forward the install checkout.',
|
|
188
|
+
);
|
|
189
|
+
assert.equal(
|
|
190
|
+
fs.readFileSync(path.join(install, 'tracked.txt'), 'utf8'),
|
|
191
|
+
'new\n',
|
|
192
|
+
'Safe updater should apply the remote tracked file after stashing local edits.',
|
|
193
|
+
);
|
|
194
|
+
assert.match(
|
|
195
|
+
run('git', ['stash', 'list'], install),
|
|
196
|
+
/pixcode-auto-update-/,
|
|
197
|
+
'Safe updater should leave local dirty files recoverable in git stash.',
|
|
198
|
+
);
|
|
199
|
+
assert.equal(
|
|
200
|
+
fs.readFileSync(path.join(install, 'install-ran.txt'), 'utf8'),
|
|
201
|
+
'install',
|
|
202
|
+
'Dependency updates should run npm install.',
|
|
203
|
+
);
|
|
204
|
+
assert.equal(
|
|
205
|
+
fs.readFileSync(path.join(install, 'built.txt'), 'utf8'),
|
|
206
|
+
'built',
|
|
207
|
+
'Safe updater should run the repository build after dependency updates.',
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
{
|
|
212
|
+
const { source, install } = makeTempRepo('source');
|
|
213
|
+
|
|
214
|
+
fs.writeFileSync(path.join(source, 'src', 'app.js'), 'new source\n');
|
|
215
|
+
run('git', ['add', '.'], source);
|
|
216
|
+
run('git', ['commit', '-m', 'source update'], source);
|
|
217
|
+
run('git', ['push', 'origin', 'main'], source);
|
|
218
|
+
|
|
219
|
+
run(process.execPath, [updaterPath], install);
|
|
220
|
+
|
|
221
|
+
assert.equal(
|
|
222
|
+
fs.existsSync(path.join(install, 'install-ran.txt')),
|
|
223
|
+
false,
|
|
224
|
+
'Source-only updates should skip npm install.',
|
|
225
|
+
);
|
|
226
|
+
assert.equal(
|
|
227
|
+
fs.readFileSync(path.join(install, 'built.txt'), 'utf8'),
|
|
228
|
+
'built',
|
|
229
|
+
'Source-only updates should produce a fresh build output.',
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
{
|
|
234
|
+
const { source, install } = makeTempRepo('docs');
|
|
235
|
+
|
|
236
|
+
fs.writeFileSync(path.join(source, 'README.md'), 'new docs\n');
|
|
237
|
+
run('git', ['add', '.'], source);
|
|
238
|
+
run('git', ['commit', '-m', 'docs update'], source);
|
|
239
|
+
run('git', ['push', 'origin', 'main'], source);
|
|
240
|
+
|
|
241
|
+
run(process.execPath, [updaterPath], install);
|
|
242
|
+
|
|
243
|
+
assert.equal(
|
|
244
|
+
fs.existsSync(path.join(install, 'install-ran.txt')),
|
|
245
|
+
false,
|
|
246
|
+
'Docs-only updates should skip npm install.',
|
|
247
|
+
);
|
|
248
|
+
assert.equal(
|
|
249
|
+
fs.existsSync(path.join(install, 'built.txt')),
|
|
250
|
+
false,
|
|
251
|
+
'Docs-only updates should not create build output.',
|
|
252
|
+
);
|
|
253
|
+
}
|
|
175
254
|
|
|
176
255
|
console.log('git install update smoke passed');
|
|
@@ -18,6 +18,7 @@ const serverIndex = read('server/index.js');
|
|
|
18
18
|
const hermesRoutes = read('server/modules/orchestration/hermes/hermes.routes.ts');
|
|
19
19
|
const shellTerminal = read('src/components/shell/hooks/useShellTerminal.ts');
|
|
20
20
|
const gitPanelHeader = read('src/components/git-panel/view/GitPanelHeader.tsx');
|
|
21
|
+
const themeContext = read('src/contexts/ThemeContext.jsx');
|
|
21
22
|
|
|
22
23
|
assert.match(
|
|
23
24
|
preferenceHook,
|
|
@@ -52,13 +53,25 @@ assert.match(workbench, /onClose=\{closeTerminal\}/, 'Closing the workbench term
|
|
|
52
53
|
assert.match(workbench, /WorkbenchCliPanelToolbar/, 'CLI terminal should keep history and new-session actions visible.');
|
|
53
54
|
assert.match(workbench, /WORKBENCH_CLI_STATE_STORAGE_KEY/, 'CLI terminal should remember per-project open state across workspace switches.');
|
|
54
55
|
assert.match(workbench, /function WorkbenchBottomTerminal/, 'Terminal activity should render as a bottom plain-shell panel.');
|
|
56
|
+
assert.match(workbench, /BOTTOM_TERMINAL_MIN_HEIGHT/, 'Bottom terminal should support height resizing.');
|
|
57
|
+
assert.match(workbench, /isBottomTerminalMinimized/, 'Bottom terminal should support minimizing without closing.');
|
|
55
58
|
assert.match(workbench, /isPlainShell/, 'Bottom terminal should open the selected project folder without starting the selected AI CLI.');
|
|
56
|
-
assert.match(workbench, /
|
|
59
|
+
assert.match(workbench, /HERMES_AGENT_START_COMMAND/, 'Hermes Agent should launch from the bottom terminal through a server-side sentinel.');
|
|
60
|
+
assert.doesNotMatch(workbench, /Project-scoped agent terminal\. Installs Hermes when missing/, 'Right CLI panel should not show the old Hermes card.');
|
|
61
|
+
assert.match(workbench, /shrinkCliPanel/, 'Right CLI panel should expose a shrink action.');
|
|
62
|
+
assert.match(workbench, /expandCliPanel/, 'Right CLI panel should expose an expand action.');
|
|
63
|
+
assert.match(workbench, /vscodeWorkbench\.welcome\.openProject/, 'Workbench welcome should expose a simple Open Project action.');
|
|
64
|
+
assert.match(workbench, /vscodeWorkbench\.welcome\.cloneProject/, 'Workbench welcome should expose a simple Clone action.');
|
|
65
|
+
assert.match(workbench, /vscodeWorkbench\.welcome\.startHermes/, 'Workbench welcome should expose a Hermes start action.');
|
|
66
|
+
assert.match(workbench, /DarkModeToggle/, 'Workbench welcome should expose a dark-mode toggle.');
|
|
67
|
+
assert.match(themeContext, /return true;/, 'Pixcode should default new installs to dark mode.');
|
|
57
68
|
assert.match(workbench, /openNewCliSessionPicker/, 'CLI terminal plus should return to provider selection before starting a fresh session.');
|
|
58
69
|
assert.match(workbench, /terminateCurrentCliSession\(selectedProvider\)/, 'CLI terminal plus should terminate the existing provider PTY before showing selection.');
|
|
59
70
|
assert.match(workbench, /forceNewSession=\{terminalLaunch\.forceNewSession\}/, 'Fresh CLI sessions should bypass the cached default PTY.');
|
|
60
71
|
assert.match(serverIndex, /\/api\/shell\/sessions\/terminate/, 'Backend should expose an authenticated endpoint to terminate cached provider PTYs immediately.');
|
|
61
72
|
assert.match(serverIndex, /isPlainShell && !initialCommand/, 'Backend should spawn an interactive plain shell when no terminal command is provided.');
|
|
73
|
+
assert.match(serverIndex, /pixcode:hermes:start/, 'Backend should expand Hermes terminal sentinels on the server host.');
|
|
74
|
+
assert.doesNotMatch(serverIndex, /iex \(irm https:\/\/raw\.githubusercontent\.com\/NousResearch\/hermes-agent\/main\/scripts\/install\.ps1\)/, 'Windows Hermes install should avoid the old inline iex pattern.');
|
|
62
75
|
assert.doesNotMatch(shellTerminal, /new WebglAddon\(\)/, 'Workbench terminal should use the stable xterm renderer.');
|
|
63
76
|
assert.match(workbench, /setActivityPanel\('explorer'\)/, 'Selecting a project should return the side panel to Explorer.');
|
|
64
77
|
assert.match(gitPanelHeader, /compact/, 'Workbench Source Control should have compact icon-only controls.');
|
|
@@ -80,6 +93,7 @@ assert.doesNotMatch(workbench, /tabs\.orchestration/, 'Workbench menus should no
|
|
|
80
93
|
assert.match(serverIndex, /app\.use\('\/hermes', createHermesTaskRouter\(\)\)/, 'Internal task router should be mounted behind Hermes.');
|
|
81
94
|
assert.doesNotMatch(serverIndex, /app\.use\('\/a2a'/, 'Server should not expose the old A2A route.');
|
|
82
95
|
assert.match(hermesRoutes, /createHermesRouter/, 'Hermes should have a dedicated orchestration API router.');
|
|
96
|
+
assert.match(hermesRoutes, /terminal-launches/, 'Hermes MCP should be able to request visible Pixcode CLI terminal launches.');
|
|
83
97
|
assert.match(serverIndex, /forceNewSession/, 'Shell backend should support explicit fresh-session launches from the workbench.');
|
|
84
98
|
assert.match(serverIndex, /killProviderPtySessions/, 'Shell backend should terminate old provider PTYs when a fresh CLI session is requested.');
|
|
85
99
|
|
|
@@ -51,6 +51,15 @@ assert.match(
|
|
|
51
51
|
'Workbench center should show a project landing page instead of a blank editor when no project is selected.',
|
|
52
52
|
);
|
|
53
53
|
|
|
54
|
+
for (const token of [
|
|
55
|
+
'vscodeWorkbench.welcome.openProject',
|
|
56
|
+
'vscodeWorkbench.welcome.cloneProject',
|
|
57
|
+
'vscodeWorkbench.welcome.startHermes',
|
|
58
|
+
'DarkModeToggle',
|
|
59
|
+
]) {
|
|
60
|
+
assert.match(workbench, new RegExp(token.replaceAll('.', '\\.')), `Workbench welcome should include ${token}.`);
|
|
61
|
+
}
|
|
62
|
+
|
|
54
63
|
assert.match(
|
|
55
64
|
workbench,
|
|
56
65
|
/function WorkbenchCliPanel/,
|
|
@@ -244,6 +253,10 @@ assert.match(
|
|
|
244
253
|
'Terminal activity should open a VS Code-style bottom terminal instead of the provider CLI picker.',
|
|
245
254
|
);
|
|
246
255
|
|
|
256
|
+
for (const token of ['BOTTOM_TERMINAL_MIN_HEIGHT', 'isBottomTerminalMinimized', 'shrinkCliPanel', 'expandCliPanel']) {
|
|
257
|
+
assert.match(workbench, new RegExp(token), `Workbench should include ${token}.`);
|
|
258
|
+
}
|
|
259
|
+
|
|
247
260
|
assert.match(
|
|
248
261
|
workbench,
|
|
249
262
|
/isPlainShell/,
|
|
@@ -252,8 +265,14 @@ assert.match(
|
|
|
252
265
|
|
|
253
266
|
assert.match(
|
|
254
267
|
workbench,
|
|
255
|
-
/
|
|
256
|
-
'
|
|
268
|
+
/HERMES_AGENT_START_COMMAND/,
|
|
269
|
+
'Hermes Agent should launch through the bottom terminal with a server-side command sentinel.',
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
assert.doesNotMatch(
|
|
273
|
+
workbench,
|
|
274
|
+
/Project-scoped agent terminal\. Installs Hermes when missing/,
|
|
275
|
+
'Right CLI picker should not show the old Hermes install card.',
|
|
257
276
|
);
|
|
258
277
|
|
|
259
278
|
assert.match(
|