@serviceme/devtools-core 0.1.5 → 0.1.7
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/index.d.mts +194 -2
- package/dist/index.d.ts +194 -2
- package/dist/index.js +716 -115
- package/dist/index.mjs +708 -115
- package/package.json +3 -2
package/dist/index.mjs
CHANGED
|
@@ -5,10 +5,316 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
|
|
|
5
5
|
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
6
|
});
|
|
7
7
|
|
|
8
|
+
// src/agents/AgentCatalogClient.ts
|
|
9
|
+
import { createServicemeError } from "@serviceme/devtools-protocol";
|
|
10
|
+
var AgentCatalogClient = class {
|
|
11
|
+
constructor(options = {}) {
|
|
12
|
+
this.fetchImpl = options.fetchImpl ?? fetch;
|
|
13
|
+
this.baseUrl = options.baseUrl;
|
|
14
|
+
}
|
|
15
|
+
async getCatalog() {
|
|
16
|
+
if (!this.baseUrl) {
|
|
17
|
+
return {
|
|
18
|
+
agents: [],
|
|
19
|
+
fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
const response = await this.fetchImpl(
|
|
23
|
+
`${this.baseUrl}/api/v1/marketplace/agents`
|
|
24
|
+
);
|
|
25
|
+
if (!response.ok) {
|
|
26
|
+
throw new Error(`Failed to fetch agents catalog: ${response.status}`);
|
|
27
|
+
}
|
|
28
|
+
const data = await response.json();
|
|
29
|
+
return {
|
|
30
|
+
agents: data.agents ?? [],
|
|
31
|
+
fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
async downloadAgent(remoteId) {
|
|
35
|
+
if (!this.baseUrl) {
|
|
36
|
+
throw createServicemeError(
|
|
37
|
+
"workspace_not_found",
|
|
38
|
+
"Agent catalog baseUrl is not configured."
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
const response = await this.fetchImpl(
|
|
42
|
+
`${this.baseUrl}/api/v1/marketplace/agents/download/${remoteId}`
|
|
43
|
+
);
|
|
44
|
+
if (!response.ok) {
|
|
45
|
+
if (response.status === 404) {
|
|
46
|
+
throw createServicemeError(
|
|
47
|
+
"not_found",
|
|
48
|
+
`Agent '${remoteId}' not found`
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
throw new Error(
|
|
52
|
+
`Failed to download agent ${remoteId}: ${response.status}`
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
const payload = await response.json();
|
|
56
|
+
return payload.data?.files ?? [];
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// src/permissions/agent-permissions.ts
|
|
61
|
+
var TOOL_RISK_MAP = {
|
|
62
|
+
shell: "high",
|
|
63
|
+
terminal: "high",
|
|
64
|
+
run_in_terminal: "high",
|
|
65
|
+
execution_subagent: "high",
|
|
66
|
+
filesystem: "medium",
|
|
67
|
+
fetch: "medium",
|
|
68
|
+
fetch_webpage: "medium",
|
|
69
|
+
create_file: "medium",
|
|
70
|
+
replace_string_in_file: "medium",
|
|
71
|
+
multi_replace_string_in_file: "medium",
|
|
72
|
+
read_file: "low",
|
|
73
|
+
search: "low",
|
|
74
|
+
grep_search: "low",
|
|
75
|
+
file_search: "low",
|
|
76
|
+
semantic_search: "low",
|
|
77
|
+
list_dir: "low"
|
|
78
|
+
};
|
|
79
|
+
var FRONTMATTER_REGEX = /^---\r?\n([\s\S]*?)\r?\n---/;
|
|
80
|
+
var TOOLS_LINE_REGEX = /^tools:\s*$/m;
|
|
81
|
+
var TOOLS_INLINE_REGEX = /^tools:\s*\[([^\]]*)\]/m;
|
|
82
|
+
var LIST_ITEM_REGEX = /^\s*-\s+(.+)$/;
|
|
83
|
+
function parseAgentToolPermissions(content) {
|
|
84
|
+
const fmMatch = content.match(FRONTMATTER_REGEX);
|
|
85
|
+
if (!fmMatch?.[1]) return [];
|
|
86
|
+
const frontmatter = fmMatch[1];
|
|
87
|
+
const inlineMatch = frontmatter.match(TOOLS_INLINE_REGEX);
|
|
88
|
+
if (inlineMatch?.[1] != null) {
|
|
89
|
+
const raw = inlineMatch[1];
|
|
90
|
+
return raw.split(",").map((t) => t.trim()).filter(Boolean).map((tool) => ({
|
|
91
|
+
tool,
|
|
92
|
+
riskLevel: TOOL_RISK_MAP[tool] ?? "medium"
|
|
93
|
+
}));
|
|
94
|
+
}
|
|
95
|
+
const blockMatch = frontmatter.match(TOOLS_LINE_REGEX);
|
|
96
|
+
if (!blockMatch?.[0]) return [];
|
|
97
|
+
const toolsStartIndex = frontmatter.indexOf(blockMatch[0]) + blockMatch[0].length;
|
|
98
|
+
const remaining = frontmatter.slice(toolsStartIndex);
|
|
99
|
+
const lines = remaining.split(/\r?\n/);
|
|
100
|
+
const tools = [];
|
|
101
|
+
for (const line of lines) {
|
|
102
|
+
const itemMatch = line.match(LIST_ITEM_REGEX);
|
|
103
|
+
if (itemMatch?.[1]) {
|
|
104
|
+
const tool = itemMatch[1].trim();
|
|
105
|
+
tools.push({ tool, riskLevel: TOOL_RISK_MAP[tool] ?? "medium" });
|
|
106
|
+
} else if (line.trim() !== "" && !line.startsWith(" ") && !line.startsWith(" ")) {
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return tools;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// src/agents/AgentReconciler.ts
|
|
114
|
+
var AgentReconciler = class {
|
|
115
|
+
constructor(deps) {
|
|
116
|
+
this.deps = deps;
|
|
117
|
+
}
|
|
118
|
+
async mutate(request) {
|
|
119
|
+
if (request.targetScope !== "workspace" && request.targetScope !== "user") {
|
|
120
|
+
throw new Error(`Invalid target scope: ${String(request.targetScope)}`);
|
|
121
|
+
}
|
|
122
|
+
if (request.action === "uninstall" || request.action === "move" || request.action === "removeExternal") {
|
|
123
|
+
return {
|
|
124
|
+
status: "success",
|
|
125
|
+
changed: true,
|
|
126
|
+
message: `Agent ${request.action} completed.`
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
if (request.action !== "install") {
|
|
130
|
+
return {
|
|
131
|
+
status: "blocked",
|
|
132
|
+
changed: false,
|
|
133
|
+
message: `Agent action is not supported by bridge reconciler: ${request.action}`
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
const catalog = await this.deps.catalogClient.getCatalog();
|
|
137
|
+
const remoteAgent = catalog.agents.find(
|
|
138
|
+
(agent) => this.deps.agentStore.normalizeRemoteAgentId(agent.id) === request.agentId
|
|
139
|
+
);
|
|
140
|
+
if (!remoteAgent) {
|
|
141
|
+
return {
|
|
142
|
+
status: "blocked",
|
|
143
|
+
changed: false,
|
|
144
|
+
message: "Agent not found in catalog."
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
if (!request.confirmed && this.hasHighRiskTool(remoteAgent.tools)) {
|
|
148
|
+
return {
|
|
149
|
+
status: "requires_confirmation",
|
|
150
|
+
changed: false,
|
|
151
|
+
message: "This agent uses high-risk tools that require confirmation.",
|
|
152
|
+
tools: remoteAgent.tools
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
return {
|
|
156
|
+
status: "success",
|
|
157
|
+
changed: true,
|
|
158
|
+
message: "Agent installed."
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
getPermissionSummary(agentId, agentName, content) {
|
|
162
|
+
const tools = parseAgentToolPermissions(content);
|
|
163
|
+
return {
|
|
164
|
+
agentId,
|
|
165
|
+
agentName,
|
|
166
|
+
tools,
|
|
167
|
+
highRiskCount: tools.filter((tool) => tool.riskLevel === "high").length,
|
|
168
|
+
mediumRiskCount: tools.filter((tool) => tool.riskLevel === "medium").length,
|
|
169
|
+
lowRiskCount: tools.filter((tool) => tool.riskLevel === "low").length
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
hasHighRiskTool(tools) {
|
|
173
|
+
return tools.some((tool) => TOOL_RISK_MAP[tool] === "high");
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
// src/agents/AgentStore.ts
|
|
178
|
+
import * as fs from "fs/promises";
|
|
179
|
+
import * as path from "path";
|
|
180
|
+
var WORKSPACE_AGENTS_ROOT_RELATIVE = ".github/agents";
|
|
181
|
+
var WORKSPACE_AGENTS_STATE_RELATIVE = ".github/.ms-devtools-agents.yml";
|
|
182
|
+
var SAFE_LOCAL_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
183
|
+
function assertSafeLocalAgentId(agentId) {
|
|
184
|
+
if (typeof agentId !== "string" || agentId.length === 0 || agentId === "." || agentId === ".." || agentId.includes("/") || agentId.includes("\\") || !SAFE_LOCAL_ID_PATTERN.test(agentId)) {
|
|
185
|
+
throw new Error(`Invalid agent id: ${agentId}`);
|
|
186
|
+
}
|
|
187
|
+
return agentId;
|
|
188
|
+
}
|
|
189
|
+
var AgentStore = class {
|
|
190
|
+
constructor(options) {
|
|
191
|
+
this.workspacePath = options.workspacePath;
|
|
192
|
+
this.userAgentsRoot = options.userAgentsRoot;
|
|
193
|
+
this.fileSystem = options.fileSystem ?? fs;
|
|
194
|
+
this.schemaVersion = options.schemaVersion ?? 1;
|
|
195
|
+
}
|
|
196
|
+
normalizeRemoteAgentId(remoteId) {
|
|
197
|
+
if (remoteId.startsWith("official/")) {
|
|
198
|
+
return assertSafeLocalAgentId(remoteId.slice("official/".length));
|
|
199
|
+
}
|
|
200
|
+
if (remoteId.startsWith("community/")) {
|
|
201
|
+
const lastSlash = remoteId.lastIndexOf("/");
|
|
202
|
+
return assertSafeLocalAgentId(remoteId.slice(lastSlash + 1));
|
|
203
|
+
}
|
|
204
|
+
return assertSafeLocalAgentId(remoteId);
|
|
205
|
+
}
|
|
206
|
+
getWorkspaceAgentsRootPath() {
|
|
207
|
+
return WORKSPACE_AGENTS_ROOT_RELATIVE;
|
|
208
|
+
}
|
|
209
|
+
getWorkspaceStateFilePath() {
|
|
210
|
+
return WORKSPACE_AGENTS_STATE_RELATIVE;
|
|
211
|
+
}
|
|
212
|
+
getUserAgentsRootPath() {
|
|
213
|
+
return this.userAgentsRoot;
|
|
214
|
+
}
|
|
215
|
+
async listWorkspaceAgentIds() {
|
|
216
|
+
return this.listAgentIds(
|
|
217
|
+
path.join(this.workspacePath, WORKSPACE_AGENTS_ROOT_RELATIVE)
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
async listUserAgentIds() {
|
|
221
|
+
return this.listAgentIds(this.userAgentsRoot);
|
|
222
|
+
}
|
|
223
|
+
async readState() {
|
|
224
|
+
try {
|
|
225
|
+
const raw = await this.fileSystem.readFile(
|
|
226
|
+
path.join(this.workspacePath, WORKSPACE_AGENTS_STATE_RELATIVE),
|
|
227
|
+
"utf-8"
|
|
228
|
+
);
|
|
229
|
+
const parsed = JSON.parse(raw);
|
|
230
|
+
if (typeof parsed.schemaVersion !== "number" || !Array.isArray(parsed.installedAgents)) {
|
|
231
|
+
return null;
|
|
232
|
+
}
|
|
233
|
+
return parsed;
|
|
234
|
+
} catch {
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
async writeState(state) {
|
|
239
|
+
const statePath = path.join(
|
|
240
|
+
this.workspacePath,
|
|
241
|
+
WORKSPACE_AGENTS_STATE_RELATIVE
|
|
242
|
+
);
|
|
243
|
+
await this.fileSystem.mkdir(path.dirname(statePath), { recursive: true });
|
|
244
|
+
await this.fileSystem.writeFile(
|
|
245
|
+
statePath,
|
|
246
|
+
JSON.stringify(state, null, 2),
|
|
247
|
+
"utf-8"
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
async addInstalledAgent(entry) {
|
|
251
|
+
const state = await this.readState() ?? {
|
|
252
|
+
schemaVersion: this.schemaVersion,
|
|
253
|
+
installedAgents: []
|
|
254
|
+
};
|
|
255
|
+
state.installedAgents = state.installedAgents.filter(
|
|
256
|
+
(agent) => agent.id !== entry.id
|
|
257
|
+
);
|
|
258
|
+
state.installedAgents.push(entry);
|
|
259
|
+
await this.writeState(state);
|
|
260
|
+
}
|
|
261
|
+
async removeInstalledAgent(agentId) {
|
|
262
|
+
const state = await this.readState();
|
|
263
|
+
if (!state) {
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
state.installedAgents = state.installedAgents.filter(
|
|
267
|
+
(agent) => agent.id !== agentId
|
|
268
|
+
);
|
|
269
|
+
await this.writeState(state);
|
|
270
|
+
}
|
|
271
|
+
async writeAgentFiles(agentId, scope, files) {
|
|
272
|
+
const root = scope === "workspace" ? path.join(this.workspacePath, WORKSPACE_AGENTS_ROOT_RELATIVE) : this.userAgentsRoot;
|
|
273
|
+
const firstFile = files[0];
|
|
274
|
+
const isSingleFlatFile = files.length === 1 && firstFile !== void 0 && firstFile.path === `${agentId}.agent.md` && !firstFile.path.includes("/");
|
|
275
|
+
const targetDir = isSingleFlatFile ? root : path.join(root, agentId);
|
|
276
|
+
await this.fileSystem.mkdir(targetDir, { recursive: true });
|
|
277
|
+
for (const file of files) {
|
|
278
|
+
const filePath = path.join(targetDir, file.path);
|
|
279
|
+
await this.fileSystem.mkdir(path.dirname(filePath), { recursive: true });
|
|
280
|
+
await this.fileSystem.writeFile(filePath, file.content, "utf-8");
|
|
281
|
+
if (file.executable) {
|
|
282
|
+
try {
|
|
283
|
+
await this.fileSystem.chmod(filePath, 493);
|
|
284
|
+
} catch {
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
async listAgentIds(dir) {
|
|
290
|
+
try {
|
|
291
|
+
const entries = await this.fileSystem.readdir(dir, {
|
|
292
|
+
withFileTypes: true
|
|
293
|
+
});
|
|
294
|
+
const ids = [];
|
|
295
|
+
for (const entry of entries) {
|
|
296
|
+
if (entry.name.startsWith(".")) {
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
if (entry.isDirectory()) {
|
|
300
|
+
ids.push(entry.name);
|
|
301
|
+
continue;
|
|
302
|
+
}
|
|
303
|
+
if (entry.isFile() && entry.name.endsWith(".agent.md")) {
|
|
304
|
+
ids.push(entry.name.replace(/\.agent\.md$/, ""));
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
return ids.sort();
|
|
308
|
+
} catch {
|
|
309
|
+
return [];
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
};
|
|
313
|
+
|
|
8
314
|
// src/copilot/doctor.ts
|
|
9
315
|
import {
|
|
10
316
|
COPILOT_ERROR_CODES,
|
|
11
|
-
createServicemeError
|
|
317
|
+
createServicemeError as createServicemeError2
|
|
12
318
|
} from "@serviceme/devtools-protocol";
|
|
13
319
|
|
|
14
320
|
// src/process/runCommand.ts
|
|
@@ -153,13 +459,13 @@ async function copilotDoctor() {
|
|
|
153
459
|
return { installed: true, version, authenticated };
|
|
154
460
|
}
|
|
155
461
|
function createCopilotNotInstalledError() {
|
|
156
|
-
return
|
|
462
|
+
return createServicemeError2(
|
|
157
463
|
COPILOT_ERROR_CODES.NOT_INSTALLED,
|
|
158
464
|
"GitHub Copilot CLI is not installed. Visit https://docs.github.com/en/copilot/using-github-copilot/using-github-copilot-in-the-command-line to install."
|
|
159
465
|
);
|
|
160
466
|
}
|
|
161
467
|
function createCopilotAuthRequiredError() {
|
|
162
|
-
return
|
|
468
|
+
return createServicemeError2(
|
|
163
469
|
COPILOT_ERROR_CODES.AUTH_REQUIRED,
|
|
164
470
|
"GitHub Copilot CLI requires authentication. Run `copilot auth login` to sign in."
|
|
165
471
|
);
|
|
@@ -168,7 +474,7 @@ function createCopilotAuthRequiredError() {
|
|
|
168
474
|
// src/copilot/prompt.ts
|
|
169
475
|
import {
|
|
170
476
|
COPILOT_ERROR_CODES as COPILOT_ERROR_CODES2,
|
|
171
|
-
createServicemeError as
|
|
477
|
+
createServicemeError as createServicemeError3
|
|
172
478
|
} from "@serviceme/devtools-protocol";
|
|
173
479
|
var COPILOT_COMMAND2 = "copilot";
|
|
174
480
|
var DEFAULT_TIMEOUT_MS = 12e4;
|
|
@@ -203,7 +509,7 @@ async function copilotPrompt(options) {
|
|
|
203
509
|
});
|
|
204
510
|
const output = stripAnsi(result.stdout);
|
|
205
511
|
if (output.includes("I'm sorry, but I cannot assist") && result.stderr?.includes("Stream completed without a response")) {
|
|
206
|
-
throw
|
|
512
|
+
throw createServicemeError3(
|
|
207
513
|
COPILOT_ERROR_CODES2.AUTH_REQUIRED,
|
|
208
514
|
"Copilot CLI returned a refusal response. This typically indicates an expired authentication token. Run `gh auth login` to re-authenticate.",
|
|
209
515
|
{ exitCode: 0, output, stderr: result.stderr }
|
|
@@ -213,7 +519,7 @@ async function copilotPrompt(options) {
|
|
|
213
519
|
} catch (error) {
|
|
214
520
|
const err = error;
|
|
215
521
|
if (err.code === "ETIMEDOUT") {
|
|
216
|
-
throw
|
|
522
|
+
throw createServicemeError3(
|
|
217
523
|
COPILOT_ERROR_CODES2.TIMEOUT,
|
|
218
524
|
`Copilot prompt timed out after ${String(timeout)}ms.`
|
|
219
525
|
);
|
|
@@ -222,7 +528,7 @@ async function copilotPrompt(options) {
|
|
|
222
528
|
const output = stripAnsi(err.stdout ?? "");
|
|
223
529
|
const stderr = err.stderr ?? err.message ?? "";
|
|
224
530
|
if (output.includes("I'm sorry, but I cannot assist") || stderr.includes("Stream completed without a response")) {
|
|
225
|
-
throw
|
|
531
|
+
throw createServicemeError3(
|
|
226
532
|
COPILOT_ERROR_CODES2.AUTH_REQUIRED,
|
|
227
533
|
"Copilot CLI returned a refusal response. This typically indicates an expired authentication token. Run `gh auth login` to re-authenticate.",
|
|
228
534
|
{ exitCode, output, stderr }
|
|
@@ -231,7 +537,7 @@ async function copilotPrompt(options) {
|
|
|
231
537
|
if (exitCode !== 0 && output) {
|
|
232
538
|
return { output, exitCode };
|
|
233
539
|
}
|
|
234
|
-
throw
|
|
540
|
+
throw createServicemeError3(
|
|
235
541
|
COPILOT_ERROR_CODES2.EXECUTION_FAILED,
|
|
236
542
|
stderr || `Copilot exited with code ${String(exitCode)}.`,
|
|
237
543
|
{ exitCode, stderr }
|
|
@@ -241,14 +547,20 @@ async function copilotPrompt(options) {
|
|
|
241
547
|
|
|
242
548
|
// src/env/environmentInspector.ts
|
|
243
549
|
import {
|
|
244
|
-
createServicemeError as
|
|
550
|
+
createServicemeError as createServicemeError4,
|
|
245
551
|
KNOWN_ENVIRONMENT_TOOLS
|
|
246
552
|
} from "@serviceme/devtools-protocol";
|
|
247
553
|
var DEFAULT_TOOL_CHECK_TIMEOUT_MS = 5e3;
|
|
248
554
|
var TOOL_CHECK_TIMEOUT_MS = {
|
|
249
555
|
nuget: 12e3,
|
|
250
556
|
nvm: 8e3,
|
|
251
|
-
dotnet: 8e3
|
|
557
|
+
dotnet: 8e3,
|
|
558
|
+
// nvm4w/npm shims and globally-installed .cmd tools (pnpm, nrm) are slow to
|
|
559
|
+
// resolve on cold PATHs. Give them extra headroom so the version probe
|
|
560
|
+
// doesn't fall through to the generic 5s default.
|
|
561
|
+
npm: 15e3,
|
|
562
|
+
pnpm: 15e3,
|
|
563
|
+
nrm: 15e3
|
|
252
564
|
};
|
|
253
565
|
var ERROR_CODE_NOT_FOUND = 127;
|
|
254
566
|
var ERROR_CODE_TIMEOUT = "ETIMEDOUT";
|
|
@@ -263,7 +575,7 @@ var EnvironmentInspector = class {
|
|
|
263
575
|
}
|
|
264
576
|
async checkTool(toolName) {
|
|
265
577
|
if (!/^[a-zA-Z0-9-]+$/.test(toolName)) {
|
|
266
|
-
throw
|
|
578
|
+
throw createServicemeError4("invalid_params", "Invalid tool name.");
|
|
267
579
|
}
|
|
268
580
|
try {
|
|
269
581
|
if (toolName === "nvm") {
|
|
@@ -295,8 +607,29 @@ var EnvironmentInspector = class {
|
|
|
295
607
|
return void 0;
|
|
296
608
|
}
|
|
297
609
|
}
|
|
610
|
+
/**
|
|
611
|
+
* On Windows, prefer the `.cmd` shim for npm/pnpm/nrm. nvm4w registers
|
|
612
|
+
* BOTH an extensionless entry (pointing to node.exe) and a `<tool>.cmd`
|
|
613
|
+
* wrapper; `where` returns them in that order, and the extensionless one
|
|
614
|
+
* would just print node's own version.
|
|
615
|
+
*/
|
|
616
|
+
async getToolShimPath(toolName) {
|
|
617
|
+
try {
|
|
618
|
+
const result = await runCommand("where", {
|
|
619
|
+
args: [toolName],
|
|
620
|
+
timeoutMs: this.getToolTimeout(toolName)
|
|
621
|
+
});
|
|
622
|
+
const candidates = result.stdout.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0);
|
|
623
|
+
const cmdShim = candidates.find(
|
|
624
|
+
(line) => line.toLowerCase().endsWith(".cmd")
|
|
625
|
+
);
|
|
626
|
+
return cmdShim ?? candidates[0];
|
|
627
|
+
} catch {
|
|
628
|
+
return void 0;
|
|
629
|
+
}
|
|
630
|
+
}
|
|
298
631
|
async getToolVersion(toolName) {
|
|
299
|
-
const invocation = this.getVersionInvocation(toolName);
|
|
632
|
+
const invocation = await this.getVersionInvocation(toolName);
|
|
300
633
|
const result = await runCommand(invocation.command, {
|
|
301
634
|
args: invocation.args,
|
|
302
635
|
timeoutMs: this.getToolTimeout(toolName)
|
|
@@ -371,12 +704,19 @@ var EnvironmentInspector = class {
|
|
|
371
704
|
return this.handleToolCheckError(error);
|
|
372
705
|
}
|
|
373
706
|
}
|
|
374
|
-
getVersionInvocation(toolName) {
|
|
707
|
+
async getVersionInvocation(toolName) {
|
|
375
708
|
const isWindows = process.platform === "win32";
|
|
376
709
|
if (isWindows && ["npm", "pnpm", "nrm"].includes(toolName)) {
|
|
710
|
+
const shim = await this.getToolShimPath(toolName);
|
|
711
|
+
if (shim) {
|
|
712
|
+
return {
|
|
713
|
+
command: "cmd.exe",
|
|
714
|
+
args: ["/c", shim, "--version"]
|
|
715
|
+
};
|
|
716
|
+
}
|
|
377
717
|
return {
|
|
378
718
|
command: "cmd.exe",
|
|
379
|
-
args: ["/
|
|
719
|
+
args: ["/c", toolName, "--version"]
|
|
380
720
|
};
|
|
381
721
|
}
|
|
382
722
|
const commands = {
|
|
@@ -419,7 +759,7 @@ var EnvironmentInspector = class {
|
|
|
419
759
|
const execError = error;
|
|
420
760
|
const message = execError.message || String(error);
|
|
421
761
|
const code = execError.code;
|
|
422
|
-
const isNotFound = message.includes("command not found") || message.includes("not recognized") || message.includes("ENOENT") || code === "ENOENT" || code === ERROR_CODE_NOT_FOUND;
|
|
762
|
+
const isNotFound = message.includes("command not found") || message.includes("not recognized") || message.includes("ENOENT") || message.includes("EACCES") || code === "ENOENT" || code === "EACCES" || code === ERROR_CODE_NOT_FOUND;
|
|
423
763
|
const isTimeout = code === ERROR_CODE_TIMEOUT || message.toLowerCase().includes("timed out");
|
|
424
764
|
return {
|
|
425
765
|
installed: false,
|
|
@@ -429,10 +769,10 @@ var EnvironmentInspector = class {
|
|
|
429
769
|
};
|
|
430
770
|
|
|
431
771
|
// src/image/imageTools.ts
|
|
432
|
-
import * as
|
|
433
|
-
import * as
|
|
772
|
+
import * as fs2 from "fs/promises";
|
|
773
|
+
import * as path2 from "path";
|
|
434
774
|
import {
|
|
435
|
-
createServicemeError as
|
|
775
|
+
createServicemeError as createServicemeError5
|
|
436
776
|
} from "@serviceme/devtools-protocol";
|
|
437
777
|
var SUPPORTED_FORMATS = [
|
|
438
778
|
".jpg",
|
|
@@ -453,7 +793,7 @@ var ImageTools = class {
|
|
|
453
793
|
if (!await this.pathExists(filePath)) {
|
|
454
794
|
return { valid: false };
|
|
455
795
|
}
|
|
456
|
-
if (!SUPPORTED_FORMATS.includes(
|
|
796
|
+
if (!SUPPORTED_FORMATS.includes(path2.extname(filePath).toLowerCase())) {
|
|
457
797
|
return { valid: false };
|
|
458
798
|
}
|
|
459
799
|
await this.getInfo(filePath, sharpModulePath);
|
|
@@ -465,7 +805,7 @@ var ImageTools = class {
|
|
|
465
805
|
async getInfo(imagePath, sharpModulePath) {
|
|
466
806
|
const sharp = this.loadSharp(sharpModulePath);
|
|
467
807
|
const metadata = await sharp(imagePath).metadata();
|
|
468
|
-
const stats = await
|
|
808
|
+
const stats = await fs2.stat(imagePath);
|
|
469
809
|
return {
|
|
470
810
|
width: metadata.width ?? 0,
|
|
471
811
|
height: metadata.height ?? 0,
|
|
@@ -477,12 +817,12 @@ var ImageTools = class {
|
|
|
477
817
|
async compress(imagePath, options) {
|
|
478
818
|
const validation = await this.validate(imagePath, options.sharpModulePath);
|
|
479
819
|
if (!validation.valid) {
|
|
480
|
-
throw
|
|
820
|
+
throw createServicemeError5(
|
|
481
821
|
"invalid_params",
|
|
482
822
|
`Invalid image file: ${imagePath}`
|
|
483
823
|
);
|
|
484
824
|
}
|
|
485
|
-
const originalStats = await
|
|
825
|
+
const originalStats = await fs2.stat(imagePath);
|
|
486
826
|
const originalSize = originalStats.size;
|
|
487
827
|
const outputPath = this.getOutputPath(imagePath, options);
|
|
488
828
|
const compressedBuffer = await this.compressWithSharp(imagePath, options);
|
|
@@ -497,7 +837,7 @@ var ImageTools = class {
|
|
|
497
837
|
outputPath: imagePath
|
|
498
838
|
};
|
|
499
839
|
}
|
|
500
|
-
await
|
|
840
|
+
await fs2.writeFile(outputPath, compressedBuffer);
|
|
501
841
|
return {
|
|
502
842
|
originalSize,
|
|
503
843
|
compressedSize,
|
|
@@ -508,7 +848,7 @@ var ImageTools = class {
|
|
|
508
848
|
async compressWithSharp(imagePath, options) {
|
|
509
849
|
const sharp = this.loadSharp(options.sharpModulePath);
|
|
510
850
|
let pipeline = sharp(imagePath);
|
|
511
|
-
switch (options.format ??
|
|
851
|
+
switch (options.format ?? path2.extname(imagePath).toLowerCase().slice(1)) {
|
|
512
852
|
case "jpg":
|
|
513
853
|
case "jpeg":
|
|
514
854
|
pipeline = pipeline.jpeg({ quality: options.quality });
|
|
@@ -534,14 +874,14 @@ var ImageTools = class {
|
|
|
534
874
|
if (options.replaceOriginImage) {
|
|
535
875
|
return inputPath;
|
|
536
876
|
}
|
|
537
|
-
const dir =
|
|
538
|
-
const ext =
|
|
539
|
-
const name =
|
|
540
|
-
return
|
|
877
|
+
const dir = path2.dirname(inputPath);
|
|
878
|
+
const ext = path2.extname(inputPath);
|
|
879
|
+
const name = path2.basename(inputPath, ext);
|
|
880
|
+
return path2.join(dir, `${name}_compressed${ext}`);
|
|
541
881
|
}
|
|
542
882
|
async pathExists(targetPath) {
|
|
543
883
|
try {
|
|
544
|
-
await
|
|
884
|
+
await fs2.access(targetPath);
|
|
545
885
|
return true;
|
|
546
886
|
} catch {
|
|
547
887
|
return false;
|
|
@@ -551,7 +891,7 @@ var ImageTools = class {
|
|
|
551
891
|
try {
|
|
552
892
|
return sharpModulePath ? __require(sharpModulePath) : __require("sharp");
|
|
553
893
|
} catch (error) {
|
|
554
|
-
throw
|
|
894
|
+
throw createServicemeError5(
|
|
555
895
|
"internal_error",
|
|
556
896
|
`sharp is required for image operations: ${error instanceof Error ? error.message : String(error)}`
|
|
557
897
|
);
|
|
@@ -564,7 +904,7 @@ function createImageTools() {
|
|
|
564
904
|
|
|
565
905
|
// src/json/jsonTools.ts
|
|
566
906
|
import {
|
|
567
|
-
createServicemeError as
|
|
907
|
+
createServicemeError as createServicemeError6,
|
|
568
908
|
DEFAULT_JSON_SORT_OPTIONS
|
|
569
909
|
} from "@serviceme/devtools-protocol";
|
|
570
910
|
import { parse as parseCommentJson } from "comment-json";
|
|
@@ -608,7 +948,7 @@ function parseJson(text) {
|
|
|
608
948
|
} catch {
|
|
609
949
|
}
|
|
610
950
|
}
|
|
611
|
-
throw
|
|
951
|
+
throw createServicemeError6("json_invalid_input", "Invalid JSON format.");
|
|
612
952
|
}
|
|
613
953
|
function sortObject(obj, options) {
|
|
614
954
|
if (Array.isArray(obj)) {
|
|
@@ -700,10 +1040,10 @@ function createConsoleLogger(prefix = "serviceme") {
|
|
|
700
1040
|
}
|
|
701
1041
|
|
|
702
1042
|
// src/project/projectTools.ts
|
|
703
|
-
import * as
|
|
704
|
-
import * as
|
|
1043
|
+
import * as fs3 from "fs/promises";
|
|
1044
|
+
import * as path3 from "path";
|
|
705
1045
|
import {
|
|
706
|
-
createServicemeError as
|
|
1046
|
+
createServicemeError as createServicemeError7
|
|
707
1047
|
} from "@serviceme/devtools-protocol";
|
|
708
1048
|
|
|
709
1049
|
// src/utils/fileUtils.ts
|
|
@@ -717,7 +1057,7 @@ import {
|
|
|
717
1057
|
rename,
|
|
718
1058
|
rm
|
|
719
1059
|
} from "fs/promises";
|
|
720
|
-
import { dirname as
|
|
1060
|
+
import { dirname as dirname3, join as join3 } from "path";
|
|
721
1061
|
import { open } from "yauzl";
|
|
722
1062
|
var unzipFile = (zipPath, dest) => {
|
|
723
1063
|
return new Promise((resolve, reject) => {
|
|
@@ -730,12 +1070,12 @@ var unzipFile = (zipPath, dest) => {
|
|
|
730
1070
|
zipfile.readEntry();
|
|
731
1071
|
zipfile.on("entry", (entry) => {
|
|
732
1072
|
if (/\/$/.test(entry.fileName)) {
|
|
733
|
-
void mkdir(
|
|
1073
|
+
void mkdir(join3(dest, entry.fileName), { recursive: true }).then(() => {
|
|
734
1074
|
zipfile.readEntry();
|
|
735
1075
|
}).catch(reject);
|
|
736
1076
|
} else {
|
|
737
|
-
const outputPath =
|
|
738
|
-
void mkdir(
|
|
1077
|
+
const outputPath = join3(dest, entry.fileName);
|
|
1078
|
+
void mkdir(dirname3(outputPath), { recursive: true }).then(() => {
|
|
739
1079
|
zipfile.openReadStream(
|
|
740
1080
|
entry,
|
|
741
1081
|
(streamError, readStream) => {
|
|
@@ -788,8 +1128,8 @@ var mergeEntry = async (sourcePath, destPath, overwrite) => {
|
|
|
788
1128
|
const children = await readdir(sourcePath);
|
|
789
1129
|
for (const child of children) {
|
|
790
1130
|
await mergeEntry(
|
|
791
|
-
|
|
792
|
-
|
|
1131
|
+
join3(sourcePath, child),
|
|
1132
|
+
join3(destPath, child),
|
|
793
1133
|
overwrite
|
|
794
1134
|
);
|
|
795
1135
|
}
|
|
@@ -814,8 +1154,8 @@ var moveFiles = async (sourceDir, destDir, overwrite = false) => {
|
|
|
814
1154
|
await mkdir(destDir, { recursive: true });
|
|
815
1155
|
const files = await readdir(sourceDir);
|
|
816
1156
|
for (const file of files) {
|
|
817
|
-
const sourceFile =
|
|
818
|
-
const destFile =
|
|
1157
|
+
const sourceFile = join3(sourceDir, file);
|
|
1158
|
+
const destFile = join3(destDir, file);
|
|
819
1159
|
if (!overwrite) {
|
|
820
1160
|
try {
|
|
821
1161
|
await access2(destFile, constants.F_OK);
|
|
@@ -832,10 +1172,10 @@ var moveFiles = async (sourceDir, destDir, overwrite = false) => {
|
|
|
832
1172
|
var ProjectTools = class {
|
|
833
1173
|
async extractTemplate(zipPath, workspacePath, tempExtractDir, input) {
|
|
834
1174
|
await unzipFile(zipPath, tempExtractDir);
|
|
835
|
-
let sourceDir =
|
|
1175
|
+
let sourceDir = path3.join(tempExtractDir, input.extractedDirName);
|
|
836
1176
|
let actualDirName = input.extractedDirName;
|
|
837
1177
|
if (!await this.pathExists(sourceDir)) {
|
|
838
|
-
const entries = await
|
|
1178
|
+
const entries = await fs3.readdir(tempExtractDir, { withFileTypes: true });
|
|
839
1179
|
const directories = entries.filter(
|
|
840
1180
|
(entry) => entry.isDirectory() && !entry.name.startsWith(".")
|
|
841
1181
|
);
|
|
@@ -847,7 +1187,7 @@ var ProjectTools = class {
|
|
|
847
1187
|
);
|
|
848
1188
|
if (selectedDirectory) {
|
|
849
1189
|
actualDirName = selectedDirectory;
|
|
850
|
-
sourceDir =
|
|
1190
|
+
sourceDir = path3.join(tempExtractDir, actualDirName);
|
|
851
1191
|
} else if (directories.length === 0) {
|
|
852
1192
|
throw new Error(
|
|
853
1193
|
`No directory found after extraction. Expected directory: ${input.extractedDirName}`
|
|
@@ -868,7 +1208,7 @@ var ProjectTools = class {
|
|
|
868
1208
|
}
|
|
869
1209
|
async installDependencies(workspacePath, command) {
|
|
870
1210
|
if (!command) {
|
|
871
|
-
throw
|
|
1211
|
+
throw createServicemeError7("invalid_params", "Expected install command.");
|
|
872
1212
|
}
|
|
873
1213
|
await runCommand(command, {
|
|
874
1214
|
cwd: workspacePath,
|
|
@@ -905,7 +1245,7 @@ var ProjectTools = class {
|
|
|
905
1245
|
} else {
|
|
906
1246
|
for (const scriptPath of scripts) {
|
|
907
1247
|
try {
|
|
908
|
-
await
|
|
1248
|
+
await fs3.chmod(scriptPath, 493);
|
|
909
1249
|
updatedCount += 1;
|
|
910
1250
|
} catch {
|
|
911
1251
|
}
|
|
@@ -931,13 +1271,16 @@ var ProjectTools = class {
|
|
|
931
1271
|
}
|
|
932
1272
|
async runScaffoldPrune(workspacePath, preset) {
|
|
933
1273
|
if (!preset) {
|
|
934
|
-
throw
|
|
1274
|
+
throw createServicemeError7("invalid_params", "Expected scaffold preset.");
|
|
935
1275
|
}
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
];
|
|
1276
|
+
await this.ensurePresetManifest(workspacePath, preset);
|
|
1277
|
+
const pruneCommand = `pnpm run scaffold:prune -- --preset ${preset} --project-root .`;
|
|
1278
|
+
const initMetadataCommand = `pnpm run scaffold:init-metadata -- --preset ${preset} --project-root . --force`;
|
|
1279
|
+
const commands = [pruneCommand, initMetadataCommand];
|
|
940
1280
|
for (const command of commands) {
|
|
1281
|
+
if (command === initMetadataCommand) {
|
|
1282
|
+
await this.ensurePresetManifest(workspacePath, preset);
|
|
1283
|
+
}
|
|
941
1284
|
await runCommand(command, {
|
|
942
1285
|
cwd: workspacePath,
|
|
943
1286
|
shell: true
|
|
@@ -949,16 +1292,54 @@ var ProjectTools = class {
|
|
|
949
1292
|
commands
|
|
950
1293
|
};
|
|
951
1294
|
}
|
|
1295
|
+
async ensurePresetManifest(workspacePath, preset) {
|
|
1296
|
+
const presetManifestPath = path3.join(
|
|
1297
|
+
workspacePath,
|
|
1298
|
+
".ms-scaffold",
|
|
1299
|
+
"presets",
|
|
1300
|
+
`${preset}.json`
|
|
1301
|
+
);
|
|
1302
|
+
if (await this.pathExists(presetManifestPath)) {
|
|
1303
|
+
return;
|
|
1304
|
+
}
|
|
1305
|
+
const projectModePath = path3.join(
|
|
1306
|
+
workspacePath,
|
|
1307
|
+
".ms-scaffold",
|
|
1308
|
+
"project-mode.json"
|
|
1309
|
+
);
|
|
1310
|
+
if (!await this.pathExists(projectModePath)) {
|
|
1311
|
+
return;
|
|
1312
|
+
}
|
|
1313
|
+
const projectModeRaw = await fs3.readFile(projectModePath, "utf8");
|
|
1314
|
+
const projectMode = JSON.parse(projectModeRaw);
|
|
1315
|
+
const synthesizedPreset = {
|
|
1316
|
+
preset,
|
|
1317
|
+
selectedModules: Array.isArray(projectMode.selectedModules) ? projectMode.selectedModules : [],
|
|
1318
|
+
prunedModules: Array.isArray(projectMode.prunedModules) ? projectMode.prunedModules : [],
|
|
1319
|
+
exclude: [],
|
|
1320
|
+
recommendedFollowUps: [],
|
|
1321
|
+
managedFiles: [],
|
|
1322
|
+
mergeManagedFiles: [],
|
|
1323
|
+
userOwnedPaths: []
|
|
1324
|
+
};
|
|
1325
|
+
await fs3.mkdir(path3.dirname(presetManifestPath), { recursive: true });
|
|
1326
|
+
await fs3.writeFile(
|
|
1327
|
+
presetManifestPath,
|
|
1328
|
+
`${JSON.stringify(synthesizedPreset, null, 2)}
|
|
1329
|
+
`,
|
|
1330
|
+
"utf8"
|
|
1331
|
+
);
|
|
1332
|
+
}
|
|
952
1333
|
async findScripts(dir, extensions) {
|
|
953
1334
|
const results = [];
|
|
954
1335
|
let entries;
|
|
955
1336
|
try {
|
|
956
|
-
entries = await
|
|
1337
|
+
entries = await fs3.readdir(dir, { withFileTypes: true });
|
|
957
1338
|
} catch {
|
|
958
1339
|
return results;
|
|
959
1340
|
}
|
|
960
1341
|
for (const entry of entries) {
|
|
961
|
-
const fullPath =
|
|
1342
|
+
const fullPath = path3.join(dir, entry.name);
|
|
962
1343
|
if (entry.isDirectory() && entry.name !== "node_modules" && !entry.name.startsWith(".")) {
|
|
963
1344
|
results.push(...await this.findScripts(fullPath, extensions));
|
|
964
1345
|
} else if (entry.isFile() && extensions.some((ext) => entry.name.endsWith(ext))) {
|
|
@@ -969,7 +1350,7 @@ var ProjectTools = class {
|
|
|
969
1350
|
}
|
|
970
1351
|
async pathExists(targetPath) {
|
|
971
1352
|
try {
|
|
972
|
-
await
|
|
1353
|
+
await fs3.access(targetPath);
|
|
973
1354
|
return true;
|
|
974
1355
|
} catch {
|
|
975
1356
|
return false;
|
|
@@ -988,7 +1369,7 @@ var ProjectTools = class {
|
|
|
988
1369
|
const matches = [];
|
|
989
1370
|
for (const directoryName of directoryNames) {
|
|
990
1371
|
if (await this.directoryMatchesProjectPattern(
|
|
991
|
-
|
|
1372
|
+
path3.join(tempExtractDir, directoryName),
|
|
992
1373
|
projectFilePattern
|
|
993
1374
|
)) {
|
|
994
1375
|
matches.push(directoryName);
|
|
@@ -1000,7 +1381,7 @@ var ProjectTools = class {
|
|
|
1000
1381
|
return null;
|
|
1001
1382
|
}
|
|
1002
1383
|
async directoryMatchesProjectPattern(directoryPath, projectFilePattern) {
|
|
1003
|
-
const entries = await
|
|
1384
|
+
const entries = await fs3.readdir(directoryPath);
|
|
1004
1385
|
if (projectFilePattern.includes("*")) {
|
|
1005
1386
|
const regex = new RegExp(`^${projectFilePattern.replace("*", ".*")}$`);
|
|
1006
1387
|
return entries.some((entry) => regex.test(entry));
|
|
@@ -1013,17 +1394,17 @@ function createProjectTools() {
|
|
|
1013
1394
|
}
|
|
1014
1395
|
|
|
1015
1396
|
// src/scheduled-tasks/daemon/DaemonLogger.ts
|
|
1016
|
-
import * as
|
|
1017
|
-
import * as
|
|
1397
|
+
import * as fs4 from "fs";
|
|
1398
|
+
import * as path4 from "path";
|
|
1018
1399
|
var CONFIG_DIR = ".serviceme";
|
|
1019
1400
|
var LOG_FILE = "scheduler.log";
|
|
1020
1401
|
var MAX_LOG_SIZE = 1024 * 1024;
|
|
1021
1402
|
var DaemonLogger = class {
|
|
1022
1403
|
constructor(workspacePath) {
|
|
1023
|
-
this.logPath =
|
|
1024
|
-
const dir =
|
|
1025
|
-
if (!
|
|
1026
|
-
|
|
1404
|
+
this.logPath = path4.join(workspacePath, CONFIG_DIR, LOG_FILE);
|
|
1405
|
+
const dir = path4.dirname(this.logPath);
|
|
1406
|
+
if (!fs4.existsSync(dir)) {
|
|
1407
|
+
fs4.mkdirSync(dir, { recursive: true });
|
|
1027
1408
|
}
|
|
1028
1409
|
}
|
|
1029
1410
|
getLogPath() {
|
|
@@ -1034,16 +1415,16 @@ var DaemonLogger = class {
|
|
|
1034
1415
|
const line = `[${ts}] [${level.toUpperCase()}] ${message}
|
|
1035
1416
|
`;
|
|
1036
1417
|
this.rotateIfNeeded();
|
|
1037
|
-
|
|
1418
|
+
fs4.appendFileSync(this.logPath, line, "utf-8");
|
|
1038
1419
|
}
|
|
1039
1420
|
rotateIfNeeded() {
|
|
1040
1421
|
try {
|
|
1041
|
-
const stats =
|
|
1422
|
+
const stats = fs4.statSync(this.logPath);
|
|
1042
1423
|
if (stats.size > MAX_LOG_SIZE) {
|
|
1043
|
-
const content =
|
|
1424
|
+
const content = fs4.readFileSync(this.logPath, "utf-8");
|
|
1044
1425
|
const halfIdx = content.indexOf("\n", Math.floor(content.length / 2));
|
|
1045
1426
|
if (halfIdx > 0) {
|
|
1046
|
-
|
|
1427
|
+
fs4.writeFileSync(this.logPath, content.slice(halfIdx + 1), "utf-8");
|
|
1047
1428
|
}
|
|
1048
1429
|
}
|
|
1049
1430
|
} catch {
|
|
@@ -1052,33 +1433,33 @@ var DaemonLogger = class {
|
|
|
1052
1433
|
};
|
|
1053
1434
|
|
|
1054
1435
|
// src/scheduled-tasks/daemon/PidManager.ts
|
|
1055
|
-
import * as
|
|
1056
|
-
import * as
|
|
1436
|
+
import * as fs5 from "fs";
|
|
1437
|
+
import * as path5 from "path";
|
|
1057
1438
|
var CONFIG_DIR2 = ".serviceme";
|
|
1058
1439
|
var PID_FILE = "scheduler.pid";
|
|
1059
1440
|
var PidManager = class {
|
|
1060
1441
|
constructor(workspacePath) {
|
|
1061
|
-
this.pidPath =
|
|
1442
|
+
this.pidPath = path5.join(workspacePath, CONFIG_DIR2, PID_FILE);
|
|
1062
1443
|
}
|
|
1063
1444
|
getPidPath() {
|
|
1064
1445
|
return this.pidPath;
|
|
1065
1446
|
}
|
|
1066
1447
|
writePid(pid) {
|
|
1067
|
-
const dir =
|
|
1068
|
-
if (!
|
|
1069
|
-
|
|
1448
|
+
const dir = path5.dirname(this.pidPath);
|
|
1449
|
+
if (!fs5.existsSync(dir)) {
|
|
1450
|
+
fs5.mkdirSync(dir, { recursive: true });
|
|
1070
1451
|
}
|
|
1071
|
-
|
|
1452
|
+
fs5.writeFileSync(this.pidPath, String(pid), "utf-8");
|
|
1072
1453
|
}
|
|
1073
1454
|
readPid() {
|
|
1074
|
-
if (!
|
|
1075
|
-
const raw =
|
|
1455
|
+
if (!fs5.existsSync(this.pidPath)) return null;
|
|
1456
|
+
const raw = fs5.readFileSync(this.pidPath, "utf-8").trim();
|
|
1076
1457
|
const pid = Number.parseInt(raw, 10);
|
|
1077
1458
|
return Number.isNaN(pid) ? null : pid;
|
|
1078
1459
|
}
|
|
1079
1460
|
removePid() {
|
|
1080
|
-
if (
|
|
1081
|
-
|
|
1461
|
+
if (fs5.existsSync(this.pidPath)) {
|
|
1462
|
+
fs5.unlinkSync(this.pidPath);
|
|
1082
1463
|
}
|
|
1083
1464
|
}
|
|
1084
1465
|
isProcessRunning(pid) {
|
|
@@ -1099,12 +1480,12 @@ var PidManager = class {
|
|
|
1099
1480
|
};
|
|
1100
1481
|
|
|
1101
1482
|
// src/scheduled-tasks/daemon/SchedulerDaemon.ts
|
|
1102
|
-
import * as
|
|
1483
|
+
import * as fs10 from "fs";
|
|
1103
1484
|
import * as os from "os";
|
|
1104
1485
|
|
|
1105
1486
|
// src/scheduled-tasks/executors/GithubCopilotCliExecutor.ts
|
|
1106
1487
|
import { spawn as spawn2 } from "child_process";
|
|
1107
|
-
import * as
|
|
1488
|
+
import * as fs6 from "fs";
|
|
1108
1489
|
|
|
1109
1490
|
// src/scheduled-tasks/executors/timeout.ts
|
|
1110
1491
|
function resolveConfiguredTimeoutMs(timeoutSeconds, defaultTimeoutMs) {
|
|
@@ -1139,7 +1520,7 @@ function redactArgs(args) {
|
|
|
1139
1520
|
function writeDiagnostic(message) {
|
|
1140
1521
|
const logPath = process.env.SERVICEME_SCHEDULER_LOG_PATH;
|
|
1141
1522
|
if (logPath) {
|
|
1142
|
-
|
|
1523
|
+
fs6.appendFileSync(logPath, message);
|
|
1143
1524
|
return;
|
|
1144
1525
|
}
|
|
1145
1526
|
process.stderr.write(message);
|
|
@@ -1349,15 +1730,15 @@ ${body}`.trim()
|
|
|
1349
1730
|
|
|
1350
1731
|
// src/scheduled-tasks/executors/ShellExecutor.ts
|
|
1351
1732
|
import { spawn as spawn3 } from "child_process";
|
|
1352
|
-
import * as
|
|
1353
|
-
import * as
|
|
1733
|
+
import * as fs7 from "fs";
|
|
1734
|
+
import * as path6 from "path";
|
|
1354
1735
|
var MAX_OUTPUT_BYTES2 = 1024 * 1024;
|
|
1355
1736
|
var DEFAULT_TIMEOUT_MS4 = 6e4;
|
|
1356
1737
|
var POSIX_SHELL_CANDIDATES = ["bash.exe", "sh.exe"];
|
|
1357
1738
|
function resolveShellExecution(script, options = {}) {
|
|
1358
1739
|
const platform = options.platform ?? process.platform;
|
|
1359
1740
|
const env = options.env ?? process.env;
|
|
1360
|
-
const fileExists = options.fileExists ??
|
|
1741
|
+
const fileExists = options.fileExists ?? fs7.existsSync;
|
|
1361
1742
|
if (platform === "win32") {
|
|
1362
1743
|
const posixShell = usesPosixShellSyntax(script) ? findWindowsPosixShell(env, fileExists) : null;
|
|
1363
1744
|
if (posixShell) {
|
|
@@ -1402,10 +1783,10 @@ function findWindowsPosixShell(env, fileExists) {
|
|
|
1402
1783
|
if (fileExists(candidate)) return candidate;
|
|
1403
1784
|
}
|
|
1404
1785
|
const pathValue = env.Path ?? env.PATH ?? "";
|
|
1405
|
-
for (const dir of pathValue.split(
|
|
1786
|
+
for (const dir of pathValue.split(path6.win32.delimiter)) {
|
|
1406
1787
|
if (!dir) continue;
|
|
1407
1788
|
for (const executable of POSIX_SHELL_CANDIDATES) {
|
|
1408
|
-
const candidate =
|
|
1789
|
+
const candidate = path6.win32.join(dir, executable);
|
|
1409
1790
|
if (fileExists(candidate) && !isWindowsWslLauncher(candidate)) {
|
|
1410
1791
|
return candidate;
|
|
1411
1792
|
}
|
|
@@ -1414,13 +1795,13 @@ function findWindowsPosixShell(env, fileExists) {
|
|
|
1414
1795
|
return null;
|
|
1415
1796
|
}
|
|
1416
1797
|
function isWindowsWslLauncher(candidate) {
|
|
1417
|
-
const normalized =
|
|
1798
|
+
const normalized = path6.win32.normalize(candidate).toLowerCase();
|
|
1418
1799
|
return normalized.endsWith("\\windows\\system32\\bash.exe") || normalized.endsWith("\\windows\\syswow64\\bash.exe");
|
|
1419
1800
|
}
|
|
1420
1801
|
function writeDiagnostic2(message) {
|
|
1421
1802
|
const logPath = process.env.SERVICEME_SCHEDULER_LOG_PATH;
|
|
1422
1803
|
if (logPath) {
|
|
1423
|
-
|
|
1804
|
+
fs7.appendFileSync(logPath, message);
|
|
1424
1805
|
return;
|
|
1425
1806
|
}
|
|
1426
1807
|
process.stderr.write(message);
|
|
@@ -1567,9 +1948,9 @@ function getExecutor(taskType) {
|
|
|
1567
1948
|
|
|
1568
1949
|
// src/scheduled-tasks/TaskConfigManager.ts
|
|
1569
1950
|
import { randomUUID } from "crypto";
|
|
1570
|
-
import * as
|
|
1571
|
-
import * as
|
|
1572
|
-
import { createServicemeError as
|
|
1951
|
+
import * as fs8 from "fs";
|
|
1952
|
+
import * as path7 from "path";
|
|
1953
|
+
import { createServicemeError as createServicemeError8 } from "@serviceme/devtools-protocol";
|
|
1573
1954
|
var CONFIG_DIR3 = ".serviceme";
|
|
1574
1955
|
var CONFIG_FILE = "scheduled-tasks.json";
|
|
1575
1956
|
function emptyConfig() {
|
|
@@ -1580,7 +1961,7 @@ function isRecord(value) {
|
|
|
1580
1961
|
}
|
|
1581
1962
|
function requireNonEmptyString(payload, field, taskType) {
|
|
1582
1963
|
if (!isRecord(payload) || typeof payload[field] !== "string" || !payload[field].trim()) {
|
|
1583
|
-
throw
|
|
1964
|
+
throw createServicemeError8(
|
|
1584
1965
|
"invalid_payload",
|
|
1585
1966
|
`${taskType} payload ${field} is required`
|
|
1586
1967
|
);
|
|
@@ -1605,27 +1986,27 @@ function validateTaskPayload(taskType, payload) {
|
|
|
1605
1986
|
}
|
|
1606
1987
|
var TaskConfigManager = class {
|
|
1607
1988
|
constructor(workspacePath) {
|
|
1608
|
-
this.configPath =
|
|
1989
|
+
this.configPath = path7.join(workspacePath, CONFIG_DIR3, CONFIG_FILE);
|
|
1609
1990
|
}
|
|
1610
1991
|
getConfigPath() {
|
|
1611
1992
|
return this.configPath;
|
|
1612
1993
|
}
|
|
1613
1994
|
readConfig() {
|
|
1614
|
-
if (!
|
|
1995
|
+
if (!fs8.existsSync(this.configPath)) {
|
|
1615
1996
|
return emptyConfig();
|
|
1616
1997
|
}
|
|
1617
|
-
const raw =
|
|
1998
|
+
const raw = fs8.readFileSync(this.configPath, "utf-8");
|
|
1618
1999
|
const parsed = JSON.parse(raw);
|
|
1619
2000
|
return parsed;
|
|
1620
2001
|
}
|
|
1621
2002
|
writeConfig(config) {
|
|
1622
|
-
const dir =
|
|
1623
|
-
if (!
|
|
1624
|
-
|
|
2003
|
+
const dir = path7.dirname(this.configPath);
|
|
2004
|
+
if (!fs8.existsSync(dir)) {
|
|
2005
|
+
fs8.mkdirSync(dir, { recursive: true });
|
|
1625
2006
|
}
|
|
1626
2007
|
const tmp = `${this.configPath}.tmp`;
|
|
1627
|
-
|
|
1628
|
-
|
|
2008
|
+
fs8.writeFileSync(tmp, JSON.stringify(config, null, " "), "utf-8");
|
|
2009
|
+
fs8.renameSync(tmp, this.configPath);
|
|
1629
2010
|
}
|
|
1630
2011
|
listTasks() {
|
|
1631
2012
|
return this.readConfig().tasks;
|
|
@@ -1896,8 +2277,8 @@ var TaskExecutionEngine = class {
|
|
|
1896
2277
|
|
|
1897
2278
|
// src/scheduled-tasks/TaskLogManager.ts
|
|
1898
2279
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
1899
|
-
import * as
|
|
1900
|
-
import * as
|
|
2280
|
+
import * as fs9 from "fs";
|
|
2281
|
+
import * as path8 from "path";
|
|
1901
2282
|
var CONFIG_DIR4 = ".serviceme";
|
|
1902
2283
|
var LOG_FILE2 = "scheduled-tasks-log.json";
|
|
1903
2284
|
var MAX_LOGS = 200;
|
|
@@ -1922,17 +2303,17 @@ function validateAndRepairLogFile(raw) {
|
|
|
1922
2303
|
}
|
|
1923
2304
|
var TaskLogManager = class {
|
|
1924
2305
|
constructor(workspacePath) {
|
|
1925
|
-
this.logPath =
|
|
2306
|
+
this.logPath = path8.join(workspacePath, CONFIG_DIR4, LOG_FILE2);
|
|
1926
2307
|
}
|
|
1927
2308
|
getLogPath() {
|
|
1928
2309
|
return this.logPath;
|
|
1929
2310
|
}
|
|
1930
2311
|
readLogFile() {
|
|
1931
|
-
if (!
|
|
2312
|
+
if (!fs9.existsSync(this.logPath)) {
|
|
1932
2313
|
return emptyLogFile();
|
|
1933
2314
|
}
|
|
1934
2315
|
try {
|
|
1935
|
-
const raw =
|
|
2316
|
+
const raw = fs9.readFileSync(this.logPath, "utf-8");
|
|
1936
2317
|
const parsed = JSON.parse(raw);
|
|
1937
2318
|
const file = validateAndRepairLogFile(parsed);
|
|
1938
2319
|
if (!parsed || typeof parsed !== "object" || !Array.isArray(parsed.logs) || parsed.logs.length !== file.logs.length) {
|
|
@@ -1948,21 +2329,21 @@ var TaskLogManager = class {
|
|
|
1948
2329
|
}
|
|
1949
2330
|
backupCorruptedFile() {
|
|
1950
2331
|
try {
|
|
1951
|
-
if (
|
|
2332
|
+
if (fs9.existsSync(this.logPath)) {
|
|
1952
2333
|
const backupPath = `${this.logPath}.corrupted.${Date.now()}`;
|
|
1953
|
-
|
|
2334
|
+
fs9.copyFileSync(this.logPath, backupPath);
|
|
1954
2335
|
}
|
|
1955
2336
|
} catch {
|
|
1956
2337
|
}
|
|
1957
2338
|
}
|
|
1958
2339
|
writeLogFile(file) {
|
|
1959
|
-
const dir =
|
|
1960
|
-
if (!
|
|
1961
|
-
|
|
2340
|
+
const dir = path8.dirname(this.logPath);
|
|
2341
|
+
if (!fs9.existsSync(dir)) {
|
|
2342
|
+
fs9.mkdirSync(dir, { recursive: true });
|
|
1962
2343
|
}
|
|
1963
2344
|
const tmp = `${this.logPath}.tmp`;
|
|
1964
|
-
|
|
1965
|
-
|
|
2345
|
+
fs9.writeFileSync(tmp, JSON.stringify(file, null, " "), "utf-8");
|
|
2346
|
+
fs9.renameSync(tmp, this.logPath);
|
|
1966
2347
|
}
|
|
1967
2348
|
appendLog(input) {
|
|
1968
2349
|
const file = this.readLogFile();
|
|
@@ -2073,8 +2454,8 @@ var SchedulerDaemon = class {
|
|
|
2073
2454
|
const configPath = this.configManager.getConfigPath();
|
|
2074
2455
|
const dir = configPath.substring(0, configPath.lastIndexOf("/"));
|
|
2075
2456
|
try {
|
|
2076
|
-
if (
|
|
2077
|
-
this.watcher =
|
|
2457
|
+
if (fs10.existsSync(dir)) {
|
|
2458
|
+
this.watcher = fs10.watch(dir, (_eventType, filename) => {
|
|
2078
2459
|
if (filename === "scheduled-tasks.json") {
|
|
2079
2460
|
this.logger.log("info", "Config file changed, reconciling...");
|
|
2080
2461
|
}
|
|
@@ -2226,7 +2607,214 @@ function matchCronField(field, value) {
|
|
|
2226
2607
|
const values = field.split(",");
|
|
2227
2608
|
return values.some((v) => Number.parseInt(v, 10) === value);
|
|
2228
2609
|
}
|
|
2610
|
+
|
|
2611
|
+
// src/skills/SkillCatalogClient.ts
|
|
2612
|
+
import { createServicemeError as createServicemeError9 } from "@serviceme/devtools-protocol";
|
|
2613
|
+
var SkillCatalogClient = class {
|
|
2614
|
+
constructor(options = {}) {
|
|
2615
|
+
this.fetchImpl = options.fetchImpl ?? fetch;
|
|
2616
|
+
this.baseUrl = options.baseUrl;
|
|
2617
|
+
}
|
|
2618
|
+
async getCatalog() {
|
|
2619
|
+
if (!this.baseUrl) {
|
|
2620
|
+
return {
|
|
2621
|
+
skills: [],
|
|
2622
|
+
fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2623
|
+
};
|
|
2624
|
+
}
|
|
2625
|
+
const response = await this.fetchImpl(
|
|
2626
|
+
`${this.baseUrl}/api/v1/marketplace/skills`
|
|
2627
|
+
);
|
|
2628
|
+
if (!response.ok) {
|
|
2629
|
+
throw new Error(`Failed to fetch skills catalog: ${response.status}`);
|
|
2630
|
+
}
|
|
2631
|
+
const data = await response.json();
|
|
2632
|
+
return {
|
|
2633
|
+
skills: data.skills ?? [],
|
|
2634
|
+
fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2635
|
+
};
|
|
2636
|
+
}
|
|
2637
|
+
async downloadSkill(remoteId) {
|
|
2638
|
+
if (!this.baseUrl) {
|
|
2639
|
+
throw createServicemeError9(
|
|
2640
|
+
"workspace_not_found",
|
|
2641
|
+
"Skill catalog baseUrl is not configured."
|
|
2642
|
+
);
|
|
2643
|
+
}
|
|
2644
|
+
const response = await this.fetchImpl(
|
|
2645
|
+
`${this.baseUrl}/api/v1/marketplace/skills/download/${remoteId}`
|
|
2646
|
+
);
|
|
2647
|
+
if (!response.ok) {
|
|
2648
|
+
if (response.status === 404) {
|
|
2649
|
+
throw createServicemeError9(
|
|
2650
|
+
"not_found",
|
|
2651
|
+
`Skill '${remoteId}' not found`
|
|
2652
|
+
);
|
|
2653
|
+
}
|
|
2654
|
+
throw new Error(
|
|
2655
|
+
`Failed to download skill ${remoteId}: ${response.status}`
|
|
2656
|
+
);
|
|
2657
|
+
}
|
|
2658
|
+
const payload = await response.json();
|
|
2659
|
+
return payload.data?.files ?? [];
|
|
2660
|
+
}
|
|
2661
|
+
};
|
|
2662
|
+
|
|
2663
|
+
// src/skills/SkillReconciler.ts
|
|
2664
|
+
var SkillReconciler = class {
|
|
2665
|
+
constructor(deps) {
|
|
2666
|
+
this.deps = deps;
|
|
2667
|
+
}
|
|
2668
|
+
async mutate(request) {
|
|
2669
|
+
if (request.targetScope !== "workspace" && request.targetScope !== "user") {
|
|
2670
|
+
throw new Error(`Invalid target scope: ${String(request.targetScope)}`);
|
|
2671
|
+
}
|
|
2672
|
+
if (request.action === "uninstall" || request.action === "move" || request.action === "removeExternal") {
|
|
2673
|
+
return {
|
|
2674
|
+
status: "success",
|
|
2675
|
+
changed: true,
|
|
2676
|
+
message: `Skill ${request.action} completed.`
|
|
2677
|
+
};
|
|
2678
|
+
}
|
|
2679
|
+
if (request.action !== "install") {
|
|
2680
|
+
return {
|
|
2681
|
+
status: "blocked",
|
|
2682
|
+
changed: false,
|
|
2683
|
+
message: `Skill action is not supported by bridge reconciler: ${request.action}`
|
|
2684
|
+
};
|
|
2685
|
+
}
|
|
2686
|
+
const catalog = await this.deps.catalogClient.getCatalog();
|
|
2687
|
+
const remoteSkill = catalog.skills.find(
|
|
2688
|
+
(skill) => this.deps.skillStore.normalizeRemoteSkillId(skill.id) === request.skillId
|
|
2689
|
+
);
|
|
2690
|
+
if (!remoteSkill) {
|
|
2691
|
+
return {
|
|
2692
|
+
status: "blocked",
|
|
2693
|
+
changed: false,
|
|
2694
|
+
message: "Skill not found in catalog."
|
|
2695
|
+
};
|
|
2696
|
+
}
|
|
2697
|
+
if (!request.confirmed && (remoteSkill.hasScripts || remoteSkill.hasHooks)) {
|
|
2698
|
+
return {
|
|
2699
|
+
status: "requires_confirmation",
|
|
2700
|
+
changed: false,
|
|
2701
|
+
hasScripts: Boolean(remoteSkill.hasScripts),
|
|
2702
|
+
hasHooks: Boolean(remoteSkill.hasHooks),
|
|
2703
|
+
message: "This skill contains executable scripts that require confirmation."
|
|
2704
|
+
};
|
|
2705
|
+
}
|
|
2706
|
+
return {
|
|
2707
|
+
status: "success",
|
|
2708
|
+
changed: true,
|
|
2709
|
+
message: "Skill installed."
|
|
2710
|
+
};
|
|
2711
|
+
}
|
|
2712
|
+
};
|
|
2713
|
+
|
|
2714
|
+
// src/skills/SkillStore.ts
|
|
2715
|
+
import * as fs11 from "fs/promises";
|
|
2716
|
+
import * as path9 from "path";
|
|
2717
|
+
var USER_SKILL_MARKER_FILE = ".ms-devtools-skill.json";
|
|
2718
|
+
var WORKSPACE_SKILLS_ROOT_RELATIVE = ".github/skills";
|
|
2719
|
+
var WORKSPACE_SKILLS_MARKER_RELATIVE = ".github/.ms-devtools-skills.yml";
|
|
2720
|
+
var SAFE_LOCAL_ID_PATTERN2 = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
2721
|
+
function assertSafeLocalSkillId(skillId) {
|
|
2722
|
+
if (typeof skillId !== "string" || skillId.length === 0 || skillId === "." || skillId === ".." || skillId.includes("/") || skillId.includes("\\") || !SAFE_LOCAL_ID_PATTERN2.test(skillId)) {
|
|
2723
|
+
throw new Error(`Invalid skill id: ${skillId}`);
|
|
2724
|
+
}
|
|
2725
|
+
return skillId;
|
|
2726
|
+
}
|
|
2727
|
+
var SkillStore = class {
|
|
2728
|
+
constructor(options) {
|
|
2729
|
+
this.workspacePath = options.workspacePath;
|
|
2730
|
+
this.userSkillsRoot = options.userSkillsRoot;
|
|
2731
|
+
this.fileSystem = options.fileSystem ?? fs11;
|
|
2732
|
+
}
|
|
2733
|
+
normalizeRemoteSkillId(remoteId) {
|
|
2734
|
+
if (remoteId.startsWith("official/")) {
|
|
2735
|
+
return assertSafeLocalSkillId(remoteId.slice("official/".length));
|
|
2736
|
+
}
|
|
2737
|
+
if (remoteId.startsWith("community/")) {
|
|
2738
|
+
const lastSlash = remoteId.lastIndexOf("/");
|
|
2739
|
+
return assertSafeLocalSkillId(remoteId.slice(lastSlash + 1));
|
|
2740
|
+
}
|
|
2741
|
+
return assertSafeLocalSkillId(remoteId);
|
|
2742
|
+
}
|
|
2743
|
+
getWorkspaceSkillPath(skillId) {
|
|
2744
|
+
return `${WORKSPACE_SKILLS_ROOT_RELATIVE}/${skillId}`;
|
|
2745
|
+
}
|
|
2746
|
+
getWorkspaceMarkerPath() {
|
|
2747
|
+
return WORKSPACE_SKILLS_MARKER_RELATIVE;
|
|
2748
|
+
}
|
|
2749
|
+
getUserSkillPath(skillId) {
|
|
2750
|
+
return path9.join(this.userSkillsRoot, skillId);
|
|
2751
|
+
}
|
|
2752
|
+
async listWorkspaceSkillIds() {
|
|
2753
|
+
const skillsRootPath = path9.join(
|
|
2754
|
+
this.workspacePath,
|
|
2755
|
+
WORKSPACE_SKILLS_ROOT_RELATIVE
|
|
2756
|
+
);
|
|
2757
|
+
try {
|
|
2758
|
+
const entries = await this.fileSystem.readdir(skillsRootPath, {
|
|
2759
|
+
withFileTypes: true
|
|
2760
|
+
});
|
|
2761
|
+
return entries.filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => entry.name).sort();
|
|
2762
|
+
} catch {
|
|
2763
|
+
return [];
|
|
2764
|
+
}
|
|
2765
|
+
}
|
|
2766
|
+
async listUserSkillIds() {
|
|
2767
|
+
try {
|
|
2768
|
+
const entries = await this.fileSystem.readdir(this.userSkillsRoot, {
|
|
2769
|
+
withFileTypes: true
|
|
2770
|
+
});
|
|
2771
|
+
return entries.filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => entry.name).sort();
|
|
2772
|
+
} catch {
|
|
2773
|
+
return [];
|
|
2774
|
+
}
|
|
2775
|
+
}
|
|
2776
|
+
async writeManagedUserSkillMarker(skillId) {
|
|
2777
|
+
const targetDir = this.getUserSkillPath(skillId);
|
|
2778
|
+
await this.fileSystem.mkdir(targetDir, { recursive: true });
|
|
2779
|
+
await this.fileSystem.writeFile(
|
|
2780
|
+
path9.join(targetDir, USER_SKILL_MARKER_FILE),
|
|
2781
|
+
JSON.stringify({ skillId, installedBy: "ms-devtools" }, null, 2),
|
|
2782
|
+
"utf-8"
|
|
2783
|
+
);
|
|
2784
|
+
}
|
|
2785
|
+
async isManagedUserSkill(skillId) {
|
|
2786
|
+
try {
|
|
2787
|
+
const marker = await this.fileSystem.readFile(
|
|
2788
|
+
path9.join(this.getUserSkillPath(skillId), USER_SKILL_MARKER_FILE),
|
|
2789
|
+
"utf-8"
|
|
2790
|
+
);
|
|
2791
|
+
const parsed = JSON.parse(marker);
|
|
2792
|
+
return parsed.skillId === skillId;
|
|
2793
|
+
} catch {
|
|
2794
|
+
return false;
|
|
2795
|
+
}
|
|
2796
|
+
}
|
|
2797
|
+
async writeSkillFiles(skillId, scope, files) {
|
|
2798
|
+
const root = scope === "workspace" ? path9.join(this.workspacePath, WORKSPACE_SKILLS_ROOT_RELATIVE) : this.userSkillsRoot;
|
|
2799
|
+
const targetDir = path9.join(root, skillId);
|
|
2800
|
+
await this.fileSystem.mkdir(targetDir, { recursive: true });
|
|
2801
|
+
for (const file of files) {
|
|
2802
|
+
const filePath = path9.join(targetDir, file.path);
|
|
2803
|
+
await this.fileSystem.mkdir(path9.dirname(filePath), { recursive: true });
|
|
2804
|
+
await this.fileSystem.writeFile(filePath, file.content, "utf-8");
|
|
2805
|
+
if (file.executable) {
|
|
2806
|
+
try {
|
|
2807
|
+
await this.fileSystem.chmod(filePath, 493);
|
|
2808
|
+
} catch {
|
|
2809
|
+
}
|
|
2810
|
+
}
|
|
2811
|
+
}
|
|
2812
|
+
}
|
|
2813
|
+
};
|
|
2229
2814
|
export {
|
|
2815
|
+
AgentCatalogClient,
|
|
2816
|
+
AgentReconciler,
|
|
2817
|
+
AgentStore,
|
|
2230
2818
|
DaemonLogger,
|
|
2231
2819
|
EnvironmentInspector,
|
|
2232
2820
|
GithubCopilotCliExecutor,
|
|
@@ -2236,6 +2824,10 @@ export {
|
|
|
2236
2824
|
ProjectTools,
|
|
2237
2825
|
SchedulerDaemon,
|
|
2238
2826
|
ShellExecutor,
|
|
2827
|
+
SkillCatalogClient,
|
|
2828
|
+
SkillReconciler,
|
|
2829
|
+
SkillStore,
|
|
2830
|
+
TOOL_RISK_MAP,
|
|
2239
2831
|
TaskConfigManager,
|
|
2240
2832
|
TaskExecutionEngine,
|
|
2241
2833
|
TaskLogManager,
|
|
@@ -2253,6 +2845,7 @@ export {
|
|
|
2253
2845
|
matchesCron,
|
|
2254
2846
|
moveFiles,
|
|
2255
2847
|
noopLogger,
|
|
2848
|
+
parseAgentToolPermissions,
|
|
2256
2849
|
parseIntervalMs,
|
|
2257
2850
|
resolveTaskExecutionPayload,
|
|
2258
2851
|
unzipFile,
|