@markus-global/cli 0.4.21 → 0.4.22

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/markus.mjs CHANGED
@@ -3566,7 +3566,7 @@ function buildManifest(type, raw) {
3566
3566
  if (type === "agent") {
3567
3567
  const agentRaw = raw.agent ?? raw;
3568
3568
  base.agent = {
3569
- roleName: agentRaw.roleName ?? raw.roleName ?? "developer",
3569
+ roleName: agentRaw.roleName || raw.roleName || void 0,
3570
3570
  agentRole: agentRaw.agentRole ?? raw.agentRole ?? "worker",
3571
3571
  llmProvider: agentRaw.llmProvider || raw.llmProvider || void 0,
3572
3572
  llmModel: agentRaw.llmModel || raw.llmModel || void 0,
@@ -3585,7 +3585,7 @@ function buildManifest(type, raw) {
3585
3585
  members: rawMembers.map((m) => ({
3586
3586
  name: m.name ?? "Agent",
3587
3587
  role: m.role ?? "worker",
3588
- roleName: m.roleName ?? "developer",
3588
+ roleName: m.roleName || void 0,
3589
3589
  count: m.count ?? 1,
3590
3590
  skills: toArr(m.skills).length > 0 ? toArr(m.skills) : void 0
3591
3591
  }))
@@ -3652,8 +3652,8 @@ function validateManifest(m) {
3652
3652
  errors.push("version must be semver (e.g. 1.0.0)");
3653
3653
  if (o.type === "agent" && o.agent) {
3654
3654
  const a = o.agent;
3655
- if (!a.roleName || typeof a.roleName !== "string")
3656
- errors.push("agent.roleName is required");
3655
+ if (a.roleName !== void 0 && typeof a.roleName !== "string")
3656
+ errors.push("agent.roleName must be a string if provided");
3657
3657
  }
3658
3658
  if (o.type === "team" && o.team) {
3659
3659
  const t = o.team;
@@ -3662,10 +3662,12 @@ function validateManifest(m) {
3662
3662
  }
3663
3663
  return errors;
3664
3664
  }
3665
- function kebab(s) {
3665
+ function kebab(s, fallback) {
3666
3666
  const result = s.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "").replace(/^-+|-+$/g, "");
3667
3667
  if (result)
3668
3668
  return result;
3669
+ if (fallback)
3670
+ return fallback;
3669
3671
  let hash = 0;
3670
3672
  for (let i = 0; i < s.length; i++)
3671
3673
  hash = (hash << 5) - hash + s.charCodeAt(i) | 0;
@@ -4409,6 +4411,7 @@ __export(dist_exports, {
4409
4411
  getTextContent: () => getTextContent,
4410
4412
  isPlaceholder: () => isPlaceholder,
4411
4413
  isValidTaskTransition: () => isValidTaskTransition,
4414
+ kebab: () => kebab,
4412
4415
  loadConfig: () => loadConfig,
4413
4416
  manifestFilename: () => manifestFilename,
4414
4417
  msgId: () => msgId,
@@ -45193,6 +45196,7 @@ ${notification.stdoutTail}`);
45193
45196
  });
45194
45197
  });
45195
45198
  const roleFilePath = join10(this.dataDir, "role", "ROLE.md");
45199
+ const heartbeatFilePath = join10(this.dataDir, "role", "HEARTBEAT.md");
45196
45200
  this.toolHooks.register({
45197
45201
  name: "role-auto-reload",
45198
45202
  after: async (ctx) => {
@@ -45202,6 +45206,10 @@ ${notification.stdoutTail}`);
45202
45206
  log17.info("Agent modified its own ROLE.md \u2014 reloading role definition");
45203
45207
  this.reloadRole();
45204
45208
  }
45209
+ if (targetPath === heartbeatFilePath || targetPath.endsWith("/role/HEARTBEAT.md")) {
45210
+ log17.info("Agent modified its own HEARTBEAT.md \u2014 reloading heartbeat checklist");
45211
+ this.reloadHeartbeat();
45212
+ }
45205
45213
  }
45206
45214
  }
45207
45215
  });
@@ -45710,6 +45718,25 @@ ${notification.stdoutTail}`);
45710
45718
  log17.warn(`Failed to reload role for agent ${this.config.name}`, { error: String(err) });
45711
45719
  }
45712
45720
  }
45721
+ /**
45722
+ * Reload the agent's heartbeat checklist from its HEARTBEAT.md file on disk.
45723
+ * Called when the agent modifies its own HEARTBEAT.md via file_edit/file_write.
45724
+ */
45725
+ reloadHeartbeat() {
45726
+ const heartbeatFile = join10(this.dataDir, "role", "HEARTBEAT.md");
45727
+ if (!existsSync15(heartbeatFile))
45728
+ return;
45729
+ try {
45730
+ const content = readFileSync10(heartbeatFile, "utf-8");
45731
+ this.role = {
45732
+ ...this.role,
45733
+ heartbeatChecklist: content
45734
+ };
45735
+ log17.info(`Heartbeat checklist reloaded from disk for agent ${this.config.name}`);
45736
+ } catch (err) {
45737
+ log17.warn(`Failed to reload heartbeat for agent ${this.config.name}`, { error: String(err) });
45738
+ }
45739
+ }
45713
45740
  /**
45714
45741
  * Start a fresh conversation session, discarding the current in-memory session context.
45715
45742
  * Called when the user explicitly starts a "New Chat".
@@ -48627,6 +48654,10 @@ ${todayLog.slice(0, HEARTBEAT_DAILY_LOG_CHARS)}
48627
48654
  '- Check existing skills first: `discover_tools({ mode: "list_skills" })` and `builder_list`.',
48628
48655
  "- To update an existing skill: edit files in `~/.markus/builder-artifacts/skills/{name}/`, bump version, re-install with `builder_install`.",
48629
48656
  "",
48657
+ "**Direct self-evolution** (simplest and most impactful):",
48658
+ "- **Update ROLE.md** \u2014 When you discover a behavioral rule, working style, or guiding principle that should always apply, append it to your ROLE.md via `file_edit`. ROLE.md is loaded into every conversation, so changes take effect immediately. Read first, then append. No need to accumulate 3 insights \u2014 even a single validated lesson can warrant a role update if it is fundamental.",
48659
+ "- **Update HEARTBEAT.md** \u2014 When you realize your patrol routine should include a new recurring check (or remove an obsolete one), modify your HEARTBEAT.md via `file_edit`. This is your personal checklist \u2014 customize it to match your actual responsibilities. Changes take effect at the next heartbeat.",
48660
+ "",
48630
48661
  "**Decision guide \u2014 where does this insight go?**",
48631
48662
  "| Observation type | Action |",
48632
48663
  "|---|---|",
@@ -48634,7 +48665,8 @@ ${todayLog.slice(0, HEARTBEAT_DAILY_LOG_CHARS)}
48634
48665
  '| Tool tip or preference | `memory_save` with tags: `["insight", "tool:<name>"]` |',
48635
48666
  '| Multi-step repeatable workflow | `memory_update_longterm({ section: "procedures", mode: "patch" })` |',
48636
48667
  "| Practice worth sharing with the team | Create skill via **skill-building**, then install with `builder_install` |",
48637
- "| 3+ related insights \u2192 behavioral rule | Update ROLE.md (read first, append, log change) |",
48668
+ "| Behavioral rule or guiding principle | Update ROLE.md (`file_read` \u2192 `file_edit` to append) |",
48669
+ "| New recurring check for your patrol | Update HEARTBEAT.md (`file_read` \u2192 `file_edit`) |",
48638
48670
  "",
48639
48671
  "Quality bar: Only record insights that are **specific**, **actionable**, and **non-obvious**.",
48640
48672
  "Skip if nothing meaningful happened since last heartbeat."
@@ -48662,7 +48694,8 @@ ${todayLog.slice(0, HEARTBEAT_DAILY_LOG_CHARS)}
48662
48694
  "- Tasks with `executionRound > 1` required revision \u2014 your initial approach had issues.",
48663
48695
  "- A high revision rate (>30%) suggests your knowledge is not being applied effectively.",
48664
48696
  "- Check: does your MEMORY.md knowledge actually cover the failure patterns you see?",
48665
- "- If you keep making the same type of mistake, escalate: save as insight \u2192 add to MEMORY.md \u2192 update ROLE.md."
48697
+ "- If you keep making the same type of mistake, escalate: save as insight \u2192 add to MEMORY.md \u2192 update ROLE.md or HEARTBEAT.md.",
48698
+ "- Consider: would a ROLE.md rule or a HEARTBEAT.md check have prevented any recent failures?"
48666
48699
  ].join("\n");
48667
48700
  const prompt = [
48668
48701
  "[HEARTBEAT CHECK-IN]",
@@ -53209,24 +53242,39 @@ Priority: ${delegation.priority}`, envelope.from, { name: envelope.from, role: "
53209
53242
  if (!request.name?.trim())
53210
53243
  throw new Error("Agent name is required");
53211
53244
  const id = agentId();
53212
- const role = this.roleLoader.loadRole(request.roleName);
53245
+ const roleName = request.roleName || "custom";
53246
+ const isCustomRole = roleName === "custom";
53247
+ const role = isCustomRole ? {
53248
+ id: generateId("role"),
53249
+ name: request.name,
53250
+ description: "",
53251
+ category: "custom",
53252
+ systemPrompt: `# ${request.name}
53253
+
53254
+ You are ${request.name}.`,
53255
+ defaultSkills: [],
53256
+ heartbeatChecklist: "",
53257
+ defaultPolicies: [],
53258
+ builtIn: false
53259
+ } : this.roleLoader.loadRole(roleName);
53213
53260
  const agentDataDir = join13(this.dataDir, id);
53214
53261
  mkdirSync12(agentDataDir, { recursive: true });
53215
53262
  const agentRoleDir = join13(agentDataDir, "role");
53216
53263
  mkdirSync12(agentRoleDir, { recursive: true });
53217
- const templateDir = this.roleLoader.resolveTemplateDir(request.roleName);
53218
- if (templateDir) {
53219
- for (const file of ["ROLE.md", "HEARTBEAT.md", "POLICIES.md", "CONTEXT.md"]) {
53220
- const src = join13(templateDir, file);
53221
- if (existsSync18(src))
53222
- copyFileSync(src, join13(agentRoleDir, file));
53264
+ if (!isCustomRole && !request.skipTemplateCopy) {
53265
+ const templateDir = this.roleLoader.resolveTemplateDir(roleName);
53266
+ if (templateDir) {
53267
+ for (const file of ["ROLE.md", "HEARTBEAT.md", "POLICIES.md", "CONTEXT.md"]) {
53268
+ const src = join13(templateDir, file);
53269
+ if (existsSync18(src))
53270
+ copyFileSync(src, join13(agentRoleDir, file));
53271
+ }
53223
53272
  }
53224
53273
  }
53225
53274
  const config = {
53226
53275
  id,
53227
53276
  name: request.name,
53228
- // Store the template folder name so agents can be restored on restart
53229
- roleId: request.roleName,
53277
+ roleId: roleName,
53230
53278
  orgId: request.orgId ?? "default",
53231
53279
  teamId: request.teamId,
53232
53280
  agentRole: request.agentRole ?? "worker",
@@ -53742,6 +53790,20 @@ Known issues: ${knownIssues}` : ""}`
53742
53790
  let role;
53743
53791
  if (existsSync18(join13(agentRoleDir, "ROLE.md"))) {
53744
53792
  role = this.roleLoader.loadRole(agentRoleDir);
53793
+ } else if (row.roleId === "custom") {
53794
+ role = {
53795
+ id: generateId("role"),
53796
+ name: row.name,
53797
+ description: "",
53798
+ category: "custom",
53799
+ systemPrompt: `# ${row.name}
53800
+
53801
+ You are ${row.name}.`,
53802
+ defaultSkills: [],
53803
+ heartbeatChecklist: "",
53804
+ defaultPolicies: [],
53805
+ builtIn: false
53806
+ };
53745
53807
  } else {
53746
53808
  role = (() => {
53747
53809
  try {
@@ -58049,7 +58111,7 @@ var init_registry = __esm({
58049
58111
  skills = /* @__PURE__ */ new Map();
58050
58112
  aliases = /* @__PURE__ */ new Map();
58051
58113
  static normalize(s) {
58052
- return s.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
58114
+ return kebab(s);
58053
58115
  }
58054
58116
  register(skill) {
58055
58117
  const name = skill.manifest.name;
@@ -59794,15 +59856,17 @@ var init_org_service = __esm({
59794
59856
  }
59795
59857
  }
59796
59858
  this.refreshIdentityContextsForOrg(request.orgId);
59797
- try {
59798
- await this.agentManager.startAgent(agent.id);
59799
- log50.info(`Agent onboarded: ${request.name} (auto-started)`, {
59800
- orgId: request.orgId,
59801
- agentId: agent.id,
59802
- agentRole: request.agentRole ?? "worker"
59803
- });
59804
- } catch (error) {
59805
- log50.warn(`Agent created but auto-start failed: ${request.name}`, { error: String(error) });
59859
+ if (!request.skipAutoStart) {
59860
+ try {
59861
+ await this.agentManager.startAgent(agent.id);
59862
+ log50.info(`Agent onboarded: ${request.name} (auto-started)`, {
59863
+ orgId: request.orgId,
59864
+ agentId: agent.id,
59865
+ agentRole: request.agentRole ?? "worker"
59866
+ });
59867
+ } catch (error) {
59868
+ log50.warn(`Agent created but auto-start failed: ${request.name}`, { error: String(error) });
59869
+ }
59806
59870
  }
59807
59871
  return agent;
59808
59872
  }
@@ -60125,9 +60189,9 @@ var init_org_service = __esm({
60125
60189
  const roleNames = this.listAvailableRoles();
60126
60190
  if (roleNames.length > 0) {
60127
60191
  parts.push("");
60128
- parts.push("## Available Role Templates (live from system)");
60192
+ parts.push("## Built-in Role Templates (for reference only)");
60129
60193
  parts.push("");
60130
- parts.push("The `roleName` field must be one of:");
60194
+ parts.push("These templates are available for reading as **reference** when writing custom ROLE.md files. You do NOT need to set `roleName` \u2014 the agent's identity is fully defined by its ROLE.md.");
60131
60195
  parts.push("");
60132
60196
  for (const r of roleNames) {
60133
60197
  parts.push(`- \`${r}\``);
@@ -60135,9 +60199,9 @@ var init_org_service = __esm({
60135
60199
  const templateDirs = this.roleLoader.getTemplateDirs();
60136
60200
  if (templateDirs.length > 0) {
60137
60201
  parts.push("");
60138
- parts.push("**Tip**: You can read existing role templates for reference when writing custom ROLE.md files.");
60202
+ parts.push("**Tip**: Read existing templates for inspiration when writing custom ROLE.md files.");
60139
60203
  parts.push(`Use \`file_read\` to inspect any template, e.g.: \`file_read("${templateDirs[0]}/developer/ROLE.md")\``);
60140
- parts.push("This is especially useful for understanding the level of detail and workflow guidance expected in a good ROLE.md.");
60204
+ parts.push("This is useful for understanding the level of detail and workflow guidance expected in a good ROLE.md.");
60141
60205
  }
60142
60206
  }
60143
60207
  parts.push("");
@@ -62714,7 +62778,11 @@ Action: ${guidance}` : ""
62714
62778
  "",
62715
62779
  'Save each lesson using `memory_save` with tags `["lesson", ...]`.',
62716
62780
  'If it is a repeatable multi-step procedure, promote to SOP via `memory_update_longterm({ section: "sops", mode: "patch" })`.',
62717
- "If the best practice would benefit other agents on the team, create a shareable skill via **skill-building** and install it with `builder_install`."
62781
+ "If the best practice would benefit other agents on the team, create a shareable skill via **skill-building** and install it with `builder_install`.",
62782
+ "",
62783
+ "**Direct self-evolution** \u2014 consider the simplest, most impactful options:",
62784
+ "- If this lesson reveals a behavioral rule that should always guide your work, append it to your ROLE.md via `file_edit`.",
62785
+ "- If you should be checking for this class of issue regularly, add a check to your HEARTBEAT.md via `file_edit`."
62718
62786
  ].join("\n") : [
62719
62787
  "[SELF-EVOLUTION \u2014 Post-Task Reflection (Success)]",
62720
62788
  "",
@@ -62731,6 +62799,11 @@ Action: ${guidance}` : ""
62731
62799
  'If you identify a meaningful insight, save it using `memory_save` with tags `["lesson", "best-practice", ...]`.',
62732
62800
  'If it is a multi-step workflow, promote to SOP via `memory_update_longterm({ section: "sops", mode: "patch" })`.',
62733
62801
  "If worth sharing with the team, create a skill via **skill-building** and install with `builder_install`.",
62802
+ "",
62803
+ "**Direct self-evolution** \u2014 consider the simplest, most impactful options:",
62804
+ "- If this success reveals a guiding principle or working style worth keeping, append it to your ROLE.md via `file_edit`.",
62805
+ "- If there is a periodic check that would help maintain this quality, add it to your HEARTBEAT.md via `file_edit`.",
62806
+ "",
62734
62807
  "If nothing noteworthy stands out, it is fine to skip saving."
62735
62808
  ].join("\n");
62736
62809
  void agent.sendMessage(prompt, void 0, void 0, {
@@ -63413,7 +63486,7 @@ ${task.description}`;
63413
63486
 
63414
63487
  // ../org-manager/dist/builder-service.js
63415
63488
  import { join as join21 } from "node:path";
63416
- import { readdirSync as readdirSync8, readFileSync as readFileSync19, existsSync as existsSync25, writeFileSync as writeFileSync14, mkdirSync as mkdirSync17, copyFileSync as copyFileSync2, statSync as statSync4 } from "node:fs";
63489
+ import { readdirSync as readdirSync8, readFileSync as readFileSync19, existsSync as existsSync25, writeFileSync as writeFileSync14, mkdirSync as mkdirSync17, copyFileSync as copyFileSync2, statSync as statSync4, cpSync as cpSync2 } from "node:fs";
63417
63490
  import { homedir as homedir12 } from "node:os";
63418
63491
  var log52, FS_HELPER, BuilderService;
63419
63492
  var init_builder_service = __esm({
@@ -63497,17 +63570,17 @@ var init_builder_service = __esm({
63497
63570
  async installAgent(artDir, manifest, mfName, artifactName) {
63498
63571
  const agentManager = this.orgService.getAgentManager();
63499
63572
  const agentName = manifest.displayName ?? manifest.name ?? artifactName;
63500
- const knownRoles = this.orgService.listAvailableRoles();
63501
- const requestedRole = manifest.agent?.roleName ?? "developer";
63502
- const roleName = knownRoles.includes(requestedRole) ? requestedRole : "developer";
63573
+ const hasCustomRole = existsSync25(join21(artDir, "ROLE.md"));
63503
63574
  const skills = manifest.dependencies?.skills ?? [];
63504
63575
  const agentRole = manifest.agent?.agentRole ?? "worker";
63505
63576
  const agent = await this.orgService.hireAgent({
63506
63577
  name: agentName,
63507
- roleName,
63578
+ roleName: manifest.agent?.roleName || (hasCustomRole ? "custom" : "developer"),
63508
63579
  orgId: "default",
63509
63580
  agentRole,
63510
- skills
63581
+ skills,
63582
+ skipAutoStart: true,
63583
+ skipTemplateCopy: hasCustomRole
63511
63584
  });
63512
63585
  const agentRoleDir = join21(agentManager.getDataDir(), agent.id, "role");
63513
63586
  mkdirSync17(agentRoleDir, { recursive: true });
@@ -63547,38 +63620,42 @@ var init_builder_service = __esm({
63547
63620
  const norms = existsSync25(normsPath) ? readFileSync19(normsPath, "utf-8") : "";
63548
63621
  this.orgService.ensureTeamDataDir(team.id, announcements, norms);
63549
63622
  const members = manifest.team?.members ?? [];
63550
- const knownRoles = this.orgService.listAvailableRoles();
63551
63623
  const createdAgents = [];
63624
+ const usedMemberDirs = /* @__PURE__ */ new Set();
63552
63625
  for (const member of members) {
63553
63626
  const count = member.count ?? 1;
63554
63627
  const memberRole = member.role ?? "worker";
63555
63628
  const memberName = member.name ?? "Agent";
63556
- const roleName = knownRoles.includes(member.roleName) ? member.roleName : "developer";
63557
63629
  const memberSkills = member.skills ?? [];
63558
- const memberSlug = memberName.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
63559
- const memberFilesDir = join21(artDir, "members", memberSlug);
63630
+ const memberFilesDir = this.findMemberDir(artDir, memberName, usedMemberDirs);
63631
+ const hasCustomRole = !!memberFilesDir && existsSync25(join21(memberFilesDir, "ROLE.md"));
63632
+ if (memberFilesDir)
63633
+ usedMemberDirs.add(memberFilesDir);
63634
+ log52.info("installTeam: member lookup", { memberName, memberFilesDir, hasCustomRole });
63560
63635
  for (let i = 0; i < count; i++) {
63561
63636
  const displayName = count > 1 ? `${memberName} ${i + 1}` : memberName;
63562
63637
  const agent = await this.orgService.hireAgent({
63563
63638
  name: displayName,
63564
- roleName,
63639
+ roleName: member.roleName || (hasCustomRole ? "custom" : "developer"),
63565
63640
  orgId: "default",
63566
63641
  teamId: team.id,
63567
63642
  agentRole: memberRole,
63568
- skills: memberSkills.length > 0 ? memberSkills : void 0
63643
+ skills: memberSkills.length > 0 ? memberSkills : void 0,
63644
+ skipAutoStart: true,
63645
+ skipTemplateCopy: hasCustomRole
63569
63646
  });
63570
63647
  const agentRoleDir = join21(agentManager.getDataDir(), agent.id, "role");
63571
63648
  mkdirSync17(agentRoleDir, { recursive: true });
63572
- if (existsSync25(memberFilesDir)) {
63649
+ if (memberFilesDir && existsSync25(memberFilesDir)) {
63573
63650
  for (const fname of readdirSync8(memberFilesDir)) {
63574
63651
  const srcFile = join21(memberFilesDir, fname);
63575
63652
  if (statSync4(srcFile).isFile()) {
63576
63653
  copyFileSync2(srcFile, join21(agentRoleDir, fname));
63577
63654
  }
63578
63655
  }
63579
- agent.reloadRole();
63580
63656
  }
63581
63657
  writeFileSync14(join21(agentRoleDir, ".role-origin.json"), JSON.stringify({ customRole: true, source: "builder-artifact", artifact: artifactName, artifactType: "team" }));
63658
+ agent.reloadRole();
63582
63659
  if (memberRole === "manager") {
63583
63660
  await this.orgService.updateTeam(team.id, { managerId: agent.id, managerType: "agent" });
63584
63661
  }
@@ -63591,13 +63668,56 @@ var init_builder_service = __esm({
63591
63668
  installed: { team: { id: team.id, name: teamName }, agents: createdAgents }
63592
63669
  };
63593
63670
  }
63671
+ /**
63672
+ * Find the member directory under artDir/members/ by trying multiple slug strategies.
63673
+ * Returns the absolute path to the member directory, or null if not found.
63674
+ */
63675
+ findMemberDir(artDir, memberName, usedDirs) {
63676
+ const membersBase = join21(artDir, "members");
63677
+ if (!existsSync25(membersBase))
63678
+ return null;
63679
+ const slug = kebab(memberName, "agent");
63680
+ const exact = join21(membersBase, slug);
63681
+ if (existsSync25(exact) && !usedDirs.has(exact))
63682
+ return exact;
63683
+ try {
63684
+ for (const entry of readdirSync8(membersBase, { withFileTypes: true })) {
63685
+ if (!entry.isDirectory())
63686
+ continue;
63687
+ const candidateDir = join21(membersBase, entry.name);
63688
+ if (usedDirs.has(candidateDir))
63689
+ continue;
63690
+ const rolePath = join21(candidateDir, "ROLE.md");
63691
+ if (!existsSync25(rolePath))
63692
+ continue;
63693
+ try {
63694
+ const content = readFileSync19(rolePath, "utf-8");
63695
+ const title = content.match(/^#\s+(.+)$/m)?.[1]?.trim();
63696
+ if (title && title.toLowerCase() === memberName.toLowerCase())
63697
+ return candidateDir;
63698
+ } catch {
63699
+ }
63700
+ }
63701
+ } catch {
63702
+ }
63703
+ try {
63704
+ const remaining = readdirSync8(membersBase, { withFileTypes: true }).filter((e) => e.isDirectory() && !usedDirs.has(join21(membersBase, e.name)));
63705
+ if (remaining.length === 1)
63706
+ return join21(membersBase, remaining[0].name);
63707
+ } catch {
63708
+ }
63709
+ return null;
63710
+ }
63594
63711
  async installSkill(artDir, manifest, artifactName) {
63595
63712
  const skillDir = join21(homedir12(), ".markus", "skills", artifactName);
63596
63713
  mkdirSync17(skillDir, { recursive: true });
63597
63714
  for (const fname of readdirSync8(artDir)) {
63598
63715
  const srcFile = join21(artDir, fname);
63716
+ const destFile = join21(skillDir, fname);
63599
63717
  if (statSync4(srcFile).isFile()) {
63600
- copyFileSync2(srcFile, join21(skillDir, fname));
63718
+ copyFileSync2(srcFile, destFile);
63719
+ } else if (statSync4(srcFile).isDirectory()) {
63720
+ cpSync2(srcFile, destFile, { recursive: true });
63601
63721
  }
63602
63722
  }
63603
63723
  if (this.skillRegistry) {
@@ -64876,7 +64996,7 @@ var init_api_server = __esm({
64876
64996
  throw new Error(`Hub download failed: ${res.status}`);
64877
64997
  const data = await res.json();
64878
64998
  const name = data.name;
64879
- const slug = name.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
64999
+ const slug = kebab(name, "hub-pkg");
64880
65000
  const mode = data.itemType === "team" ? "team" : data.itemType === "skill" ? "skill" : "agent";
64881
65001
  const typeDir = mode === "agent" ? "agents" : mode === "team" ? "teams" : "skills";
64882
65002
  const artDir = join23(homedir14(), ".markus", "builder-artifacts", typeDir, slug);
@@ -65705,13 +65825,9 @@ var init_api_server = __esm({
65705
65825
  this.json(res, 400, { error: "name is required" });
65706
65826
  return;
65707
65827
  }
65708
- if (!roleName?.trim()) {
65709
- this.json(res, 400, { error: "roleName is required" });
65710
- return;
65711
- }
65712
65828
  const agent = await this.orgService.hireAgent({
65713
65829
  name: agentName,
65714
- roleName,
65830
+ roleName: roleName?.trim() || void 0,
65715
65831
  orgId: body["orgId"] ?? "default",
65716
65832
  teamId: body["teamId"],
65717
65833
  skills: body["skills"],
@@ -66685,7 +66801,7 @@ var init_api_server = __esm({
66685
66801
  const roleDir = this.resolveAgentRoleDir(agent);
66686
66802
  if (!roleDir)
66687
66803
  continue;
66688
- const slug = agent.config.name.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "") || agentId2;
66804
+ const slug = kebab(agent.config.name, agentId2);
66689
66805
  for (const fname of roleFileNames) {
66690
66806
  const fpath = join23(roleDir, fname);
66691
66807
  if (existsSync27(fpath)) {
@@ -68363,7 +68479,7 @@ EXPLANATION_END`;
68363
68479
  const rawMembers = Array.isArray(artifact.team?.members) ? artifact.team.members : Array.isArray(artifact.members) ? artifact.members : [];
68364
68480
  for (const m of rawMembers) {
68365
68481
  const mName = m.name ?? "Agent";
68366
- const slug = mName.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "") || "agent";
68482
+ const slug = kebab(mName, "agent");
68367
68483
  const memberDir = join23(artDir, "members", slug);
68368
68484
  const roleContent = m.roleContent || m.role_md;
68369
68485
  const policiesContent = m.policiesContent || m.policies_md;
@@ -69168,11 +69284,18 @@ EXPLANATION_END`;
69168
69284
  "Content-Type": "application/json",
69169
69285
  "Authorization": `Bearer ${hubToken}`
69170
69286
  };
69171
- const hubRes = await fetch(`${hubUrl}/api/items`, {
69287
+ let hubRes = await fetch(`${hubUrl}/api/items`, {
69172
69288
  method: "POST",
69173
69289
  headers,
69174
- body: JSON.stringify(body["payload"])
69290
+ body: JSON.stringify(body["payload"]),
69291
+ redirect: "manual"
69175
69292
  });
69293
+ if (hubRes.status >= 300 && hubRes.status < 400) {
69294
+ const location = hubRes.headers.get("location");
69295
+ if (location) {
69296
+ hubRes = await fetch(location, { method: "POST", headers, body: JSON.stringify(body["payload"]), redirect: "manual" });
69297
+ }
69298
+ }
69176
69299
  const hubData = await hubRes.json();
69177
69300
  this.json(res, hubRes.status, hubData);
69178
69301
  } catch (err) {
@@ -69180,6 +69303,43 @@ EXPLANATION_END`;
69180
69303
  }
69181
69304
  return;
69182
69305
  }
69306
+ if (path.startsWith("/api/hub/")) {
69307
+ const hubPath = path.slice("/api/hub".length);
69308
+ const reqUrl = new URL(req.url, `http://${req.headers.host}`);
69309
+ const hubTargetUrl = `${this.hubUrl}/api${hubPath}${reqUrl.search}`;
69310
+ const proxyHeaders = { "Content-Type": "application/json" };
69311
+ const authHeader = req.headers["authorization"];
69312
+ if (authHeader)
69313
+ proxyHeaders["Authorization"] = authHeader;
69314
+ try {
69315
+ let body;
69316
+ if (req.method === "POST" || req.method === "PUT" || req.method === "PATCH") {
69317
+ body = JSON.stringify(await this.readBody(req));
69318
+ }
69319
+ let hubRes = await fetch(hubTargetUrl, {
69320
+ method: req.method,
69321
+ headers: proxyHeaders,
69322
+ body,
69323
+ redirect: "manual"
69324
+ });
69325
+ if (hubRes.status >= 300 && hubRes.status < 400) {
69326
+ const location = hubRes.headers.get("location");
69327
+ if (location) {
69328
+ hubRes = await fetch(location, {
69329
+ method: req.method,
69330
+ headers: proxyHeaders,
69331
+ body,
69332
+ redirect: "manual"
69333
+ });
69334
+ }
69335
+ }
69336
+ const data = await hubRes.json();
69337
+ this.json(res, hubRes.status, data);
69338
+ } catch (err) {
69339
+ this.json(res, 502, { error: `Hub request failed: ${String(err)}` });
69340
+ }
69341
+ return;
69342
+ }
69183
69343
  if (path === "/api/settings/hub" && req.method === "GET") {
69184
69344
  this.json(res, 200, { hubUrl: this.hubUrl });
69185
69345
  return;
@@ -77725,7 +77885,7 @@ var init_startupProgress = __esm({
77725
77885
 
77726
77886
  // src/connector-service.ts
77727
77887
  import { resolve as resolve15, join as join28, dirname as dirname8 } from "node:path";
77728
- import { existsSync as existsSync31, readFileSync as readFileSync23, writeFileSync as writeFileSync18, mkdirSync as mkdirSync24, readdirSync as readdirSync12, cpSync as cpSync2 } from "node:fs";
77888
+ import { existsSync as existsSync31, readFileSync as readFileSync23, writeFileSync as writeFileSync18, mkdirSync as mkdirSync24, readdirSync as readdirSync12, cpSync as cpSync3 } from "node:fs";
77729
77889
  import { homedir as homedir18 } from "node:os";
77730
77890
  import { execSync as execSync4 } from "node:child_process";
77731
77891
  import { fileURLToPath as fileURLToPath4 } from "node:url";
@@ -77856,7 +78016,7 @@ function installSkillTemplate(connector) {
77856
78016
  mkdirSync24(targetDir, { recursive: true });
77857
78017
  }
77858
78018
  try {
77859
- cpSync2(sourceDir, targetDir, { recursive: true });
78019
+ cpSync3(sourceDir, targetDir, { recursive: true });
77860
78020
  return true;
77861
78021
  } catch {
77862
78022
  return false;
@@ -77906,7 +78066,7 @@ __export(init_exports, {
77906
78066
  registerInitCommand: () => registerInitCommand
77907
78067
  });
77908
78068
  import { resolve as resolve16 } from "node:path";
77909
- import { readFileSync as readFileSync24, existsSync as existsSync32, cpSync as cpSync3 } from "node:fs";
78069
+ import { readFileSync as readFileSync24, existsSync as existsSync32, cpSync as cpSync4 } from "node:fs";
77910
78070
  import { homedir as homedir19 } from "node:os";
77911
78071
  function registerInitCommand(program2) {
77912
78072
  program2.command("init").description("Setup wizard: configure LLM provider, API keys, and server settings").option("--force", "Overwrite existing configuration").option("--non-interactive", "Run without prompts (use env vars or --import-from)").option("--provider <name>", "LLM provider (anthropic/openai/google/minimax/siliconflow/zai/ollama)").option("--api-key <key>", "LLM API key").option("--port <port>", "API server port", "8056").option("--import-from <platform>", "Import LLM config from an installed agent platform (e.g. openclaw, hermes)").option("--auto-connect", "Auto-connect detected agent platforms after init").action(async (opts) => {
@@ -78195,7 +78355,7 @@ async function quickInit(options) {
78195
78355
  if (builtinTemplatesDir && existsSync32(builtinTemplatesDir) && !existsSync32(userTemplatesDir)) {
78196
78356
  const builtinRoot = resolve16(builtinTemplatesDir, "..");
78197
78357
  mkdirSync26(userTemplatesDir, { recursive: true });
78198
- cpSync3(builtinRoot, userTemplatesDir, { recursive: true });
78358
+ cpSync4(builtinRoot, userTemplatesDir, { recursive: true });
78199
78359
  console.log(` Copied templates to ${userTemplatesDir}`);
78200
78360
  }
78201
78361
  const devRoleDir = pathJoin(userTemplatesDir || pathJoin(process.cwd(), "templates"), "roles", "developer");