@serviceme/devtools-core 0.1.6 → 0.1.8

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.mjs CHANGED
@@ -6,6 +6,7 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
6
6
  });
7
7
 
8
8
  // src/agents/AgentCatalogClient.ts
9
+ import { createServicemeError } from "@serviceme/devtools-protocol";
9
10
  var AgentCatalogClient = class {
10
11
  constructor(options = {}) {
11
12
  this.fetchImpl = options.fetchImpl ?? fetch;
@@ -13,10 +14,10 @@ var AgentCatalogClient = class {
13
14
  }
14
15
  async getCatalog() {
15
16
  if (!this.baseUrl) {
16
- return {
17
- agents: [],
18
- fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
19
- };
17
+ throw createServicemeError(
18
+ "workspace_not_found",
19
+ "Agent catalog baseUrl is not configured."
20
+ );
20
21
  }
21
22
  const response = await this.fetchImpl(
22
23
  `${this.baseUrl}/api/v1/marketplace/agents`
@@ -30,6 +31,30 @@ var AgentCatalogClient = class {
30
31
  fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
31
32
  };
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
+ }
33
58
  };
34
59
 
35
60
  // src/permissions/agent-permissions.ts
@@ -289,7 +314,7 @@ var AgentStore = class {
289
314
  // src/copilot/doctor.ts
290
315
  import {
291
316
  COPILOT_ERROR_CODES,
292
- createServicemeError
317
+ createServicemeError as createServicemeError2
293
318
  } from "@serviceme/devtools-protocol";
294
319
 
295
320
  // src/process/runCommand.ts
@@ -434,13 +459,13 @@ async function copilotDoctor() {
434
459
  return { installed: true, version, authenticated };
435
460
  }
436
461
  function createCopilotNotInstalledError() {
437
- return createServicemeError(
462
+ return createServicemeError2(
438
463
  COPILOT_ERROR_CODES.NOT_INSTALLED,
439
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."
440
465
  );
441
466
  }
442
467
  function createCopilotAuthRequiredError() {
443
- return createServicemeError(
468
+ return createServicemeError2(
444
469
  COPILOT_ERROR_CODES.AUTH_REQUIRED,
445
470
  "GitHub Copilot CLI requires authentication. Run `copilot auth login` to sign in."
446
471
  );
@@ -449,7 +474,8 @@ function createCopilotAuthRequiredError() {
449
474
  // src/copilot/prompt.ts
450
475
  import {
451
476
  COPILOT_ERROR_CODES as COPILOT_ERROR_CODES2,
452
- createServicemeError as createServicemeError2
477
+ createServicemeError as createServicemeError3,
478
+ ServicemeProtocolError
453
479
  } from "@serviceme/devtools-protocol";
454
480
  var COPILOT_COMMAND2 = "copilot";
455
481
  var DEFAULT_TIMEOUT_MS = 12e4;
@@ -484,7 +510,7 @@ async function copilotPrompt(options) {
484
510
  });
485
511
  const output = stripAnsi(result.stdout);
486
512
  if (output.includes("I'm sorry, but I cannot assist") && result.stderr?.includes("Stream completed without a response")) {
487
- throw createServicemeError2(
513
+ throw createServicemeError3(
488
514
  COPILOT_ERROR_CODES2.AUTH_REQUIRED,
489
515
  "Copilot CLI returned a refusal response. This typically indicates an expired authentication token. Run `gh auth login` to re-authenticate.",
490
516
  { exitCode: 0, output, stderr: result.stderr }
@@ -492,9 +518,12 @@ async function copilotPrompt(options) {
492
518
  }
493
519
  return { output, exitCode: 0 };
494
520
  } catch (error) {
521
+ if (error instanceof ServicemeProtocolError) {
522
+ throw error;
523
+ }
495
524
  const err = error;
496
525
  if (err.code === "ETIMEDOUT") {
497
- throw createServicemeError2(
526
+ throw createServicemeError3(
498
527
  COPILOT_ERROR_CODES2.TIMEOUT,
499
528
  `Copilot prompt timed out after ${String(timeout)}ms.`
500
529
  );
@@ -503,7 +532,7 @@ async function copilotPrompt(options) {
503
532
  const output = stripAnsi(err.stdout ?? "");
504
533
  const stderr = err.stderr ?? err.message ?? "";
505
534
  if (output.includes("I'm sorry, but I cannot assist") || stderr.includes("Stream completed without a response")) {
506
- throw createServicemeError2(
535
+ throw createServicemeError3(
507
536
  COPILOT_ERROR_CODES2.AUTH_REQUIRED,
508
537
  "Copilot CLI returned a refusal response. This typically indicates an expired authentication token. Run `gh auth login` to re-authenticate.",
509
538
  { exitCode, output, stderr }
@@ -512,7 +541,7 @@ async function copilotPrompt(options) {
512
541
  if (exitCode !== 0 && output) {
513
542
  return { output, exitCode };
514
543
  }
515
- throw createServicemeError2(
544
+ throw createServicemeError3(
516
545
  COPILOT_ERROR_CODES2.EXECUTION_FAILED,
517
546
  stderr || `Copilot exited with code ${String(exitCode)}.`,
518
547
  { exitCode, stderr }
@@ -522,7 +551,7 @@ async function copilotPrompt(options) {
522
551
 
523
552
  // src/env/environmentInspector.ts
524
553
  import {
525
- createServicemeError as createServicemeError3,
554
+ createServicemeError as createServicemeError4,
526
555
  KNOWN_ENVIRONMENT_TOOLS
527
556
  } from "@serviceme/devtools-protocol";
528
557
  var DEFAULT_TOOL_CHECK_TIMEOUT_MS = 5e3;
@@ -540,17 +569,19 @@ var TOOL_CHECK_TIMEOUT_MS = {
540
569
  var ERROR_CODE_NOT_FOUND = 127;
541
570
  var ERROR_CODE_TIMEOUT = "ETIMEDOUT";
542
571
  var EnvironmentInspector = class {
572
+ constructor(options = {}) {
573
+ this.runCommandFn = options.runCommand ?? runCommand;
574
+ this.platform = options.platform ?? process.platform;
575
+ }
543
576
  async checkEnvironment() {
544
577
  const results = await Promise.all(
545
- KNOWN_ENVIRONMENT_TOOLS.map(
546
- async (tool) => [tool, await this.checkTool(tool)]
547
- )
578
+ KNOWN_ENVIRONMENT_TOOLS.map(async (tool) => [tool, await this.checkTool(tool)])
548
579
  );
549
580
  return Object.fromEntries(results);
550
581
  }
551
582
  async checkTool(toolName) {
552
583
  if (!/^[a-zA-Z0-9-]+$/.test(toolName)) {
553
- throw createServicemeError3("invalid_params", "Invalid tool name.");
584
+ throw createServicemeError4("invalid_params", "Invalid tool name.");
554
585
  }
555
586
  try {
556
587
  if (toolName === "nvm") {
@@ -572,8 +603,8 @@ var EnvironmentInspector = class {
572
603
  }
573
604
  async getToolPath(toolName) {
574
605
  try {
575
- const isWindows = process.platform === "win32";
576
- const result = await runCommand(isWindows ? "where" : "which", {
606
+ const isWindows = this.platform === "win32";
607
+ const result = await this.runCommandFn(isWindows ? "where" : "which", {
577
608
  args: [toolName],
578
609
  timeoutMs: this.getToolTimeout(toolName)
579
610
  });
@@ -590,14 +621,12 @@ var EnvironmentInspector = class {
590
621
  */
591
622
  async getToolShimPath(toolName) {
592
623
  try {
593
- const result = await runCommand("where", {
624
+ const result = await this.runCommandFn("where", {
594
625
  args: [toolName],
595
626
  timeoutMs: this.getToolTimeout(toolName)
596
627
  });
597
628
  const candidates = result.stdout.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0);
598
- const cmdShim = candidates.find(
599
- (line) => line.toLowerCase().endsWith(".cmd")
600
- );
629
+ const cmdShim = candidates.find((line) => line.toLowerCase().endsWith(".cmd"));
601
630
  return cmdShim ?? candidates[0];
602
631
  } catch {
603
632
  return void 0;
@@ -605,16 +634,16 @@ var EnvironmentInspector = class {
605
634
  }
606
635
  async getToolVersion(toolName) {
607
636
  const invocation = await this.getVersionInvocation(toolName);
608
- const result = await runCommand(invocation.command, {
637
+ const result = await this.runCommandFn(invocation.command, {
609
638
  args: invocation.args,
610
639
  timeoutMs: this.getToolTimeout(toolName)
611
640
  });
612
641
  return this.parseVersion(toolName, result.stdout || result.stderr);
613
642
  }
614
643
  async checkNvm() {
615
- if (process.platform === "win32") {
644
+ if (this.platform === "win32") {
616
645
  try {
617
- const result = await runCommand("cmd.exe", {
646
+ const result = await this.runCommandFn("cmd.exe", {
618
647
  args: ["/c", "nvm version"],
619
648
  timeoutMs: this.getToolTimeout("nvm")
620
649
  });
@@ -631,7 +660,7 @@ var EnvironmentInspector = class {
631
660
  }
632
661
  }
633
662
  try {
634
- const result = await runCommand("/bin/bash", {
663
+ const result = await this.runCommandFn("/bin/bash", {
635
664
  args: [
636
665
  "-lc",
637
666
  'export NVM_DIR="$HOME/.nvm"; [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"; nvm --version'
@@ -659,7 +688,7 @@ var EnvironmentInspector = class {
659
688
  };
660
689
  }
661
690
  try {
662
- const result = await runCommand("dotnet", {
691
+ const result = await this.runCommandFn("dotnet", {
663
692
  args: ["nuget", "list", "source"],
664
693
  timeoutMs: this.getToolTimeout("nuget")
665
694
  });
@@ -680,7 +709,7 @@ var EnvironmentInspector = class {
680
709
  }
681
710
  }
682
711
  async getVersionInvocation(toolName) {
683
- const isWindows = process.platform === "win32";
712
+ const isWindows = this.platform === "win32";
684
713
  if (isWindows && ["npm", "pnpm", "nrm"].includes(toolName)) {
685
714
  const shim = await this.getToolShimPath(toolName);
686
715
  if (shim) {
@@ -734,7 +763,7 @@ var EnvironmentInspector = class {
734
763
  const execError = error;
735
764
  const message = execError.message || String(error);
736
765
  const code = execError.code;
737
- const isNotFound = message.includes("command not found") || message.includes("not recognized") || message.includes("ENOENT") || code === "ENOENT" || code === ERROR_CODE_NOT_FOUND;
766
+ 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;
738
767
  const isTimeout = code === ERROR_CODE_TIMEOUT || message.toLowerCase().includes("timed out");
739
768
  return {
740
769
  installed: false,
@@ -747,7 +776,7 @@ var EnvironmentInspector = class {
747
776
  import * as fs2 from "fs/promises";
748
777
  import * as path2 from "path";
749
778
  import {
750
- createServicemeError as createServicemeError4
779
+ createServicemeError as createServicemeError5
751
780
  } from "@serviceme/devtools-protocol";
752
781
  var SUPPORTED_FORMATS = [
753
782
  ".jpg",
@@ -792,7 +821,7 @@ var ImageTools = class {
792
821
  async compress(imagePath, options) {
793
822
  const validation = await this.validate(imagePath, options.sharpModulePath);
794
823
  if (!validation.valid) {
795
- throw createServicemeError4(
824
+ throw createServicemeError5(
796
825
  "invalid_params",
797
826
  `Invalid image file: ${imagePath}`
798
827
  );
@@ -866,7 +895,7 @@ var ImageTools = class {
866
895
  try {
867
896
  return sharpModulePath ? __require(sharpModulePath) : __require("sharp");
868
897
  } catch (error) {
869
- throw createServicemeError4(
898
+ throw createServicemeError5(
870
899
  "internal_error",
871
900
  `sharp is required for image operations: ${error instanceof Error ? error.message : String(error)}`
872
901
  );
@@ -879,7 +908,7 @@ function createImageTools() {
879
908
 
880
909
  // src/json/jsonTools.ts
881
910
  import {
882
- createServicemeError as createServicemeError5,
911
+ createServicemeError as createServicemeError6,
883
912
  DEFAULT_JSON_SORT_OPTIONS
884
913
  } from "@serviceme/devtools-protocol";
885
914
  import { parse as parseCommentJson } from "comment-json";
@@ -923,7 +952,7 @@ function parseJson(text) {
923
952
  } catch {
924
953
  }
925
954
  }
926
- throw createServicemeError5("json_invalid_input", "Invalid JSON format.");
955
+ throw createServicemeError6("json_invalid_input", "Invalid JSON format.");
927
956
  }
928
957
  function sortObject(obj, options) {
929
958
  if (Array.isArray(obj)) {
@@ -1018,67 +1047,52 @@ function createConsoleLogger(prefix = "serviceme") {
1018
1047
  import * as fs3 from "fs/promises";
1019
1048
  import * as path3 from "path";
1020
1049
  import {
1021
- createServicemeError as createServicemeError6
1050
+ createServicemeError as createServicemeError7
1022
1051
  } from "@serviceme/devtools-protocol";
1023
1052
 
1024
1053
  // src/utils/fileUtils.ts
1025
1054
  import { constants, createWriteStream } from "fs";
1026
- import {
1027
- access as access2,
1028
- copyFile,
1029
- lstat,
1030
- mkdir,
1031
- readdir,
1032
- rename,
1033
- rm
1034
- } from "fs/promises";
1055
+ import { access as access2, copyFile, lstat, mkdir, readdir, rename, rm } from "fs/promises";
1035
1056
  import { dirname as dirname3, join as join3 } from "path";
1036
- import { open } from "yauzl";
1057
+ import yauzl from "yauzl";
1037
1058
  var unzipFile = (zipPath, dest) => {
1038
1059
  return new Promise((resolve, reject) => {
1039
- open(
1040
- zipPath,
1041
- { lazyEntries: true },
1042
- (err, zipfile) => {
1043
- if (err) return reject(err);
1044
- if (!zipfile) return reject(new Error("Failed to open zip file."));
1045
- zipfile.readEntry();
1046
- zipfile.on("entry", (entry) => {
1047
- if (/\/$/.test(entry.fileName)) {
1048
- void mkdir(join3(dest, entry.fileName), { recursive: true }).then(() => {
1049
- zipfile.readEntry();
1050
- }).catch(reject);
1051
- } else {
1052
- const outputPath = join3(dest, entry.fileName);
1053
- void mkdir(dirname3(outputPath), { recursive: true }).then(() => {
1054
- zipfile.openReadStream(
1055
- entry,
1056
- (streamError, readStream) => {
1057
- if (streamError) return reject(streamError);
1058
- if (!readStream)
1059
- return reject(
1060
- new Error("Failed to open zip entry stream.")
1061
- );
1062
- const writeStream = createWriteStream(outputPath);
1063
- readStream.on("error", reject);
1064
- writeStream.on("error", reject);
1065
- writeStream.on("close", () => {
1066
- zipfile.readEntry();
1067
- });
1068
- readStream.pipe(writeStream);
1069
- }
1070
- );
1071
- }).catch(reject);
1072
- }
1073
- });
1074
- zipfile.on("end", () => {
1075
- resolve();
1076
- });
1077
- zipfile.on("error", (zipError) => {
1078
- reject(zipError);
1079
- });
1080
- }
1081
- );
1060
+ yauzl.open(zipPath, { lazyEntries: true }, (err, zipfile) => {
1061
+ if (err) return reject(err);
1062
+ if (!zipfile) return reject(new Error("Failed to open zip file."));
1063
+ zipfile.readEntry();
1064
+ zipfile.on("entry", (entry) => {
1065
+ if (/\/$/.test(entry.fileName)) {
1066
+ void mkdir(join3(dest, entry.fileName), { recursive: true }).then(() => {
1067
+ zipfile.readEntry();
1068
+ }).catch(reject);
1069
+ } else {
1070
+ const outputPath = join3(dest, entry.fileName);
1071
+ void mkdir(dirname3(outputPath), { recursive: true }).then(() => {
1072
+ zipfile.openReadStream(
1073
+ entry,
1074
+ (streamError, readStream) => {
1075
+ if (streamError) return reject(streamError);
1076
+ if (!readStream) return reject(new Error("Failed to open zip entry stream."));
1077
+ const writeStream = createWriteStream(outputPath);
1078
+ readStream.on("error", reject);
1079
+ writeStream.on("error", reject);
1080
+ writeStream.on("close", () => {
1081
+ zipfile.readEntry();
1082
+ });
1083
+ readStream.pipe(writeStream);
1084
+ }
1085
+ );
1086
+ }).catch(reject);
1087
+ }
1088
+ });
1089
+ zipfile.on("end", () => {
1090
+ resolve();
1091
+ });
1092
+ zipfile.on("error", (zipError) => {
1093
+ reject(zipError);
1094
+ });
1095
+ });
1082
1096
  });
1083
1097
  };
1084
1098
  var tryLstat = async (targetPath) => {
@@ -1102,11 +1116,7 @@ var mergeEntry = async (sourcePath, destPath, overwrite) => {
1102
1116
  await mkdir(destPath, { recursive: true });
1103
1117
  const children = await readdir(sourcePath);
1104
1118
  for (const child of children) {
1105
- await mergeEntry(
1106
- join3(sourcePath, child),
1107
- join3(destPath, child),
1108
- overwrite
1109
- );
1119
+ await mergeEntry(join3(sourcePath, child), join3(destPath, child), overwrite);
1110
1120
  }
1111
1121
  await rm(sourcePath, { recursive: true, force: true });
1112
1122
  return;
@@ -1183,7 +1193,7 @@ var ProjectTools = class {
1183
1193
  }
1184
1194
  async installDependencies(workspacePath, command) {
1185
1195
  if (!command) {
1186
- throw createServicemeError6("invalid_params", "Expected install command.");
1196
+ throw createServicemeError7("invalid_params", "Expected install command.");
1187
1197
  }
1188
1198
  await runCommand(command, {
1189
1199
  cwd: workspacePath,
@@ -1196,23 +1206,15 @@ var ProjectTools = class {
1196
1206
  }
1197
1207
  async makeScriptsExecutable(workspacePath) {
1198
1208
  const isWindows = process.platform === "win32";
1199
- const scripts = await this.findScripts(
1200
- workspacePath,
1201
- isWindows ? [".ps1", ".bat"] : [".sh"]
1202
- );
1209
+ const scripts = await this.findScripts(workspacePath, isWindows ? [".ps1", ".bat"] : [".sh"]);
1203
1210
  let updatedCount = 0;
1204
1211
  if (isWindows) {
1205
- for (const scriptPath of scripts.filter(
1206
- (script) => script.endsWith(".ps1")
1207
- )) {
1212
+ for (const scriptPath of scripts.filter((script) => script.endsWith(".ps1"))) {
1208
1213
  try {
1209
- await runCommand(
1210
- `powershell -Command "Unblock-File -Path '${scriptPath}'"`,
1211
- {
1212
- cwd: workspacePath,
1213
- shell: true
1214
- }
1215
- );
1214
+ await runCommand(`powershell -Command "Unblock-File -Path '${scriptPath}'"`, {
1215
+ cwd: workspacePath,
1216
+ shell: true
1217
+ });
1216
1218
  updatedCount += 1;
1217
1219
  } catch {
1218
1220
  }
@@ -1246,13 +1248,16 @@ var ProjectTools = class {
1246
1248
  }
1247
1249
  async runScaffoldPrune(workspacePath, preset) {
1248
1250
  if (!preset) {
1249
- throw createServicemeError6("invalid_params", "Expected scaffold preset.");
1251
+ throw createServicemeError7("invalid_params", "Expected scaffold preset.");
1250
1252
  }
1251
- const commands = [
1252
- `pnpm run scaffold:prune -- --preset ${preset} --project-root .`,
1253
- `pnpm run scaffold:init-metadata -- --preset ${preset} --project-root . --force`
1254
- ];
1253
+ await this.ensurePresetManifest(workspacePath, preset);
1254
+ const pruneCommand = `pnpm run scaffold:prune -- --preset ${preset} --project-root .`;
1255
+ const initMetadataCommand = `pnpm run scaffold:init-metadata -- --preset ${preset} --project-root . --force`;
1256
+ const commands = [pruneCommand, initMetadataCommand];
1255
1257
  for (const command of commands) {
1258
+ if (command === initMetadataCommand) {
1259
+ await this.ensurePresetManifest(workspacePath, preset);
1260
+ }
1256
1261
  await runCommand(command, {
1257
1262
  cwd: workspacePath,
1258
1263
  shell: true
@@ -1264,6 +1269,40 @@ var ProjectTools = class {
1264
1269
  commands
1265
1270
  };
1266
1271
  }
1272
+ async ensurePresetManifest(workspacePath, preset) {
1273
+ const presetManifestPath = path3.join(
1274
+ workspacePath,
1275
+ ".ms-scaffold",
1276
+ "presets",
1277
+ `${preset}.json`
1278
+ );
1279
+ if (await this.pathExists(presetManifestPath)) {
1280
+ return;
1281
+ }
1282
+ const projectModePath = path3.join(workspacePath, ".ms-scaffold", "project-mode.json");
1283
+ if (!await this.pathExists(projectModePath)) {
1284
+ return;
1285
+ }
1286
+ const projectModeRaw = await fs3.readFile(projectModePath, "utf8");
1287
+ const projectMode = JSON.parse(projectModeRaw);
1288
+ const synthesizedPreset = {
1289
+ preset,
1290
+ selectedModules: Array.isArray(projectMode.selectedModules) ? projectMode.selectedModules : [],
1291
+ prunedModules: Array.isArray(projectMode.prunedModules) ? projectMode.prunedModules : [],
1292
+ exclude: [],
1293
+ recommendedFollowUps: [],
1294
+ managedFiles: [],
1295
+ mergeManagedFiles: [],
1296
+ userOwnedPaths: []
1297
+ };
1298
+ await fs3.mkdir(path3.dirname(presetManifestPath), { recursive: true });
1299
+ await fs3.writeFile(
1300
+ presetManifestPath,
1301
+ `${JSON.stringify(synthesizedPreset, null, 2)}
1302
+ `,
1303
+ "utf8"
1304
+ );
1305
+ }
1267
1306
  async findScripts(dir, extensions) {
1268
1307
  const results = [];
1269
1308
  let entries;
@@ -1294,9 +1333,7 @@ var ProjectTools = class {
1294
1333
  if (directoryNames.length === 1) {
1295
1334
  return directoryNames[0] ?? null;
1296
1335
  }
1297
- const exactMatch = directoryNames.find(
1298
- (directoryName) => directoryName === expectedDirName
1299
- );
1336
+ const exactMatch = directoryNames.find((directoryName) => directoryName === expectedDirName);
1300
1337
  if (exactMatch) {
1301
1338
  return exactMatch;
1302
1339
  }
@@ -1537,10 +1574,8 @@ var GithubCopilotCliExecutor = class {
1537
1574
  windowsHide: true
1538
1575
  });
1539
1576
  if (child.pid) {
1540
- writeDiagnostic(
1541
- `[GithubCopilotCliExecutor] spawned child PID=${child.pid}
1542
- `
1543
- );
1577
+ writeDiagnostic(`[GithubCopilotCliExecutor] spawned child PID=${child.pid}
1578
+ `);
1544
1579
  }
1545
1580
  const timeoutMs = execution.timeoutMs;
1546
1581
  const timer = timeoutMs != null ? setTimeout(() => {
@@ -1558,15 +1593,13 @@ var GithubCopilotCliExecutor = class {
1558
1593
  child.stdout.on("data", (chunk) => {
1559
1594
  const data = chunk.toString();
1560
1595
  stdoutBuf += data;
1561
- if (stdoutBuf.length > MAX_OUTPUT_BYTES)
1562
- stdoutBuf = stdoutBuf.slice(-MAX_OUTPUT_BYTES);
1596
+ if (stdoutBuf.length > MAX_OUTPUT_BYTES) stdoutBuf = stdoutBuf.slice(-MAX_OUTPUT_BYTES);
1563
1597
  onOutput("stdout", data);
1564
1598
  });
1565
1599
  child.stderr.on("data", (chunk) => {
1566
1600
  const data = chunk.toString();
1567
1601
  stderrBuf += data;
1568
- if (stderrBuf.length > MAX_OUTPUT_BYTES)
1569
- stderrBuf = stderrBuf.slice(-MAX_OUTPUT_BYTES);
1602
+ if (stderrBuf.length > MAX_OUTPUT_BYTES) stderrBuf = stderrBuf.slice(-MAX_OUTPUT_BYTES);
1570
1603
  onOutput("stderr", data);
1571
1604
  });
1572
1605
  child.on("close", (code) => {
@@ -1884,7 +1917,7 @@ function getExecutor(taskType) {
1884
1917
  import { randomUUID } from "crypto";
1885
1918
  import * as fs8 from "fs";
1886
1919
  import * as path7 from "path";
1887
- import { createServicemeError as createServicemeError7 } from "@serviceme/devtools-protocol";
1920
+ import { createServicemeError as createServicemeError8 } from "@serviceme/devtools-protocol";
1888
1921
  var CONFIG_DIR3 = ".serviceme";
1889
1922
  var CONFIG_FILE = "scheduled-tasks.json";
1890
1923
  function emptyConfig() {
@@ -1895,10 +1928,7 @@ function isRecord(value) {
1895
1928
  }
1896
1929
  function requireNonEmptyString(payload, field, taskType) {
1897
1930
  if (!isRecord(payload) || typeof payload[field] !== "string" || !payload[field].trim()) {
1898
- throw createServicemeError7(
1899
- "invalid_payload",
1900
- `${taskType} payload ${field} is required`
1901
- );
1931
+ throw createServicemeError8("invalid_payload", `${taskType} payload ${field} is required`);
1902
1932
  }
1903
1933
  }
1904
1934
  function validateTaskPayload(taskType, payload) {
@@ -2061,9 +2091,7 @@ var TaskExecutionEngine = class {
2061
2091
  if (policy === "reject") {
2062
2092
  const existingIds = this.taskExecutions.get(snapshot.taskId);
2063
2093
  if (existingIds && existingIds.size > 0) {
2064
- throw new Error(
2065
- `TASK_ALREADY_RUNNING: Task ${snapshot.taskId} is already running`
2066
- );
2094
+ throw new Error(`TASK_ALREADY_RUNNING: Task ${snapshot.taskId} is already running`);
2067
2095
  }
2068
2096
  }
2069
2097
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -2328,7 +2356,7 @@ var TaskLogManager = class {
2328
2356
  var TICK_INTERVAL = 1e3;
2329
2357
  var MIN_SCHEDULE_INTERVAL = 1e3;
2330
2358
  var SchedulerDaemon = class {
2331
- constructor(workspacePath) {
2359
+ constructor(workspacePath, options = {}) {
2332
2360
  this.tickTimer = null;
2333
2361
  this.watcher = null;
2334
2362
  this.running = false;
@@ -2341,6 +2369,7 @@ var SchedulerDaemon = class {
2341
2369
  this.logManager = new TaskLogManager(workspacePath);
2342
2370
  this.pidManager = new PidManager(workspacePath);
2343
2371
  this.logger = new DaemonLogger(workspacePath);
2372
+ this.getExecutor = options.getExecutor ?? getExecutor;
2344
2373
  }
2345
2374
  start() {
2346
2375
  if (this.running) return;
@@ -2446,7 +2475,7 @@ var SchedulerDaemon = class {
2446
2475
  this.workspacePath
2447
2476
  );
2448
2477
  validateTaskPayload(task.taskType, executionPayload);
2449
- const executor = getExecutor(task.taskType);
2478
+ const executor = this.getExecutor(task.taskType);
2450
2479
  const result = await executor.execute(executionPayload);
2451
2480
  const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
2452
2481
  const durationMs = Date.now() - startMs;
@@ -2543,6 +2572,7 @@ function matchCronField(field, value) {
2543
2572
  }
2544
2573
 
2545
2574
  // src/skills/SkillCatalogClient.ts
2575
+ import { createServicemeError as createServicemeError9 } from "@serviceme/devtools-protocol";
2546
2576
  var SkillCatalogClient = class {
2547
2577
  constructor(options = {}) {
2548
2578
  this.fetchImpl = options.fetchImpl ?? fetch;
@@ -2550,10 +2580,10 @@ var SkillCatalogClient = class {
2550
2580
  }
2551
2581
  async getCatalog() {
2552
2582
  if (!this.baseUrl) {
2553
- return {
2554
- skills: [],
2555
- fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
2556
- };
2583
+ throw createServicemeError9(
2584
+ "workspace_not_found",
2585
+ "Skill catalog baseUrl is not configured."
2586
+ );
2557
2587
  }
2558
2588
  const response = await this.fetchImpl(
2559
2589
  `${this.baseUrl}/api/v1/marketplace/skills`
@@ -2567,6 +2597,30 @@ var SkillCatalogClient = class {
2567
2597
  fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
2568
2598
  };
2569
2599
  }
2600
+ async downloadSkill(remoteId) {
2601
+ if (!this.baseUrl) {
2602
+ throw createServicemeError9(
2603
+ "workspace_not_found",
2604
+ "Skill catalog baseUrl is not configured."
2605
+ );
2606
+ }
2607
+ const response = await this.fetchImpl(
2608
+ `${this.baseUrl}/api/v1/marketplace/skills/download/${remoteId}`
2609
+ );
2610
+ if (!response.ok) {
2611
+ if (response.status === 404) {
2612
+ throw createServicemeError9(
2613
+ "not_found",
2614
+ `Skill '${remoteId}' not found`
2615
+ );
2616
+ }
2617
+ throw new Error(
2618
+ `Failed to download skill ${remoteId}: ${response.status}`
2619
+ );
2620
+ }
2621
+ const payload = await response.json();
2622
+ return payload.data?.files ?? [];
2623
+ }
2570
2624
  };
2571
2625
 
2572
2626
  // src/skills/SkillReconciler.ts
@@ -2703,6 +2757,22 @@ var SkillStore = class {
2703
2757
  return false;
2704
2758
  }
2705
2759
  }
2760
+ async writeSkillFiles(skillId, scope, files) {
2761
+ const root = scope === "workspace" ? path9.join(this.workspacePath, WORKSPACE_SKILLS_ROOT_RELATIVE) : this.userSkillsRoot;
2762
+ const targetDir = path9.join(root, skillId);
2763
+ await this.fileSystem.mkdir(targetDir, { recursive: true });
2764
+ for (const file of files) {
2765
+ const filePath = path9.join(targetDir, file.path);
2766
+ await this.fileSystem.mkdir(path9.dirname(filePath), { recursive: true });
2767
+ await this.fileSystem.writeFile(filePath, file.content, "utf-8");
2768
+ if (file.executable) {
2769
+ try {
2770
+ await this.fileSystem.chmod(filePath, 493);
2771
+ } catch {
2772
+ }
2773
+ }
2774
+ }
2775
+ }
2706
2776
  };
2707
2777
  export {
2708
2778
  AgentCatalogClient,