@serviceme/devtools-core 2.0.0 → 2.0.2
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/auth-Bzss8zOv.mjs +995 -0
- package/dist/auth-Bzss8zOv.mjs.map +1 -0
- package/dist/auth-DXam3c4S.js +1078 -0
- package/dist/auth-DXam3c4S.js.map +1 -0
- package/dist/auth.d.mts +2 -2
- package/dist/auth.d.ts +2 -2
- package/dist/auth.js +14 -1029
- package/dist/auth.mjs +2 -991
- package/dist/device-CDDcwDY6.mjs +758 -0
- package/dist/device-CDDcwDY6.mjs.map +1 -0
- package/dist/device-DuAYH8bk.js +839 -0
- package/dist/device-DuAYH8bk.js.map +1 -0
- package/dist/device.d.mts +2 -455
- package/dist/device.d.ts +2 -455
- package/dist/device.js +15 -728
- package/dist/device.mjs +2 -679
- package/dist/index-2rX86yMq.d.mts +331 -0
- package/dist/index-BI11AfMH.d.mts +377 -0
- package/dist/index-BI11AfMH.d.ts +377 -0
- package/dist/index-CoA6y0MP.d.mts +498 -0
- package/dist/index-CoA6y0MP.d.ts +498 -0
- package/dist/index-DaW7IYA-.d.mts +185 -0
- package/dist/index-DaW7IYA-.d.ts +185 -0
- package/dist/index-Dp6FwWKh.d.ts +331 -0
- package/dist/index.d.mts +2340 -2101
- package/dist/index.d.ts +2340 -2101
- package/dist/index.js +8117 -10538
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +7895 -10318
- package/dist/index.mjs.map +1 -1
- package/dist/rolldown-runtime-VH7oDXx4.js +28 -0
- package/dist/skill-linker.d.mts +82 -81
- package/dist/skill-linker.d.ts +82 -81
- package/dist/skill-linker.js +359 -402
- package/dist/skill-linker.js.map +1 -1
- package/dist/skill-linker.mjs +345 -357
- package/dist/skill-linker.mjs.map +1 -1
- package/dist/skill-store-Bym2yinU.mjs +388 -0
- package/dist/skill-store-Bym2yinU.mjs.map +1 -0
- package/dist/skill-store-CJdYyyxu.js +408 -0
- package/dist/skill-store-CJdYyyxu.js.map +1 -0
- package/dist/skill-store.d.mts +41 -42
- package/dist/skill-store.d.ts +41 -42
- package/dist/skill-store.js +5 -338
- package/dist/skill-store.mjs +2 -301
- package/dist/submit-Cg7wSa3x.mjs +84 -0
- package/dist/submit-Cg7wSa3x.mjs.map +1 -0
- package/dist/submit-CltE6YOZ.js +98 -0
- package/dist/submit-CltE6YOZ.js.map +1 -0
- package/dist/submit.d.mts +2 -3
- package/dist/submit.d.ts +2 -3
- package/dist/submit.js +4 -166
- package/dist/submit.mjs +2 -130
- package/dist/toolbox-CbfxpNg-.mjs +540 -0
- package/dist/toolbox-CbfxpNg-.mjs.map +1 -0
- package/dist/toolbox-CxnOr-FY.js +614 -0
- package/dist/toolbox-CxnOr-FY.js.map +1 -0
- package/dist/toolbox.d.mts +2 -244
- package/dist/toolbox.d.ts +2 -244
- package/dist/toolbox.js +14 -584
- package/dist/toolbox.mjs +2 -536
- package/dist/{types-B9gk3dXH.d.ts → types-BCV3IedE.d.mts} +25 -23
- package/dist/{types-B9gk3dXH.d.mts → types-BCV3IedE.d.ts} +25 -23
- package/dist/userHome-BquDeFVE.js +526 -0
- package/dist/userHome-BquDeFVE.js.map +1 -0
- package/dist/userHome-CcnAQPG1.mjs +212 -0
- package/dist/userHome-CcnAQPG1.mjs.map +1 -0
- package/package.json +10 -10
- package/dist/auth.js.map +0 -1
- package/dist/auth.mjs.map +0 -1
- package/dist/device.js.map +0 -1
- package/dist/device.mjs.map +0 -1
- package/dist/index-BMk4tqIT.d.mts +0 -328
- package/dist/index-CymN0x9Z.d.ts +0 -328
- package/dist/index-PD135hlB.d.mts +0 -626
- package/dist/index-PD135hlB.d.ts +0 -626
- package/dist/skill-store.js.map +0 -1
- package/dist/skill-store.mjs.map +0 -1
- package/dist/submit.js.map +0 -1
- package/dist/submit.mjs.map +0 -1
- package/dist/toolbox.js.map +0 -1
- package/dist/toolbox.mjs.map +0 -1
package/dist/skill-store.mjs
CHANGED
|
@@ -1,301 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import * as path2 from "path";
|
|
4
|
-
|
|
5
|
-
// src/repo-layout/index.ts
|
|
6
|
-
import * as fs from "fs/promises";
|
|
7
|
-
import * as path from "path";
|
|
8
|
-
var MARKER_FILENAME = ".serviceme-repo.json";
|
|
9
|
-
var SUPPORTED_SCHEMA = 1;
|
|
10
|
-
var DEFAULT_REPO_LAYOUT = {
|
|
11
|
-
schema: SUPPORTED_SCHEMA,
|
|
12
|
-
include: [],
|
|
13
|
-
exclude: [],
|
|
14
|
-
treatFilesAsSkills: false
|
|
15
|
-
};
|
|
16
|
-
async function loadRepoLayout(repoRoot) {
|
|
17
|
-
const markerPath = path.join(repoRoot, MARKER_FILENAME);
|
|
18
|
-
let raw;
|
|
19
|
-
try {
|
|
20
|
-
raw = await fs.readFile(markerPath, "utf8");
|
|
21
|
-
} catch {
|
|
22
|
-
return DEFAULT_REPO_LAYOUT;
|
|
23
|
-
}
|
|
24
|
-
let parsed;
|
|
25
|
-
try {
|
|
26
|
-
parsed = JSON.parse(raw);
|
|
27
|
-
} catch {
|
|
28
|
-
return null;
|
|
29
|
-
}
|
|
30
|
-
if (!parsed || typeof parsed !== "object") return null;
|
|
31
|
-
const obj = parsed;
|
|
32
|
-
if (obj.schema !== SUPPORTED_SCHEMA) return null;
|
|
33
|
-
if (!Array.isArray(obj.include) || obj.include.some((s) => typeof s !== "string")) {
|
|
34
|
-
return null;
|
|
35
|
-
}
|
|
36
|
-
if (!Array.isArray(obj.exclude) || obj.exclude.some((s) => typeof s !== "string")) {
|
|
37
|
-
return null;
|
|
38
|
-
}
|
|
39
|
-
if (typeof obj.treatFilesAsSkills !== "boolean") return null;
|
|
40
|
-
return {
|
|
41
|
-
schema: SUPPORTED_SCHEMA,
|
|
42
|
-
include: obj.include,
|
|
43
|
-
exclude: obj.exclude,
|
|
44
|
-
treatFilesAsSkills: obj.treatFilesAsSkills
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// src/skill-store/types.ts
|
|
49
|
-
var SkillNotFoundError = class extends Error {
|
|
50
|
-
constructor(repoId, skillName) {
|
|
51
|
-
super(`skill/agent not found: ${repoId}/${skillName}`);
|
|
52
|
-
this.repoId = repoId;
|
|
53
|
-
this.skillName = skillName;
|
|
54
|
-
this.name = "SkillNotFoundError";
|
|
55
|
-
}
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
// src/skill-store/index.ts
|
|
59
|
-
function extractFrontmatter(raw) {
|
|
60
|
-
if (typeof raw !== "string") return null;
|
|
61
|
-
const match = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
|
|
62
|
-
if (!match) return null;
|
|
63
|
-
const yamlBody = match[1] ?? "";
|
|
64
|
-
const body = match[2] ?? "";
|
|
65
|
-
const data = {};
|
|
66
|
-
const lines = yamlBody.split(/\r?\n/);
|
|
67
|
-
for (let i = 0; i < lines.length; i++) {
|
|
68
|
-
const line = lines[i] ?? "";
|
|
69
|
-
if (line.trim().length === 0) continue;
|
|
70
|
-
if (line.trim().startsWith("#")) continue;
|
|
71
|
-
const kv = line.match(/^([a-zA-Z_][\w-]*)\s*:\s*(.*)$/);
|
|
72
|
-
if (!kv?.[1]) continue;
|
|
73
|
-
let value = (kv[2] ?? "").trim();
|
|
74
|
-
if (typeof value === "string" && /^[>|][+-]?$/.test(value)) {
|
|
75
|
-
const folded = [];
|
|
76
|
-
let j = i + 1;
|
|
77
|
-
for (; j < lines.length; j++) {
|
|
78
|
-
const next = lines[j] ?? "";
|
|
79
|
-
if (next.trim().length === 0) {
|
|
80
|
-
const after = lines[j + 1];
|
|
81
|
-
if (after !== void 0 && /^[ \t]/.test(after)) {
|
|
82
|
-
folded.push("");
|
|
83
|
-
continue;
|
|
84
|
-
}
|
|
85
|
-
break;
|
|
86
|
-
}
|
|
87
|
-
if (!/^[ \t]/.test(next)) break;
|
|
88
|
-
folded.push(next.trim());
|
|
89
|
-
}
|
|
90
|
-
i = j - 1;
|
|
91
|
-
value = folded.join(" ").replace(/\s+/g, " ").trim();
|
|
92
|
-
data[kv[1]] = value;
|
|
93
|
-
continue;
|
|
94
|
-
}
|
|
95
|
-
if (typeof value === "string") {
|
|
96
|
-
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
97
|
-
value = value.slice(1, -1);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
data[kv[1]] = value;
|
|
101
|
-
}
|
|
102
|
-
return { data, body };
|
|
103
|
-
}
|
|
104
|
-
var SkillStore = class {
|
|
105
|
-
constructor(opts) {
|
|
106
|
-
/**
|
|
107
|
-
* Per-instance memo of repo walks. The catalog walk is a recursive
|
|
108
|
-
* readdir over the whole checkout — the dominant cost of every
|
|
109
|
-
* list/get — so results (and the in-flight promise) are reused for
|
|
110
|
-
* the store's lifetime. The handler that owns long-lived stores
|
|
111
|
-
* bounds staleness by rebuilding the store on a short TTL; within
|
|
112
|
-
* one store instance the tree is treated as immutable.
|
|
113
|
-
*/
|
|
114
|
-
this.entriesMemo = /* @__PURE__ */ new Map();
|
|
115
|
-
this.repoRoots = /* @__PURE__ */ new Map();
|
|
116
|
-
for (const r of opts.repos) this.repoRoots.set(r.id, r.rootPath);
|
|
117
|
-
}
|
|
118
|
-
/** All skills + agents across all registered repos. Repos are walked
|
|
119
|
-
* concurrently — one slow checkout no longer stretches the total. */
|
|
120
|
-
async listAll() {
|
|
121
|
-
const perRepo = await Promise.all(
|
|
122
|
-
[...this.repoRoots.keys()].map((repoId) => this.listByRepo(repoId))
|
|
123
|
-
);
|
|
124
|
-
return perRepo.flat();
|
|
125
|
-
}
|
|
126
|
-
/** All skills + agents under a single repo. */
|
|
127
|
-
async listByRepo(repoId) {
|
|
128
|
-
const entries = await this.listByRepoCached(repoId);
|
|
129
|
-
return [...entries];
|
|
130
|
-
}
|
|
131
|
-
/** Single entry detail (manifest + all files inside its dir). */
|
|
132
|
-
async get(repoId, name) {
|
|
133
|
-
const entries = await this.listByRepoCached(repoId);
|
|
134
|
-
const found = entries.find((e) => e.name === name);
|
|
135
|
-
if (!found) throw new SkillNotFoundError(repoId, name);
|
|
136
|
-
const files = await collectEntryFiles(found);
|
|
137
|
-
return { ...found, files };
|
|
138
|
-
}
|
|
139
|
-
/** All files inside a single entry's directory (manifest + extras). */
|
|
140
|
-
async getFiles(repoId, name) {
|
|
141
|
-
const entries = await this.listByRepoCached(repoId);
|
|
142
|
-
const found = entries.find((e) => e.name === name);
|
|
143
|
-
if (!found) throw new SkillNotFoundError(repoId, name);
|
|
144
|
-
return collectEntryFiles(found);
|
|
145
|
-
}
|
|
146
|
-
/**
|
|
147
|
-
* Memoized walk. Sharing the promise collapses concurrent list/get
|
|
148
|
-
* calls over the same repo into a single traversal; a rejected walk
|
|
149
|
-
* is dropped so the next call retries from disk.
|
|
150
|
-
*/
|
|
151
|
-
listByRepoCached(repoId) {
|
|
152
|
-
const memoized = this.entriesMemo.get(repoId);
|
|
153
|
-
if (memoized) return memoized;
|
|
154
|
-
const walk = this.walkRepo(repoId).catch((error) => {
|
|
155
|
-
this.entriesMemo.delete(repoId);
|
|
156
|
-
throw error;
|
|
157
|
-
});
|
|
158
|
-
this.entriesMemo.set(repoId, walk);
|
|
159
|
-
return walk;
|
|
160
|
-
}
|
|
161
|
-
async walkRepo(repoId) {
|
|
162
|
-
const root = this.repoRoots.get(repoId);
|
|
163
|
-
if (!root) return [];
|
|
164
|
-
const layout = await loadRepoLayout(root) ?? DEFAULT_REPO_LAYOUT;
|
|
165
|
-
const entries = [];
|
|
166
|
-
await walkForEntries(root, repoId, entries, layout);
|
|
167
|
-
return entries;
|
|
168
|
-
}
|
|
169
|
-
};
|
|
170
|
-
async function collectEntryFiles(found) {
|
|
171
|
-
const out = [];
|
|
172
|
-
if (found.dir === found.manifestPath) {
|
|
173
|
-
const content = await fs2.readFile(found.manifestPath, "utf8");
|
|
174
|
-
return [{ path: path2.basename(found.manifestPath), content }];
|
|
175
|
-
}
|
|
176
|
-
await collectFilesRecursive(found.dir, found.dir, out);
|
|
177
|
-
out.sort((a, b) => {
|
|
178
|
-
const aIsManifest = a.path === "SKILL.md" || a.path === "AGENT.md";
|
|
179
|
-
const bIsManifest = b.path === "SKILL.md" || b.path === "AGENT.md";
|
|
180
|
-
if (aIsManifest && !bIsManifest) return -1;
|
|
181
|
-
if (bIsManifest && !aIsManifest) return 1;
|
|
182
|
-
return a.path.localeCompare(b.path);
|
|
183
|
-
});
|
|
184
|
-
return out;
|
|
185
|
-
}
|
|
186
|
-
var SKIP_DIRS = /* @__PURE__ */ new Set([".git", "node_modules", ".vscode", "dist", "build", "out"]);
|
|
187
|
-
var SKIP_TOP_LEVEL_SCAN_DIRS = /* @__PURE__ */ new Set([...SKIP_DIRS, "plugins"]);
|
|
188
|
-
var FLAT_AGENT_FILE_SUFFIX = ".agent.md";
|
|
189
|
-
async function walkForEntries(rootDir, repoId, out, layout = DEFAULT_REPO_LAYOUT) {
|
|
190
|
-
let dirents;
|
|
191
|
-
try {
|
|
192
|
-
dirents = await fs2.readdir(rootDir, { withFileTypes: true });
|
|
193
|
-
} catch {
|
|
194
|
-
return;
|
|
195
|
-
}
|
|
196
|
-
for (const d of dirents) {
|
|
197
|
-
if (d.isFile() && d.name.endsWith(FLAT_AGENT_FILE_SUFFIX)) {
|
|
198
|
-
const filePath = path2.join(rootDir, d.name);
|
|
199
|
-
const stat2 = await fs2.stat(filePath);
|
|
200
|
-
const content = await fs2.readFile(filePath, "utf8");
|
|
201
|
-
const parsed = extractFrontmatter(content);
|
|
202
|
-
out.push({
|
|
203
|
-
repoId,
|
|
204
|
-
name: d.name.slice(0, -FLAT_AGENT_FILE_SUFFIX.length),
|
|
205
|
-
kind: "agent",
|
|
206
|
-
manifestPath: filePath,
|
|
207
|
-
// Sentinel: `dir === manifestPath` marks a flat single-file
|
|
208
|
-
// entry (no directory of its own — see getFiles()).
|
|
209
|
-
dir: filePath,
|
|
210
|
-
frontmatter: parsed?.data ?? {},
|
|
211
|
-
modifiedAt: stat2.mtime.toISOString()
|
|
212
|
-
});
|
|
213
|
-
continue;
|
|
214
|
-
}
|
|
215
|
-
if (!d.isDirectory()) continue;
|
|
216
|
-
if (SKIP_TOP_LEVEL_SCAN_DIRS.has(d.name)) continue;
|
|
217
|
-
if (layout.exclude.includes(d.name)) continue;
|
|
218
|
-
const childDir = path2.join(rootDir, d.name);
|
|
219
|
-
const kind = await detectKind(childDir);
|
|
220
|
-
if (kind) {
|
|
221
|
-
const manifestFilename = kind === "skill" ? "SKILL.md" : "AGENT.md";
|
|
222
|
-
const manifestPath = path2.join(childDir, manifestFilename);
|
|
223
|
-
const stat2 = await fs2.stat(manifestPath);
|
|
224
|
-
const content = await fs2.readFile(manifestPath, "utf8");
|
|
225
|
-
const parsed = extractFrontmatter(content);
|
|
226
|
-
out.push({
|
|
227
|
-
repoId,
|
|
228
|
-
name: d.name,
|
|
229
|
-
kind,
|
|
230
|
-
manifestPath,
|
|
231
|
-
dir: childDir,
|
|
232
|
-
frontmatter: parsed?.data ?? {},
|
|
233
|
-
modifiedAt: stat2.mtime.toISOString()
|
|
234
|
-
});
|
|
235
|
-
continue;
|
|
236
|
-
}
|
|
237
|
-
await walkForEntries(childDir, repoId, out, layout);
|
|
238
|
-
if (layout.treatFilesAsSkills && layout.include.includes(d.name)) {
|
|
239
|
-
await surfaceFilesAsSkills(childDir, d.name, repoId, out);
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
async function surfaceFilesAsSkills(absDir, relName, repoId, out) {
|
|
244
|
-
let dirents;
|
|
245
|
-
try {
|
|
246
|
-
dirents = await fs2.readdir(absDir, { withFileTypes: true });
|
|
247
|
-
} catch {
|
|
248
|
-
return;
|
|
249
|
-
}
|
|
250
|
-
for (const d of dirents) {
|
|
251
|
-
if (!d.isFile()) continue;
|
|
252
|
-
if (!d.name.endsWith(".md")) continue;
|
|
253
|
-
const filePath = path2.join(absDir, d.name);
|
|
254
|
-
const stat2 = await fs2.stat(filePath);
|
|
255
|
-
const content = await fs2.readFile(filePath, "utf8");
|
|
256
|
-
const parsed = extractFrontmatter(content);
|
|
257
|
-
const entryName = `${relName}/${d.name.replace(/\.md$/, "")}`;
|
|
258
|
-
out.push({
|
|
259
|
-
repoId,
|
|
260
|
-
name: entryName,
|
|
261
|
-
kind: "skill",
|
|
262
|
-
manifestPath: filePath,
|
|
263
|
-
dir: absDir,
|
|
264
|
-
frontmatter: parsed?.data ?? {},
|
|
265
|
-
modifiedAt: stat2.mtime.toISOString()
|
|
266
|
-
});
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
async function detectKind(dir) {
|
|
270
|
-
const [hasSkill, hasAgent] = await Promise.all([
|
|
271
|
-
fs2.access(path2.join(dir, "SKILL.md")).then(() => true).catch(() => false),
|
|
272
|
-
fs2.access(path2.join(dir, "AGENT.md")).then(() => true).catch(() => false)
|
|
273
|
-
]);
|
|
274
|
-
if (hasSkill) return "skill";
|
|
275
|
-
if (hasAgent) return "agent";
|
|
276
|
-
return null;
|
|
277
|
-
}
|
|
278
|
-
async function collectFilesRecursive(absDir, entryRoot, out) {
|
|
279
|
-
let dirents;
|
|
280
|
-
try {
|
|
281
|
-
dirents = await fs2.readdir(absDir, { withFileTypes: true });
|
|
282
|
-
} catch {
|
|
283
|
-
return;
|
|
284
|
-
}
|
|
285
|
-
for (const d of dirents) {
|
|
286
|
-
if (SKIP_DIRS.has(d.name)) continue;
|
|
287
|
-
const full = path2.join(absDir, d.name);
|
|
288
|
-
if (d.isDirectory()) {
|
|
289
|
-
await collectFilesRecursive(full, entryRoot, out);
|
|
290
|
-
} else if (d.isFile()) {
|
|
291
|
-
const content = await fs2.readFile(full, "utf8");
|
|
292
|
-
out.push({ path: path2.relative(entryRoot, full), content });
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
export {
|
|
297
|
-
SkillNotFoundError,
|
|
298
|
-
SkillStore,
|
|
299
|
-
extractFrontmatter
|
|
300
|
-
};
|
|
301
|
-
//# sourceMappingURL=skill-store.mjs.map
|
|
1
|
+
import { n as extractFrontmatter, r as SkillNotFoundError, t as SkillStore } from "./skill-store-Bym2yinU.mjs";
|
|
2
|
+
export { SkillNotFoundError, SkillStore, extractFrontmatter };
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { R as getRepoDir } from "./userHome-CcnAQPG1.mjs";
|
|
2
|
+
import * as fs from "node:fs/promises";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
//#region src/submit/types.ts
|
|
5
|
+
/** Sentinel error thrown by SubmitClient.submit() / validate(). */
|
|
6
|
+
var SubmitError = class extends Error {
|
|
7
|
+
constructor(reason, detail, options) {
|
|
8
|
+
super(`submit failed (${reason}): ${detail}`);
|
|
9
|
+
this.name = "SubmitError";
|
|
10
|
+
this.reason = reason;
|
|
11
|
+
this.detail = detail;
|
|
12
|
+
this.status = options?.status;
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
//#endregion
|
|
16
|
+
//#region src/submit/index.ts
|
|
17
|
+
var SubmitClient = class {
|
|
18
|
+
constructor(opts) {
|
|
19
|
+
this.git = opts.gitClient;
|
|
20
|
+
this.getRepoBranch = opts.getRepoBranch ?? (() => void 0);
|
|
21
|
+
this.fetcher = opts.fetcher ?? globalThis.fetch;
|
|
22
|
+
this.defaultServerBaseUrl = opts.defaultServerBaseUrl ?? "http://localhost:3000";
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Validate-only path. Useful for the UI's "Save Draft" flow which
|
|
26
|
+
* wants to surface validation errors without committing or pushing.
|
|
27
|
+
*/
|
|
28
|
+
async validate(req) {
|
|
29
|
+
const url = `${this.defaultServerBaseUrl}/api/v1/skills/validate`;
|
|
30
|
+
const res = await this.fetcher(url, {
|
|
31
|
+
method: "POST",
|
|
32
|
+
headers: { "content-type": "application/json" },
|
|
33
|
+
body: JSON.stringify(req)
|
|
34
|
+
});
|
|
35
|
+
if (!res.ok) throw new SubmitError("network_error", `validate request failed: HTTP ${res.status}`);
|
|
36
|
+
return await res.json();
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Full submit pipeline. Throws `SubmitError` on:
|
|
40
|
+
* - validate deny (reason echoed)
|
|
41
|
+
* - network failure (network_error)
|
|
42
|
+
* - local write failure
|
|
43
|
+
* - commit/push failure
|
|
44
|
+
*/
|
|
45
|
+
async submit(repoId, skillName, files, opts = {}) {
|
|
46
|
+
const v = await this.validate({
|
|
47
|
+
repoId,
|
|
48
|
+
skillName,
|
|
49
|
+
files
|
|
50
|
+
});
|
|
51
|
+
if (!v.allow) throw new SubmitError(v.reason ?? "unknown", v.detail ?? "validation denied");
|
|
52
|
+
const localRepoPath = getRepoDir(repoId);
|
|
53
|
+
const targetDir = path.join(localRepoPath, "skills", skillName);
|
|
54
|
+
await fs.mkdir(targetDir, { recursive: true });
|
|
55
|
+
for (const f of files) {
|
|
56
|
+
const full = path.join(targetDir, f.path);
|
|
57
|
+
await fs.mkdir(path.dirname(full), { recursive: true });
|
|
58
|
+
const tmp = `${full}.${process.pid}.${Date.now()}.tmp`;
|
|
59
|
+
await fs.writeFile(tmp, f.content, "utf8");
|
|
60
|
+
await fs.rename(tmp, full);
|
|
61
|
+
}
|
|
62
|
+
const commitMessage = `feat(skills): add ${skillName}`;
|
|
63
|
+
const { commitSha } = await this.git.commit(localRepoPath, commitMessage);
|
|
64
|
+
let pushedRef;
|
|
65
|
+
let pushedSha;
|
|
66
|
+
if (!opts.skipPush) {
|
|
67
|
+
const branch = opts.branch ?? this.getRepoBranch(repoId) ?? "main";
|
|
68
|
+
const pushResult = await this.git.push(repoId, localRepoPath, branch);
|
|
69
|
+
pushedRef = pushResult.ref;
|
|
70
|
+
pushedSha = pushResult.commitSha;
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
repoId,
|
|
74
|
+
skillName,
|
|
75
|
+
commitSha,
|
|
76
|
+
pushedRef,
|
|
77
|
+
pushedSha
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
//#endregion
|
|
82
|
+
export { SubmitError as n, SubmitClient as t };
|
|
83
|
+
|
|
84
|
+
//# sourceMappingURL=submit-Cg7wSa3x.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"submit-Cg7wSa3x.mjs","names":[],"sources":["../src/submit/types.ts","../src/submit/index.ts"],"sourcesContent":["/**\n * Skill & Agent v2 — SubmitClient Types (M4)\n *\n * Mirrors the server's POST /api/v1/skills/validate contract (M2\n * SubmitApi). Defined here as a separate types file so the test\n * fixtures + the client can both import without circular deps.\n */\n\n/** Reasons the server may deny a submission. */\nexport type DenyReason =\n\t| \"repo_not_writable\"\n\t| \"file_too_large\"\n\t| \"path_traversal\"\n\t| \"invalid_frontmatter\"\n\t| \"name_conflict\"\n\t/** Local-client-side errors that don't come from the server. */\n\t| \"network_error\"\n\t| \"write_error\"\n\t| \"commit_error\"\n\t| \"push_error\"\n\t| \"unknown\";\n\n/** Request payload. Identical to the server's SubmitValidationRequest. */\nexport interface SubmitValidationRequest {\n\trepoId: string;\n\tskillName: string;\n\tfiles: Array<{ path: string; content: string }>;\n}\n\n/** Response payload. Identical to the server's SubmitValidationResponse. */\nexport interface SubmitValidationResponse {\n\tallow: boolean;\n\treason?: DenyReason;\n\tdetail?: string;\n}\n\n/** Sentinel error thrown by SubmitClient.submit() / validate(). */\nexport class SubmitError extends Error {\n\treadonly reason: DenyReason;\n\treadonly detail: string;\n\treadonly status?: number;\n\n\tconstructor(reason: DenyReason, detail: string, options?: { status?: number }) {\n\t\tsuper(`submit failed (${reason}): ${detail}`);\n\t\tthis.name = \"SubmitError\";\n\t\tthis.reason = reason;\n\t\tthis.detail = detail;\n\t\tthis.status = options?.status;\n\t}\n}\n","import * as fs from \"node:fs/promises\";\nimport * as path from \"node:path\";\nimport type { GitClient } from \"../git-client\";\nimport { getRepoDir } from \"../paths/userHome\";\nimport type { SkillFile } from \"../skill-store/types\";\nimport type { SubmitValidationRequest, SubmitValidationResponse } from \"./types\";\nimport { SubmitError } from \"./types\";\n\n// Re-export so consumers (bridge handlers, CLI commands) can detect\n// SubmitError via `instanceof` without reaching into the internal\n// `./types` module.\nexport { SubmitError } from \"./types\";\n\n/**\n * Skill & Agent v2 — SubmitClient (M4)\n *\n * SubmitClient orchestrates the \"validate then push\" flow described\n * in docs/architecture/skill-agent-v2-repo.md §5.7:\n *\n * 1. **validate** — POST the candidate files to the server's\n * `/api/v1/skills/validate` endpoint (5 deny reasons: repo_not_\n * writable, file_too_large, path_traversal, invalid_frontmatter,\n * name_conflict).\n * 2. **write** — materialize the files into the local repo clone at\n * `~/.serviceme/repos/<repoId>/skills/<name>/` (or `agents/`).\n * 3. **commit** — `git add . && git commit -m \"feat(skills): add <name>\"`.\n * 4. **push** — `git push origin <branch>` via the server proxy.\n *\n * The client is intentionally thin: it does NOT do its own validation\n * (the server is the gate), and it does NOT cache anything across\n * calls. The single source of truth for \"is this submission allowed?\"\n * is the server's validate endpoint.\n *\n * @see docs/architecture/skill-agent-v2-repo.md §5.7 SubmitClient\n */\n\nexport interface SubmitOptions {\n\t/** Override the server's validate URL. Defaults to `http://localhost:3000/api/v1`. */\n\tserverBaseUrl?: string;\n\t/** Override the branch to push. Defaults to the repo's `branch` field. */\n\tbranch?: string;\n\t/** Skip the actual push (for tests + dry-runs). When true, step 4 returns a synthetic PushResult. */\n\tskipPush?: boolean;\n}\n\nexport interface SubmitResult {\n\trepoId: string;\n\tskillName: string;\n\tcommitSha: string;\n\tpushedRef?: string;\n\tpushedSha?: string;\n}\n\nexport interface SubmitClientOptions {\n\tgitClient: GitClient;\n\t/** Lookup the default branch for a repo (config-driven). */\n\tgetRepoBranch?: (repoId: string) => string | undefined;\n\t/** HTTP fetch impl (defaults to the global `fetch`). */\n\tfetcher?: typeof fetch;\n\t/** Default server base URL when no override is supplied. */\n\tdefaultServerBaseUrl?: string;\n}\n\nexport class SubmitClient {\n\tprivate readonly git: GitClient;\n\tprivate readonly getRepoBranch: (repoId: string) => string | undefined;\n\tprivate readonly fetcher: typeof fetch;\n\tprivate readonly defaultServerBaseUrl: string;\n\n\tconstructor(opts: SubmitClientOptions) {\n\t\tthis.git = opts.gitClient;\n\t\tthis.getRepoBranch = opts.getRepoBranch ?? (() => undefined);\n\t\tthis.fetcher = opts.fetcher ?? (globalThis.fetch as typeof fetch);\n\t\tthis.defaultServerBaseUrl = opts.defaultServerBaseUrl ?? \"http://localhost:3000\";\n\t}\n\n\t/**\n\t * Validate-only path. Useful for the UI's \"Save Draft\" flow which\n\t * wants to surface validation errors without committing or pushing.\n\t */\n\tasync validate(req: SubmitValidationRequest): Promise<SubmitValidationResponse> {\n\t\tconst url = `${this.defaultServerBaseUrl}/api/v1/skills/validate`;\n\t\tconst res = await this.fetcher(url, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: { \"content-type\": \"application/json\" },\n\t\t\tbody: JSON.stringify(req),\n\t\t});\n\t\tif (!res.ok) {\n\t\t\tthrow new SubmitError(\"network_error\", `validate request failed: HTTP ${res.status}`);\n\t\t}\n\t\treturn (await res.json()) as SubmitValidationResponse;\n\t}\n\n\t/**\n\t * Full submit pipeline. Throws `SubmitError` on:\n\t * - validate deny (reason echoed)\n\t * - network failure (network_error)\n\t * - local write failure\n\t * - commit/push failure\n\t */\n\tasync submit(\n\t\trepoId: string,\n\t\tskillName: string,\n\t\tfiles: SkillFile[],\n\t\topts: SubmitOptions = {}\n\t): Promise<SubmitResult> {\n\t\t// (1) Validate\n\t\tconst v = await this.validate({ repoId, skillName, files });\n\t\tif (!v.allow) {\n\t\t\tthrow new SubmitError(v.reason ?? \"unknown\", v.detail ?? \"validation denied\");\n\t\t}\n\n\t\t// (2) Write files into the local repo clone\n\t\tconst localRepoPath = getRepoDir(repoId);\n\t\tconst targetDir = path.join(localRepoPath, \"skills\", skillName);\n\t\tawait fs.mkdir(targetDir, { recursive: true });\n\t\tfor (const f of files) {\n\t\t\tconst full = path.join(targetDir, f.path);\n\t\t\tawait fs.mkdir(path.dirname(full), { recursive: true });\n\t\t\tconst tmp = `${full}.${process.pid}.${Date.now()}.tmp`;\n\t\t\tawait fs.writeFile(tmp, f.content, \"utf8\");\n\t\t\tawait fs.rename(tmp, full);\n\t\t}\n\n\t\t// (3) Commit (convention: `feat(skills): add <name>`)\n\t\tconst commitMessage = `feat(skills): add ${skillName}`;\n\t\tconst { commitSha } = await this.git.commit(localRepoPath, commitMessage);\n\n\t\t// (4) Push\n\t\tlet pushedRef: string | undefined;\n\t\tlet pushedSha: string | undefined;\n\t\tif (!opts.skipPush) {\n\t\t\tconst branch = opts.branch ?? this.getRepoBranch(repoId) ?? \"main\";\n\t\t\tconst pushResult = await this.git.push(repoId, localRepoPath, branch);\n\t\t\tpushedRef = pushResult.ref;\n\t\t\tpushedSha = pushResult.commitSha;\n\t\t}\n\n\t\treturn {\n\t\t\trepoId,\n\t\t\tskillName,\n\t\t\tcommitSha,\n\t\t\tpushedRef,\n\t\t\tpushedSha,\n\t\t};\n\t}\n}\n"],"mappings":";;;;;AAqCA,IAAa,cAAb,cAAiC,MAAM;CAKtC,YAAY,QAAoB,QAAgB,SAA+B;EAC9E,MAAM,kBAAkB,OAAO,KAAK,QAAQ;EAC5C,KAAK,OAAO;EACZ,KAAK,SAAS;EACd,KAAK,SAAS;EACd,KAAK,SAAS,SAAS;CACxB;AACD;;;ACcA,IAAa,eAAb,MAA0B;CAMzB,YAAY,MAA2B;EACtC,KAAK,MAAM,KAAK;EAChB,KAAK,gBAAgB,KAAK,wBAAwB,KAAA;EAClD,KAAK,UAAU,KAAK,WAAY,WAAW;EAC3C,KAAK,uBAAuB,KAAK,wBAAwB;CAC1D;;;;;CAMA,MAAM,SAAS,KAAiE;EAC/E,MAAM,MAAM,GAAG,KAAK,qBAAqB;EACzC,MAAM,MAAM,MAAM,KAAK,QAAQ,KAAK;GACnC,QAAQ;GACR,SAAS,EAAE,gBAAgB,mBAAmB;GAC9C,MAAM,KAAK,UAAU,GAAG;EACzB,CAAC;EACD,IAAI,CAAC,IAAI,IACR,MAAM,IAAI,YAAY,iBAAiB,iCAAiC,IAAI,QAAQ;EAErF,OAAQ,MAAM,IAAI,KAAK;CACxB;;;;;;;;CASA,MAAM,OACL,QACA,WACA,OACA,OAAsB,CAAC,GACC;EAExB,MAAM,IAAI,MAAM,KAAK,SAAS;GAAE;GAAQ;GAAW;EAAM,CAAC;EAC1D,IAAI,CAAC,EAAE,OACN,MAAM,IAAI,YAAY,EAAE,UAAU,WAAW,EAAE,UAAU,mBAAmB;EAI7E,MAAM,gBAAgB,WAAW,MAAM;EACvC,MAAM,YAAY,KAAK,KAAK,eAAe,UAAU,SAAS;EAC9D,MAAM,GAAG,MAAM,WAAW,EAAE,WAAW,KAAK,CAAC;EAC7C,KAAK,MAAM,KAAK,OAAO;GACtB,MAAM,OAAO,KAAK,KAAK,WAAW,EAAE,IAAI;GACxC,MAAM,GAAG,MAAM,KAAK,QAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;GACtD,MAAM,MAAM,GAAG,KAAK,GAAG,QAAQ,IAAI,GAAG,KAAK,IAAI,EAAE;GACjD,MAAM,GAAG,UAAU,KAAK,EAAE,SAAS,MAAM;GACzC,MAAM,GAAG,OAAO,KAAK,IAAI;EAC1B;EAGA,MAAM,gBAAgB,qBAAqB;EAC3C,MAAM,EAAE,cAAc,MAAM,KAAK,IAAI,OAAO,eAAe,aAAa;EAGxE,IAAI;EACJ,IAAI;EACJ,IAAI,CAAC,KAAK,UAAU;GACnB,MAAM,SAAS,KAAK,UAAU,KAAK,cAAc,MAAM,KAAK;GAC5D,MAAM,aAAa,MAAM,KAAK,IAAI,KAAK,QAAQ,eAAe,MAAM;GACpE,YAAY,WAAW;GACvB,YAAY,WAAW;EACxB;EAEA,OAAO;GACN;GACA;GACA;GACA;GACA;EACD;CACD;AACD"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
const require_rolldown_runtime = require("./rolldown-runtime-VH7oDXx4.js");
|
|
2
|
+
const require_userHome = require("./userHome-BquDeFVE.js");
|
|
3
|
+
let node_fs_promises = require("node:fs/promises");
|
|
4
|
+
node_fs_promises = require_rolldown_runtime.__toESM(node_fs_promises);
|
|
5
|
+
let node_path = require("node:path");
|
|
6
|
+
node_path = require_rolldown_runtime.__toESM(node_path);
|
|
7
|
+
//#region src/submit/types.ts
|
|
8
|
+
/** Sentinel error thrown by SubmitClient.submit() / validate(). */
|
|
9
|
+
var SubmitError = class extends Error {
|
|
10
|
+
constructor(reason, detail, options) {
|
|
11
|
+
super(`submit failed (${reason}): ${detail}`);
|
|
12
|
+
this.name = "SubmitError";
|
|
13
|
+
this.reason = reason;
|
|
14
|
+
this.detail = detail;
|
|
15
|
+
this.status = options?.status;
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
//#endregion
|
|
19
|
+
//#region src/submit/index.ts
|
|
20
|
+
var SubmitClient = class {
|
|
21
|
+
constructor(opts) {
|
|
22
|
+
this.git = opts.gitClient;
|
|
23
|
+
this.getRepoBranch = opts.getRepoBranch ?? (() => void 0);
|
|
24
|
+
this.fetcher = opts.fetcher ?? globalThis.fetch;
|
|
25
|
+
this.defaultServerBaseUrl = opts.defaultServerBaseUrl ?? "http://localhost:3000";
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Validate-only path. Useful for the UI's "Save Draft" flow which
|
|
29
|
+
* wants to surface validation errors without committing or pushing.
|
|
30
|
+
*/
|
|
31
|
+
async validate(req) {
|
|
32
|
+
const url = `${this.defaultServerBaseUrl}/api/v1/skills/validate`;
|
|
33
|
+
const res = await this.fetcher(url, {
|
|
34
|
+
method: "POST",
|
|
35
|
+
headers: { "content-type": "application/json" },
|
|
36
|
+
body: JSON.stringify(req)
|
|
37
|
+
});
|
|
38
|
+
if (!res.ok) throw new SubmitError("network_error", `validate request failed: HTTP ${res.status}`);
|
|
39
|
+
return await res.json();
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Full submit pipeline. Throws `SubmitError` on:
|
|
43
|
+
* - validate deny (reason echoed)
|
|
44
|
+
* - network failure (network_error)
|
|
45
|
+
* - local write failure
|
|
46
|
+
* - commit/push failure
|
|
47
|
+
*/
|
|
48
|
+
async submit(repoId, skillName, files, opts = {}) {
|
|
49
|
+
const v = await this.validate({
|
|
50
|
+
repoId,
|
|
51
|
+
skillName,
|
|
52
|
+
files
|
|
53
|
+
});
|
|
54
|
+
if (!v.allow) throw new SubmitError(v.reason ?? "unknown", v.detail ?? "validation denied");
|
|
55
|
+
const localRepoPath = require_userHome.getRepoDir(repoId);
|
|
56
|
+
const targetDir = node_path.join(localRepoPath, "skills", skillName);
|
|
57
|
+
await node_fs_promises.mkdir(targetDir, { recursive: true });
|
|
58
|
+
for (const f of files) {
|
|
59
|
+
const full = node_path.join(targetDir, f.path);
|
|
60
|
+
await node_fs_promises.mkdir(node_path.dirname(full), { recursive: true });
|
|
61
|
+
const tmp = `${full}.${process.pid}.${Date.now()}.tmp`;
|
|
62
|
+
await node_fs_promises.writeFile(tmp, f.content, "utf8");
|
|
63
|
+
await node_fs_promises.rename(tmp, full);
|
|
64
|
+
}
|
|
65
|
+
const commitMessage = `feat(skills): add ${skillName}`;
|
|
66
|
+
const { commitSha } = await this.git.commit(localRepoPath, commitMessage);
|
|
67
|
+
let pushedRef;
|
|
68
|
+
let pushedSha;
|
|
69
|
+
if (!opts.skipPush) {
|
|
70
|
+
const branch = opts.branch ?? this.getRepoBranch(repoId) ?? "main";
|
|
71
|
+
const pushResult = await this.git.push(repoId, localRepoPath, branch);
|
|
72
|
+
pushedRef = pushResult.ref;
|
|
73
|
+
pushedSha = pushResult.commitSha;
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
repoId,
|
|
77
|
+
skillName,
|
|
78
|
+
commitSha,
|
|
79
|
+
pushedRef,
|
|
80
|
+
pushedSha
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
//#endregion
|
|
85
|
+
Object.defineProperty(exports, "SubmitClient", {
|
|
86
|
+
enumerable: true,
|
|
87
|
+
get: function() {
|
|
88
|
+
return SubmitClient;
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
Object.defineProperty(exports, "SubmitError", {
|
|
92
|
+
enumerable: true,
|
|
93
|
+
get: function() {
|
|
94
|
+
return SubmitError;
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
//# sourceMappingURL=submit-CltE6YOZ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"submit-CltE6YOZ.js","names":["getRepoDir","path","fs"],"sources":["../src/submit/types.ts","../src/submit/index.ts"],"sourcesContent":["/**\n * Skill & Agent v2 — SubmitClient Types (M4)\n *\n * Mirrors the server's POST /api/v1/skills/validate contract (M2\n * SubmitApi). Defined here as a separate types file so the test\n * fixtures + the client can both import without circular deps.\n */\n\n/** Reasons the server may deny a submission. */\nexport type DenyReason =\n\t| \"repo_not_writable\"\n\t| \"file_too_large\"\n\t| \"path_traversal\"\n\t| \"invalid_frontmatter\"\n\t| \"name_conflict\"\n\t/** Local-client-side errors that don't come from the server. */\n\t| \"network_error\"\n\t| \"write_error\"\n\t| \"commit_error\"\n\t| \"push_error\"\n\t| \"unknown\";\n\n/** Request payload. Identical to the server's SubmitValidationRequest. */\nexport interface SubmitValidationRequest {\n\trepoId: string;\n\tskillName: string;\n\tfiles: Array<{ path: string; content: string }>;\n}\n\n/** Response payload. Identical to the server's SubmitValidationResponse. */\nexport interface SubmitValidationResponse {\n\tallow: boolean;\n\treason?: DenyReason;\n\tdetail?: string;\n}\n\n/** Sentinel error thrown by SubmitClient.submit() / validate(). */\nexport class SubmitError extends Error {\n\treadonly reason: DenyReason;\n\treadonly detail: string;\n\treadonly status?: number;\n\n\tconstructor(reason: DenyReason, detail: string, options?: { status?: number }) {\n\t\tsuper(`submit failed (${reason}): ${detail}`);\n\t\tthis.name = \"SubmitError\";\n\t\tthis.reason = reason;\n\t\tthis.detail = detail;\n\t\tthis.status = options?.status;\n\t}\n}\n","import * as fs from \"node:fs/promises\";\nimport * as path from \"node:path\";\nimport type { GitClient } from \"../git-client\";\nimport { getRepoDir } from \"../paths/userHome\";\nimport type { SkillFile } from \"../skill-store/types\";\nimport type { SubmitValidationRequest, SubmitValidationResponse } from \"./types\";\nimport { SubmitError } from \"./types\";\n\n// Re-export so consumers (bridge handlers, CLI commands) can detect\n// SubmitError via `instanceof` without reaching into the internal\n// `./types` module.\nexport { SubmitError } from \"./types\";\n\n/**\n * Skill & Agent v2 — SubmitClient (M4)\n *\n * SubmitClient orchestrates the \"validate then push\" flow described\n * in docs/architecture/skill-agent-v2-repo.md §5.7:\n *\n * 1. **validate** — POST the candidate files to the server's\n * `/api/v1/skills/validate` endpoint (5 deny reasons: repo_not_\n * writable, file_too_large, path_traversal, invalid_frontmatter,\n * name_conflict).\n * 2. **write** — materialize the files into the local repo clone at\n * `~/.serviceme/repos/<repoId>/skills/<name>/` (or `agents/`).\n * 3. **commit** — `git add . && git commit -m \"feat(skills): add <name>\"`.\n * 4. **push** — `git push origin <branch>` via the server proxy.\n *\n * The client is intentionally thin: it does NOT do its own validation\n * (the server is the gate), and it does NOT cache anything across\n * calls. The single source of truth for \"is this submission allowed?\"\n * is the server's validate endpoint.\n *\n * @see docs/architecture/skill-agent-v2-repo.md §5.7 SubmitClient\n */\n\nexport interface SubmitOptions {\n\t/** Override the server's validate URL. Defaults to `http://localhost:3000/api/v1`. */\n\tserverBaseUrl?: string;\n\t/** Override the branch to push. Defaults to the repo's `branch` field. */\n\tbranch?: string;\n\t/** Skip the actual push (for tests + dry-runs). When true, step 4 returns a synthetic PushResult. */\n\tskipPush?: boolean;\n}\n\nexport interface SubmitResult {\n\trepoId: string;\n\tskillName: string;\n\tcommitSha: string;\n\tpushedRef?: string;\n\tpushedSha?: string;\n}\n\nexport interface SubmitClientOptions {\n\tgitClient: GitClient;\n\t/** Lookup the default branch for a repo (config-driven). */\n\tgetRepoBranch?: (repoId: string) => string | undefined;\n\t/** HTTP fetch impl (defaults to the global `fetch`). */\n\tfetcher?: typeof fetch;\n\t/** Default server base URL when no override is supplied. */\n\tdefaultServerBaseUrl?: string;\n}\n\nexport class SubmitClient {\n\tprivate readonly git: GitClient;\n\tprivate readonly getRepoBranch: (repoId: string) => string | undefined;\n\tprivate readonly fetcher: typeof fetch;\n\tprivate readonly defaultServerBaseUrl: string;\n\n\tconstructor(opts: SubmitClientOptions) {\n\t\tthis.git = opts.gitClient;\n\t\tthis.getRepoBranch = opts.getRepoBranch ?? (() => undefined);\n\t\tthis.fetcher = opts.fetcher ?? (globalThis.fetch as typeof fetch);\n\t\tthis.defaultServerBaseUrl = opts.defaultServerBaseUrl ?? \"http://localhost:3000\";\n\t}\n\n\t/**\n\t * Validate-only path. Useful for the UI's \"Save Draft\" flow which\n\t * wants to surface validation errors without committing or pushing.\n\t */\n\tasync validate(req: SubmitValidationRequest): Promise<SubmitValidationResponse> {\n\t\tconst url = `${this.defaultServerBaseUrl}/api/v1/skills/validate`;\n\t\tconst res = await this.fetcher(url, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: { \"content-type\": \"application/json\" },\n\t\t\tbody: JSON.stringify(req),\n\t\t});\n\t\tif (!res.ok) {\n\t\t\tthrow new SubmitError(\"network_error\", `validate request failed: HTTP ${res.status}`);\n\t\t}\n\t\treturn (await res.json()) as SubmitValidationResponse;\n\t}\n\n\t/**\n\t * Full submit pipeline. Throws `SubmitError` on:\n\t * - validate deny (reason echoed)\n\t * - network failure (network_error)\n\t * - local write failure\n\t * - commit/push failure\n\t */\n\tasync submit(\n\t\trepoId: string,\n\t\tskillName: string,\n\t\tfiles: SkillFile[],\n\t\topts: SubmitOptions = {}\n\t): Promise<SubmitResult> {\n\t\t// (1) Validate\n\t\tconst v = await this.validate({ repoId, skillName, files });\n\t\tif (!v.allow) {\n\t\t\tthrow new SubmitError(v.reason ?? \"unknown\", v.detail ?? \"validation denied\");\n\t\t}\n\n\t\t// (2) Write files into the local repo clone\n\t\tconst localRepoPath = getRepoDir(repoId);\n\t\tconst targetDir = path.join(localRepoPath, \"skills\", skillName);\n\t\tawait fs.mkdir(targetDir, { recursive: true });\n\t\tfor (const f of files) {\n\t\t\tconst full = path.join(targetDir, f.path);\n\t\t\tawait fs.mkdir(path.dirname(full), { recursive: true });\n\t\t\tconst tmp = `${full}.${process.pid}.${Date.now()}.tmp`;\n\t\t\tawait fs.writeFile(tmp, f.content, \"utf8\");\n\t\t\tawait fs.rename(tmp, full);\n\t\t}\n\n\t\t// (3) Commit (convention: `feat(skills): add <name>`)\n\t\tconst commitMessage = `feat(skills): add ${skillName}`;\n\t\tconst { commitSha } = await this.git.commit(localRepoPath, commitMessage);\n\n\t\t// (4) Push\n\t\tlet pushedRef: string | undefined;\n\t\tlet pushedSha: string | undefined;\n\t\tif (!opts.skipPush) {\n\t\t\tconst branch = opts.branch ?? this.getRepoBranch(repoId) ?? \"main\";\n\t\t\tconst pushResult = await this.git.push(repoId, localRepoPath, branch);\n\t\t\tpushedRef = pushResult.ref;\n\t\t\tpushedSha = pushResult.commitSha;\n\t\t}\n\n\t\treturn {\n\t\t\trepoId,\n\t\t\tskillName,\n\t\t\tcommitSha,\n\t\t\tpushedRef,\n\t\t\tpushedSha,\n\t\t};\n\t}\n}\n"],"mappings":";;;;;;;;AAqCA,IAAa,cAAb,cAAiC,MAAM;CAKtC,YAAY,QAAoB,QAAgB,SAA+B;EAC9E,MAAM,kBAAkB,OAAO,KAAK,QAAQ;EAC5C,KAAK,OAAO;EACZ,KAAK,SAAS;EACd,KAAK,SAAS;EACd,KAAK,SAAS,SAAS;CACxB;AACD;;;ACcA,IAAa,eAAb,MAA0B;CAMzB,YAAY,MAA2B;EACtC,KAAK,MAAM,KAAK;EAChB,KAAK,gBAAgB,KAAK,wBAAwB,KAAA;EAClD,KAAK,UAAU,KAAK,WAAY,WAAW;EAC3C,KAAK,uBAAuB,KAAK,wBAAwB;CAC1D;;;;;CAMA,MAAM,SAAS,KAAiE;EAC/E,MAAM,MAAM,GAAG,KAAK,qBAAqB;EACzC,MAAM,MAAM,MAAM,KAAK,QAAQ,KAAK;GACnC,QAAQ;GACR,SAAS,EAAE,gBAAgB,mBAAmB;GAC9C,MAAM,KAAK,UAAU,GAAG;EACzB,CAAC;EACD,IAAI,CAAC,IAAI,IACR,MAAM,IAAI,YAAY,iBAAiB,iCAAiC,IAAI,QAAQ;EAErF,OAAQ,MAAM,IAAI,KAAK;CACxB;;;;;;;;CASA,MAAM,OACL,QACA,WACA,OACA,OAAsB,CAAC,GACC;EAExB,MAAM,IAAI,MAAM,KAAK,SAAS;GAAE;GAAQ;GAAW;EAAM,CAAC;EAC1D,IAAI,CAAC,EAAE,OACN,MAAM,IAAI,YAAY,EAAE,UAAU,WAAW,EAAE,UAAU,mBAAmB;EAI7E,MAAM,gBAAgBA,iBAAAA,WAAW,MAAM;EACvC,MAAM,YAAYC,UAAK,KAAK,eAAe,UAAU,SAAS;EAC9D,MAAMC,iBAAG,MAAM,WAAW,EAAE,WAAW,KAAK,CAAC;EAC7C,KAAK,MAAM,KAAK,OAAO;GACtB,MAAM,OAAOD,UAAK,KAAK,WAAW,EAAE,IAAI;GACxC,MAAMC,iBAAG,MAAMD,UAAK,QAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;GACtD,MAAM,MAAM,GAAG,KAAK,GAAG,QAAQ,IAAI,GAAG,KAAK,IAAI,EAAE;GACjD,MAAMC,iBAAG,UAAU,KAAK,EAAE,SAAS,MAAM;GACzC,MAAMA,iBAAG,OAAO,KAAK,IAAI;EAC1B;EAGA,MAAM,gBAAgB,qBAAqB;EAC3C,MAAM,EAAE,cAAc,MAAM,KAAK,IAAI,OAAO,eAAe,aAAa;EAGxE,IAAI;EACJ,IAAI;EACJ,IAAI,CAAC,KAAK,UAAU;GACnB,MAAM,SAAS,KAAK,UAAU,KAAK,cAAc,MAAM,KAAK;GAC5D,MAAM,aAAa,MAAM,KAAK,IAAI,KAAK,QAAQ,eAAe,MAAM;GACpE,YAAY,WAAW;GACvB,YAAY,WAAW;EACxB;EAEA,OAAO;GACN;GACA;GACA;GACA;GACA;EACD;CACD;AACD"}
|
package/dist/submit.d.mts
CHANGED
|
@@ -1,3 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import 'node:child_process';
|
|
1
|
+
import { a as SubmitError, i as SubmitResult, n as SubmitClientOptions, r as SubmitOptions, t as SubmitClient } from "./index-2rX86yMq.mjs";
|
|
2
|
+
export { SubmitClient, SubmitClientOptions, SubmitError, SubmitOptions, SubmitResult };
|
package/dist/submit.d.ts
CHANGED
|
@@ -1,3 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import 'node:child_process';
|
|
1
|
+
import { a as SubmitError, i as SubmitResult, n as SubmitClientOptions, r as SubmitOptions, t as SubmitClient } from "./index-Dp6FwWKh.js";
|
|
2
|
+
export { SubmitClient, SubmitClientOptions, SubmitError, SubmitOptions, SubmitResult };
|