@officexapp/vidfarm-devcli 0.21.54 → 0.21.56

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.
@@ -7,7 +7,7 @@
7
7
  // else prints as ⚠/✗ but does not fail the command, so agents can run doctor
8
8
  // unconditionally at session start.
9
9
  import { spawnSync } from "node:child_process";
10
- import { existsSync, readdirSync } from "node:fs";
10
+ import { existsSync, readdirSync, readFileSync } from "node:fs";
11
11
  import { createRequire } from "node:module";
12
12
  import net from "node:net";
13
13
  import { homedir } from "node:os";
@@ -143,6 +143,28 @@ export async function runDoctorCommand(argv) {
143
143
  const killOrphans = Boolean(parsed.values["kill-orphans"]);
144
144
  const checks = [];
145
145
  const add = (name, level, detail) => checks.push({ name, level, detail });
146
+ // 0. Are the two halves current, and are they the SAME version? The skill and
147
+ // the devcli ship on one semver, and a gap between them is the usual cause of
148
+ // "that command doesn't exist" — so it belongs in the health check, not only
149
+ // in the update runbook. Never fails the doctor: offline is not a defect, and
150
+ // a pending update is information, not breakage.
151
+ try {
152
+ const { installedDevcliVersion, installedSkillPath, readSkillVersion, compareSemver, readUpdateState, hoursSinceLastCheck } = await import("./update-check.js");
153
+ const devcli = installedDevcliVersion();
154
+ const skillPath = installedSkillPath();
155
+ const skill = skillPath ? readSkillVersion(readFileSync(skillPath, "utf8")) : null;
156
+ const since = hoursSinceLastCheck(readUpdateState());
157
+ const staleNote = since === null ? " · never checked for updates" : since >= 24 ? ` · last update check ${Math.round(since / 24)}d ago` : "";
158
+ if (devcli && skill && compareSemver(devcli, skill) !== 0) {
159
+ add("versions", "warn", `devcli ${devcli} vs skill ${skill} — MISMATCHED. They ship together; run: vidfarm update-check`);
160
+ }
161
+ else {
162
+ add("versions", since !== null && since < 24 ? "ok" : "warn", `devcli ${devcli ?? "?"} · skill ${skill ?? "not installed"}${staleNote}${staleNote ? " — vidfarm update-check" : ""}`);
163
+ }
164
+ }
165
+ catch {
166
+ // A version probe must never take the doctor down with it.
167
+ }
146
168
  // 1. Node version.
147
169
  const nodeMajor = Number(process.versions.node.split(".")[0]);
148
170
  add("node", nodeMajor >= 22 ? "ok" : "fail", `v${process.versions.node}${nodeMajor >= 22 ? "" : " — vidfarm-devcli needs Node >= 22"}`);
@@ -59,10 +59,10 @@ const DISCOVERY_NAMES = ["HARNESS.md", "harness.md", "QA_REGIME.md", "qa_regime.
59
59
  // They ship inside the skill pack (.agents/skills/vidfarm/harnesses/) rather
60
60
  // than as TS string constants, so a director can read, diff, and copy them as
61
61
  // normal files — the file IS the documentation.
62
- function builtinDir() {
62
+ function packDir(...segments) {
63
63
  let dir = path.dirname(fileURLToPath(import.meta.url));
64
64
  for (let i = 0; i < 6; i += 1) {
65
- const candidate = path.join(dir, ".agents", "skills", "vidfarm", "harnesses");
65
+ const candidate = path.join(dir, ...segments);
66
66
  if (existsSync(candidate))
67
67
  return candidate;
68
68
  const parent = path.dirname(dir);
@@ -72,32 +72,77 @@ function builtinDir() {
72
72
  }
73
73
  return null;
74
74
  }
75
- export function listBuiltinHarnesses() {
76
- const dir = builtinDir();
75
+ function builtinDir() {
76
+ return packDir(".agents", "skills", "vidfarm", "harnesses");
77
+ }
78
+ // The EXPERIMENTAL harnesses — full format contracts under live testing, served
79
+ // at https://vidfarm.cc/experimental/<slug>.md. They ship inside the npm package
80
+ // too (`experimental/**/*.md` in package.json `files`), so an agent that already
81
+ // has the CLI reaches them by NAME, offline, with no fetch: `vidfarm harness show
82
+ // meme-recaption`, `vidfarm qa ./work --harness wall-text-pov-ugc`. The web index
83
+ // stays the source of truth for what exists; this is the same shelf, local.
84
+ function experimentalDir() {
85
+ return packDir("experimental");
86
+ }
87
+ function readEntry(full, name, origin) {
88
+ const parsed = parseHarness(readFileSync(full, "utf8"), full);
89
+ return {
90
+ name,
91
+ path: full,
92
+ video_type: parsed.video_type,
93
+ summary: parsed.summary,
94
+ origin,
95
+ ...(origin === "experimental" ? { url: `https://vidfarm.cc/experimental/${name}.md` } : {})
96
+ };
97
+ }
98
+ export function listExperimentalHarnesses() {
99
+ const dir = experimentalDir();
77
100
  if (!dir)
78
101
  return [];
79
102
  return readdirSync(dir)
80
- .filter((file) => file.endsWith(BUILTIN_SUFFIX))
103
+ .filter((file) => file.endsWith(".md"))
81
104
  .sort()
82
- .map((file) => {
83
- const full = path.join(dir, file);
84
- const parsed = parseHarness(readFileSync(full, "utf8"), full);
85
- return { name: file.slice(0, -BUILTIN_SUFFIX.length), path: full, video_type: parsed.video_type, summary: parsed.summary };
86
- });
105
+ .map((file) => readEntry(path.join(dir, file), file.slice(0, -".md".length), "experimental"));
87
106
  }
88
- /** Resolve `hooks` (built-in) or `./my/HARNESS.md` (a path) to a file. */
107
+ /** Both shelves, built-ins first. `vidfarm harness list` prints exactly this. */
108
+ export function listBuiltinHarnesses() {
109
+ const dir = builtinDir();
110
+ const builtins = !dir
111
+ ? []
112
+ : readdirSync(dir)
113
+ .filter((file) => file.endsWith(BUILTIN_SUFFIX))
114
+ .sort()
115
+ .map((file) => readEntry(path.join(dir, file), file.slice(0, -BUILTIN_SUFFIX.length), "builtin"));
116
+ return [...builtins, ...listExperimentalHarnesses()];
117
+ }
118
+ /**
119
+ * Resolve `hooks` (built-in), `meme-recaption` (experimental), or
120
+ * `./my/HARNESS.md` (a path) to a file. Underscore spellings resolve too, so the
121
+ * URL slug and the CLI name never disagree — the same near-miss the web route
122
+ * forgives with its aliases.
123
+ */
89
124
  export function resolveHarnessPath(ref) {
90
125
  const direct = path.resolve(ref);
91
126
  if (existsSync(direct) && !direct.endsWith(path.sep))
92
127
  return direct;
93
- const dir = builtinDir();
94
- if (dir) {
95
- const candidate = path.join(dir, `${ref}${BUILTIN_SUFFIX}`);
128
+ const slug = ref.trim().toLowerCase().replace(/\.md$/, "").replace(/_/g, "-");
129
+ const builtins = builtinDir();
130
+ if (builtins) {
131
+ for (const name of [ref, slug]) {
132
+ const candidate = path.join(builtins, `${name}${BUILTIN_SUFFIX}`);
133
+ if (existsSync(candidate))
134
+ return candidate;
135
+ }
136
+ }
137
+ const experimental = experimentalDir();
138
+ if (experimental) {
139
+ const candidate = path.join(experimental, `${slug}.md`);
96
140
  if (existsSync(candidate))
97
141
  return candidate;
98
142
  }
99
143
  const names = listBuiltinHarnesses().map((entry) => entry.name);
100
- throw new Error(`No harness "${ref}". Pass a file path, or one of the built-ins: ${names.join(", ") || "(none bundled)"}. ` +
144
+ throw new Error(`No harness "${ref}". Pass a file path, or one of the bundled names: ${names.join(", ") || "(none bundled)"}. ` +
145
+ `The live index is https://vidfarm.cc/experimental. ` +
101
146
  `Scaffold your own with \`vidfarm harness init <name> --out ./work/HARNESS.md\`, ` +
102
147
  `or derive one from a decomposed template with \`vidfarm harness derive <forkId>\`.`);
103
148
  }