studioflow 0.1.4 → 0.1.5

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
@@ -5898,11 +5898,15 @@ import fs11 from "node:fs/promises";
5898
5898
  import path12 from "node:path";
5899
5899
 
5900
5900
  // src/commands/install-skills.ts
5901
+ import { createHash } from "node:crypto";
5901
5902
  import fs10 from "node:fs/promises";
5902
5903
  import path11 from "node:path";
5903
5904
  import { fileURLToPath as fileURLToPath2 } from "node:url";
5904
5905
  var __dirname2 = path11.dirname(fileURLToPath2(import.meta.url));
5905
5906
  var bundledSkillNames = ["studioflow-cli", "studioflow-investigate"];
5907
+ var skillManifestFile = "manifest.json";
5908
+ var skillMetadataFile = ".studioflow-skill.json";
5909
+ var packageName = "studioflow";
5906
5910
  async function pathExists(target) {
5907
5911
  try {
5908
5912
  await fs10.access(target);
@@ -5911,6 +5915,116 @@ async function pathExists(target) {
5911
5915
  return false;
5912
5916
  }
5913
5917
  }
5918
+ function toPosixPath(value) {
5919
+ return value.split(path11.sep).join("/");
5920
+ }
5921
+ async function listFilesRecursively(rootDir2) {
5922
+ const entries = await fs10.readdir(rootDir2, { withFileTypes: true });
5923
+ const files = [];
5924
+ for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
5925
+ const fullPath = path11.join(rootDir2, entry.name);
5926
+ if (entry.isDirectory()) {
5927
+ files.push(...await listFilesRecursively(fullPath));
5928
+ continue;
5929
+ }
5930
+ if (entry.isFile()) {
5931
+ files.push(fullPath);
5932
+ }
5933
+ }
5934
+ return files;
5935
+ }
5936
+ async function hashDirectoryContents(rootDir2) {
5937
+ const hasher = createHash("sha256");
5938
+ const files = await listFilesRecursively(rootDir2);
5939
+ for (const filePath of files) {
5940
+ const relativePath = toPosixPath(path11.relative(rootDir2, filePath));
5941
+ hasher.update(relativePath);
5942
+ hasher.update("\n");
5943
+ hasher.update(await fs10.readFile(filePath));
5944
+ hasher.update("\n");
5945
+ }
5946
+ return hasher.digest("hex");
5947
+ }
5948
+ async function resolveCliPackageVersion(sourceDir) {
5949
+ const candidates = [
5950
+ path11.resolve(sourceDir, "../package.json"),
5951
+ path11.resolve(__dirname2, "../../package.json"),
5952
+ path11.resolve(process.cwd(), "apps/cli/package.json")
5953
+ ];
5954
+ for (const candidate of candidates) {
5955
+ try {
5956
+ const raw = await fs10.readFile(candidate, "utf8");
5957
+ const parsed = JSON.parse(raw);
5958
+ if (parsed.name === packageName && typeof parsed.version === "string") {
5959
+ return parsed.version;
5960
+ }
5961
+ } catch {
5962
+ }
5963
+ }
5964
+ return "0.0.0";
5965
+ }
5966
+ function isValidManifest(value) {
5967
+ if (!value || typeof value !== "object") return false;
5968
+ const manifest = value;
5969
+ if (manifest.schemaVersion !== 1 || manifest.packageName !== packageName || typeof manifest.packageVersion !== "string" || !manifest.skills || typeof manifest.skills !== "object") {
5970
+ return false;
5971
+ }
5972
+ return bundledSkillNames.every((name) => {
5973
+ const entry = manifest.skills[name];
5974
+ return Boolean(entry && typeof entry.hash === "string" && entry.hash.length > 0);
5975
+ });
5976
+ }
5977
+ async function resolveBundledSkillsManifest(sourceDir) {
5978
+ const manifestPath = path11.join(sourceDir, skillManifestFile);
5979
+ if (await pathExists(manifestPath)) {
5980
+ try {
5981
+ const raw = await fs10.readFile(manifestPath, "utf8");
5982
+ const parsed = JSON.parse(raw);
5983
+ if (isValidManifest(parsed)) {
5984
+ return parsed;
5985
+ }
5986
+ } catch {
5987
+ }
5988
+ }
5989
+ const skills = {};
5990
+ for (const skillName of bundledSkillNames) {
5991
+ const skillPath = path11.join(sourceDir, skillName);
5992
+ skills[skillName] = {
5993
+ hash: await hashDirectoryContents(skillPath)
5994
+ };
5995
+ }
5996
+ return {
5997
+ schemaVersion: 1,
5998
+ packageName,
5999
+ packageVersion: await resolveCliPackageVersion(sourceDir),
6000
+ skills
6001
+ };
6002
+ }
6003
+ async function readInstalledSkillMetadata(skillDir) {
6004
+ const metadataPath = path11.join(skillDir, skillMetadataFile);
6005
+ if (!await pathExists(metadataPath)) {
6006
+ return null;
6007
+ }
6008
+ try {
6009
+ const raw = await fs10.readFile(metadataPath, "utf8");
6010
+ const parsed = JSON.parse(raw);
6011
+ if (typeof parsed.packageName === "string" && typeof parsed.cliVersion === "string" && typeof parsed.skillHash === "string" && typeof parsed.installedAt === "string") {
6012
+ return parsed;
6013
+ }
6014
+ } catch {
6015
+ }
6016
+ return null;
6017
+ }
6018
+ async function writeInstalledSkillMetadata(skillDir, expectedVersion, expectedHash) {
6019
+ const metadata = {
6020
+ packageName,
6021
+ cliVersion: expectedVersion,
6022
+ skillHash: expectedHash,
6023
+ installedAt: (/* @__PURE__ */ new Date()).toISOString()
6024
+ };
6025
+ await fs10.writeFile(path11.join(skillDir, skillMetadataFile), `${JSON.stringify(metadata, null, 2)}
6026
+ `);
6027
+ }
5914
6028
  async function resolveBundledSkillsDir() {
5915
6029
  const candidates = [
5916
6030
  process.env.STUDIOFLOW_SKILLS_SOURCE,
@@ -5934,28 +6048,48 @@ async function resolveBundledSkillsDir() {
5934
6048
  `Could not locate bundled skills. Checked: ${candidates.join(", ")}. Set STUDIOFLOW_SKILLS_SOURCE if needed.`
5935
6049
  );
5936
6050
  }
5937
- async function syncSkillsToTarget(sourceDir, targetDir, force) {
6051
+ async function syncSkillsToTarget(sourceDir, targetDir, force, manifest) {
5938
6052
  await fs10.mkdir(targetDir, { recursive: true });
5939
6053
  const installed = [];
6054
+ const updated = [];
5940
6055
  const skipped = [];
5941
6056
  for (const skillName of bundledSkillNames) {
5942
6057
  const source = path11.join(sourceDir, skillName);
5943
6058
  const target = path11.join(targetDir, skillName);
5944
6059
  const exists = await pathExists(target);
5945
- if (exists && !force) {
5946
- skipped.push(skillName);
6060
+ const expectedHash = manifest.skills[skillName]?.hash;
6061
+ if (!expectedHash) {
6062
+ throw new Error(`Missing skill hash in manifest for ${skillName}.`);
6063
+ }
6064
+ if (!exists) {
6065
+ await fs10.cp(source, target, { recursive: true });
6066
+ await writeInstalledSkillMetadata(target, manifest.packageVersion, expectedHash);
6067
+ installed.push(skillName);
5947
6068
  continue;
5948
6069
  }
5949
- if (exists && force) {
6070
+ if (force) {
5950
6071
  await fs10.rm(target, { recursive: true, force: true });
6072
+ await fs10.cp(source, target, { recursive: true });
6073
+ await writeInstalledSkillMetadata(target, manifest.packageVersion, expectedHash);
6074
+ installed.push(skillName);
6075
+ continue;
6076
+ }
6077
+ const currentMetadata = await readInstalledSkillMetadata(target);
6078
+ const matchesInstalledVersion = currentMetadata?.packageName === manifest.packageName && currentMetadata.cliVersion === manifest.packageVersion && currentMetadata.skillHash === expectedHash;
6079
+ if (matchesInstalledVersion) {
6080
+ skipped.push(skillName);
6081
+ continue;
5951
6082
  }
6083
+ await fs10.rm(target, { recursive: true, force: true });
5952
6084
  await fs10.cp(source, target, { recursive: true });
5953
- installed.push(skillName);
6085
+ await writeInstalledSkillMetadata(target, manifest.packageVersion, expectedHash);
6086
+ updated.push(skillName);
5954
6087
  }
5955
- return { installed, skipped };
6088
+ return { installed, updated, skipped };
5956
6089
  }
5957
6090
  async function installBundledSkills(opts = {}) {
5958
6091
  const sourceDir = await resolveBundledSkillsDir();
6092
+ const manifest = await resolveBundledSkillsManifest(sourceDir);
5959
6093
  const force = opts.force ?? false;
5960
6094
  const targets = [];
5961
6095
  if (opts.targetDir && (opts.agent || opts.codexTargetDir || opts.claudeTargetDir)) {
@@ -5980,11 +6114,12 @@ async function installBundledSkills(opts = {}) {
5980
6114
  }
5981
6115
  const results = [];
5982
6116
  for (const target of targets) {
5983
- const synced = await syncSkillsToTarget(sourceDir, target.targetDir, force);
6117
+ const synced = await syncSkillsToTarget(sourceDir, target.targetDir, force, manifest);
5984
6118
  results.push({
5985
6119
  targetId: target.targetId,
5986
6120
  targetDir: target.targetDir,
5987
6121
  installed: synced.installed,
6122
+ updated: synced.updated,
5988
6123
  skipped: synced.skipped
5989
6124
  });
5990
6125
  }
@@ -6001,8 +6136,11 @@ async function installSkillsCommand(opts = {}) {
6001
6136
  if (target.installed.length > 0) {
6002
6137
  console.log(`- Installed (${label}): ${target.installed.join(", ")}`);
6003
6138
  }
6139
+ if (target.updated.length > 0) {
6140
+ console.log(`- Updated (${label}): ${target.updated.join(", ")}`);
6141
+ }
6004
6142
  if (target.skipped.length > 0) {
6005
- console.log(kleur_default.yellow(`- Skipped (${label}, already present): ${target.skipped.join(", ")}`));
6143
+ console.log(kleur_default.yellow(`- Skipped (${label}, up to date): ${target.skipped.join(", ")}`));
6006
6144
  }
6007
6145
  }
6008
6146
  if (result.targets.some((target) => target.skipped.length > 0)) {
@@ -6049,8 +6187,11 @@ async function setupCommand(opts = {}) {
6049
6187
  if (target.installed.length > 0) {
6050
6188
  console.log(`- Installed (${label}): ${target.installed.join(", ")}`);
6051
6189
  }
6190
+ if (target.updated.length > 0) {
6191
+ console.log(`- Updated (${label}): ${target.updated.join(", ")}`);
6192
+ }
6052
6193
  if (target.skipped.length > 0) {
6053
- console.log(kleur_default.yellow(`- Skipped (${label}, already present): ${target.skipped.join(", ")}`));
6194
+ console.log(kleur_default.yellow(`- Skipped (${label}, up to date): ${target.skipped.join(", ")}`));
6054
6195
  }
6055
6196
  }
6056
6197
  }
@@ -6081,6 +6222,7 @@ async function setupCommand(opts = {}) {
6081
6222
  targetId: target.targetId,
6082
6223
  targetDir: target.targetDir,
6083
6224
  installed: target.installed,
6225
+ updated: target.updated,
6084
6226
  skipped: target.skipped
6085
6227
  }))
6086
6228
  } : { skipped: true }