@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.js CHANGED
@@ -72,6 +72,7 @@ __export(index_exports, {
72
72
  module.exports = __toCommonJS(index_exports);
73
73
 
74
74
  // src/agents/AgentCatalogClient.ts
75
+ var import_devtools_protocol = require("@serviceme/devtools-protocol");
75
76
  var AgentCatalogClient = class {
76
77
  constructor(options = {}) {
77
78
  this.fetchImpl = options.fetchImpl ?? fetch;
@@ -79,10 +80,10 @@ var AgentCatalogClient = class {
79
80
  }
80
81
  async getCatalog() {
81
82
  if (!this.baseUrl) {
82
- return {
83
- agents: [],
84
- fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
85
- };
83
+ throw (0, import_devtools_protocol.createServicemeError)(
84
+ "workspace_not_found",
85
+ "Agent catalog baseUrl is not configured."
86
+ );
86
87
  }
87
88
  const response = await this.fetchImpl(
88
89
  `${this.baseUrl}/api/v1/marketplace/agents`
@@ -96,6 +97,30 @@ var AgentCatalogClient = class {
96
97
  fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
97
98
  };
98
99
  }
100
+ async downloadAgent(remoteId) {
101
+ if (!this.baseUrl) {
102
+ throw (0, import_devtools_protocol.createServicemeError)(
103
+ "workspace_not_found",
104
+ "Agent catalog baseUrl is not configured."
105
+ );
106
+ }
107
+ const response = await this.fetchImpl(
108
+ `${this.baseUrl}/api/v1/marketplace/agents/download/${remoteId}`
109
+ );
110
+ if (!response.ok) {
111
+ if (response.status === 404) {
112
+ throw (0, import_devtools_protocol.createServicemeError)(
113
+ "not_found",
114
+ `Agent '${remoteId}' not found`
115
+ );
116
+ }
117
+ throw new Error(
118
+ `Failed to download agent ${remoteId}: ${response.status}`
119
+ );
120
+ }
121
+ const payload = await response.json();
122
+ return payload.data?.files ?? [];
123
+ }
99
124
  };
100
125
 
101
126
  // src/permissions/agent-permissions.ts
@@ -353,7 +378,7 @@ var AgentStore = class {
353
378
  };
354
379
 
355
380
  // src/copilot/doctor.ts
356
- var import_devtools_protocol = require("@serviceme/devtools-protocol");
381
+ var import_devtools_protocol2 = require("@serviceme/devtools-protocol");
357
382
 
358
383
  // src/process/runCommand.ts
359
384
  var import_node_child_process = require("child_process");
@@ -497,20 +522,20 @@ async function copilotDoctor() {
497
522
  return { installed: true, version, authenticated };
498
523
  }
499
524
  function createCopilotNotInstalledError() {
500
- return (0, import_devtools_protocol.createServicemeError)(
501
- import_devtools_protocol.COPILOT_ERROR_CODES.NOT_INSTALLED,
525
+ return (0, import_devtools_protocol2.createServicemeError)(
526
+ import_devtools_protocol2.COPILOT_ERROR_CODES.NOT_INSTALLED,
502
527
  "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."
503
528
  );
504
529
  }
505
530
  function createCopilotAuthRequiredError() {
506
- return (0, import_devtools_protocol.createServicemeError)(
507
- import_devtools_protocol.COPILOT_ERROR_CODES.AUTH_REQUIRED,
531
+ return (0, import_devtools_protocol2.createServicemeError)(
532
+ import_devtools_protocol2.COPILOT_ERROR_CODES.AUTH_REQUIRED,
508
533
  "GitHub Copilot CLI requires authentication. Run `copilot auth login` to sign in."
509
534
  );
510
535
  }
511
536
 
512
537
  // src/copilot/prompt.ts
513
- var import_devtools_protocol2 = require("@serviceme/devtools-protocol");
538
+ var import_devtools_protocol3 = require("@serviceme/devtools-protocol");
514
539
  var COPILOT_COMMAND2 = "copilot";
515
540
  var DEFAULT_TIMEOUT_MS = 12e4;
516
541
  function stripAnsi(text) {
@@ -544,18 +569,21 @@ async function copilotPrompt(options) {
544
569
  });
545
570
  const output = stripAnsi(result.stdout);
546
571
  if (output.includes("I'm sorry, but I cannot assist") && result.stderr?.includes("Stream completed without a response")) {
547
- throw (0, import_devtools_protocol2.createServicemeError)(
548
- import_devtools_protocol2.COPILOT_ERROR_CODES.AUTH_REQUIRED,
572
+ throw (0, import_devtools_protocol3.createServicemeError)(
573
+ import_devtools_protocol3.COPILOT_ERROR_CODES.AUTH_REQUIRED,
549
574
  "Copilot CLI returned a refusal response. This typically indicates an expired authentication token. Run `gh auth login` to re-authenticate.",
550
575
  { exitCode: 0, output, stderr: result.stderr }
551
576
  );
552
577
  }
553
578
  return { output, exitCode: 0 };
554
579
  } catch (error) {
580
+ if (error instanceof import_devtools_protocol3.ServicemeProtocolError) {
581
+ throw error;
582
+ }
555
583
  const err = error;
556
584
  if (err.code === "ETIMEDOUT") {
557
- throw (0, import_devtools_protocol2.createServicemeError)(
558
- import_devtools_protocol2.COPILOT_ERROR_CODES.TIMEOUT,
585
+ throw (0, import_devtools_protocol3.createServicemeError)(
586
+ import_devtools_protocol3.COPILOT_ERROR_CODES.TIMEOUT,
559
587
  `Copilot prompt timed out after ${String(timeout)}ms.`
560
588
  );
561
589
  }
@@ -563,8 +591,8 @@ async function copilotPrompt(options) {
563
591
  const output = stripAnsi(err.stdout ?? "");
564
592
  const stderr = err.stderr ?? err.message ?? "";
565
593
  if (output.includes("I'm sorry, but I cannot assist") || stderr.includes("Stream completed without a response")) {
566
- throw (0, import_devtools_protocol2.createServicemeError)(
567
- import_devtools_protocol2.COPILOT_ERROR_CODES.AUTH_REQUIRED,
594
+ throw (0, import_devtools_protocol3.createServicemeError)(
595
+ import_devtools_protocol3.COPILOT_ERROR_CODES.AUTH_REQUIRED,
568
596
  "Copilot CLI returned a refusal response. This typically indicates an expired authentication token. Run `gh auth login` to re-authenticate.",
569
597
  { exitCode, output, stderr }
570
598
  );
@@ -572,8 +600,8 @@ async function copilotPrompt(options) {
572
600
  if (exitCode !== 0 && output) {
573
601
  return { output, exitCode };
574
602
  }
575
- throw (0, import_devtools_protocol2.createServicemeError)(
576
- import_devtools_protocol2.COPILOT_ERROR_CODES.EXECUTION_FAILED,
603
+ throw (0, import_devtools_protocol3.createServicemeError)(
604
+ import_devtools_protocol3.COPILOT_ERROR_CODES.EXECUTION_FAILED,
577
605
  stderr || `Copilot exited with code ${String(exitCode)}.`,
578
606
  { exitCode, stderr }
579
607
  );
@@ -581,7 +609,7 @@ async function copilotPrompt(options) {
581
609
  }
582
610
 
583
611
  // src/env/environmentInspector.ts
584
- var import_devtools_protocol3 = require("@serviceme/devtools-protocol");
612
+ var import_devtools_protocol4 = require("@serviceme/devtools-protocol");
585
613
  var DEFAULT_TOOL_CHECK_TIMEOUT_MS = 5e3;
586
614
  var TOOL_CHECK_TIMEOUT_MS = {
587
615
  nuget: 12e3,
@@ -597,17 +625,19 @@ var TOOL_CHECK_TIMEOUT_MS = {
597
625
  var ERROR_CODE_NOT_FOUND = 127;
598
626
  var ERROR_CODE_TIMEOUT = "ETIMEDOUT";
599
627
  var EnvironmentInspector = class {
628
+ constructor(options = {}) {
629
+ this.runCommandFn = options.runCommand ?? runCommand;
630
+ this.platform = options.platform ?? process.platform;
631
+ }
600
632
  async checkEnvironment() {
601
633
  const results = await Promise.all(
602
- import_devtools_protocol3.KNOWN_ENVIRONMENT_TOOLS.map(
603
- async (tool) => [tool, await this.checkTool(tool)]
604
- )
634
+ import_devtools_protocol4.KNOWN_ENVIRONMENT_TOOLS.map(async (tool) => [tool, await this.checkTool(tool)])
605
635
  );
606
636
  return Object.fromEntries(results);
607
637
  }
608
638
  async checkTool(toolName) {
609
639
  if (!/^[a-zA-Z0-9-]+$/.test(toolName)) {
610
- throw (0, import_devtools_protocol3.createServicemeError)("invalid_params", "Invalid tool name.");
640
+ throw (0, import_devtools_protocol4.createServicemeError)("invalid_params", "Invalid tool name.");
611
641
  }
612
642
  try {
613
643
  if (toolName === "nvm") {
@@ -629,8 +659,8 @@ var EnvironmentInspector = class {
629
659
  }
630
660
  async getToolPath(toolName) {
631
661
  try {
632
- const isWindows = process.platform === "win32";
633
- const result = await runCommand(isWindows ? "where" : "which", {
662
+ const isWindows = this.platform === "win32";
663
+ const result = await this.runCommandFn(isWindows ? "where" : "which", {
634
664
  args: [toolName],
635
665
  timeoutMs: this.getToolTimeout(toolName)
636
666
  });
@@ -647,14 +677,12 @@ var EnvironmentInspector = class {
647
677
  */
648
678
  async getToolShimPath(toolName) {
649
679
  try {
650
- const result = await runCommand("where", {
680
+ const result = await this.runCommandFn("where", {
651
681
  args: [toolName],
652
682
  timeoutMs: this.getToolTimeout(toolName)
653
683
  });
654
684
  const candidates = result.stdout.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0);
655
- const cmdShim = candidates.find(
656
- (line) => line.toLowerCase().endsWith(".cmd")
657
- );
685
+ const cmdShim = candidates.find((line) => line.toLowerCase().endsWith(".cmd"));
658
686
  return cmdShim ?? candidates[0];
659
687
  } catch {
660
688
  return void 0;
@@ -662,16 +690,16 @@ var EnvironmentInspector = class {
662
690
  }
663
691
  async getToolVersion(toolName) {
664
692
  const invocation = await this.getVersionInvocation(toolName);
665
- const result = await runCommand(invocation.command, {
693
+ const result = await this.runCommandFn(invocation.command, {
666
694
  args: invocation.args,
667
695
  timeoutMs: this.getToolTimeout(toolName)
668
696
  });
669
697
  return this.parseVersion(toolName, result.stdout || result.stderr);
670
698
  }
671
699
  async checkNvm() {
672
- if (process.platform === "win32") {
700
+ if (this.platform === "win32") {
673
701
  try {
674
- const result = await runCommand("cmd.exe", {
702
+ const result = await this.runCommandFn("cmd.exe", {
675
703
  args: ["/c", "nvm version"],
676
704
  timeoutMs: this.getToolTimeout("nvm")
677
705
  });
@@ -688,7 +716,7 @@ var EnvironmentInspector = class {
688
716
  }
689
717
  }
690
718
  try {
691
- const result = await runCommand("/bin/bash", {
719
+ const result = await this.runCommandFn("/bin/bash", {
692
720
  args: [
693
721
  "-lc",
694
722
  'export NVM_DIR="$HOME/.nvm"; [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"; nvm --version'
@@ -716,7 +744,7 @@ var EnvironmentInspector = class {
716
744
  };
717
745
  }
718
746
  try {
719
- const result = await runCommand("dotnet", {
747
+ const result = await this.runCommandFn("dotnet", {
720
748
  args: ["nuget", "list", "source"],
721
749
  timeoutMs: this.getToolTimeout("nuget")
722
750
  });
@@ -737,7 +765,7 @@ var EnvironmentInspector = class {
737
765
  }
738
766
  }
739
767
  async getVersionInvocation(toolName) {
740
- const isWindows = process.platform === "win32";
768
+ const isWindows = this.platform === "win32";
741
769
  if (isWindows && ["npm", "pnpm", "nrm"].includes(toolName)) {
742
770
  const shim = await this.getToolShimPath(toolName);
743
771
  if (shim) {
@@ -791,7 +819,7 @@ var EnvironmentInspector = class {
791
819
  const execError = error;
792
820
  const message = execError.message || String(error);
793
821
  const code = execError.code;
794
- const isNotFound = message.includes("command not found") || message.includes("not recognized") || message.includes("ENOENT") || code === "ENOENT" || code === ERROR_CODE_NOT_FOUND;
822
+ 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;
795
823
  const isTimeout = code === ERROR_CODE_TIMEOUT || message.toLowerCase().includes("timed out");
796
824
  return {
797
825
  installed: false,
@@ -803,7 +831,7 @@ var EnvironmentInspector = class {
803
831
  // src/image/imageTools.ts
804
832
  var fs2 = __toESM(require("fs/promises"));
805
833
  var path2 = __toESM(require("path"));
806
- var import_devtools_protocol4 = require("@serviceme/devtools-protocol");
834
+ var import_devtools_protocol5 = require("@serviceme/devtools-protocol");
807
835
  var SUPPORTED_FORMATS = [
808
836
  ".jpg",
809
837
  ".jpeg",
@@ -847,7 +875,7 @@ var ImageTools = class {
847
875
  async compress(imagePath, options) {
848
876
  const validation = await this.validate(imagePath, options.sharpModulePath);
849
877
  if (!validation.valid) {
850
- throw (0, import_devtools_protocol4.createServicemeError)(
878
+ throw (0, import_devtools_protocol5.createServicemeError)(
851
879
  "invalid_params",
852
880
  `Invalid image file: ${imagePath}`
853
881
  );
@@ -921,7 +949,7 @@ var ImageTools = class {
921
949
  try {
922
950
  return sharpModulePath ? require(sharpModulePath) : require("sharp");
923
951
  } catch (error) {
924
- throw (0, import_devtools_protocol4.createServicemeError)(
952
+ throw (0, import_devtools_protocol5.createServicemeError)(
925
953
  "internal_error",
926
954
  `sharp is required for image operations: ${error instanceof Error ? error.message : String(error)}`
927
955
  );
@@ -933,13 +961,13 @@ function createImageTools() {
933
961
  }
934
962
 
935
963
  // src/json/jsonTools.ts
936
- var import_devtools_protocol5 = require("@serviceme/devtools-protocol");
964
+ var import_devtools_protocol6 = require("@serviceme/devtools-protocol");
937
965
  var import_comment_json = require("comment-json");
938
966
  var import_json5 = __toESM(require("json5"));
939
967
  function createJsonTools() {
940
968
  return {
941
969
  sort(text, options) {
942
- const finalOptions = { ...import_devtools_protocol5.DEFAULT_JSON_SORT_OPTIONS, ...options };
970
+ const finalOptions = { ...import_devtools_protocol6.DEFAULT_JSON_SORT_OPTIONS, ...options };
943
971
  const json = parseJson(text);
944
972
  const sorted = sortObject(json, finalOptions);
945
973
  return JSON.stringify(sorted, null, detectIndent(text));
@@ -975,7 +1003,7 @@ function parseJson(text) {
975
1003
  } catch {
976
1004
  }
977
1005
  }
978
- throw (0, import_devtools_protocol5.createServicemeError)("json_invalid_input", "Invalid JSON format.");
1006
+ throw (0, import_devtools_protocol6.createServicemeError)("json_invalid_input", "Invalid JSON format.");
979
1007
  }
980
1008
  function sortObject(obj, options) {
981
1009
  if (Array.isArray(obj)) {
@@ -1069,58 +1097,51 @@ function createConsoleLogger(prefix = "serviceme") {
1069
1097
  // src/project/projectTools.ts
1070
1098
  var fs3 = __toESM(require("fs/promises"));
1071
1099
  var path3 = __toESM(require("path"));
1072
- var import_devtools_protocol6 = require("@serviceme/devtools-protocol");
1100
+ var import_devtools_protocol7 = require("@serviceme/devtools-protocol");
1073
1101
 
1074
1102
  // src/utils/fileUtils.ts
1075
1103
  var import_node_fs = require("fs");
1076
1104
  var import_promises = require("fs/promises");
1077
1105
  var import_node_path = require("path");
1078
- var import_yauzl = require("yauzl");
1106
+ var import_yauzl = __toESM(require("yauzl"));
1079
1107
  var unzipFile = (zipPath, dest) => {
1080
1108
  return new Promise((resolve, reject) => {
1081
- (0, import_yauzl.open)(
1082
- zipPath,
1083
- { lazyEntries: true },
1084
- (err, zipfile) => {
1085
- if (err) return reject(err);
1086
- if (!zipfile) return reject(new Error("Failed to open zip file."));
1087
- zipfile.readEntry();
1088
- zipfile.on("entry", (entry) => {
1089
- if (/\/$/.test(entry.fileName)) {
1090
- void (0, import_promises.mkdir)((0, import_node_path.join)(dest, entry.fileName), { recursive: true }).then(() => {
1091
- zipfile.readEntry();
1092
- }).catch(reject);
1093
- } else {
1094
- const outputPath = (0, import_node_path.join)(dest, entry.fileName);
1095
- void (0, import_promises.mkdir)((0, import_node_path.dirname)(outputPath), { recursive: true }).then(() => {
1096
- zipfile.openReadStream(
1097
- entry,
1098
- (streamError, readStream) => {
1099
- if (streamError) return reject(streamError);
1100
- if (!readStream)
1101
- return reject(
1102
- new Error("Failed to open zip entry stream.")
1103
- );
1104
- const writeStream = (0, import_node_fs.createWriteStream)(outputPath);
1105
- readStream.on("error", reject);
1106
- writeStream.on("error", reject);
1107
- writeStream.on("close", () => {
1108
- zipfile.readEntry();
1109
- });
1110
- readStream.pipe(writeStream);
1111
- }
1112
- );
1113
- }).catch(reject);
1114
- }
1115
- });
1116
- zipfile.on("end", () => {
1117
- resolve();
1118
- });
1119
- zipfile.on("error", (zipError) => {
1120
- reject(zipError);
1121
- });
1122
- }
1123
- );
1109
+ import_yauzl.default.open(zipPath, { lazyEntries: true }, (err, zipfile) => {
1110
+ if (err) return reject(err);
1111
+ if (!zipfile) return reject(new Error("Failed to open zip file."));
1112
+ zipfile.readEntry();
1113
+ zipfile.on("entry", (entry) => {
1114
+ if (/\/$/.test(entry.fileName)) {
1115
+ void (0, import_promises.mkdir)((0, import_node_path.join)(dest, entry.fileName), { recursive: true }).then(() => {
1116
+ zipfile.readEntry();
1117
+ }).catch(reject);
1118
+ } else {
1119
+ const outputPath = (0, import_node_path.join)(dest, entry.fileName);
1120
+ void (0, import_promises.mkdir)((0, import_node_path.dirname)(outputPath), { recursive: true }).then(() => {
1121
+ zipfile.openReadStream(
1122
+ entry,
1123
+ (streamError, readStream) => {
1124
+ if (streamError) return reject(streamError);
1125
+ if (!readStream) return reject(new Error("Failed to open zip entry stream."));
1126
+ const writeStream = (0, import_node_fs.createWriteStream)(outputPath);
1127
+ readStream.on("error", reject);
1128
+ writeStream.on("error", reject);
1129
+ writeStream.on("close", () => {
1130
+ zipfile.readEntry();
1131
+ });
1132
+ readStream.pipe(writeStream);
1133
+ }
1134
+ );
1135
+ }).catch(reject);
1136
+ }
1137
+ });
1138
+ zipfile.on("end", () => {
1139
+ resolve();
1140
+ });
1141
+ zipfile.on("error", (zipError) => {
1142
+ reject(zipError);
1143
+ });
1144
+ });
1124
1145
  });
1125
1146
  };
1126
1147
  var tryLstat = async (targetPath) => {
@@ -1144,11 +1165,7 @@ var mergeEntry = async (sourcePath, destPath, overwrite) => {
1144
1165
  await (0, import_promises.mkdir)(destPath, { recursive: true });
1145
1166
  const children = await (0, import_promises.readdir)(sourcePath);
1146
1167
  for (const child of children) {
1147
- await mergeEntry(
1148
- (0, import_node_path.join)(sourcePath, child),
1149
- (0, import_node_path.join)(destPath, child),
1150
- overwrite
1151
- );
1168
+ await mergeEntry((0, import_node_path.join)(sourcePath, child), (0, import_node_path.join)(destPath, child), overwrite);
1152
1169
  }
1153
1170
  await (0, import_promises.rm)(sourcePath, { recursive: true, force: true });
1154
1171
  return;
@@ -1225,7 +1242,7 @@ var ProjectTools = class {
1225
1242
  }
1226
1243
  async installDependencies(workspacePath, command) {
1227
1244
  if (!command) {
1228
- throw (0, import_devtools_protocol6.createServicemeError)("invalid_params", "Expected install command.");
1245
+ throw (0, import_devtools_protocol7.createServicemeError)("invalid_params", "Expected install command.");
1229
1246
  }
1230
1247
  await runCommand(command, {
1231
1248
  cwd: workspacePath,
@@ -1238,23 +1255,15 @@ var ProjectTools = class {
1238
1255
  }
1239
1256
  async makeScriptsExecutable(workspacePath) {
1240
1257
  const isWindows = process.platform === "win32";
1241
- const scripts = await this.findScripts(
1242
- workspacePath,
1243
- isWindows ? [".ps1", ".bat"] : [".sh"]
1244
- );
1258
+ const scripts = await this.findScripts(workspacePath, isWindows ? [".ps1", ".bat"] : [".sh"]);
1245
1259
  let updatedCount = 0;
1246
1260
  if (isWindows) {
1247
- for (const scriptPath of scripts.filter(
1248
- (script) => script.endsWith(".ps1")
1249
- )) {
1261
+ for (const scriptPath of scripts.filter((script) => script.endsWith(".ps1"))) {
1250
1262
  try {
1251
- await runCommand(
1252
- `powershell -Command "Unblock-File -Path '${scriptPath}'"`,
1253
- {
1254
- cwd: workspacePath,
1255
- shell: true
1256
- }
1257
- );
1263
+ await runCommand(`powershell -Command "Unblock-File -Path '${scriptPath}'"`, {
1264
+ cwd: workspacePath,
1265
+ shell: true
1266
+ });
1258
1267
  updatedCount += 1;
1259
1268
  } catch {
1260
1269
  }
@@ -1288,13 +1297,16 @@ var ProjectTools = class {
1288
1297
  }
1289
1298
  async runScaffoldPrune(workspacePath, preset) {
1290
1299
  if (!preset) {
1291
- throw (0, import_devtools_protocol6.createServicemeError)("invalid_params", "Expected scaffold preset.");
1300
+ throw (0, import_devtools_protocol7.createServicemeError)("invalid_params", "Expected scaffold preset.");
1292
1301
  }
1293
- const commands = [
1294
- `pnpm run scaffold:prune -- --preset ${preset} --project-root .`,
1295
- `pnpm run scaffold:init-metadata -- --preset ${preset} --project-root . --force`
1296
- ];
1302
+ await this.ensurePresetManifest(workspacePath, preset);
1303
+ const pruneCommand = `pnpm run scaffold:prune -- --preset ${preset} --project-root .`;
1304
+ const initMetadataCommand = `pnpm run scaffold:init-metadata -- --preset ${preset} --project-root . --force`;
1305
+ const commands = [pruneCommand, initMetadataCommand];
1297
1306
  for (const command of commands) {
1307
+ if (command === initMetadataCommand) {
1308
+ await this.ensurePresetManifest(workspacePath, preset);
1309
+ }
1298
1310
  await runCommand(command, {
1299
1311
  cwd: workspacePath,
1300
1312
  shell: true
@@ -1306,6 +1318,40 @@ var ProjectTools = class {
1306
1318
  commands
1307
1319
  };
1308
1320
  }
1321
+ async ensurePresetManifest(workspacePath, preset) {
1322
+ const presetManifestPath = path3.join(
1323
+ workspacePath,
1324
+ ".ms-scaffold",
1325
+ "presets",
1326
+ `${preset}.json`
1327
+ );
1328
+ if (await this.pathExists(presetManifestPath)) {
1329
+ return;
1330
+ }
1331
+ const projectModePath = path3.join(workspacePath, ".ms-scaffold", "project-mode.json");
1332
+ if (!await this.pathExists(projectModePath)) {
1333
+ return;
1334
+ }
1335
+ const projectModeRaw = await fs3.readFile(projectModePath, "utf8");
1336
+ const projectMode = JSON.parse(projectModeRaw);
1337
+ const synthesizedPreset = {
1338
+ preset,
1339
+ selectedModules: Array.isArray(projectMode.selectedModules) ? projectMode.selectedModules : [],
1340
+ prunedModules: Array.isArray(projectMode.prunedModules) ? projectMode.prunedModules : [],
1341
+ exclude: [],
1342
+ recommendedFollowUps: [],
1343
+ managedFiles: [],
1344
+ mergeManagedFiles: [],
1345
+ userOwnedPaths: []
1346
+ };
1347
+ await fs3.mkdir(path3.dirname(presetManifestPath), { recursive: true });
1348
+ await fs3.writeFile(
1349
+ presetManifestPath,
1350
+ `${JSON.stringify(synthesizedPreset, null, 2)}
1351
+ `,
1352
+ "utf8"
1353
+ );
1354
+ }
1309
1355
  async findScripts(dir, extensions) {
1310
1356
  const results = [];
1311
1357
  let entries;
@@ -1336,9 +1382,7 @@ var ProjectTools = class {
1336
1382
  if (directoryNames.length === 1) {
1337
1383
  return directoryNames[0] ?? null;
1338
1384
  }
1339
- const exactMatch = directoryNames.find(
1340
- (directoryName) => directoryName === expectedDirName
1341
- );
1385
+ const exactMatch = directoryNames.find((directoryName) => directoryName === expectedDirName);
1342
1386
  if (exactMatch) {
1343
1387
  return exactMatch;
1344
1388
  }
@@ -1579,10 +1623,8 @@ var GithubCopilotCliExecutor = class {
1579
1623
  windowsHide: true
1580
1624
  });
1581
1625
  if (child.pid) {
1582
- writeDiagnostic(
1583
- `[GithubCopilotCliExecutor] spawned child PID=${child.pid}
1584
- `
1585
- );
1626
+ writeDiagnostic(`[GithubCopilotCliExecutor] spawned child PID=${child.pid}
1627
+ `);
1586
1628
  }
1587
1629
  const timeoutMs = execution.timeoutMs;
1588
1630
  const timer = timeoutMs != null ? setTimeout(() => {
@@ -1600,15 +1642,13 @@ var GithubCopilotCliExecutor = class {
1600
1642
  child.stdout.on("data", (chunk) => {
1601
1643
  const data = chunk.toString();
1602
1644
  stdoutBuf += data;
1603
- if (stdoutBuf.length > MAX_OUTPUT_BYTES)
1604
- stdoutBuf = stdoutBuf.slice(-MAX_OUTPUT_BYTES);
1645
+ if (stdoutBuf.length > MAX_OUTPUT_BYTES) stdoutBuf = stdoutBuf.slice(-MAX_OUTPUT_BYTES);
1605
1646
  onOutput("stdout", data);
1606
1647
  });
1607
1648
  child.stderr.on("data", (chunk) => {
1608
1649
  const data = chunk.toString();
1609
1650
  stderrBuf += data;
1610
- if (stderrBuf.length > MAX_OUTPUT_BYTES)
1611
- stderrBuf = stderrBuf.slice(-MAX_OUTPUT_BYTES);
1651
+ if (stderrBuf.length > MAX_OUTPUT_BYTES) stderrBuf = stderrBuf.slice(-MAX_OUTPUT_BYTES);
1612
1652
  onOutput("stderr", data);
1613
1653
  });
1614
1654
  child.on("close", (code) => {
@@ -1926,7 +1966,7 @@ function getExecutor(taskType) {
1926
1966
  var import_node_crypto = require("crypto");
1927
1967
  var fs8 = __toESM(require("fs"));
1928
1968
  var path7 = __toESM(require("path"));
1929
- var import_devtools_protocol7 = require("@serviceme/devtools-protocol");
1969
+ var import_devtools_protocol8 = require("@serviceme/devtools-protocol");
1930
1970
  var CONFIG_DIR3 = ".serviceme";
1931
1971
  var CONFIG_FILE = "scheduled-tasks.json";
1932
1972
  function emptyConfig() {
@@ -1937,10 +1977,7 @@ function isRecord(value) {
1937
1977
  }
1938
1978
  function requireNonEmptyString(payload, field, taskType) {
1939
1979
  if (!isRecord(payload) || typeof payload[field] !== "string" || !payload[field].trim()) {
1940
- throw (0, import_devtools_protocol7.createServicemeError)(
1941
- "invalid_payload",
1942
- `${taskType} payload ${field} is required`
1943
- );
1980
+ throw (0, import_devtools_protocol8.createServicemeError)("invalid_payload", `${taskType} payload ${field} is required`);
1944
1981
  }
1945
1982
  }
1946
1983
  function validateTaskPayload(taskType, payload) {
@@ -2103,9 +2140,7 @@ var TaskExecutionEngine = class {
2103
2140
  if (policy === "reject") {
2104
2141
  const existingIds = this.taskExecutions.get(snapshot.taskId);
2105
2142
  if (existingIds && existingIds.size > 0) {
2106
- throw new Error(
2107
- `TASK_ALREADY_RUNNING: Task ${snapshot.taskId} is already running`
2108
- );
2143
+ throw new Error(`TASK_ALREADY_RUNNING: Task ${snapshot.taskId} is already running`);
2109
2144
  }
2110
2145
  }
2111
2146
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -2370,7 +2405,7 @@ var TaskLogManager = class {
2370
2405
  var TICK_INTERVAL = 1e3;
2371
2406
  var MIN_SCHEDULE_INTERVAL = 1e3;
2372
2407
  var SchedulerDaemon = class {
2373
- constructor(workspacePath) {
2408
+ constructor(workspacePath, options = {}) {
2374
2409
  this.tickTimer = null;
2375
2410
  this.watcher = null;
2376
2411
  this.running = false;
@@ -2383,6 +2418,7 @@ var SchedulerDaemon = class {
2383
2418
  this.logManager = new TaskLogManager(workspacePath);
2384
2419
  this.pidManager = new PidManager(workspacePath);
2385
2420
  this.logger = new DaemonLogger(workspacePath);
2421
+ this.getExecutor = options.getExecutor ?? getExecutor;
2386
2422
  }
2387
2423
  start() {
2388
2424
  if (this.running) return;
@@ -2488,7 +2524,7 @@ var SchedulerDaemon = class {
2488
2524
  this.workspacePath
2489
2525
  );
2490
2526
  validateTaskPayload(task.taskType, executionPayload);
2491
- const executor = getExecutor(task.taskType);
2527
+ const executor = this.getExecutor(task.taskType);
2492
2528
  const result = await executor.execute(executionPayload);
2493
2529
  const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
2494
2530
  const durationMs = Date.now() - startMs;
@@ -2585,6 +2621,7 @@ function matchCronField(field, value) {
2585
2621
  }
2586
2622
 
2587
2623
  // src/skills/SkillCatalogClient.ts
2624
+ var import_devtools_protocol9 = require("@serviceme/devtools-protocol");
2588
2625
  var SkillCatalogClient = class {
2589
2626
  constructor(options = {}) {
2590
2627
  this.fetchImpl = options.fetchImpl ?? fetch;
@@ -2592,10 +2629,10 @@ var SkillCatalogClient = class {
2592
2629
  }
2593
2630
  async getCatalog() {
2594
2631
  if (!this.baseUrl) {
2595
- return {
2596
- skills: [],
2597
- fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
2598
- };
2632
+ throw (0, import_devtools_protocol9.createServicemeError)(
2633
+ "workspace_not_found",
2634
+ "Skill catalog baseUrl is not configured."
2635
+ );
2599
2636
  }
2600
2637
  const response = await this.fetchImpl(
2601
2638
  `${this.baseUrl}/api/v1/marketplace/skills`
@@ -2609,6 +2646,30 @@ var SkillCatalogClient = class {
2609
2646
  fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
2610
2647
  };
2611
2648
  }
2649
+ async downloadSkill(remoteId) {
2650
+ if (!this.baseUrl) {
2651
+ throw (0, import_devtools_protocol9.createServicemeError)(
2652
+ "workspace_not_found",
2653
+ "Skill catalog baseUrl is not configured."
2654
+ );
2655
+ }
2656
+ const response = await this.fetchImpl(
2657
+ `${this.baseUrl}/api/v1/marketplace/skills/download/${remoteId}`
2658
+ );
2659
+ if (!response.ok) {
2660
+ if (response.status === 404) {
2661
+ throw (0, import_devtools_protocol9.createServicemeError)(
2662
+ "not_found",
2663
+ `Skill '${remoteId}' not found`
2664
+ );
2665
+ }
2666
+ throw new Error(
2667
+ `Failed to download skill ${remoteId}: ${response.status}`
2668
+ );
2669
+ }
2670
+ const payload = await response.json();
2671
+ return payload.data?.files ?? [];
2672
+ }
2612
2673
  };
2613
2674
 
2614
2675
  // src/skills/SkillReconciler.ts
@@ -2745,6 +2806,22 @@ var SkillStore = class {
2745
2806
  return false;
2746
2807
  }
2747
2808
  }
2809
+ async writeSkillFiles(skillId, scope, files) {
2810
+ const root = scope === "workspace" ? path9.join(this.workspacePath, WORKSPACE_SKILLS_ROOT_RELATIVE) : this.userSkillsRoot;
2811
+ const targetDir = path9.join(root, skillId);
2812
+ await this.fileSystem.mkdir(targetDir, { recursive: true });
2813
+ for (const file of files) {
2814
+ const filePath = path9.join(targetDir, file.path);
2815
+ await this.fileSystem.mkdir(path9.dirname(filePath), { recursive: true });
2816
+ await this.fileSystem.writeFile(filePath, file.content, "utf-8");
2817
+ if (file.executable) {
2818
+ try {
2819
+ await this.fileSystem.chmod(filePath, 493);
2820
+ } catch {
2821
+ }
2822
+ }
2823
+ }
2824
+ }
2748
2825
  };
2749
2826
  // Annotate the CommonJS export names for ESM import in node:
2750
2827
  0 && (module.exports = {