forge-cw 0.1.11 → 0.1.12

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.
Files changed (2) hide show
  1. package/dist/index.js +42 -9
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -2642,8 +2642,18 @@ var init_plugins = __esm({
2642
2642
 
2643
2643
  // ../core/dist/skill-routes.js
2644
2644
  import { Hono as Hono2 } from "hono";
2645
- import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2, rmSync as rmSync2, existsSync as existsSync7, unlinkSync, cpSync } from "node:fs";
2645
+ import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2, rmSync as rmSync2, existsSync as existsSync7, unlinkSync, cpSync, readdirSync as readdirSync5 } from "node:fs";
2646
2646
  import { dirname as dirname2, join as join9 } from "node:path";
2647
+ function backUpSkill(backupDir, scope, ref, name, dir) {
2648
+ const root = join9(backupDir, scope, ref, name);
2649
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
2650
+ let dest = join9(root, stamp);
2651
+ for (let n = 1; existsSync7(dest); n++)
2652
+ dest = join9(root, `${stamp}-${String(n).padStart(3, "0")}`);
2653
+ cpSync(dir, dest, { recursive: true, dereference: true });
2654
+ for (const old of readdirSync5(root).sort().slice(0, -BACKUPS_KEPT))
2655
+ rmSync2(join9(root, old), { recursive: true, force: true });
2656
+ }
2647
2657
  function installFailure(result2) {
2648
2658
  try {
2649
2659
  const entries = JSON.parse(result2.stdout);
@@ -2685,6 +2695,16 @@ function skillRoutes(reader, options = {}) {
2685
2695
  }
2686
2696
  };
2687
2697
  const knownAccount = (account) => account && reader.getAccounts().includes(account) ? account : null;
2698
+ const backup = (scope, ref, name, dir) => {
2699
+ if (!options.backupDir)
2700
+ return null;
2701
+ try {
2702
+ backUpSkill(options.backupDir, scope, ref, name, dir);
2703
+ return null;
2704
+ } catch (err) {
2705
+ return `Could not back up ${name}, nothing was changed: ${err instanceof Error ? err.message : "Unknown error"}`;
2706
+ }
2707
+ };
2688
2708
  const refOf = (scope, scopeRef) => scope === "global" ? "global" : scopeRef ?? "";
2689
2709
  const knownScope = (scope, ref) => scope === "global" || (scope === "account" ? reader.getAccounts().includes(ref) : scope === "project" && ref in reader.getProjects());
2690
2710
  app.get("/", (c) => c.json(reader.getSkills()));
@@ -2765,6 +2785,9 @@ function skillRoutes(reader, options = {}) {
2765
2785
  return c.json({ error: "Skill not found" }, 404);
2766
2786
  }
2767
2787
  const { content } = await c.req.json();
2788
+ const failed = backup(scope, scopeRef, name, dir);
2789
+ if (failed)
2790
+ return c.json({ error: failed }, 500);
2768
2791
  try {
2769
2792
  writeFileSync2(skillMd, content, "utf-8");
2770
2793
  return c.json({ ok: true });
@@ -2784,6 +2807,9 @@ function skillRoutes(reader, options = {}) {
2784
2807
  return c.json({ error: "Skill not found" }, 404);
2785
2808
  }
2786
2809
  const { content } = await c.req.json();
2810
+ const failed = backup(scope, scopeRef, name, dir);
2811
+ if (failed)
2812
+ return c.json({ error: failed }, 500);
2787
2813
  try {
2788
2814
  const refsDir = join9(dir, "references");
2789
2815
  mkdirSync2(refsDir, { recursive: true });
@@ -2804,6 +2830,9 @@ function skillRoutes(reader, options = {}) {
2804
2830
  if (!existsSync7(refFile)) {
2805
2831
  return c.json({ error: "Reference not found" }, 404);
2806
2832
  }
2833
+ const failed = backup(scope, scopeRef, name, dir);
2834
+ if (failed)
2835
+ return c.json({ error: failed }, 500);
2807
2836
  try {
2808
2837
  unlinkSync(refFile);
2809
2838
  return c.json({ ok: true });
@@ -2820,6 +2849,9 @@ function skillRoutes(reader, options = {}) {
2820
2849
  if (!existsSync7(join9(dir, "SKILL.md"))) {
2821
2850
  return c.json({ error: "Skill not found" }, 404);
2822
2851
  }
2852
+ const failed = backup(scope, scopeRef, name, dir);
2853
+ if (failed)
2854
+ return c.json({ error: failed }, 500);
2823
2855
  try {
2824
2856
  rmSync2(dir, { recursive: true, force: true });
2825
2857
  return c.json({ ok: true });
@@ -2931,7 +2963,7 @@ function skillRoutes(reader, options = {}) {
2931
2963
  });
2932
2964
  return app;
2933
2965
  }
2934
- var INSTALL_TIMEOUT_MS, SKILL_NAME_RE, PLUGIN_ID_RE;
2966
+ var INSTALL_TIMEOUT_MS, SKILL_NAME_RE, PLUGIN_ID_RE, BACKUPS_KEPT;
2935
2967
  var init_skill_routes = __esm({
2936
2968
  "../core/dist/skill-routes.js"() {
2937
2969
  "use strict";
@@ -2941,6 +2973,7 @@ var init_skill_routes = __esm({
2941
2973
  INSTALL_TIMEOUT_MS = 12e4;
2942
2974
  SKILL_NAME_RE = /^\w[\w.-]*$/;
2943
2975
  PLUGIN_ID_RE = /^\w[\w.-]*@\w[\w.-]*$/;
2976
+ BACKUPS_KEPT = 10;
2944
2977
  }
2945
2978
  });
2946
2979
 
@@ -3747,7 +3780,7 @@ var init_general_sessions = __esm({
3747
3780
  });
3748
3781
 
3749
3782
  // ../core/dist/liveframe.js
3750
- import { existsSync as existsSync10, readFileSync as readFileSync8, readdirSync as readdirSync5 } from "node:fs";
3783
+ import { existsSync as existsSync10, readFileSync as readFileSync8, readdirSync as readdirSync6 } from "node:fs";
3751
3784
  import { mkdir, rm } from "node:fs/promises";
3752
3785
  import { homedir as homedir4 } from "node:os";
3753
3786
  import { join as join11 } from "node:path";
@@ -3843,10 +3876,10 @@ var init_liveframe = __esm({
3843
3876
  return [];
3844
3877
  const { api } = this.config();
3845
3878
  const frames = [];
3846
- for (const project of readdirSync5(this.root, { withFileTypes: true })) {
3879
+ for (const project of readdirSync6(this.root, { withFileTypes: true })) {
3847
3880
  if (!project.isDirectory() || !isFrameSegment(project.name))
3848
3881
  continue;
3849
- for (const frame of readdirSync5(join11(this.root, project.name), { withFileTypes: true })) {
3882
+ for (const frame of readdirSync6(join11(this.root, project.name), { withFileTypes: true })) {
3850
3883
  if (!frame.isDirectory() || !isFrameSegment(frame.name))
3851
3884
  continue;
3852
3885
  const found = this.readFrame(project.name, frame.name, api);
@@ -3886,7 +3919,7 @@ var init_liveframe = __esm({
3886
3919
  await this.lf(args(dir), this.root);
3887
3920
  } catch (err) {
3888
3921
  await rm(dir, { recursive: true, force: true });
3889
- if (readdirSync5(projectDir).length === 0)
3922
+ if (readdirSync6(projectDir).length === 0)
3890
3923
  await rm(projectDir, { recursive: true, force: true });
3891
3924
  throw err;
3892
3925
  }
@@ -4534,7 +4567,7 @@ var init_kioku_routes = __esm({
4534
4567
  import { Hono as Hono5 } from "hono";
4535
4568
  import { cors } from "hono/cors";
4536
4569
  import { join as join14, basename as basename3, resolve as resolve4 } from "node:path";
4537
- import { readdirSync as readdirSync6, existsSync as existsSync13 } from "node:fs";
4570
+ import { readdirSync as readdirSync7, existsSync as existsSync13 } from "node:fs";
4538
4571
  function createForgeServer(options) {
4539
4572
  const { dataDir, db: externalDb, authToken, localOnly = true } = options;
4540
4573
  const dbPath = join14(dataDir, "forge.db");
@@ -4551,7 +4584,7 @@ function createForgeServer(options) {
4551
4584
  const cwReader = new CWReader(options.cwDir);
4552
4585
  const loginManager = new LoginManager(resolveCwBin(cwReader.cwHome));
4553
4586
  app.route("/api/cw", cwRoutes(cwReader, { loginManager, localOnly }));
4554
- app.route("/api/skills", skillRoutes(cwReader));
4587
+ app.route("/api/skills", skillRoutes(cwReader, { backupDir: join14(dataDir, "skill-backups") }));
4555
4588
  const { remote, shadow, warning } = remoteClassifierFromEnv();
4556
4589
  if (warning)
4557
4590
  console.warn(`[state] ${warning}`);
@@ -4739,7 +4772,7 @@ data: ${JSON.stringify(data)}
4739
4772
  return c.json({ error: "Directory not found" }, 404);
4740
4773
  }
4741
4774
  try {
4742
- const entries = readdirSync6(dir, { withFileTypes: true });
4775
+ const entries = readdirSync7(dir, { withFileTypes: true });
4743
4776
  const dirs = entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => ({
4744
4777
  name: e.name,
4745
4778
  path: join14(dir, e.name),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "forge-cw",
3
- "version": "0.1.11",
3
+ "version": "0.1.12",
4
4
  "description": "A visual dashboard for your coding agents on Claude Code, Codex, Pi or OpenCode. Installs CW for you.",
5
5
  "license": "MIT",
6
6
  "type": "module",