switchroom 0.13.49 → 0.13.50
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/cli/switchroom.js
CHANGED
|
@@ -47249,6 +47249,20 @@ function dispatchTool(name, args) {
|
|
|
47249
47249
|
parseMode = "json";
|
|
47250
47250
|
break;
|
|
47251
47251
|
}
|
|
47252
|
+
case "skill_clone_to_personal": {
|
|
47253
|
+
const a = args;
|
|
47254
|
+
if (!a.source || typeof a.source !== "string") {
|
|
47255
|
+
return errorText("skill_clone_to_personal: source is required");
|
|
47256
|
+
}
|
|
47257
|
+
const base = ["skill", "clone-to-personal", a.source];
|
|
47258
|
+
if (a.agent)
|
|
47259
|
+
base.push("--agent", a.agent);
|
|
47260
|
+
if (a.name)
|
|
47261
|
+
base.push("--name", a.name);
|
|
47262
|
+
cliArgs = base;
|
|
47263
|
+
parseMode = "json";
|
|
47264
|
+
break;
|
|
47265
|
+
}
|
|
47252
47266
|
default:
|
|
47253
47267
|
return errorText(`unknown tool: ${name}`);
|
|
47254
47268
|
}
|
|
@@ -47488,6 +47502,26 @@ var init_server3 = __esm(() => {
|
|
|
47488
47502
|
type: "object",
|
|
47489
47503
|
properties: {}
|
|
47490
47504
|
}
|
|
47505
|
+
},
|
|
47506
|
+
{
|
|
47507
|
+
name: "skill_clone_to_personal",
|
|
47508
|
+
description: "Fork a shared or bundled skill into this agent's writable workspace. " + "Use when you find a defect or gap in a skill you depend on and want " + "to fix it yourself \u2014 the upstream source is untouched, your fork is " + "yours to edit via `skill_edit_personal`. Source format: " + "`shared:<name>` or `bundled:<name>`. Same validation gates as " + "init/edit. Pre-existing personal-<name> is refused (use edit or " + "remove first). No operator approval.",
|
|
47509
|
+
inputSchema: {
|
|
47510
|
+
type: "object",
|
|
47511
|
+
required: ["source"],
|
|
47512
|
+
properties: {
|
|
47513
|
+
source: {
|
|
47514
|
+
type: "string",
|
|
47515
|
+
pattern: "^(shared|bundled):[a-z0-9][a-z0-9_-]{0,62}$",
|
|
47516
|
+
description: "Source skill: `shared:<name>` (operator pool) or `bundled:<name>` (shipped)."
|
|
47517
|
+
},
|
|
47518
|
+
name: {
|
|
47519
|
+
type: "string",
|
|
47520
|
+
pattern: "^[a-z0-9][a-z0-9_-]{0,62}$",
|
|
47521
|
+
description: "Optional destination slug (defaults to the source slug)."
|
|
47522
|
+
}
|
|
47523
|
+
}
|
|
47524
|
+
}
|
|
47491
47525
|
}
|
|
47492
47526
|
];
|
|
47493
47527
|
});
|
|
@@ -47913,8 +47947,8 @@ var {
|
|
|
47913
47947
|
} = import__.default;
|
|
47914
47948
|
|
|
47915
47949
|
// src/build-info.ts
|
|
47916
|
-
var VERSION = "0.13.
|
|
47917
|
-
var COMMIT_SHA = "
|
|
47950
|
+
var VERSION = "0.13.50";
|
|
47951
|
+
var COMMIT_SHA = "99e127b1";
|
|
47918
47952
|
|
|
47919
47953
|
// src/cli/agent.ts
|
|
47920
47954
|
init_source();
|
|
@@ -77606,6 +77640,87 @@ init_source();
|
|
|
77606
77640
|
var PERSONAL_PREFIX = "personal-";
|
|
77607
77641
|
var TRASH_DIRNAME = "skills-trash";
|
|
77608
77642
|
var TRASH_TTL_MS = 24 * 60 * 60 * 1000;
|
|
77643
|
+
var PERSONAL_SKILLS_SUBPATH = "personal-skills";
|
|
77644
|
+
function resolveConfigSkillsDir(agent) {
|
|
77645
|
+
const override = process.env.SWITCHROOM_CONFIG_DIR;
|
|
77646
|
+
const candidate = override ? resolve45(override) : join69(homedir39(), ".switchroom-config");
|
|
77647
|
+
if (!existsSync76(candidate))
|
|
77648
|
+
return null;
|
|
77649
|
+
return join69(candidate, "agents", agent, PERSONAL_SKILLS_SUBPATH);
|
|
77650
|
+
}
|
|
77651
|
+
var MIRROR_PRIOR_TTL_MS = 24 * 60 * 60 * 1000;
|
|
77652
|
+
function sweepMirrorPriors(configSkillsRoot) {
|
|
77653
|
+
try {
|
|
77654
|
+
if (!existsSync76(configSkillsRoot))
|
|
77655
|
+
return;
|
|
77656
|
+
const now = Date.now();
|
|
77657
|
+
for (const ent of readdirSync30(configSkillsRoot)) {
|
|
77658
|
+
const m = /^\.(?:.+)-(?:prior|trash)-(\d+)$/.exec(ent);
|
|
77659
|
+
if (!m)
|
|
77660
|
+
continue;
|
|
77661
|
+
const ts = Number(m[1]);
|
|
77662
|
+
if (!Number.isFinite(ts))
|
|
77663
|
+
continue;
|
|
77664
|
+
if (now - ts < MIRROR_PRIOR_TTL_MS)
|
|
77665
|
+
continue;
|
|
77666
|
+
try {
|
|
77667
|
+
rmSync17(join69(configSkillsRoot, ent), { recursive: true, force: true });
|
|
77668
|
+
} catch {}
|
|
77669
|
+
}
|
|
77670
|
+
} catch {}
|
|
77671
|
+
}
|
|
77672
|
+
function mirrorToConfigRepo(agent, name, liveSkillDir) {
|
|
77673
|
+
const configSkillsRoot = resolveConfigSkillsDir(agent);
|
|
77674
|
+
if (!configSkillsRoot)
|
|
77675
|
+
return;
|
|
77676
|
+
const dest = join69(configSkillsRoot, name);
|
|
77677
|
+
try {
|
|
77678
|
+
if (liveSkillDir !== null) {
|
|
77679
|
+
try {
|
|
77680
|
+
const st = lstatSync9(liveSkillDir);
|
|
77681
|
+
if (st.isSymbolicLink()) {
|
|
77682
|
+
process.stderr.write(source_default.yellow(`warning: refusing to mirror ${liveSkillDir} \u2014 source is a symlink
|
|
77683
|
+
`));
|
|
77684
|
+
return;
|
|
77685
|
+
}
|
|
77686
|
+
} catch {}
|
|
77687
|
+
}
|
|
77688
|
+
if (liveSkillDir === null) {
|
|
77689
|
+
sweepMirrorPriors(configSkillsRoot);
|
|
77690
|
+
if (existsSync76(dest)) {
|
|
77691
|
+
const trash = join69(configSkillsRoot, `.${name}-trash-${Date.now()}`);
|
|
77692
|
+
renameSync17(dest, trash);
|
|
77693
|
+
}
|
|
77694
|
+
return;
|
|
77695
|
+
}
|
|
77696
|
+
mkdirSync41(configSkillsRoot, { recursive: true, mode: 493 });
|
|
77697
|
+
sweepMirrorPriors(configSkillsRoot);
|
|
77698
|
+
const staging = mkdtempSync6(join69(configSkillsRoot, `.${name}-staging-`));
|
|
77699
|
+
const walk2 = (src, dst) => {
|
|
77700
|
+
mkdirSync41(dst, { recursive: true, mode: 493 });
|
|
77701
|
+
for (const ent of readdirSync30(src, { withFileTypes: true })) {
|
|
77702
|
+
const s = join69(src, ent.name);
|
|
77703
|
+
const d = join69(dst, ent.name);
|
|
77704
|
+
if (ent.isSymbolicLink())
|
|
77705
|
+
continue;
|
|
77706
|
+
if (ent.isDirectory())
|
|
77707
|
+
walk2(s, d);
|
|
77708
|
+
else if (ent.isFile()) {
|
|
77709
|
+
writeFileSync35(d, readFileSync61(s));
|
|
77710
|
+
}
|
|
77711
|
+
}
|
|
77712
|
+
};
|
|
77713
|
+
walk2(liveSkillDir, staging);
|
|
77714
|
+
if (existsSync76(dest)) {
|
|
77715
|
+
const prior = join69(configSkillsRoot, `.${name}-prior-${Date.now()}`);
|
|
77716
|
+
renameSync17(dest, prior);
|
|
77717
|
+
}
|
|
77718
|
+
renameSync17(staging, dest);
|
|
77719
|
+
} catch (err2) {
|
|
77720
|
+
process.stderr.write(source_default.yellow(`warning: mirror to ${dest} failed (${err2.message ?? err2}); ` + `live copy still works, but this skill is not version-controlled until next successful sync.
|
|
77721
|
+
`));
|
|
77722
|
+
}
|
|
77723
|
+
}
|
|
77609
77724
|
function fail3(msg, exit = 2) {
|
|
77610
77725
|
console.error(source_default.red(`error: ${msg}`));
|
|
77611
77726
|
process.exit(exit);
|
|
@@ -77842,14 +77957,6 @@ function loadValidateWrite(agentsRoot, agent, name, files, ensureNew) {
|
|
|
77842
77957
|
process.exit(3);
|
|
77843
77958
|
}
|
|
77844
77959
|
writePersonalSkill(target, files);
|
|
77845
|
-
console.log(JSON.stringify({
|
|
77846
|
-
ok: true,
|
|
77847
|
-
action: ensureNew ? "init" : "edit",
|
|
77848
|
-
agent,
|
|
77849
|
-
name,
|
|
77850
|
-
path: target,
|
|
77851
|
-
files: Object.keys(files).length
|
|
77852
|
-
}));
|
|
77853
77960
|
}
|
|
77854
77961
|
function loadFiles(opts) {
|
|
77855
77962
|
if (opts.from === undefined) {
|
|
@@ -77873,6 +77980,16 @@ function initPersonalAction(name, opts) {
|
|
|
77873
77980
|
const agentsRoot = resolveAgentsRoot(opts);
|
|
77874
77981
|
const files = loadFiles(opts);
|
|
77875
77982
|
loadValidateWrite(agentsRoot, agent, name, files, true);
|
|
77983
|
+
const skillDir = personalSkillDir(agentsRoot, agent, name);
|
|
77984
|
+
mirrorToConfigRepo(agent, name, skillDir);
|
|
77985
|
+
console.log(JSON.stringify({
|
|
77986
|
+
ok: true,
|
|
77987
|
+
action: "init",
|
|
77988
|
+
agent,
|
|
77989
|
+
name,
|
|
77990
|
+
path: skillDir,
|
|
77991
|
+
files: Object.keys(files).length
|
|
77992
|
+
}));
|
|
77876
77993
|
appendAudit(agent, "skill.init_personal", { name, files: Object.keys(files).length }, 0);
|
|
77877
77994
|
}
|
|
77878
77995
|
function editPersonalAction(name, opts) {
|
|
@@ -77880,8 +77997,126 @@ function editPersonalAction(name, opts) {
|
|
|
77880
77997
|
const agentsRoot = resolveAgentsRoot(opts);
|
|
77881
77998
|
const files = loadFiles(opts);
|
|
77882
77999
|
loadValidateWrite(agentsRoot, agent, name, files, false);
|
|
78000
|
+
const skillDir = personalSkillDir(agentsRoot, agent, name);
|
|
78001
|
+
mirrorToConfigRepo(agent, name, skillDir);
|
|
78002
|
+
console.log(JSON.stringify({
|
|
78003
|
+
ok: true,
|
|
78004
|
+
action: "edit",
|
|
78005
|
+
agent,
|
|
78006
|
+
name,
|
|
78007
|
+
path: skillDir,
|
|
78008
|
+
files: Object.keys(files).length
|
|
78009
|
+
}));
|
|
77883
78010
|
appendAudit(agent, "skill.edit_personal", { name, files: Object.keys(files).length }, 0);
|
|
77884
78011
|
}
|
|
78012
|
+
var CLONE_SOURCE_RE = /^(shared|bundled):([a-z0-9][a-z0-9_-]{0,62})$/;
|
|
78013
|
+
function defaultSharedRoot() {
|
|
78014
|
+
return join69(homedir39(), ".switchroom", "skills");
|
|
78015
|
+
}
|
|
78016
|
+
function defaultBundledRoot() {
|
|
78017
|
+
return join69(homedir39(), ".switchroom", "skills", "_bundled");
|
|
78018
|
+
}
|
|
78019
|
+
function resolveCloneSource(source, opts) {
|
|
78020
|
+
const m = CLONE_SOURCE_RE.exec(source);
|
|
78021
|
+
if (!m) {
|
|
78022
|
+
fail3(`source must be \`shared:<name>\` or \`bundled:<name>\` (got ${JSON.stringify(source)})`);
|
|
78023
|
+
}
|
|
78024
|
+
const tier = m[1];
|
|
78025
|
+
const slug = m[2];
|
|
78026
|
+
const root = tier === "bundled" ? opts.bundledRoot ?? defaultBundledRoot() : opts.sharedRoot ?? defaultSharedRoot();
|
|
78027
|
+
const dir = join69(root, slug);
|
|
78028
|
+
if (!existsSync76(dir)) {
|
|
78029
|
+
fail3(`clone source ${JSON.stringify(source)} not found at ${dir}; ` + `check \`switchroom skill search --tier ${tier}\``, 1);
|
|
78030
|
+
}
|
|
78031
|
+
const st = lstatSync9(dir);
|
|
78032
|
+
if (st.isSymbolicLink()) {
|
|
78033
|
+
fail3(`clone source ${JSON.stringify(source)} is a symlink at ${dir}; ` + `point clone at the canonical pool path instead`);
|
|
78034
|
+
}
|
|
78035
|
+
return { tier, slug, dir };
|
|
78036
|
+
}
|
|
78037
|
+
var CLONE_MAX_FILE_BYTES = 1024 * 1024;
|
|
78038
|
+
function readSourceFiles(dir) {
|
|
78039
|
+
const files = {};
|
|
78040
|
+
const walk2 = (sub) => {
|
|
78041
|
+
for (const ent of readdirSync30(sub, { withFileTypes: true })) {
|
|
78042
|
+
const full = join69(sub, ent.name);
|
|
78043
|
+
if (ent.isSymbolicLink()) {
|
|
78044
|
+
continue;
|
|
78045
|
+
}
|
|
78046
|
+
if (ent.isDirectory()) {
|
|
78047
|
+
walk2(full);
|
|
78048
|
+
continue;
|
|
78049
|
+
}
|
|
78050
|
+
if (ent.isFile()) {
|
|
78051
|
+
const rel = relative3(dir, full).replace(/\\/g, "/");
|
|
78052
|
+
try {
|
|
78053
|
+
const st = lstatSync9(full);
|
|
78054
|
+
if (st.size > CLONE_MAX_FILE_BYTES) {
|
|
78055
|
+
fail3(`clone source has oversized file ${rel} (${st.size} bytes > ${CLONE_MAX_FILE_BYTES}); ` + `refuse to read`, 3);
|
|
78056
|
+
}
|
|
78057
|
+
} catch {}
|
|
78058
|
+
files[rel] = readFileSync61(full, "utf-8");
|
|
78059
|
+
}
|
|
78060
|
+
}
|
|
78061
|
+
};
|
|
78062
|
+
walk2(dir);
|
|
78063
|
+
return files;
|
|
78064
|
+
}
|
|
78065
|
+
function rewriteSkillMdName(content, newName) {
|
|
78066
|
+
if (!content.startsWith(`---
|
|
78067
|
+
`) && !content.startsWith(`---\r
|
|
78068
|
+
`)) {
|
|
78069
|
+
return content;
|
|
78070
|
+
}
|
|
78071
|
+
const rest = content.slice(content.indexOf(`
|
|
78072
|
+
`) + 1);
|
|
78073
|
+
const endIdx = rest.indexOf(`
|
|
78074
|
+
---`);
|
|
78075
|
+
if (endIdx < 0)
|
|
78076
|
+
return content;
|
|
78077
|
+
const fm = rest.slice(0, endIdx);
|
|
78078
|
+
const body = rest.slice(endIdx);
|
|
78079
|
+
const patched = fm.replace(/^(\s*name\s*:)[ \t]*\S.*$/m, `$1 ${newName}`);
|
|
78080
|
+
return `---
|
|
78081
|
+
` + patched + body;
|
|
78082
|
+
}
|
|
78083
|
+
function clonePersonalAction(source, opts) {
|
|
78084
|
+
const agent = resolveAgent(opts);
|
|
78085
|
+
const agentsRoot = resolveAgentsRoot(opts);
|
|
78086
|
+
const src = resolveCloneSource(source, opts);
|
|
78087
|
+
const newName = opts.name ?? src.slug;
|
|
78088
|
+
if (!SKILL_SLUG_RE.test(newName)) {
|
|
78089
|
+
fail3(`destination name must match ${SKILL_SLUG_RE.source}: got ${JSON.stringify(newName)}`);
|
|
78090
|
+
}
|
|
78091
|
+
const files = readSourceFiles(src.dir);
|
|
78092
|
+
if (!files["SKILL.md"]) {
|
|
78093
|
+
fail3(`source ${JSON.stringify(source)} has no SKILL.md at ${src.dir}`);
|
|
78094
|
+
}
|
|
78095
|
+
if (newName !== src.slug) {
|
|
78096
|
+
files["SKILL.md"] = rewriteSkillMdName(files["SKILL.md"], newName);
|
|
78097
|
+
}
|
|
78098
|
+
loadValidateWrite(agentsRoot, agent, newName, files, true);
|
|
78099
|
+
const skillDir = personalSkillDir(agentsRoot, agent, newName);
|
|
78100
|
+
mirrorToConfigRepo(agent, newName, skillDir);
|
|
78101
|
+
console.log(JSON.stringify({
|
|
78102
|
+
ok: true,
|
|
78103
|
+
action: "clone_to_personal",
|
|
78104
|
+
agent,
|
|
78105
|
+
source,
|
|
78106
|
+
source_tier: src.tier,
|
|
78107
|
+
source_slug: src.slug,
|
|
78108
|
+
name: newName,
|
|
78109
|
+
path: skillDir,
|
|
78110
|
+
files: Object.keys(files).length
|
|
78111
|
+
}));
|
|
78112
|
+
appendAudit(agent, "skill.clone_to_personal", {
|
|
78113
|
+
source,
|
|
78114
|
+
source_tier: src.tier,
|
|
78115
|
+
source_slug: src.slug,
|
|
78116
|
+
name: newName,
|
|
78117
|
+
files: Object.keys(files).length
|
|
78118
|
+
}, 0);
|
|
78119
|
+
}
|
|
77885
78120
|
function removePersonalAction(name, opts) {
|
|
77886
78121
|
const agent = resolveAgent(opts);
|
|
77887
78122
|
const agentsRoot = resolveAgentsRoot(opts);
|
|
@@ -77909,6 +78144,7 @@ function removePersonalAction(name, opts) {
|
|
|
77909
78144
|
renameSync17(target, trashTarget);
|
|
77910
78145
|
const now = new Date(ts);
|
|
77911
78146
|
utimesSync(trashTarget, now, now);
|
|
78147
|
+
mirrorToConfigRepo(agent, name, null);
|
|
77912
78148
|
console.log(JSON.stringify({
|
|
77913
78149
|
ok: true,
|
|
77914
78150
|
action: "remove",
|
|
@@ -77974,6 +78210,9 @@ function registerSkillPersonalCommands(program3) {
|
|
|
77974
78210
|
parent.command("list-personal").description("List personal skills owned by this agent. JSON output by default.").option("--agent <name>", "Agent name (defaults to $SWITCHROOM_AGENT_NAME)").option("--root <path>", "Test-only override for agents-root dir").action(withConfigError(async (opts) => {
|
|
77975
78211
|
listPersonalAction(opts);
|
|
77976
78212
|
}));
|
|
78213
|
+
parent.command("clone-to-personal <source>").description("Fork a shared or bundled skill into this agent's writable workspace. " + "Source format: `shared:<name>` or `bundled:<name>`. The personal copy " + "becomes mutable via edit-personal; the upstream source is untouched. " + "Use --name to give the fork a different slug. No operator approval \u2014 " + "agent's own workspace.").option("--agent <name>", "Agent name (defaults to $SWITCHROOM_AGENT_NAME)").option("--name <slug>", "Override destination slug (default: source slug)").addOption(new Option("--root <path>").hideHelp()).addOption(new Option("--shared-root <path>").hideHelp()).addOption(new Option("--bundled-root <path>").hideHelp()).action(withConfigError(async (source, opts) => {
|
|
78214
|
+
clonePersonalAction(source, opts);
|
|
78215
|
+
}));
|
|
77977
78216
|
}
|
|
77978
78217
|
|
|
77979
78218
|
// src/cli/skill-search.ts
|
|
@@ -77988,10 +78227,10 @@ var AGENT_NAME_RE3 = /^[a-z][a-z0-9_-]{0,62}$/;
|
|
|
77988
78227
|
function defaultAgentsRoot() {
|
|
77989
78228
|
return resolve46(homedir40(), ".switchroom/agents");
|
|
77990
78229
|
}
|
|
77991
|
-
function
|
|
78230
|
+
function defaultSharedRoot2() {
|
|
77992
78231
|
return resolve46(homedir40(), ".switchroom/skills");
|
|
77993
78232
|
}
|
|
77994
|
-
function
|
|
78233
|
+
function defaultBundledRoot2() {
|
|
77995
78234
|
return resolve46(homedir40(), ".switchroom/skills/_bundled");
|
|
77996
78235
|
}
|
|
77997
78236
|
function readSkillFrontmatter(skillDir) {
|
|
@@ -78077,7 +78316,7 @@ function listPersonalSkills(agent, agentsRoot = defaultAgentsRoot()) {
|
|
|
78077
78316
|
}
|
|
78078
78317
|
return out;
|
|
78079
78318
|
}
|
|
78080
|
-
function listSharedSkills(sharedRoot =
|
|
78319
|
+
function listSharedSkills(sharedRoot = defaultSharedRoot2()) {
|
|
78081
78320
|
if (!existsSync77(sharedRoot))
|
|
78082
78321
|
return [];
|
|
78083
78322
|
const out = [];
|
|
@@ -78115,7 +78354,7 @@ function listSharedSkills(sharedRoot = defaultSharedRoot()) {
|
|
|
78115
78354
|
}
|
|
78116
78355
|
return out;
|
|
78117
78356
|
}
|
|
78118
|
-
function listBundledSkills(bundledRoot =
|
|
78357
|
+
function listBundledSkills(bundledRoot = defaultBundledRoot2()) {
|
|
78119
78358
|
if (!existsSync77(bundledRoot))
|
|
78120
78359
|
return [];
|
|
78121
78360
|
const out = [];
|
package/package.json
CHANGED
|
@@ -48732,10 +48732,10 @@ function sweepStaleTurnActiveMarker(stateDir, opts) {
|
|
|
48732
48732
|
}
|
|
48733
48733
|
|
|
48734
48734
|
// ../src/build-info.ts
|
|
48735
|
-
var VERSION = "0.13.
|
|
48736
|
-
var COMMIT_SHA = "
|
|
48737
|
-
var COMMIT_DATE = "2026-05-
|
|
48738
|
-
var LATEST_PR =
|
|
48735
|
+
var VERSION = "0.13.50";
|
|
48736
|
+
var COMMIT_SHA = "99e127b1";
|
|
48737
|
+
var COMMIT_DATE = "2026-05-25T22:55:41Z";
|
|
48738
|
+
var LATEST_PR = 1845;
|
|
48739
48739
|
var COMMITS_AHEAD_OF_TAG = 0;
|
|
48740
48740
|
|
|
48741
48741
|
// gateway/boot-version.ts
|