@starlein/paperclip-plugin-company-wizard 0.6.3 → 0.6.4
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/CHANGELOG.md +15 -0
- package/README.md +2 -2
- package/dist/manifest.js +2 -2
- package/dist/manifest.js.map +1 -1
- package/dist/ui/index.js +38 -5
- package/dist/ui/index.js.map +3 -3
- package/dist/worker.js +95 -14
- package/dist/worker.js.map +2 -2
- package/docs/PAPERCLIP-COMPATIBILITY.md +3 -1
- package/package.json +1 -1
package/dist/worker.js
CHANGED
|
@@ -29352,7 +29352,7 @@ import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
|
29352
29352
|
var manifest = {
|
|
29353
29353
|
id: "starlein.paperclip-plugin-company-wizard",
|
|
29354
29354
|
apiVersion: 1,
|
|
29355
|
-
version: "0.6.
|
|
29355
|
+
version: "0.6.4",
|
|
29356
29356
|
displayName: "Company Wizard",
|
|
29357
29357
|
description: "AI-powered wizard to bootstrap agent companies from composable templates",
|
|
29358
29358
|
author: "Sascha Pietrowski <sp@speednetwork.de>",
|
|
@@ -29388,7 +29388,7 @@ var manifest = {
|
|
|
29388
29388
|
},
|
|
29389
29389
|
templatesPath: {
|
|
29390
29390
|
type: "string",
|
|
29391
|
-
description: "Optional existing template root containing roles/, modules/, and presets/. Overrides the GitHub URL. Leave empty to use bundled release templates or a custom GitHub source. Refresh never
|
|
29391
|
+
description: "Optional existing template root containing roles/, modules/, and presets/. Overrides the GitHub URL. Leave empty to use bundled release templates or a custom GitHub source. Refresh never overwrites this directory. For an existing empty directory, the wizard offers an explicit sync from the configured template URL."
|
|
29392
29392
|
},
|
|
29393
29393
|
templatesRepoUrl: {
|
|
29394
29394
|
type: "string",
|
|
@@ -29562,6 +29562,16 @@ function humanizeSkillName(baseName, variant) {
|
|
|
29562
29562
|
function hashSkillContent(markdown) {
|
|
29563
29563
|
return createHash("sha256").update(String(markdown), "utf-8").digest("hex").slice(0, 16);
|
|
29564
29564
|
}
|
|
29565
|
+
function ensureSkillFrontmatter(slug, description, markdown) {
|
|
29566
|
+
const content = String(markdown);
|
|
29567
|
+
if (/^---\r?\n[\s\S]*?\r?\n---(?:\r?\n|$)/.test(content)) return content;
|
|
29568
|
+
return `---
|
|
29569
|
+
name: ${slug}
|
|
29570
|
+
description: ${JSON.stringify(String(description || slug))}
|
|
29571
|
+
---
|
|
29572
|
+
|
|
29573
|
+
${content}`;
|
|
29574
|
+
}
|
|
29565
29575
|
function buildCompanySkillSet(records) {
|
|
29566
29576
|
const byBaseSlug = /* @__PURE__ */ new Map();
|
|
29567
29577
|
for (const rec of records) {
|
|
@@ -29585,13 +29595,14 @@ function buildCompanySkillSet(records) {
|
|
|
29585
29595
|
const groups = [...byHash.values()].map((g) => ({ ...g, repRole: [...g.roles].sort()[0] })).sort((a, b) => a.repRole.localeCompare(b.repRole));
|
|
29586
29596
|
groups.forEach((group, idx) => {
|
|
29587
29597
|
const slug = idx === 0 ? baseSlug : `${baseSlug}-${group.repRole}`;
|
|
29598
|
+
const markdown = ensureSkillFrontmatter(slug, group.rec.description, group.rec.markdown);
|
|
29588
29599
|
companySkills.push({
|
|
29589
29600
|
slug,
|
|
29590
29601
|
name: group.rec.name,
|
|
29591
29602
|
description: group.rec.description,
|
|
29592
29603
|
categories: group.rec.categories,
|
|
29593
|
-
markdown
|
|
29594
|
-
contentHash:
|
|
29604
|
+
markdown,
|
|
29605
|
+
contentHash: hashSkillContent(markdown)
|
|
29595
29606
|
});
|
|
29596
29607
|
for (const roleName of group.roles) addRoleSlug(roleName, slug);
|
|
29597
29608
|
});
|
|
@@ -31627,19 +31638,25 @@ function copyDirSync(src, dest) {
|
|
|
31627
31638
|
const s = path2.join(src, entry.name);
|
|
31628
31639
|
const d = path2.join(dest, entry.name);
|
|
31629
31640
|
if (entry.isDirectory()) copyDirSync(s, d);
|
|
31630
|
-
else fs2.copyFileSync(s, d);
|
|
31641
|
+
else if (entry.isFile()) fs2.copyFileSync(s, d);
|
|
31642
|
+
else throw new Error("Template downloads must contain only regular files and directories.");
|
|
31631
31643
|
}
|
|
31632
31644
|
}
|
|
31633
31645
|
function downloadTemplatesFromGithub(destDir, githubUrl) {
|
|
31634
|
-
const match = githubUrl.match(
|
|
31646
|
+
const match = githubUrl.match(
|
|
31647
|
+
/^https:\/\/github\.com\/([\w-]+)\/([\w.-]+)\/tree\/([\w.-]+)\/([\w./-]+)$/
|
|
31648
|
+
);
|
|
31635
31649
|
if (!match) {
|
|
31636
31650
|
throw new Error(
|
|
31637
|
-
|
|
31651
|
+
"Unsupported templates URL. Expected https://github.com/{owner}/{repo}/tree/{branch}/{path} (no credentials or query string)."
|
|
31638
31652
|
);
|
|
31639
31653
|
}
|
|
31640
31654
|
const [, owner, repo, branch, subpath] = match;
|
|
31655
|
+
if (subpath.split("/").some((part) => !part || part === "." || part === ".." || part.startsWith("-"))) {
|
|
31656
|
+
throw new Error("Unsupported templates URL path.");
|
|
31657
|
+
}
|
|
31641
31658
|
const cloneUrl = `https://github.com/${owner}/${repo}.git`;
|
|
31642
|
-
const tmpDir = path2.join(os.tmpdir(),
|
|
31659
|
+
const tmpDir = fs2.mkdtempSync(path2.join(os.tmpdir(), "plugin-templates-dl-"));
|
|
31643
31660
|
try {
|
|
31644
31661
|
execFileSync(
|
|
31645
31662
|
"git",
|
|
@@ -31658,6 +31675,9 @@ function downloadTemplatesFromGithub(destDir, githubUrl) {
|
|
|
31658
31675
|
);
|
|
31659
31676
|
execFileSync("git", ["sparse-checkout", "set", subpath], { cwd: tmpDir, stdio: "pipe" });
|
|
31660
31677
|
const srcDir = path2.join(tmpDir, subpath);
|
|
31678
|
+
if (fs2.existsSync(srcDir) && (!fs2.lstatSync(srcDir).isDirectory() || !fs2.realpathSync(srcDir).startsWith(`${tmpDir}${path2.sep}`))) {
|
|
31679
|
+
throw new Error("Template source must be a directory inside the downloaded repository.");
|
|
31680
|
+
}
|
|
31661
31681
|
if (!fs2.existsSync(srcDir)) throw new Error(`Path '${subpath}' not found in ${cloneUrl}`);
|
|
31662
31682
|
copyDirSync(srcDir, destDir);
|
|
31663
31683
|
} finally {
|
|
@@ -31724,6 +31744,48 @@ function refreshTemplatesCache(cfg, log) {
|
|
|
31724
31744
|
log?.(`\u2713 Refreshed templates cache from ${repoUrl} \u2192 ${targetDir}`);
|
|
31725
31745
|
return targetDir;
|
|
31726
31746
|
}
|
|
31747
|
+
function emptyTemplateSyncOffer(cfg) {
|
|
31748
|
+
try {
|
|
31749
|
+
if (!cfg.templatesPath) return void 0;
|
|
31750
|
+
const stat = fs2.lstatSync(cfg.templatesPath);
|
|
31751
|
+
if (!stat.isDirectory() || fs2.readdirSync(cfg.templatesPath).length) return void 0;
|
|
31752
|
+
const targetDir = fs2.realpathSync(cfg.templatesPath);
|
|
31753
|
+
const token = createHash2("sha256").update(
|
|
31754
|
+
JSON.stringify([
|
|
31755
|
+
cfg.templatesPath,
|
|
31756
|
+
targetDir,
|
|
31757
|
+
cfg.templatesRepoUrl || "",
|
|
31758
|
+
stat.dev,
|
|
31759
|
+
stat.ino
|
|
31760
|
+
])
|
|
31761
|
+
).digest("hex");
|
|
31762
|
+
return { targetDir, token, canSync: !!cfg.templatesRepoUrl?.trim() };
|
|
31763
|
+
} catch {
|
|
31764
|
+
return void 0;
|
|
31765
|
+
}
|
|
31766
|
+
}
|
|
31767
|
+
function syncEmptyTemplates(cfg, confirmation) {
|
|
31768
|
+
const offer = emptyTemplateSyncOffer(cfg);
|
|
31769
|
+
if (!offer?.canSync || confirmation !== offer.token) {
|
|
31770
|
+
throw new Error(
|
|
31771
|
+
"Sync requires confirmation for the current empty templatesPath and a configured templatesRepoUrl. Reload the page and check settings."
|
|
31772
|
+
);
|
|
31773
|
+
}
|
|
31774
|
+
const stagingDir = fs2.mkdtempSync(`${offer.targetDir}-sync-`);
|
|
31775
|
+
try {
|
|
31776
|
+
downloadTemplatesFromGithub(stagingDir, cfg.templatesRepoUrl);
|
|
31777
|
+
validateTemplatesDir(stagingDir, "Downloaded templates");
|
|
31778
|
+
const { loadErrors } = loadTemplates(stagingDir);
|
|
31779
|
+
if (loadErrors.length) throw new Error("Downloaded template metadata is invalid.");
|
|
31780
|
+
if (emptyTemplateSyncOffer(cfg)?.token !== offer.token) {
|
|
31781
|
+
throw new Error("Template directory changed during sync. No existing files were replaced.");
|
|
31782
|
+
}
|
|
31783
|
+
fs2.renameSync(stagingDir, offer.targetDir);
|
|
31784
|
+
return offer.targetDir;
|
|
31785
|
+
} finally {
|
|
31786
|
+
fs2.rmSync(stagingDir, { recursive: true, force: true });
|
|
31787
|
+
}
|
|
31788
|
+
}
|
|
31727
31789
|
var ANTHROPIC_AI_MODEL = "claude-opus-5";
|
|
31728
31790
|
var OPENAI_AI_MODEL = "gpt-6-astra";
|
|
31729
31791
|
function isLikelyAnthropicApiKey(value) {
|
|
@@ -32102,13 +32164,14 @@ async function provisionCompanySkills(client, companyId, companySkills, log) {
|
|
|
32102
32164
|
}
|
|
32103
32165
|
}
|
|
32104
32166
|
for (const skill of companySkills) {
|
|
32167
|
+
const markdown = ensureSkillFrontmatter(skill.slug, skill.description, skill.markdown);
|
|
32105
32168
|
const found = bySlug.get(skill.slug);
|
|
32106
32169
|
if (!found) {
|
|
32107
32170
|
const created = await client.createCompanySkill(companyId, {
|
|
32108
|
-
name: skill.
|
|
32171
|
+
name: skill.slug,
|
|
32109
32172
|
slug: skill.slug,
|
|
32110
32173
|
description: skill.description,
|
|
32111
|
-
markdown
|
|
32174
|
+
markdown,
|
|
32112
32175
|
categories: skill.categories
|
|
32113
32176
|
});
|
|
32114
32177
|
slugToKey.set(skill.slug, created?.key || created?.slug || skill.slug);
|
|
@@ -32117,16 +32180,16 @@ async function provisionCompanySkills(client, companyId, companySkills, log) {
|
|
|
32117
32180
|
}
|
|
32118
32181
|
slugToKey.set(skill.slug, found.key || skill.slug);
|
|
32119
32182
|
let updated = false;
|
|
32120
|
-
if ((found.markdown ?? "") !==
|
|
32183
|
+
if ((found.markdown ?? "") !== markdown) {
|
|
32121
32184
|
await client.updateCompanySkillFile(companyId, found.id, {
|
|
32122
32185
|
path: "SKILL.md",
|
|
32123
|
-
content:
|
|
32186
|
+
content: markdown
|
|
32124
32187
|
});
|
|
32125
32188
|
updated = true;
|
|
32126
32189
|
}
|
|
32127
|
-
if ((found.name ?? "") !== skill.
|
|
32190
|
+
if ((found.name ?? "") !== skill.slug) {
|
|
32128
32191
|
const renamed = await client.renameCompanySkill(companyId, found.id, {
|
|
32129
|
-
name: skill.
|
|
32192
|
+
name: skill.slug,
|
|
32130
32193
|
slug: found.slug
|
|
32131
32194
|
});
|
|
32132
32195
|
if (renamed == null) {
|
|
@@ -32389,6 +32452,11 @@ var plugin = definePlugin({
|
|
|
32389
32452
|
ctx.data.register("templates", async () => {
|
|
32390
32453
|
try {
|
|
32391
32454
|
const cfg = await ctx.config.get() ?? {};
|
|
32455
|
+
const syncOffer = emptyTemplateSyncOffer(cfg);
|
|
32456
|
+
if (syncOffer) {
|
|
32457
|
+
const error61 = `Configured templatesPath "${syncOffer.targetDir}" is empty. ` + (syncOffer.canSync ? "Confirm sync from the configured template URL to populate it." : "Set templatesRepoUrl in plugin settings to enable sync, or clear templatesPath to use bundled templates.");
|
|
32458
|
+
return { presets: [], modules: [], roles: [], loadErrors: [error61], error: error61, syncOffer };
|
|
32459
|
+
}
|
|
32392
32460
|
const templates = loadTemplates(await ensureTemplatesDir(cfg));
|
|
32393
32461
|
for (const warning of templates.loadErrors) {
|
|
32394
32462
|
ctx.logger.info(`\u26A0 Template load warning: ${warning}`);
|
|
@@ -32409,6 +32477,19 @@ var plugin = definePlugin({
|
|
|
32409
32477
|
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
32410
32478
|
}
|
|
32411
32479
|
});
|
|
32480
|
+
ctx.actions.register("sync-empty-templates", async (params) => {
|
|
32481
|
+
try {
|
|
32482
|
+
const cfg = await ctx.config.get() ?? {};
|
|
32483
|
+
const targetDir = syncEmptyTemplates(cfg, params.confirmation);
|
|
32484
|
+
ctx.logger.info("Synced empty configured template directory.");
|
|
32485
|
+
return { ok: true, targetDir };
|
|
32486
|
+
} catch {
|
|
32487
|
+
return {
|
|
32488
|
+
ok: false,
|
|
32489
|
+
error: "Template sync failed. Check the configured GitHub tree URL, permissions, and that the directory is still empty. Existing files were not overwritten. Reload to retry."
|
|
32490
|
+
};
|
|
32491
|
+
}
|
|
32492
|
+
});
|
|
32412
32493
|
ctx.actions.register("check-update", async () => {
|
|
32413
32494
|
try {
|
|
32414
32495
|
const latestVersion = await fetchLatestPluginVersion();
|