agenticloops 0.1.0 → 0.1.1
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/bin/cli.mjs +1 -1
- package/package.json +1 -1
- package/src/config.mjs +1 -1
- package/src/install.mjs +2 -1
- package/src/skills.mjs +45 -3
package/bin/cli.mjs
CHANGED
|
@@ -7,7 +7,7 @@ import { listRecords, parseSchedule } from "../src/schedule.mjs";
|
|
|
7
7
|
import { fetchInstalls } from "../src/telemetry.mjs";
|
|
8
8
|
import { c, sym, fail, info, CliError } from "../src/util.mjs";
|
|
9
9
|
|
|
10
|
-
const VERSION = "0.1.
|
|
10
|
+
const VERSION = "0.1.1";
|
|
11
11
|
|
|
12
12
|
function parseArgs(argv) {
|
|
13
13
|
const flags = {};
|
package/package.json
CHANGED
package/src/config.mjs
CHANGED
|
@@ -15,4 +15,4 @@ export const SKILLS_REGISTRY_URL = "https://skills.sh/index.json";
|
|
|
15
15
|
export const SPEC_VERSION = "0.1";
|
|
16
16
|
|
|
17
17
|
// User-Agent so server logs can attribute pings to the CLI (still anonymous).
|
|
18
|
-
export const UA = `agenticloops-cli/0.1.
|
|
18
|
+
export const UA = `agenticloops-cli/0.1.1 (+${SITE})`;
|
package/src/install.mjs
CHANGED
|
@@ -76,7 +76,8 @@ export async function install(ref, opts = {}) {
|
|
|
76
76
|
step("Skills");
|
|
77
77
|
for (const s of skillPlan) {
|
|
78
78
|
if (s.action === "host") info(`${s.id} ${c.dim("(provided by harness — skipped)")}`);
|
|
79
|
-
else if (s.action === "unresolved")
|
|
79
|
+
else if (s.action === "unresolved")
|
|
80
|
+
warn(`${s.id} — skipped (loop will degrade)${s.reason ? c.dim(" — " + s.reason) : ""}`);
|
|
80
81
|
else {
|
|
81
82
|
const r = installSkill(s, { dryRun });
|
|
82
83
|
if (r.ok) ok(`${s.id} ${c.dim("<- " + s.owner + "/" + s.repo)}`);
|
package/src/skills.mjs
CHANGED
|
@@ -20,6 +20,27 @@ export function parseSkillEntry(entry) {
|
|
|
20
20
|
return { id: parts[0], bare: true };
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
// Does <owner/repo> actually contain a skill named <id>? A skills.sh skill is a
|
|
24
|
+
// top-level directory with a SKILL.md. Returns "yes" | "no" | "unknown" — we
|
|
25
|
+
// only downgrade to unresolved on a definite "no" (404), so a network blip
|
|
26
|
+
// never turns into a false negative.
|
|
27
|
+
async function existsInRepo(owner, repo, id, timeout = 8000) {
|
|
28
|
+
const url = `https://raw.githubusercontent.com/${owner}/${repo}/HEAD/${id}/SKILL.md`;
|
|
29
|
+
const ctrl = new AbortController();
|
|
30
|
+
const t = setTimeout(() => ctrl.abort(), timeout);
|
|
31
|
+
try {
|
|
32
|
+
let r = await fetch(url, { method: "HEAD", signal: ctrl.signal });
|
|
33
|
+
if (r.status !== 200 && r.status !== 404) r = await fetch(url, { signal: ctrl.signal });
|
|
34
|
+
if (r.status === 200) return "yes";
|
|
35
|
+
if (r.status === 404) return "no";
|
|
36
|
+
return "unknown";
|
|
37
|
+
} catch {
|
|
38
|
+
return "unknown";
|
|
39
|
+
} finally {
|
|
40
|
+
clearTimeout(t);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
23
44
|
async function resolveBare(id) {
|
|
24
45
|
const r = await fetchJson(SKILLS_REGISTRY_URL);
|
|
25
46
|
if (!r.ok || !r.json) return null;
|
|
@@ -47,16 +68,37 @@ export async function planSkills(skills = [], hostSkills = []) {
|
|
|
47
68
|
continue;
|
|
48
69
|
}
|
|
49
70
|
if (s.owner && s.repo) {
|
|
50
|
-
plan.push(
|
|
71
|
+
plan.push(await verifiedEntry(s.id, s.owner, s.repo));
|
|
51
72
|
continue;
|
|
52
73
|
}
|
|
53
74
|
const resolved = await resolveBare(s.id);
|
|
54
|
-
if (resolved) plan.push(
|
|
55
|
-
else
|
|
75
|
+
if (resolved) plan.push(await verifiedEntry(resolved.id, resolved.owner, resolved.repo));
|
|
76
|
+
else
|
|
77
|
+
plan.push({
|
|
78
|
+
id: s.id,
|
|
79
|
+
action: "unresolved",
|
|
80
|
+
reason: "no source given and not in the skills registry (may be a harness built-in)",
|
|
81
|
+
});
|
|
56
82
|
}
|
|
57
83
|
return plan;
|
|
58
84
|
}
|
|
59
85
|
|
|
86
|
+
// Confirm the skill is actually fetchable before promising to install it — this
|
|
87
|
+
// is what keeps --dry-run honest (it predicts the real outcome instead of
|
|
88
|
+
// claiming a resolve that the real install would skip).
|
|
89
|
+
async function verifiedEntry(id, owner, repo) {
|
|
90
|
+
const present = await existsInRepo(owner, repo, id);
|
|
91
|
+
if (present === "no")
|
|
92
|
+
return {
|
|
93
|
+
id,
|
|
94
|
+
action: "unresolved",
|
|
95
|
+
owner,
|
|
96
|
+
repo,
|
|
97
|
+
reason: `not found in ${owner}/${repo} (may be a harness built-in)`,
|
|
98
|
+
};
|
|
99
|
+
return { id, action: "fetch", owner, repo };
|
|
100
|
+
}
|
|
101
|
+
|
|
60
102
|
// Execute one fetch via the skills.sh CLI: `npx skills add <owner/repo> --skill <id>`.
|
|
61
103
|
export function installSkill({ owner, repo, id }, { dryRun = false } = {}) {
|
|
62
104
|
const args = ["--yes", "skills", "add", `${owner}/${repo}`, "--skill", id];
|