@senpi/runtime 1.1.0-dev.runtime-phase-2.20260512102629 → 1.1.0-dev.runtime-phase-2.20260514134437

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 (57) hide show
  1. package/dist/cli/senpi-cli-config.d.ts +15 -1
  2. package/dist/cli/senpi-cli-config.d.ts.map +1 -1
  3. package/dist/cli/senpi-cli-config.js +19 -2
  4. package/dist/cli/senpi-cli-config.js.map +1 -1
  5. package/dist/cli/senpi-commands.d.ts.map +1 -1
  6. package/dist/cli/senpi-commands.js +96 -0
  7. package/dist/cli/senpi-commands.js.map +1 -1
  8. package/dist/index.d.ts +21 -0
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +84 -1
  11. package/dist/index.js.map +1 -1
  12. package/dist/runtime/auto-update/coordinator.d.ts +13 -0
  13. package/dist/runtime/auto-update/coordinator.d.ts.map +1 -1
  14. package/dist/runtime/auto-update/coordinator.js +14 -0
  15. package/dist/runtime/auto-update/coordinator.js.map +1 -1
  16. package/dist/skills-manager/bootstrap.d.ts +26 -0
  17. package/dist/skills-manager/bootstrap.d.ts.map +1 -0
  18. package/dist/skills-manager/bootstrap.js +86 -0
  19. package/dist/skills-manager/bootstrap.js.map +1 -0
  20. package/dist/skills-manager/constants.d.ts +31 -0
  21. package/dist/skills-manager/constants.d.ts.map +1 -0
  22. package/dist/skills-manager/constants.js +40 -0
  23. package/dist/skills-manager/constants.js.map +1 -0
  24. package/dist/skills-manager/coordinator.d.ts +336 -0
  25. package/dist/skills-manager/coordinator.d.ts.map +1 -0
  26. package/dist/skills-manager/coordinator.js +931 -0
  27. package/dist/skills-manager/coordinator.js.map +1 -0
  28. package/dist/skills-manager/fetcher.d.ts +28 -0
  29. package/dist/skills-manager/fetcher.d.ts.map +1 -0
  30. package/dist/skills-manager/fetcher.js +92 -0
  31. package/dist/skills-manager/fetcher.js.map +1 -0
  32. package/dist/skills-manager/index.d.ts +5 -0
  33. package/dist/skills-manager/index.d.ts.map +1 -0
  34. package/dist/skills-manager/index.js +4 -0
  35. package/dist/skills-manager/index.js.map +1 -0
  36. package/dist/skills-manager/state-store.d.ts +57 -0
  37. package/dist/skills-manager/state-store.d.ts.map +1 -0
  38. package/dist/skills-manager/state-store.js +129 -0
  39. package/dist/skills-manager/state-store.js.map +1 -0
  40. package/dist/skills-manager/types.d.ts +165 -0
  41. package/dist/skills-manager/types.d.ts.map +1 -0
  42. package/dist/skills-manager/types.js +2 -0
  43. package/dist/skills-manager/types.js.map +1 -0
  44. package/dist/skills-manager/utils.d.ts +63 -0
  45. package/dist/skills-manager/utils.d.ts.map +1 -0
  46. package/dist/skills-manager/utils.js +119 -0
  47. package/dist/skills-manager/utils.js.map +1 -0
  48. package/dist/skills-manager/version-manager.d.ts +146 -0
  49. package/dist/skills-manager/version-manager.d.ts.map +1 -0
  50. package/dist/skills-manager/version-manager.js +299 -0
  51. package/dist/skills-manager/version-manager.js.map +1 -0
  52. package/dist/skills-manager/writer.d.ts +28 -0
  53. package/dist/skills-manager/writer.d.ts.map +1 -0
  54. package/dist/skills-manager/writer.js +101 -0
  55. package/dist/skills-manager/writer.js.map +1 -0
  56. package/openclaw.plugin.json +27 -0
  57. package/package.json +3 -1
@@ -0,0 +1,63 @@
1
+ import type { RawSkillsManagerConfig, SkillsManagerConfig, SkillsManagerState, SkillState } from "./types.js";
2
+ /**
3
+ * Canonical default list of managed skill names when the user omits the
4
+ * `skills` field. Kept in sync with the JSON Schema default in
5
+ * `openclaw.plugin.json` (configSchema.properties.skillsManager.default.skills).
6
+ *
7
+ * Semantics for the resolved `skills` array:
8
+ * - Field omitted entirely → defaults to this list. New installs and partial
9
+ * configs like `{ enabled: true }` get the canonical skill bootstrapped
10
+ * automatically without requiring users to repeat the array in JSON.
11
+ * - Field present and non-empty → use the provided names (filtered for
12
+ * non-empty strings).
13
+ * - Field present and explicitly empty (`"skills": []`) → discovery mode:
14
+ * scan subdirs under `skillsDir`.
15
+ */
16
+ export declare const DEFAULT_MANAGED_SKILLS: readonly string[];
17
+ /**
18
+ * Parses and normalises the raw `skillsManager` config block from the plugin
19
+ * config into a fully-resolved {@link SkillsManagerConfig}.
20
+ *
21
+ * Any missing or null fields fall back to safe defaults so callers never have
22
+ * to guard against undefined values.
23
+ *
24
+ * @param value - The optional raw config block from the plugin config.
25
+ * @returns A fully-populated {@link SkillsManagerConfig} with all defaults applied.
26
+ */
27
+ export declare function resolveSkillsManagerConfig(value?: RawSkillsManagerConfig | null): SkillsManagerConfig;
28
+ /**
29
+ * Creates a blank {@link SkillsManagerState} with no tracked skills.
30
+ * Used as the merge base when loading persisted state, and as the fallback
31
+ * when state is corrupt.
32
+ *
33
+ * @returns An empty {@link SkillsManagerState}.
34
+ */
35
+ export declare function createDefaultSkillsManagerState(): SkillsManagerState;
36
+ /**
37
+ * Creates a zeroed-out {@link SkillState} for a skill that has not yet been
38
+ * tracked. All version fields are null and the failure counter starts at zero.
39
+ *
40
+ * @param name - The skill name (directory name under `skillsDir`).
41
+ * @returns A fresh {@link SkillState} for the given skill.
42
+ */
43
+ export declare function createDefaultSkillState(name: string): SkillState;
44
+ /** Formats an unknown thrown value as a short string for log messages. */
45
+ export declare function formatErr(e: unknown): string;
46
+ /**
47
+ * Normalise the raw `skillsDir` value into a non-empty `string[]` (the
48
+ * "search path" form used internally).
49
+ *
50
+ * Resolution rules:
51
+ * - Single string → wrap as `[value]` (legacy single-dir form)
52
+ * - Array → filter out non-strings and empty/whitespace entries; if anything
53
+ * survives, use the filtered array
54
+ * - Anything else (undefined, null, empty array post-filter, wrong type) →
55
+ * fall back to `DEFAULT_SKILLS_DIRS` (which itself reads `SENPI_SKILLS_DIR`
56
+ * as a comma-separated list, or the canonical install path as the final
57
+ * fallback)
58
+ *
59
+ * Always returns a non-empty array — callers can rely on `[0]` existing as
60
+ * the *primary* (writes go here; reads scan in order).
61
+ */
62
+ export declare function normalizeSkillsDir(value: unknown): string[];
63
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/skills-manager/utils.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,sBAAsB,EACtB,mBAAmB,EACnB,kBAAkB,EAClB,UAAU,EACX,MAAM,YAAY,CAAC;AAEpB;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,sBAAsB,EAAE,SAAS,MAAM,EAA8B,CAAC;AAEnF;;;;;;;;;GASG;AACH,wBAAgB,0BAA0B,CACxC,KAAK,CAAC,EAAE,sBAAsB,GAAG,IAAI,GACpC,mBAAmB,CAcrB;AAED;;;;;;GAMG;AACH,wBAAgB,+BAA+B,IAAI,kBAAkB,CAEpE;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,CAWhE;AAED,0EAA0E;AAC1E,wBAAgB,SAAS,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,CAE5C;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,EAAE,CAc3D"}
@@ -0,0 +1,119 @@
1
+ import { DEFAULT_SKILLS_DIRS } from "./constants.js";
2
+ /**
3
+ * Canonical default list of managed skill names when the user omits the
4
+ * `skills` field. Kept in sync with the JSON Schema default in
5
+ * `openclaw.plugin.json` (configSchema.properties.skillsManager.default.skills).
6
+ *
7
+ * Semantics for the resolved `skills` array:
8
+ * - Field omitted entirely → defaults to this list. New installs and partial
9
+ * configs like `{ enabled: true }` get the canonical skill bootstrapped
10
+ * automatically without requiring users to repeat the array in JSON.
11
+ * - Field present and non-empty → use the provided names (filtered for
12
+ * non-empty strings).
13
+ * - Field present and explicitly empty (`"skills": []`) → discovery mode:
14
+ * scan subdirs under `skillsDir`.
15
+ */
16
+ export const DEFAULT_MANAGED_SKILLS = ["senpi-trading-runtime"];
17
+ /**
18
+ * Parses and normalises the raw `skillsManager` config block from the plugin
19
+ * config into a fully-resolved {@link SkillsManagerConfig}.
20
+ *
21
+ * Any missing or null fields fall back to safe defaults so callers never have
22
+ * to guard against undefined values.
23
+ *
24
+ * @param value - The optional raw config block from the plugin config.
25
+ * @returns A fully-populated {@link SkillsManagerConfig} with all defaults applied.
26
+ */
27
+ export function resolveSkillsManagerConfig(value) {
28
+ const raw = value ?? {};
29
+ return {
30
+ enabled: toBool(raw.enabled, true),
31
+ skillsDir: normalizeSkillsDir(raw.skillsDir),
32
+ // Field-omitted vs explicit-empty distinction matters here:
33
+ // - undefined / null / non-array → use canonical default (matches the
34
+ // JSON Schema default in openclaw.plugin.json — keeps wizard-written
35
+ // partial configs working)
36
+ // - explicit [] → preserved as discovery mode (caller asked for it)
37
+ skills: Array.isArray(raw.skills)
38
+ ? raw.skills.filter((s) => typeof s === "string" && s.length > 0)
39
+ : [...DEFAULT_MANAGED_SKILLS],
40
+ };
41
+ }
42
+ /**
43
+ * Creates a blank {@link SkillsManagerState} with no tracked skills.
44
+ * Used as the merge base when loading persisted state, and as the fallback
45
+ * when state is corrupt.
46
+ *
47
+ * @returns An empty {@link SkillsManagerState}.
48
+ */
49
+ export function createDefaultSkillsManagerState() {
50
+ return { skills: {} };
51
+ }
52
+ /**
53
+ * Creates a zeroed-out {@link SkillState} for a skill that has not yet been
54
+ * tracked. All version fields are null and the failure counter starts at zero.
55
+ *
56
+ * @param name - The skill name (directory name under `skillsDir`).
57
+ * @returns A fresh {@link SkillState} for the given skill.
58
+ */
59
+ export function createDefaultSkillState(name) {
60
+ return {
61
+ name,
62
+ installedVersion: null,
63
+ lastCheckedAt: null,
64
+ lastAppliedVersion: null,
65
+ lastNotifiedMajorVersion: null,
66
+ consecutiveFailures: 0,
67
+ lastFailureAt: null,
68
+ lastFailureReason: null,
69
+ };
70
+ }
71
+ /** Formats an unknown thrown value as a short string for log messages. */
72
+ export function formatErr(e) {
73
+ return e instanceof Error ? e.message : String(e);
74
+ }
75
+ /**
76
+ * Normalise the raw `skillsDir` value into a non-empty `string[]` (the
77
+ * "search path" form used internally).
78
+ *
79
+ * Resolution rules:
80
+ * - Single string → wrap as `[value]` (legacy single-dir form)
81
+ * - Array → filter out non-strings and empty/whitespace entries; if anything
82
+ * survives, use the filtered array
83
+ * - Anything else (undefined, null, empty array post-filter, wrong type) →
84
+ * fall back to `DEFAULT_SKILLS_DIRS` (which itself reads `SENPI_SKILLS_DIR`
85
+ * as a comma-separated list, or the canonical install path as the final
86
+ * fallback)
87
+ *
88
+ * Always returns a non-empty array — callers can rely on `[0]` existing as
89
+ * the *primary* (writes go here; reads scan in order).
90
+ */
91
+ export function normalizeSkillsDir(value) {
92
+ if (typeof value === "string") {
93
+ const trimmed = value.trim();
94
+ if (trimmed.length > 0)
95
+ return [trimmed];
96
+ }
97
+ else if (Array.isArray(value)) {
98
+ const filtered = value
99
+ .filter((v) => typeof v === "string")
100
+ .map((s) => s.trim())
101
+ .filter((s) => s.length > 0);
102
+ if (filtered.length > 0)
103
+ return filtered;
104
+ }
105
+ // DEFAULT_SKILLS_DIRS is a readonly tuple at module level — return a fresh
106
+ // mutable copy so callers that mutate (e.g. tests) don't poison the global.
107
+ return [...DEFAULT_SKILLS_DIRS];
108
+ }
109
+ // ─── Local config-coercion helpers ──────────────────────────────────────────
110
+ // `toBool` is intentionally duplicated from `src/runtime/auto-update/types.ts`
111
+ // to keep `src/skills-manager/` free of any compile- or runtime-time
112
+ // dependency on the auto-update package. An earlier attempt to consolidate
113
+ // this style of helper across the two modules (commit 48592db, "consolidate
114
+ // formatErr into policy.ts") was reverted in 5a94af8 with the explicit rule
115
+ // "do not touch auto-update module" — same rationale applies here.
116
+ function toBool(value, fallback) {
117
+ return typeof value === "boolean" ? value : fallback;
118
+ }
119
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/skills-manager/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAQrD;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAsB,CAAC,uBAAuB,CAAC,CAAC;AAEnF;;;;;;;;;GASG;AACH,MAAM,UAAU,0BAA0B,CACxC,KAAqC;IAErC,MAAM,GAAG,GAAG,KAAK,IAAI,EAAE,CAAC;IACxB,OAAO;QACL,OAAO,EAAE,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC;QAClC,SAAS,EAAE,kBAAkB,CAAC,GAAG,CAAC,SAAS,CAAC;QAC5C,4DAA4D;QAC5D,sEAAsE;QACtE,uEAAuE;QACvE,6BAA6B;QAC7B,oEAAoE;QACpE,MAAM,EAAE,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC;YAC/B,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;YACjE,CAAC,CAAC,CAAC,GAAG,sBAAsB,CAAC;KAChC,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,+BAA+B;IAC7C,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;AACxB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,uBAAuB,CAAC,IAAY;IAClD,OAAO;QACL,IAAI;QACJ,gBAAgB,EAAE,IAAI;QACtB,aAAa,EAAE,IAAI;QACnB,kBAAkB,EAAE,IAAI;QACxB,wBAAwB,EAAE,IAAI;QAC9B,mBAAmB,EAAE,CAAC;QACtB,aAAa,EAAE,IAAI;QACnB,iBAAiB,EAAE,IAAI;KACxB,CAAC;AACJ,CAAC;AAED,0EAA0E;AAC1E,MAAM,UAAU,SAAS,CAAC,CAAU;IAClC,OAAO,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;AACpD,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,kBAAkB,CAAC,KAAc;IAC/C,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;QAC7B,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC;YAAE,OAAO,CAAC,OAAO,CAAC,CAAC;IAC3C,CAAC;SAAM,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QAChC,MAAM,QAAQ,GAAG,KAAK;aACnB,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC;aACjD,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;aACpB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QAC/B,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC;YAAE,OAAO,QAAQ,CAAC;IAC3C,CAAC;IACD,2EAA2E;IAC3E,4EAA4E;IAC5E,OAAO,CAAC,GAAG,mBAAmB,CAAC,CAAC;AAClC,CAAC;AAED,+EAA+E;AAC/E,+EAA+E;AAC/E,qEAAqE;AACrE,2EAA2E;AAC3E,4EAA4E;AAC5E,4EAA4E;AAC5E,mEAAmE;AACnE,SAAS,MAAM,CAAC,KAAc,EAAE,QAAiB;IAC/C,OAAO,OAAO,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC;AACvD,CAAC"}
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Version detection for Senpi skills.
3
+ *
4
+ * Reads the installed SKILL.md version from disk and the remote version from
5
+ * raw.githubusercontent.com, then uses the existing semver helpers from
6
+ * src/runtime/auto-update/policy.ts to classify the delta.
7
+ */
8
+ import type { LoggerLike, SkillVersionInfo } from "./types.js";
9
+ /**
10
+ * Normalise a version string before passing it to parseSemver / classifyUpdate.
11
+ *
12
+ * Accepts two-part versions used in production skills (e.g. "1.0", "v2.3")
13
+ * and expands them to the full three-part form ("1.0.0", "v2.3.0") that
14
+ * parseSemver requires. All other strings are returned unchanged.
15
+ */
16
+ export declare function normalizeVersion(v: string): string;
17
+ /**
18
+ * Extract the `version` value from a SKILL.md YAML frontmatter block.
19
+ *
20
+ * Handles both nested form:
21
+ * metadata:
22
+ * version: 1.2.3
23
+ * and flat form:
24
+ * version: "1.2.3"
25
+ *
26
+ * Returns null when the frontmatter block is absent or the field is missing.
27
+ */
28
+ export declare function parseSkillMdVersion(content: string): string | null;
29
+ /**
30
+ * Extract the `description` value from a SKILL.md YAML frontmatter block.
31
+ *
32
+ * Skill descriptions can be authored a few different ways. This helper
33
+ * handles the real-world shapes that appear in the senpi-skills repo today:
34
+ *
35
+ * 1. Single-line, double-quoted:
36
+ * description: "Configure, deploy, ..."
37
+ * 2. Single-line, single-quoted or unquoted:
38
+ * description: Manage runtimes ...
39
+ * 3. YAML block scalar (folded or literal, optional chomping indicator):
40
+ * description: >
41
+ * A short summary
42
+ * continues on this line.
43
+ * description: >- # strip trailing newlines (this PR's senpi-skills repo)
44
+ * A short summary
45
+ * description: | # literal — preserves newlines (collapsed to spaces here)
46
+ * A short summary
47
+ * description: |+ # keep trailing newlines (same handling)
48
+ *
49
+ * The block scalar header is `[>|][-+]?` per the YAML 1.2 spec:
50
+ * `>` = folded, `|` = literal; `-` strips trailing newlines,
51
+ * `+` keeps them, no indicator clips. For notification rendering we
52
+ * always collapse to a single line joined by spaces, so the chomping
53
+ * indicator only affects whether we match the block-scalar branch.
54
+ *
55
+ * Returns null when the field is absent or the frontmatter block is missing.
56
+ *
57
+ * Note: kept as a regex extractor rather than introducing a full YAML
58
+ * parse here — `parseSkillMdVersion` uses the same approach, and skill
59
+ * frontmatters are constrained enough that the regex covers the shapes we
60
+ * actually encounter. If we ever adopt a full YAML parser for this file,
61
+ * both helpers should switch together.
62
+ */
63
+ export declare function parseSkillMdDescription(content: string): string | null;
64
+ /**
65
+ * Read both the installed version and description for a skill from its local
66
+ * SKILL.md. Shared core for `readLocalVersion` and `readLocalDescription` so a
67
+ * tick that needs both pays a single file read per dir rather than two.
68
+ *
69
+ * Multi-dir search path: tries each entry in order and returns the first
70
+ * readable SKILL.md's parsed fields. Either field may be null if absent in
71
+ * the frontmatter — that's distinct from "no SKILL.md found at all" (which
72
+ * returns nulls only after every dir has been tried).
73
+ */
74
+ export declare function readLocalSkillMetadata(skillsDir: string | readonly string[], skillName: string, logger?: LoggerLike): Promise<{
75
+ version: string | null;
76
+ description: string | null;
77
+ }>;
78
+ /**
79
+ * Read the installed version for a skill from its local SKILL.md.
80
+ *
81
+ * Accepts either a single dir (legacy callers) or an array of dirs (new
82
+ * multi-dir search path). When given an array, tries each entry in order and
83
+ * returns the first non-null match — this mirrors the documented behaviour
84
+ * that `skillsDir[0]` is the *primary* (writes go here) and the remaining
85
+ * entries are read-only fallbacks scanned during version checks.
86
+ *
87
+ * Returns null when no readable SKILL.md is found across any of the
88
+ * candidate dirs, or when the version field is absent. Logs a warning per
89
+ * dir for unexpected errors (anything other than ENOENT).
90
+ */
91
+ export declare function readLocalVersion(skillsDir: string | readonly string[], skillName: string, logger?: LoggerLike): Promise<string | null>;
92
+ /**
93
+ * Read the installed description for a skill from its local SKILL.md.
94
+ * Multi-dir search path semantics identical to `readLocalVersion`.
95
+ *
96
+ * Returns null when no readable SKILL.md is found across any of the
97
+ * candidate dirs, or when the description field is absent.
98
+ */
99
+ export declare function readLocalDescription(skillsDir: string | readonly string[], skillName: string, logger?: LoggerLike): Promise<string | null>;
100
+ /**
101
+ * Fetch the remote SKILL.md for a skill and return both the version and the
102
+ * description in one network round-trip.
103
+ *
104
+ * Notification text uses the description from the **remote** SKILL.md (not
105
+ * the local one) so the "About:" line reflects the version the user is being
106
+ * asked about — a description on disk for v1.x may be stale relative to the
107
+ * v2.0.0 the major-update notification is announcing.
108
+ *
109
+ * Returns null fields when the skill does not exist in the remote repo
110
+ * (HTTP 404) — that's a legitimate state ("skill removed or not yet
111
+ * published"), not an error, so it should not pollute failure counters.
112
+ * Both fields may also be null when the remote SKILL.md is present but
113
+ * its frontmatter omits the corresponding field.
114
+ *
115
+ * Throws on transient/operational failures (network errors, timeouts, 5xx
116
+ * responses, body-parse failures) so the caller's failure-recording path can
117
+ * fire.
118
+ *
119
+ * @param fetchImpl Injectable for unit testing — defaults to global `fetch`.
120
+ */
121
+ export declare function fetchRemoteSkillMetadata(skillName: string, rawBaseUrl?: string, fetchImpl?: typeof fetch): Promise<{
122
+ version: string | null;
123
+ description: string | null;
124
+ }>;
125
+ /**
126
+ * Fetch the remote SKILL.md for a skill and return its version.
127
+ *
128
+ * Thin wrapper over {@link fetchRemoteSkillMetadata} kept for backward
129
+ * compatibility with callers (and tests) that only need the version field.
130
+ * New code that wants both version + description in a single round-trip
131
+ * should call `fetchRemoteSkillMetadata` directly.
132
+ */
133
+ export declare function fetchRemoteVersion(skillName: string, rawBaseUrl?: string, fetchImpl?: typeof fetch): Promise<string | null>;
134
+ /**
135
+ * Compare the local and remote versions for a skill and return a classified diff.
136
+ *
137
+ * Special cases:
138
+ * - Local version is null (skill predates versioning) + valid remote → "minor"
139
+ * so the update is auto-applied without manual major-version approval.
140
+ * - Local version exists but is not valid semver (e.g. "latest") → "unknown",
141
+ * skip safely.
142
+ * - Remote version is absent or unparseable → "unknown", caller should log and
143
+ * skip.
144
+ */
145
+ export declare function checkSkillVersion(skillsDir: string | readonly string[], skillName: string, rawBaseUrl?: string, fetchImpl?: typeof fetch, logger?: LoggerLike): Promise<SkillVersionInfo>;
146
+ //# sourceMappingURL=version-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version-manager.d.ts","sourceRoot":"","sources":["../../src/skills-manager/version-manager.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,OAAO,KAAK,EAAE,UAAU,EAAmB,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAGhF;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAElD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAMlE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CA6CtE;AASD;;;;;;;;;GASG;AACH,wBAAsB,sBAAsB,CAC1C,SAAS,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,EACrC,SAAS,EAAE,MAAM,EACjB,MAAM,CAAC,EAAE,UAAU,GAClB,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC,CA2BjE;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,gBAAgB,CACpC,SAAS,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,EACrC,SAAS,EAAE,MAAM,EACjB,MAAM,CAAC,EAAE,UAAU,GAClB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAExB;AAED;;;;;;GAMG;AACH,wBAAsB,oBAAoB,CACxC,SAAS,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,EACrC,SAAS,EAAE,MAAM,EACjB,MAAM,CAAC,EAAE,UAAU,GAClB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAExB;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,wBAAwB,CAC5C,SAAS,EAAE,MAAM,EACjB,UAAU,GAAE,MAA4B,EACxC,SAAS,GAAE,OAAO,KAAa,GAC9B,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC,CAkCjE;AAED;;;;;;;GAOG;AACH,wBAAsB,kBAAkB,CACtC,SAAS,EAAE,MAAM,EACjB,UAAU,GAAE,MAA4B,EACxC,SAAS,GAAE,OAAO,KAAa,GAC9B,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAExB;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,iBAAiB,CACrC,SAAS,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,EACrC,SAAS,EAAE,MAAM,EACjB,UAAU,GAAE,MAA4B,EACxC,SAAS,GAAE,OAAO,KAAa,EAC/B,MAAM,CAAC,EAAE,UAAU,GAClB,OAAO,CAAC,gBAAgB,CAAC,CAgD3B"}
@@ -0,0 +1,299 @@
1
+ /**
2
+ * Version detection for Senpi skills.
3
+ *
4
+ * Reads the installed SKILL.md version from disk and the remote version from
5
+ * raw.githubusercontent.com, then uses the existing semver helpers from
6
+ * src/runtime/auto-update/policy.ts to classify the delta.
7
+ */
8
+ import { readFile } from "node:fs/promises";
9
+ import { join } from "node:path";
10
+ import { classifyUpdate, parseSemver } from "../runtime/auto-update/policy.js";
11
+ import { SKILLS_RAW_BASE_URL } from "./constants.js";
12
+ import { formatErr } from "./utils.js";
13
+ /**
14
+ * Normalise a version string before passing it to parseSemver / classifyUpdate.
15
+ *
16
+ * Accepts two-part versions used in production skills (e.g. "1.0", "v2.3")
17
+ * and expands them to the full three-part form ("1.0.0", "v2.3.0") that
18
+ * parseSemver requires. All other strings are returned unchanged.
19
+ */
20
+ export function normalizeVersion(v) {
21
+ return /^v?\d+\.\d+$/.test(v.trim()) ? `${v.trim()}.0` : v;
22
+ }
23
+ /**
24
+ * Extract the `version` value from a SKILL.md YAML frontmatter block.
25
+ *
26
+ * Handles both nested form:
27
+ * metadata:
28
+ * version: 1.2.3
29
+ * and flat form:
30
+ * version: "1.2.3"
31
+ *
32
+ * Returns null when the frontmatter block is absent or the field is missing.
33
+ */
34
+ export function parseSkillMdVersion(content) {
35
+ const match = /^---\r?\n([\s\S]*?)\r?\n---/.exec(content.trim());
36
+ if (!match)
37
+ return null;
38
+ const frontmatter = match[1];
39
+ const versionMatch = /(?:^|\n)[ \t]*version:[ \t]*["']?([^\s"'\r\n]+)["']?/i.exec(frontmatter);
40
+ return versionMatch ? versionMatch[1].trim() : null;
41
+ }
42
+ /**
43
+ * Extract the `description` value from a SKILL.md YAML frontmatter block.
44
+ *
45
+ * Skill descriptions can be authored a few different ways. This helper
46
+ * handles the real-world shapes that appear in the senpi-skills repo today:
47
+ *
48
+ * 1. Single-line, double-quoted:
49
+ * description: "Configure, deploy, ..."
50
+ * 2. Single-line, single-quoted or unquoted:
51
+ * description: Manage runtimes ...
52
+ * 3. YAML block scalar (folded or literal, optional chomping indicator):
53
+ * description: >
54
+ * A short summary
55
+ * continues on this line.
56
+ * description: >- # strip trailing newlines (this PR's senpi-skills repo)
57
+ * A short summary
58
+ * description: | # literal — preserves newlines (collapsed to spaces here)
59
+ * A short summary
60
+ * description: |+ # keep trailing newlines (same handling)
61
+ *
62
+ * The block scalar header is `[>|][-+]?` per the YAML 1.2 spec:
63
+ * `>` = folded, `|` = literal; `-` strips trailing newlines,
64
+ * `+` keeps them, no indicator clips. For notification rendering we
65
+ * always collapse to a single line joined by spaces, so the chomping
66
+ * indicator only affects whether we match the block-scalar branch.
67
+ *
68
+ * Returns null when the field is absent or the frontmatter block is missing.
69
+ *
70
+ * Note: kept as a regex extractor rather than introducing a full YAML
71
+ * parse here — `parseSkillMdVersion` uses the same approach, and skill
72
+ * frontmatters are constrained enough that the regex covers the shapes we
73
+ * actually encounter. If we ever adopt a full YAML parser for this file,
74
+ * both helpers should switch together.
75
+ */
76
+ export function parseSkillMdDescription(content) {
77
+ const match = /^---\r?\n([\s\S]*?)\r?\n---/.exec(content.trim());
78
+ if (!match)
79
+ return null;
80
+ const frontmatter = match[1];
81
+ // Shape 3: block scalar — `description:` followed by a `>` or `|` header
82
+ // (optionally with a `-` / `+` chomping indicator) on its own line, then
83
+ // indented continuation lines. Stop at the first un-indented line or the
84
+ // end of frontmatter. We collapse the indent and join lines into a single
85
+ // string so the notification renderer doesn't emit raw YAML continuations.
86
+ //
87
+ // Pre-fix this regex was `[>|]` (no indicator). A SKILL.md authored with
88
+ // `description: >-` (the YAML strip-trailing-newlines variant — what the
89
+ // senpi-skills repo actually uses) didn't match, fell through to the
90
+ // inline branch, and ended up capturing the literal "`>-`" as the value.
91
+ // That's the bug report this fixes: the Telegram notification showed
92
+ // `>-` instead of the real description.
93
+ const foldedMatch = /(?:^|\n)description:[ \t]*[>|][-+]?[ \t]*\r?\n([\s\S]*?)(?=\n[^\s]|\n?$)/i.exec(frontmatter);
94
+ if (foldedMatch) {
95
+ const lines = foldedMatch[1]
96
+ .split(/\r?\n/)
97
+ .map((l) => l.replace(/^[ \t]+/, "").trim())
98
+ .filter((l) => l.length > 0);
99
+ if (lines.length > 0)
100
+ return lines.join(" ");
101
+ }
102
+ // Shapes 1 + 2: single-line value. Allow optional surrounding quotes.
103
+ // The value runs to the end of the line; trailing whitespace + matching
104
+ // closing quote stripped.
105
+ const inlineMatch = /(?:^|\n)description:[ \t]*(?:"((?:[^"\\]|\\.)*)"|'((?:[^'\\]|\\.)*)'|([^\r\n]+?))[ \t]*(?:\r?\n|$)/i
106
+ .exec(frontmatter);
107
+ if (inlineMatch) {
108
+ // Unescape `\"` and `\\` for double-quoted form; single-quoted is taken
109
+ // literally per YAML (no escapes). Unquoted form passes through.
110
+ const raw = inlineMatch[1] ?? inlineMatch[2] ?? inlineMatch[3] ?? "";
111
+ const value = inlineMatch[1]
112
+ ? raw.replace(/\\(["\\])/g, "$1")
113
+ : raw;
114
+ const trimmed = value.trim();
115
+ return trimmed.length > 0 ? trimmed : null;
116
+ }
117
+ return null;
118
+ }
119
+ // `summarizeDescription` (sentence-cut + char-cap truncator) was removed
120
+ // alongside the notification reformat in PR #78. The new nested-bullet
121
+ // notification format renders the full description verbatim so users get
122
+ // the complete context for the skill they're being notified about.
123
+ // Telegram's own 4096-char message limit is the only remaining ceiling,
124
+ // and skill descriptions in the senpi-skills repo are nowhere near that.
125
+ /**
126
+ * Read both the installed version and description for a skill from its local
127
+ * SKILL.md. Shared core for `readLocalVersion` and `readLocalDescription` so a
128
+ * tick that needs both pays a single file read per dir rather than two.
129
+ *
130
+ * Multi-dir search path: tries each entry in order and returns the first
131
+ * readable SKILL.md's parsed fields. Either field may be null if absent in
132
+ * the frontmatter — that's distinct from "no SKILL.md found at all" (which
133
+ * returns nulls only after every dir has been tried).
134
+ */
135
+ export async function readLocalSkillMetadata(skillsDir, skillName, logger) {
136
+ const dirs = typeof skillsDir === "string" ? [skillsDir] : skillsDir;
137
+ for (const dir of dirs) {
138
+ const filePath = join(dir, skillName, "SKILL.md");
139
+ try {
140
+ const content = await readFile(filePath, "utf8");
141
+ // First readable SKILL.md wins, even if its fields are missing —
142
+ // a present-but-versionless file is meaningful state (skill predates
143
+ // versioning), not a "not found" signal that should let the next dir
144
+ // override it.
145
+ return {
146
+ version: parseSkillMdVersion(content),
147
+ description: parseSkillMdDescription(content),
148
+ };
149
+ }
150
+ catch (err) {
151
+ if (err.code === "ENOENT") {
152
+ continue; // try next dir
153
+ }
154
+ logger?.warn(`[senpi-skills-manager] failed to read SKILL.md for "${skillName}" | path=${filePath} err=${formatErr(err)}`);
155
+ // Non-ENOENT (permissions, I/O error) — log and keep trying the
156
+ // remaining dirs rather than short-circuit; if one mirror is broken,
157
+ // a healthy mirror further down the path should still serve metadata.
158
+ }
159
+ }
160
+ return { version: null, description: null };
161
+ }
162
+ /**
163
+ * Read the installed version for a skill from its local SKILL.md.
164
+ *
165
+ * Accepts either a single dir (legacy callers) or an array of dirs (new
166
+ * multi-dir search path). When given an array, tries each entry in order and
167
+ * returns the first non-null match — this mirrors the documented behaviour
168
+ * that `skillsDir[0]` is the *primary* (writes go here) and the remaining
169
+ * entries are read-only fallbacks scanned during version checks.
170
+ *
171
+ * Returns null when no readable SKILL.md is found across any of the
172
+ * candidate dirs, or when the version field is absent. Logs a warning per
173
+ * dir for unexpected errors (anything other than ENOENT).
174
+ */
175
+ export async function readLocalVersion(skillsDir, skillName, logger) {
176
+ return (await readLocalSkillMetadata(skillsDir, skillName, logger)).version;
177
+ }
178
+ /**
179
+ * Read the installed description for a skill from its local SKILL.md.
180
+ * Multi-dir search path semantics identical to `readLocalVersion`.
181
+ *
182
+ * Returns null when no readable SKILL.md is found across any of the
183
+ * candidate dirs, or when the description field is absent.
184
+ */
185
+ export async function readLocalDescription(skillsDir, skillName, logger) {
186
+ return (await readLocalSkillMetadata(skillsDir, skillName, logger)).description;
187
+ }
188
+ /**
189
+ * Fetch the remote SKILL.md for a skill and return both the version and the
190
+ * description in one network round-trip.
191
+ *
192
+ * Notification text uses the description from the **remote** SKILL.md (not
193
+ * the local one) so the "About:" line reflects the version the user is being
194
+ * asked about — a description on disk for v1.x may be stale relative to the
195
+ * v2.0.0 the major-update notification is announcing.
196
+ *
197
+ * Returns null fields when the skill does not exist in the remote repo
198
+ * (HTTP 404) — that's a legitimate state ("skill removed or not yet
199
+ * published"), not an error, so it should not pollute failure counters.
200
+ * Both fields may also be null when the remote SKILL.md is present but
201
+ * its frontmatter omits the corresponding field.
202
+ *
203
+ * Throws on transient/operational failures (network errors, timeouts, 5xx
204
+ * responses, body-parse failures) so the caller's failure-recording path can
205
+ * fire.
206
+ *
207
+ * @param fetchImpl Injectable for unit testing — defaults to global `fetch`.
208
+ */
209
+ export async function fetchRemoteSkillMetadata(skillName, rawBaseUrl = SKILLS_RAW_BASE_URL, fetchImpl = fetch) {
210
+ const url = `${rawBaseUrl}/${encodeURIComponent(skillName)}/SKILL.md`;
211
+ let res;
212
+ try {
213
+ res = await fetchImpl(url, { signal: AbortSignal.timeout(15_000) });
214
+ }
215
+ catch (err) {
216
+ throw new Error(`Network error fetching remote SKILL.md for "${skillName}": ${formatErr(err)}`);
217
+ }
218
+ // 404 = skill is not in the remote repo. Treated as "no remote metadata"
219
+ // rather than a failure so the caller can skip cleanly without escalating.
220
+ if (res.status === 404) {
221
+ return { version: null, description: null };
222
+ }
223
+ if (!res.ok) {
224
+ throw new Error(`Failed to fetch remote SKILL.md for "${skillName}": HTTP ${res.status} ${res.statusText}`);
225
+ }
226
+ try {
227
+ const text = await res.text();
228
+ return {
229
+ version: parseSkillMdVersion(text),
230
+ description: parseSkillMdDescription(text),
231
+ };
232
+ }
233
+ catch (err) {
234
+ throw new Error(`Failed to read response body for "${skillName}": ${formatErr(err)}`);
235
+ }
236
+ }
237
+ /**
238
+ * Fetch the remote SKILL.md for a skill and return its version.
239
+ *
240
+ * Thin wrapper over {@link fetchRemoteSkillMetadata} kept for backward
241
+ * compatibility with callers (and tests) that only need the version field.
242
+ * New code that wants both version + description in a single round-trip
243
+ * should call `fetchRemoteSkillMetadata` directly.
244
+ */
245
+ export async function fetchRemoteVersion(skillName, rawBaseUrl = SKILLS_RAW_BASE_URL, fetchImpl = fetch) {
246
+ return (await fetchRemoteSkillMetadata(skillName, rawBaseUrl, fetchImpl)).version;
247
+ }
248
+ /**
249
+ * Compare the local and remote versions for a skill and return a classified diff.
250
+ *
251
+ * Special cases:
252
+ * - Local version is null (skill predates versioning) + valid remote → "minor"
253
+ * so the update is auto-applied without manual major-version approval.
254
+ * - Local version exists but is not valid semver (e.g. "latest") → "unknown",
255
+ * skip safely.
256
+ * - Remote version is absent or unparseable → "unknown", caller should log and
257
+ * skip.
258
+ */
259
+ export async function checkSkillVersion(skillsDir, skillName, rawBaseUrl = SKILLS_RAW_BASE_URL, fetchImpl = fetch, logger) {
260
+ // Fetch the remote SKILL.md once and extract both version + description.
261
+ // Description is reported back to the coordinator so notifications can
262
+ // surface the *remote* (i.e. upcoming-version) description rather than the
263
+ // local one, which may be stale relative to the version the notification
264
+ // is announcing.
265
+ //
266
+ // Log the specific fetch failure for traceability, then re-throw so the
267
+ // coordinator records it as a check failure (consecutiveFailures,
268
+ // lastFailureReason). 404 is intentionally returned as null inside
269
+ // fetchRemoteSkillMetadata and never reaches this catch.
270
+ const remoteMetadataPromise = fetchRemoteSkillMetadata(skillName, rawBaseUrl, fetchImpl).catch((err) => {
271
+ logger?.warn(`[senpi-skills-manager] failed to fetch remote SKILL.md for "${skillName}" | err=${formatErr(err)}`);
272
+ throw err;
273
+ });
274
+ const [localVersion, remoteMetadata] = await Promise.all([
275
+ readLocalVersion(skillsDir, skillName, logger),
276
+ remoteMetadataPromise,
277
+ ]);
278
+ const { version: remoteVersion, description: remoteDescription } = remoteMetadata;
279
+ const normalizedRemote = normalizeVersion(remoteVersion ?? "");
280
+ if (!remoteVersion || !parseSemver(normalizedRemote)) {
281
+ return { localVersion, remoteVersion, updateKind: "unknown", remoteDescription };
282
+ }
283
+ // Skill has never had a version in SKILL.md (null) — predates the versioning
284
+ // system or was installed before the field was added. Treat as a minor update
285
+ // so it is auto-applied without requiring a manual major-version approval.
286
+ if (localVersion === null) {
287
+ return { localVersion, remoteVersion, updateKind: "minor", remoteDescription };
288
+ }
289
+ const normalizedLocal = normalizeVersion(localVersion);
290
+ // Local version string exists but is not valid semver even after normalisation
291
+ // (e.g. "latest", "nightly"). We cannot safely compare, so skip.
292
+ if (!parseSemver(normalizedLocal)) {
293
+ return { localVersion, remoteVersion, updateKind: "unknown", remoteDescription };
294
+ }
295
+ // classifyUpdate returns AutoUpdateKind which is a strict subset of SkillUpdateKind
296
+ const updateKind = classifyUpdate(normalizedLocal, normalizedRemote);
297
+ return { localVersion, remoteVersion, updateKind, remoteDescription };
298
+ }
299
+ //# sourceMappingURL=version-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version-manager.js","sourceRoot":"","sources":["../../src/skills-manager/version-manager.ts"],"names":[],"mappings":"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"}