selftune 0.2.22 → 0.2.24
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/CHANGELOG.md +6 -0
- package/README.md +95 -15
- package/apps/local-dashboard/dist/assets/index-DgY2KGP-.css +1 -0
- package/apps/local-dashboard/dist/assets/index-Dmx7LPVX.js +15 -0
- package/apps/local-dashboard/dist/assets/vendor-react-C5oyHiV1.js +11 -0
- package/apps/local-dashboard/dist/assets/{vendor-table-BIiI3YhS.js → vendor-table-Bc_bbKd8.js} +1 -1
- package/apps/local-dashboard/dist/assets/vendor-ui-B3BPIYy7.js +1 -0
- package/apps/local-dashboard/dist/index.html +5 -5
- package/cli/selftune/adapters/codex/install.ts +310 -78
- package/cli/selftune/adapters/opencode/install.ts +3 -4
- package/cli/selftune/adapters/pi/hook.ts +273 -0
- package/cli/selftune/adapters/pi/install.ts +207 -0
- package/cli/selftune/alpha-upload/build-payloads.ts +3 -3
- package/cli/selftune/alpha-upload/stage-canonical.ts +17 -11
- package/cli/selftune/auto-update.ts +200 -8
- package/cli/selftune/canonical-export.ts +55 -25
- package/cli/selftune/command-surface.ts +397 -0
- package/cli/selftune/constants.ts +10 -1
- package/cli/selftune/contribute/contribute.ts +64 -13
- package/cli/selftune/contribution-config.ts +57 -3
- package/cli/selftune/contribution-preferences.ts +117 -0
- package/cli/selftune/contribution-signals.ts +8 -4
- package/cli/selftune/contribution-staging.ts +13 -2
- package/cli/selftune/contributions.ts +55 -121
- package/cli/selftune/creator-contributions.ts +29 -10
- package/cli/selftune/cron/setup.ts +7 -3
- package/cli/selftune/dashboard-contract.ts +87 -0
- package/cli/selftune/dashboard-server.ts +168 -17
- package/cli/selftune/dashboard.ts +350 -17
- package/cli/selftune/eval/baseline.ts +21 -5
- package/cli/selftune/eval/execution-eval.ts +170 -0
- package/cli/selftune/eval/family-overlap.ts +2 -2
- package/cli/selftune/eval/hooks-to-evals.ts +228 -82
- package/cli/selftune/eval/import-skillsbench.ts +2 -2
- package/cli/selftune/eval/invocation-classifier.ts +56 -0
- package/cli/selftune/eval/synthetic-evals.ts +5 -3
- package/cli/selftune/eval/unit-test-cli.ts +7 -4
- package/cli/selftune/evolution/apply-proposal.ts +295 -0
- package/cli/selftune/evolution/engines/judge-engine.ts +96 -0
- package/cli/selftune/evolution/engines/replay-engine.ts +180 -0
- package/cli/selftune/evolution/evidence.ts +2 -6
- package/cli/selftune/evolution/evolve-body.ts +152 -38
- package/cli/selftune/evolution/evolve.ts +244 -52
- package/cli/selftune/evolution/rollback.ts +0 -1
- package/cli/selftune/evolution/validate-body.ts +111 -49
- package/cli/selftune/evolution/validate-host-replay.ts +510 -60
- package/cli/selftune/evolution/validate-proposal.ts +11 -150
- package/cli/selftune/evolution/validate-routing.ts +51 -108
- package/cli/selftune/evolution/validation-contract.ts +91 -0
- package/cli/selftune/grading/auto-grade.ts +11 -7
- package/cli/selftune/grading/grade-session.ts +10 -16
- package/cli/selftune/hooks/skill-eval.ts +2 -1
- package/cli/selftune/hooks-shared/types.ts +1 -0
- package/cli/selftune/index.ts +58 -15
- package/cli/selftune/ingestors/claude-replay.ts +15 -10
- package/cli/selftune/ingestors/codex-wrapper.ts +3 -3
- package/cli/selftune/ingestors/opencode-ingest.ts +2 -2
- package/cli/selftune/ingestors/pi-ingest.ts +727 -0
- package/cli/selftune/init.ts +38 -4
- package/cli/selftune/localdb/direct-write.ts +120 -1
- package/cli/selftune/localdb/materialize.ts +6 -7
- package/cli/selftune/localdb/queries/cron.ts +34 -0
- package/cli/selftune/localdb/queries/dashboard.ts +834 -0
- package/cli/selftune/localdb/queries/evolution.ts +158 -0
- package/cli/selftune/localdb/queries/execution.ts +133 -0
- package/cli/selftune/localdb/queries/json.ts +18 -0
- package/cli/selftune/localdb/queries/monitoring.ts +263 -0
- package/cli/selftune/localdb/queries/raw.ts +95 -0
- package/cli/selftune/localdb/queries/staging.ts +270 -0
- package/cli/selftune/localdb/queries/trust.ts +392 -0
- package/cli/selftune/localdb/queries.ts +60 -2162
- package/cli/selftune/localdb/schema.ts +59 -0
- package/cli/selftune/monitoring/watch.ts +96 -29
- package/cli/selftune/normalization.ts +3 -0
- package/cli/selftune/observability.ts +12 -3
- package/cli/selftune/orchestrate/cli.ts +161 -0
- package/cli/selftune/orchestrate/execute.ts +295 -0
- package/cli/selftune/orchestrate/finalize.ts +157 -0
- package/cli/selftune/orchestrate/locks.ts +40 -0
- package/cli/selftune/orchestrate/plan.ts +131 -0
- package/cli/selftune/orchestrate/post-run.ts +59 -0
- package/cli/selftune/orchestrate/prepare.ts +334 -0
- package/cli/selftune/orchestrate/report.ts +182 -0
- package/cli/selftune/orchestrate/runtime.ts +120 -0
- package/cli/selftune/orchestrate/signals.ts +48 -0
- package/cli/selftune/orchestrate.ts +162 -1142
- package/cli/selftune/registry/client.ts +74 -0
- package/cli/selftune/registry/history.ts +54 -0
- package/cli/selftune/registry/index.ts +90 -0
- package/cli/selftune/registry/install.ts +141 -0
- package/cli/selftune/registry/list.ts +44 -0
- package/cli/selftune/registry/push.ts +171 -0
- package/cli/selftune/registry/rollback.ts +49 -0
- package/cli/selftune/registry/status.ts +62 -0
- package/cli/selftune/registry/sync.ts +125 -0
- package/cli/selftune/repair/skill-usage.ts +9 -3
- package/cli/selftune/routes/overview.ts +5 -2
- package/cli/selftune/routes/skill-report.ts +15 -2
- package/cli/selftune/schedule.ts +5 -5
- package/cli/selftune/status.ts +70 -2
- package/cli/selftune/sync.ts +127 -23
- package/cli/selftune/testing-readiness.ts +597 -0
- package/cli/selftune/types.ts +46 -5
- package/cli/selftune/uninstall.ts +2 -1
- package/cli/selftune/utils/canonical-log.ts +1 -9
- package/cli/selftune/utils/cli-error.ts +9 -0
- package/cli/selftune/utils/jsonl.ts +1 -30
- package/cli/selftune/utils/llm-call.ts +126 -6
- package/cli/selftune/utils/skill-discovery.ts +24 -0
- package/cli/selftune/workflows/proposals.ts +184 -0
- package/cli/selftune/workflows/skill-scaffold.ts +241 -0
- package/cli/selftune/workflows/workflows.ts +100 -26
- package/node_modules/@selftune/telemetry-contract/fixtures/complete-push.ts +1 -1
- package/node_modules/@selftune/telemetry-contract/fixtures/evidence-only-push.ts +2 -2
- package/node_modules/@selftune/telemetry-contract/fixtures/golden.test.ts +0 -1
- package/node_modules/@selftune/telemetry-contract/fixtures/partial-push-no-sessions.ts +1 -1
- package/node_modules/@selftune/telemetry-contract/fixtures/partial-push-unresolved-parents.ts +2 -2
- package/node_modules/@selftune/telemetry-contract/package.json +1 -1
- package/node_modules/@selftune/telemetry-contract/src/index.ts +1 -0
- package/node_modules/@selftune/telemetry-contract/src/schemas.ts +63 -5
- package/node_modules/@selftune/telemetry-contract/src/types.ts +97 -7
- package/node_modules/@selftune/telemetry-contract/tests/compatibility.test.ts +0 -1
- package/package.json +25 -9
- package/packages/dashboard-core/AGENTS.md +18 -0
- package/packages/dashboard-core/README.md +30 -0
- package/packages/dashboard-core/index.ts +3 -0
- package/packages/dashboard-core/package.json +39 -0
- package/packages/dashboard-core/src/chrome/DashboardChrome.tsx +74 -0
- package/packages/dashboard-core/src/chrome/DashboardHeader.tsx +200 -0
- package/packages/dashboard-core/src/chrome/DashboardSidebar.tsx +219 -0
- package/packages/dashboard-core/src/chrome/RuntimeBadge.tsx +46 -0
- package/packages/dashboard-core/src/chrome/index.ts +14 -0
- package/packages/dashboard-core/src/chrome/types.ts +81 -0
- package/packages/dashboard-core/src/chrome/utils.ts +23 -0
- package/packages/dashboard-core/src/gates/FeatureGate.tsx +11 -0
- package/packages/dashboard-core/src/gates/LockedRoute.tsx +29 -0
- package/packages/dashboard-core/src/gates/UpgradeCard.tsx +89 -0
- package/packages/dashboard-core/src/gates/index.ts +3 -0
- package/packages/dashboard-core/src/host/DashboardHostProvider.tsx +62 -0
- package/packages/dashboard-core/src/host/adapter.ts +47 -0
- package/packages/dashboard-core/src/host/capabilities.ts +55 -0
- package/packages/dashboard-core/src/host/index.ts +3 -0
- package/packages/dashboard-core/src/models/analytics.ts +39 -0
- package/packages/dashboard-core/src/models/index.ts +4 -0
- package/packages/dashboard-core/src/models/overview.ts +98 -0
- package/packages/dashboard-core/src/models/runtime.ts +7 -0
- package/packages/dashboard-core/src/models/skills.ts +34 -0
- package/packages/dashboard-core/src/routes/index.ts +2 -0
- package/packages/dashboard-core/src/routes/manifest.test.ts +70 -0
- package/packages/dashboard-core/src/routes/manifest.ts +451 -0
- package/packages/dashboard-core/src/routes/types.ts +39 -0
- package/packages/dashboard-core/src/screens/analytics/AnalyticsScreen.tsx +278 -0
- package/packages/dashboard-core/src/screens/analytics/index.ts +1 -0
- package/packages/dashboard-core/src/screens/index.ts +37 -0
- package/packages/dashboard-core/src/screens/overview/OverviewComparisonSurface.test.ts +101 -0
- package/packages/dashboard-core/src/screens/overview/OverviewComparisonSurface.tsx +393 -0
- package/packages/dashboard-core/src/screens/overview/OverviewCompositionSurface.test.tsx +113 -0
- package/packages/dashboard-core/src/screens/overview/OverviewCompositionSurface.tsx +72 -0
- package/packages/dashboard-core/src/screens/overview/OverviewCoreSurface.tsx +71 -0
- package/packages/dashboard-core/src/screens/overview/OverviewOnboardingBanner.tsx +90 -0
- package/packages/dashboard-core/src/screens/overview/OverviewRunSummary.tsx +40 -0
- package/packages/dashboard-core/src/screens/overview/index.ts +16 -0
- package/packages/dashboard-core/src/screens/overview/types.ts +13 -0
- package/packages/dashboard-core/src/screens/skill-report/SkillReportDailyBreakdownSection.tsx +99 -0
- package/packages/dashboard-core/src/screens/skill-report/SkillReportDataQualityTabContent.tsx +35 -0
- package/packages/dashboard-core/src/screens/skill-report/SkillReportEvidenceRail.tsx +71 -0
- package/packages/dashboard-core/src/screens/skill-report/SkillReportEvidenceSection.tsx +63 -0
- package/packages/dashboard-core/src/screens/skill-report/SkillReportEvidenceTabContent.tsx +25 -0
- package/packages/dashboard-core/src/screens/skill-report/SkillReportInvocationsSection.tsx +24 -0
- package/packages/dashboard-core/src/screens/skill-report/SkillReportMissedQueriesSection.tsx +79 -0
- package/packages/dashboard-core/src/screens/skill-report/SkillReportScaffold.tsx +150 -0
- package/packages/dashboard-core/src/screens/skill-report/SkillReportSections.test.tsx +224 -0
- package/packages/dashboard-core/src/screens/skill-report/SkillReportTabs.test.tsx +76 -0
- package/packages/dashboard-core/src/screens/skill-report/SkillReportTabs.tsx +88 -0
- package/packages/dashboard-core/src/screens/skill-report/SkillReportTrendSection.tsx +33 -0
- package/packages/dashboard-core/src/screens/skill-report/SkillReportTrustBadge.tsx +67 -0
- package/packages/dashboard-core/src/screens/skill-report/index.ts +45 -0
- package/packages/dashboard-core/src/screens/skills/SkillsLibraryScreen.tsx +162 -0
- package/packages/dashboard-core/src/screens/skills/index.ts +6 -0
- package/packages/telemetry-contract/fixtures/complete-push.ts +1 -1
- package/packages/telemetry-contract/fixtures/evidence-only-push.ts +2 -2
- package/packages/telemetry-contract/fixtures/golden.test.ts +0 -1
- package/packages/telemetry-contract/fixtures/partial-push-no-sessions.ts +1 -1
- package/packages/telemetry-contract/fixtures/partial-push-unresolved-parents.ts +2 -2
- package/packages/telemetry-contract/package.json +1 -1
- package/packages/telemetry-contract/src/index.ts +1 -0
- package/packages/telemetry-contract/src/schemas.ts +63 -5
- package/packages/telemetry-contract/src/types.ts +97 -7
- package/packages/telemetry-contract/tests/compatibility.test.ts +0 -1
- package/packages/ui/AGENTS.md +16 -0
- package/packages/ui/README.md +1 -1
- package/packages/ui/package.json +1 -1
- package/packages/ui/src/components/ActivityTimeline.tsx +152 -168
- package/packages/ui/src/components/AnalyticsCharts.tsx +344 -0
- package/packages/ui/src/components/EvidenceViewer.tsx +229 -464
- package/packages/ui/src/components/EvolutionTimeline.tsx +34 -87
- package/packages/ui/src/components/InfoTip.tsx +1 -2
- package/packages/ui/src/components/InvocationsPanel.tsx +413 -0
- package/packages/ui/src/components/JobHistoryTimeline.tsx +156 -0
- package/packages/ui/src/components/OrchestrateRunsPanel.tsx +18 -36
- package/packages/ui/src/components/OverviewPanels.tsx +693 -0
- package/packages/ui/src/components/PipelineStatusBar.tsx +65 -0
- package/packages/ui/src/components/SkillReportGuide.tsx +215 -0
- package/packages/ui/src/components/SkillReportPanels.tsx +919 -0
- package/packages/ui/src/components/SkillsLibrary.tsx +437 -0
- package/packages/ui/src/components/index.ts +56 -1
- package/packages/ui/src/components/section-cards.tsx +18 -35
- package/packages/ui/src/components/skill-health-grid.tsx +47 -37
- package/packages/ui/src/lib/constants.tsx +0 -1
- package/packages/ui/src/primitives/card.tsx +1 -1
- package/packages/ui/src/primitives/checkbox.tsx +1 -1
- package/packages/ui/src/primitives/dropdown-menu.tsx +2 -2
- package/packages/ui/src/primitives/select.tsx +2 -2
- package/packages/ui/src/primitives/tabs.tsx +7 -6
- package/packages/ui/src/types.ts +182 -4
- package/skill/SKILL.md +130 -318
- package/skill/agents/diagnosis-analyst.md +3 -3
- package/skill/agents/evolution-reviewer.md +3 -3
- package/skill/agents/integration-guide.md +3 -3
- package/skill/agents/pattern-analyst.md +2 -2
- package/skill/references/cli-quick-reference.md +89 -0
- package/skill/references/creator-playbook.md +131 -0
- package/skill/references/examples.md +48 -0
- package/skill/references/troubleshooting.md +47 -0
- package/skill/references/version-history.md +1 -1
- package/skill/selftune.contribute.json +11 -0
- package/skill/{Workflows → workflows}/Baseline.md +20 -1
- package/skill/{Workflows → workflows}/Contribute.md +23 -10
- package/skill/{Workflows → workflows}/Contributions.md +13 -5
- package/skill/workflows/CreateTestDeploy.md +170 -0
- package/skill/{Workflows → workflows}/CreatorContributions.md +18 -6
- package/skill/{Workflows → workflows}/Cron.md +1 -1
- package/skill/{Workflows → workflows}/Dashboard.md +20 -0
- package/skill/{Workflows → workflows}/Doctor.md +1 -1
- package/skill/{Workflows → workflows}/Evals.md +67 -2
- package/skill/{Workflows → workflows}/Evolve.md +119 -30
- package/skill/{Workflows → workflows}/EvolveBody.md +41 -1
- package/skill/{Workflows → workflows}/Grade.md +1 -1
- package/skill/{Workflows → workflows}/Ingest.md +60 -2
- package/skill/{Workflows → workflows}/Initialize.md +16 -9
- package/skill/{Workflows → workflows}/Orchestrate.md +13 -3
- package/skill/{Workflows → workflows}/PlatformHooks.md +19 -3
- package/skill/workflows/Registry.md +99 -0
- package/skill/{Workflows → workflows}/Schedule.md +3 -3
- package/skill/workflows/SignalsDashboard.md +87 -0
- package/skill/{Workflows → workflows}/Sync.md +3 -1
- package/skill/{Workflows → workflows}/UnitTest.md +19 -0
- package/skill/{Workflows → workflows}/Watch.md +42 -2
- package/skill/{Workflows → workflows}/Workflows.md +39 -2
- package/apps/local-dashboard/dist/assets/index-D8O-RG1I.js +0 -60
- package/apps/local-dashboard/dist/assets/index-_EcLywDg.css +0 -1
- package/apps/local-dashboard/dist/assets/vendor-react-CKkiCskZ.js +0 -11
- package/apps/local-dashboard/dist/assets/vendor-ui-CGEmUayx.js +0 -12
- package/cli/selftune/utils/html.ts +0 -27
- package/packages/ui/src/components/RecentActivityFeed.tsx +0 -117
- /package/skill/{Workflows → workflows}/AlphaUpload.md +0 -0
- /package/skill/{Workflows → workflows}/AutoActivation.md +0 -0
- /package/skill/{Workflows → workflows}/Badge.md +0 -0
- /package/skill/{Workflows → workflows}/Composability.md +0 -0
- /package/skill/{Workflows → workflows}/EvolutionMemory.md +0 -0
- /package/skill/{Workflows → workflows}/ExportCanonical.md +0 -0
- /package/skill/{Workflows → workflows}/Hook.md +0 -0
- /package/skill/{Workflows → workflows}/ImportSkillsBench.md +0 -0
- /package/skill/{Workflows → workflows}/Quickstart.md +0 -0
- /package/skill/{Workflows → workflows}/Recover.md +0 -0
- /package/skill/{Workflows → workflows}/RepairSkillUsage.md +0 -0
- /package/skill/{Workflows → workflows}/Replay.md +0 -0
- /package/skill/{Workflows → workflows}/Rollback.md +0 -0
- /package/skill/{Workflows → workflows}/Telemetry.md +0 -0
- /package/skill/{Workflows → workflows}/Uninstall.md +0 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Registry HTTP client. Never throws — returns typed results.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { getSelftuneVersion } from "../utils/selftune-meta.js";
|
|
6
|
+
|
|
7
|
+
export interface RegistryResult<T = unknown> {
|
|
8
|
+
success: boolean;
|
|
9
|
+
data?: T;
|
|
10
|
+
error?: string;
|
|
11
|
+
status?: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function getConfig(): { apiUrl: string; apiKey: string } | null {
|
|
15
|
+
try {
|
|
16
|
+
const configPath = `${process.env.HOME}/.selftune/config.json`;
|
|
17
|
+
const raw = require("fs").readFileSync(configPath, "utf-8");
|
|
18
|
+
const config = JSON.parse(raw);
|
|
19
|
+
const apiUrl = config?.alpha?.cloud_api_url || "https://api.selftune.dev";
|
|
20
|
+
const apiKey = config?.alpha?.api_key;
|
|
21
|
+
if (!apiKey) return null;
|
|
22
|
+
return { apiUrl, apiKey };
|
|
23
|
+
} catch {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export async function registryRequest<T>(
|
|
29
|
+
method: string,
|
|
30
|
+
path: string,
|
|
31
|
+
opts?: { body?: unknown; formData?: FormData },
|
|
32
|
+
): Promise<RegistryResult<T>> {
|
|
33
|
+
const config = getConfig();
|
|
34
|
+
if (!config) {
|
|
35
|
+
return { success: false, error: "Not authenticated. Run 'selftune alpha upload' to set up." };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
const headers: Record<string, string> = {
|
|
40
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
41
|
+
"User-Agent": `selftune/${getSelftuneVersion()}`,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
let fetchBody: BodyInit | undefined;
|
|
45
|
+
if (opts?.formData) {
|
|
46
|
+
fetchBody = opts.formData;
|
|
47
|
+
// Don't set Content-Type — fetch sets multipart boundary automatically
|
|
48
|
+
} else if (opts?.body) {
|
|
49
|
+
headers["Content-Type"] = "application/json";
|
|
50
|
+
fetchBody = JSON.stringify(opts.body);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const response = await fetch(`${config.apiUrl}/api/v1/registry${path}`, {
|
|
54
|
+
method,
|
|
55
|
+
headers,
|
|
56
|
+
body: fetchBody,
|
|
57
|
+
signal: AbortSignal.timeout(60_000),
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const text = await response.text();
|
|
61
|
+
if (!response.ok) {
|
|
62
|
+
return {
|
|
63
|
+
success: false,
|
|
64
|
+
error: `HTTP ${response.status}: ${text.slice(0, 300)}`,
|
|
65
|
+
status: response.status,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const data = text ? JSON.parse(text) : {};
|
|
70
|
+
return { success: true, data: data as T, status: response.status };
|
|
71
|
+
} catch (err) {
|
|
72
|
+
return { success: false, error: err instanceof Error ? err.message : String(err) };
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* selftune registry history — Show version timeline for a skill.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { registryRequest } from "./client.js";
|
|
6
|
+
|
|
7
|
+
export async function cliMain() {
|
|
8
|
+
const args = process.argv.slice(2);
|
|
9
|
+
const name = args.find((a) => !a.startsWith("--"));
|
|
10
|
+
|
|
11
|
+
if (!name) {
|
|
12
|
+
console.error(JSON.stringify({ error: "Usage: selftune registry history <name>" }));
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const listResult = await registryRequest<{ entries: Array<{ id: string }> }>(
|
|
17
|
+
"GET",
|
|
18
|
+
`?name=${encodeURIComponent(name)}`,
|
|
19
|
+
);
|
|
20
|
+
if (!listResult.success || !listResult.data?.entries?.length) {
|
|
21
|
+
console.error(JSON.stringify({ error: `Skill '${name}' not found in registry` }));
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const entryId = listResult.data.entries[0].id;
|
|
26
|
+
const result = await registryRequest<{
|
|
27
|
+
versions: Array<{
|
|
28
|
+
version: string;
|
|
29
|
+
is_current: boolean;
|
|
30
|
+
rolled_back: boolean;
|
|
31
|
+
aggregate_pass_rate: number | null;
|
|
32
|
+
aggregate_sessions: number;
|
|
33
|
+
change_summary: string | null;
|
|
34
|
+
pushed_at: string;
|
|
35
|
+
}>;
|
|
36
|
+
}>("GET", `/${entryId}/versions`);
|
|
37
|
+
|
|
38
|
+
if (!result.success) {
|
|
39
|
+
console.error(JSON.stringify({ error: result.error }));
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const versions = result.data?.versions || [];
|
|
44
|
+
const timeline = versions.map((v) => ({
|
|
45
|
+
version: v.version,
|
|
46
|
+
status: v.is_current ? "current" : v.rolled_back ? "rolled_back" : "previous",
|
|
47
|
+
pass_rate: v.aggregate_pass_rate,
|
|
48
|
+
sessions: v.aggregate_sessions,
|
|
49
|
+
summary: v.change_summary,
|
|
50
|
+
pushed_at: v.pushed_at,
|
|
51
|
+
}));
|
|
52
|
+
|
|
53
|
+
console.log(JSON.stringify({ name, versions: timeline }));
|
|
54
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* selftune registry — Team skill distribution.
|
|
3
|
+
*
|
|
4
|
+
* Subcommands:
|
|
5
|
+
* push Push current skill folder as a new version
|
|
6
|
+
* install Download and install a skill from the registry
|
|
7
|
+
* sync Check for updates and pull latest versions
|
|
8
|
+
* status Show installed entries and version drift
|
|
9
|
+
* rollback Rollback a skill to a previous version
|
|
10
|
+
* history Show version timeline for a skill
|
|
11
|
+
* list Show all published entries in the org
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { CLIError } from "../utils/cli-error.js";
|
|
15
|
+
|
|
16
|
+
const sub = process.argv[2];
|
|
17
|
+
|
|
18
|
+
export async function cliMain() {
|
|
19
|
+
if (!sub || sub === "--help" || sub === "-h") {
|
|
20
|
+
console.log(`selftune registry — Team skill distribution
|
|
21
|
+
|
|
22
|
+
Usage:
|
|
23
|
+
selftune registry <subcommand> [options]
|
|
24
|
+
|
|
25
|
+
Subcommands:
|
|
26
|
+
push [name] Push current skill folder as a new version
|
|
27
|
+
install <name> Download and install a skill from the registry
|
|
28
|
+
sync Check for updates and pull latest versions
|
|
29
|
+
status Show installed entries and version drift
|
|
30
|
+
rollback <name> Rollback to a previous version
|
|
31
|
+
history <name> Show version timeline
|
|
32
|
+
list Show all published entries
|
|
33
|
+
|
|
34
|
+
Options:
|
|
35
|
+
--version=<semver> Set version explicitly (push)
|
|
36
|
+
--summary=<text> Change summary (push)
|
|
37
|
+
--global Install to ~/.claude/skills/ (install)
|
|
38
|
+
--to=<version> Target version (rollback)
|
|
39
|
+
--reason=<text> Rollback reason (rollback)
|
|
40
|
+
`);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Strip 'registry' from argv so subcommands see the right args
|
|
45
|
+
process.argv = [process.argv[0], process.argv[1], ...process.argv.slice(3)];
|
|
46
|
+
|
|
47
|
+
switch (sub) {
|
|
48
|
+
case "push": {
|
|
49
|
+
const { cliMain } = await import("./push.js");
|
|
50
|
+
await cliMain();
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
case "install": {
|
|
54
|
+
const { cliMain } = await import("./install.js");
|
|
55
|
+
await cliMain();
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
case "sync": {
|
|
59
|
+
const { cliMain } = await import("./sync.js");
|
|
60
|
+
await cliMain();
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
case "status": {
|
|
64
|
+
const { cliMain } = await import("./status.js");
|
|
65
|
+
await cliMain();
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
case "rollback": {
|
|
69
|
+
const { cliMain } = await import("./rollback.js");
|
|
70
|
+
await cliMain();
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
case "history": {
|
|
74
|
+
const { cliMain } = await import("./history.js");
|
|
75
|
+
await cliMain();
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
case "list": {
|
|
79
|
+
const { cliMain } = await import("./list.js");
|
|
80
|
+
await cliMain();
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
default:
|
|
84
|
+
throw new CLIError(
|
|
85
|
+
`Unknown registry subcommand: ${sub}`,
|
|
86
|
+
"UNKNOWN_COMMAND",
|
|
87
|
+
"selftune registry --help",
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* selftune registry install — Download and extract a skill from the registry.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { readFileSync } from "node:fs";
|
|
6
|
+
import { mkdir, unlink, writeFile } from "node:fs/promises";
|
|
7
|
+
import { hostname } from "node:os";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
|
|
10
|
+
import { registryRequest } from "./client.js";
|
|
11
|
+
|
|
12
|
+
export async function cliMain() {
|
|
13
|
+
const args = process.argv.slice(2);
|
|
14
|
+
const name = args.find((a) => !a.startsWith("--"));
|
|
15
|
+
const globalFlag = args.includes("--global");
|
|
16
|
+
|
|
17
|
+
if (!name) {
|
|
18
|
+
console.error(
|
|
19
|
+
JSON.stringify({
|
|
20
|
+
error: "Usage: selftune registry install <name>",
|
|
21
|
+
guidance: { next_command: "selftune registry list" },
|
|
22
|
+
}),
|
|
23
|
+
);
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Find entry by name
|
|
28
|
+
const listResult = await registryRequest<{
|
|
29
|
+
entries: Array<{
|
|
30
|
+
id: string;
|
|
31
|
+
name: string;
|
|
32
|
+
current_version?: { id: string; version: string; content_hash: string };
|
|
33
|
+
}>;
|
|
34
|
+
}>("GET", `?name=${encodeURIComponent(name)}`);
|
|
35
|
+
|
|
36
|
+
if (!listResult.success || !listResult.data?.entries?.length) {
|
|
37
|
+
console.error(
|
|
38
|
+
JSON.stringify({
|
|
39
|
+
error: `Skill '${name}' not found in registry`,
|
|
40
|
+
guidance: { next_command: "selftune registry list" },
|
|
41
|
+
}),
|
|
42
|
+
);
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const entry = listResult.data.entries[0];
|
|
47
|
+
const entryId = entry.id;
|
|
48
|
+
|
|
49
|
+
// Get detail with versions
|
|
50
|
+
const detailResult = await registryRequest<{
|
|
51
|
+
entry: { id: string; name: string };
|
|
52
|
+
versions: Array<{ id: string; version: string; content_hash: string; is_current: boolean }>;
|
|
53
|
+
}>("GET", `/${entryId}`);
|
|
54
|
+
|
|
55
|
+
if (!detailResult.success) {
|
|
56
|
+
console.error(JSON.stringify({ error: detailResult.error }));
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const currentVersion = detailResult.data?.versions?.find((v) => v.is_current);
|
|
61
|
+
if (!currentVersion) {
|
|
62
|
+
console.error(JSON.stringify({ error: "No current version found" }));
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Request presigned download via sync
|
|
67
|
+
const syncResult = await registryRequest<{
|
|
68
|
+
entries: Array<{
|
|
69
|
+
download_url?: string;
|
|
70
|
+
latest_version: string;
|
|
71
|
+
latest_content_hash: string;
|
|
72
|
+
}>;
|
|
73
|
+
}>("POST", "/sync", {
|
|
74
|
+
body: { installations: [{ entry_id: entryId, current_version_hash: "none" }] },
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const downloadUrl = syncResult.data?.entries?.[0]?.download_url;
|
|
78
|
+
if (!downloadUrl) {
|
|
79
|
+
console.error(JSON.stringify({ error: "Could not get download URL" }));
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Download archive
|
|
84
|
+
console.log(`Installing ${name} v${currentVersion.version}...`);
|
|
85
|
+
const response = await fetch(downloadUrl, { signal: AbortSignal.timeout(60_000) });
|
|
86
|
+
if (!response.ok) {
|
|
87
|
+
console.error(JSON.stringify({ error: `Download failed: HTTP ${response.status}` }));
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
const archiveBuffer = Buffer.from(await response.arrayBuffer());
|
|
91
|
+
|
|
92
|
+
// Determine install path
|
|
93
|
+
const targetBase = globalFlag
|
|
94
|
+
? join(process.env.HOME || "~", ".claude", "skills")
|
|
95
|
+
: join(process.cwd(), ".claude", "skills");
|
|
96
|
+
const targetDir = join(targetBase, name);
|
|
97
|
+
|
|
98
|
+
// Extract archive
|
|
99
|
+
await mkdir(targetDir, { recursive: true });
|
|
100
|
+
const archivePath = `/tmp/selftune-install-${Date.now()}.tar.gz`;
|
|
101
|
+
await writeFile(archivePath, archiveBuffer);
|
|
102
|
+
const proc = Bun.spawn(["tar", "xzf", archivePath, "-C", targetDir], {
|
|
103
|
+
stdout: "ignore",
|
|
104
|
+
stderr: "pipe",
|
|
105
|
+
});
|
|
106
|
+
await proc.exited;
|
|
107
|
+
|
|
108
|
+
await unlink(archivePath).catch(() => {});
|
|
109
|
+
|
|
110
|
+
if (proc.exitCode !== 0) {
|
|
111
|
+
console.error(JSON.stringify({ error: "Failed to extract archive" }));
|
|
112
|
+
process.exit(1);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Record installation on server
|
|
116
|
+
await registryRequest("POST", `/${entryId}/install`, {
|
|
117
|
+
body: { install_path: targetDir, device_id: hostname() },
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// Update local state
|
|
121
|
+
const statePath = join(process.env.HOME || "~", ".selftune", "registry-state.json");
|
|
122
|
+
let state: Array<{ entryId: string; name: string; versionHash: string; installPath: string }> =
|
|
123
|
+
[];
|
|
124
|
+
try {
|
|
125
|
+
state = JSON.parse(readFileSync(statePath, "utf-8"));
|
|
126
|
+
} catch {}
|
|
127
|
+
state = state.filter((s) => s.entryId !== entryId);
|
|
128
|
+
state.push({ entryId, name, versionHash: currentVersion.content_hash, installPath: targetDir });
|
|
129
|
+
await mkdir(join(process.env.HOME || "~", ".selftune"), { recursive: true });
|
|
130
|
+
await writeFile(statePath, JSON.stringify(state, null, 2));
|
|
131
|
+
|
|
132
|
+
console.log(
|
|
133
|
+
JSON.stringify({
|
|
134
|
+
success: true,
|
|
135
|
+
name,
|
|
136
|
+
version: currentVersion.version,
|
|
137
|
+
path: targetDir,
|
|
138
|
+
global: globalFlag,
|
|
139
|
+
}),
|
|
140
|
+
);
|
|
141
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* selftune registry list — Show all published entries in the org.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { registryRequest } from "./client.js";
|
|
6
|
+
|
|
7
|
+
export async function cliMain() {
|
|
8
|
+
const result = await registryRequest<{
|
|
9
|
+
entries: Array<{
|
|
10
|
+
name: string;
|
|
11
|
+
entry_type: string;
|
|
12
|
+
description: string | null;
|
|
13
|
+
current_version?: { version: string };
|
|
14
|
+
pass_rate: number | null;
|
|
15
|
+
eval_count: number;
|
|
16
|
+
}>;
|
|
17
|
+
}>("GET", "");
|
|
18
|
+
|
|
19
|
+
if (!result.success) {
|
|
20
|
+
console.error(JSON.stringify({ error: result.error }));
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const entries = result.data?.entries || [];
|
|
25
|
+
if (entries.length === 0) {
|
|
26
|
+
console.log(
|
|
27
|
+
JSON.stringify({
|
|
28
|
+
message: "No entries in registry. Use 'selftune registry push' to publish a skill.",
|
|
29
|
+
}),
|
|
30
|
+
);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const table = entries.map((e) => ({
|
|
35
|
+
name: e.name,
|
|
36
|
+
type: e.entry_type,
|
|
37
|
+
version: e.current_version?.version || "—",
|
|
38
|
+
pass_rate: e.pass_rate,
|
|
39
|
+
eval_count: e.eval_count,
|
|
40
|
+
description: e.description,
|
|
41
|
+
}));
|
|
42
|
+
|
|
43
|
+
console.log(JSON.stringify({ entries: table, total: entries.length }));
|
|
44
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* selftune registry push — Archive and upload a skill folder as a new version.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { createHash } from "node:crypto";
|
|
6
|
+
import { readdir, readFile, stat, unlink } from "node:fs/promises";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
|
|
9
|
+
import { registryRequest } from "./client.js";
|
|
10
|
+
|
|
11
|
+
interface FileManifestEntry {
|
|
12
|
+
path: string;
|
|
13
|
+
hash: string;
|
|
14
|
+
size: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async function collectFiles(
|
|
18
|
+
dir: string,
|
|
19
|
+
base?: string,
|
|
20
|
+
): Promise<{ path: string; content: Buffer }[]> {
|
|
21
|
+
const files: { path: string; content: Buffer }[] = [];
|
|
22
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
23
|
+
for (const entry of entries) {
|
|
24
|
+
const fullPath = join(dir, entry.name);
|
|
25
|
+
const relPath = base ? join(base, entry.name) : entry.name;
|
|
26
|
+
if (
|
|
27
|
+
entry.name === ".git" ||
|
|
28
|
+
entry.name === "node_modules" ||
|
|
29
|
+
entry.name === ".env" ||
|
|
30
|
+
entry.name.startsWith(".env.")
|
|
31
|
+
)
|
|
32
|
+
continue;
|
|
33
|
+
if (entry.isDirectory()) {
|
|
34
|
+
files.push(...(await collectFiles(fullPath, relPath)));
|
|
35
|
+
} else {
|
|
36
|
+
files.push({ path: relPath, content: await readFile(fullPath) });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return files;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export async function cliMain() {
|
|
43
|
+
const args = process.argv.slice(2);
|
|
44
|
+
const nameArg = args.find((a) => !a.startsWith("--"));
|
|
45
|
+
const versionFlag = args.find((a) => a.startsWith("--version="))?.slice("--version=".length);
|
|
46
|
+
const summaryFlag = args.find((a) => a.startsWith("--summary="))?.slice("--summary=".length);
|
|
47
|
+
|
|
48
|
+
// Find skill folder
|
|
49
|
+
const cwd = process.cwd();
|
|
50
|
+
const skillMd = join(cwd, "SKILL.md");
|
|
51
|
+
try {
|
|
52
|
+
await stat(skillMd);
|
|
53
|
+
} catch {
|
|
54
|
+
console.error(
|
|
55
|
+
JSON.stringify({
|
|
56
|
+
error: "No SKILL.md found in current directory. Navigate to a skill folder first.",
|
|
57
|
+
guidance: { next_command: "cd <skill-directory>" },
|
|
58
|
+
}),
|
|
59
|
+
);
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Read SKILL.md to extract name and description
|
|
64
|
+
const skillContent = await readFile(skillMd, "utf-8");
|
|
65
|
+
const nameMatch = skillContent.match(/^name:\s*(.+)$/m);
|
|
66
|
+
const descMatch = skillContent.match(/^description:\s*(.+)$/m);
|
|
67
|
+
const name = nameArg || nameMatch?.[1]?.trim() || "unnamed-skill";
|
|
68
|
+
const description = descMatch?.[1]?.trim() || "";
|
|
69
|
+
|
|
70
|
+
// Collect all files
|
|
71
|
+
const files = await collectFiles(cwd);
|
|
72
|
+
const manifest: FileManifestEntry[] = files.map((f) => ({
|
|
73
|
+
path: f.path,
|
|
74
|
+
hash: createHash("sha256").update(f.content).digest("hex"),
|
|
75
|
+
size: f.content.length,
|
|
76
|
+
}));
|
|
77
|
+
|
|
78
|
+
// Create tar.gz archive using system tar (available on all platforms)
|
|
79
|
+
const archivePath = `/tmp/selftune-registry-${Date.now()}.tar.gz`;
|
|
80
|
+
const proc = Bun.spawn(
|
|
81
|
+
[
|
|
82
|
+
"tar",
|
|
83
|
+
"czf",
|
|
84
|
+
archivePath,
|
|
85
|
+
"-C",
|
|
86
|
+
cwd,
|
|
87
|
+
"--exclude=.git",
|
|
88
|
+
"--exclude=node_modules",
|
|
89
|
+
"--exclude=.env",
|
|
90
|
+
"--exclude=.env.*",
|
|
91
|
+
".",
|
|
92
|
+
],
|
|
93
|
+
{
|
|
94
|
+
stdout: "ignore",
|
|
95
|
+
stderr: "pipe",
|
|
96
|
+
},
|
|
97
|
+
);
|
|
98
|
+
await proc.exited;
|
|
99
|
+
if (proc.exitCode !== 0) {
|
|
100
|
+
console.error(JSON.stringify({ error: "Failed to create archive" }));
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
const archiveBuffer = await readFile(archivePath);
|
|
104
|
+
const archiveHash = createHash("sha256").update(archiveBuffer).digest("hex");
|
|
105
|
+
|
|
106
|
+
// Clean up temp file
|
|
107
|
+
await unlink(archivePath).catch(() => {});
|
|
108
|
+
|
|
109
|
+
// Determine version
|
|
110
|
+
const version = versionFlag || `0.1.${Date.now()}`;
|
|
111
|
+
|
|
112
|
+
// Build multipart form
|
|
113
|
+
const formData = new FormData();
|
|
114
|
+
formData.append(
|
|
115
|
+
"metadata",
|
|
116
|
+
JSON.stringify({
|
|
117
|
+
name,
|
|
118
|
+
entry_type: "skill",
|
|
119
|
+
description,
|
|
120
|
+
version,
|
|
121
|
+
change_summary: summaryFlag || undefined,
|
|
122
|
+
file_manifest: manifest,
|
|
123
|
+
content_hash: archiveHash,
|
|
124
|
+
}),
|
|
125
|
+
);
|
|
126
|
+
formData.append(
|
|
127
|
+
"archive",
|
|
128
|
+
new Blob([archiveBuffer], { type: "application/gzip" }),
|
|
129
|
+
`${name}.tar.gz`,
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
// Try to push as new version first, fall back to create
|
|
133
|
+
console.log(
|
|
134
|
+
`Pushing ${name} v${version} (${(archiveBuffer.length / 1024).toFixed(1)} KB, ${files.length} files)...`,
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
// First check if entry exists
|
|
138
|
+
const listResult = await registryRequest<{ entries: Array<{ id: string; name: string }> }>(
|
|
139
|
+
"GET",
|
|
140
|
+
`?name=${encodeURIComponent(name)}`,
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
let result;
|
|
144
|
+
if (listResult.success && listResult.data?.entries?.length) {
|
|
145
|
+
const entryId = listResult.data.entries[0].id;
|
|
146
|
+
result = await registryRequest("POST", `/${entryId}/versions`, { formData });
|
|
147
|
+
} else {
|
|
148
|
+
result = await registryRequest("POST", "", { formData });
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (result.success) {
|
|
152
|
+
console.log(
|
|
153
|
+
JSON.stringify({
|
|
154
|
+
success: true,
|
|
155
|
+
name,
|
|
156
|
+
version,
|
|
157
|
+
files: files.length,
|
|
158
|
+
size: archiveBuffer.length,
|
|
159
|
+
hash: archiveHash,
|
|
160
|
+
}),
|
|
161
|
+
);
|
|
162
|
+
} else {
|
|
163
|
+
console.error(
|
|
164
|
+
JSON.stringify({
|
|
165
|
+
error: result.error,
|
|
166
|
+
guidance: { next_command: "selftune registry list" },
|
|
167
|
+
}),
|
|
168
|
+
);
|
|
169
|
+
process.exit(1);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* selftune registry rollback — Rollback a skill to a previous version.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { registryRequest } from "./client.js";
|
|
6
|
+
|
|
7
|
+
export async function cliMain() {
|
|
8
|
+
const args = process.argv.slice(2);
|
|
9
|
+
const name = args.find((a) => !a.startsWith("--"));
|
|
10
|
+
const toVersion = args.find((a) => a.startsWith("--to="))?.slice("--to=".length);
|
|
11
|
+
const reason = args.find((a) => a.startsWith("--reason="))?.slice("--reason=".length);
|
|
12
|
+
|
|
13
|
+
if (!name) {
|
|
14
|
+
console.error(
|
|
15
|
+
JSON.stringify({
|
|
16
|
+
error: "Usage: selftune registry rollback <name> [--to=version] [--reason=text]",
|
|
17
|
+
}),
|
|
18
|
+
);
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Find entry
|
|
23
|
+
const listResult = await registryRequest<{ entries: Array<{ id: string; name: string }> }>(
|
|
24
|
+
"GET",
|
|
25
|
+
`?name=${encodeURIComponent(name)}`,
|
|
26
|
+
);
|
|
27
|
+
if (!listResult.success || !listResult.data?.entries?.length) {
|
|
28
|
+
console.error(JSON.stringify({ error: `Skill '${name}' not found in registry` }));
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const entryId = listResult.data.entries[0].id;
|
|
33
|
+
const result = await registryRequest("POST", `/${entryId}/rollback`, {
|
|
34
|
+
body: { target_version: toVersion, reason },
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
if (result.success) {
|
|
38
|
+
console.log(
|
|
39
|
+
JSON.stringify({
|
|
40
|
+
success: true,
|
|
41
|
+
name,
|
|
42
|
+
message: "Rolled back. Run 'selftune registry sync' to update local installations.",
|
|
43
|
+
}),
|
|
44
|
+
);
|
|
45
|
+
} else {
|
|
46
|
+
console.error(JSON.stringify({ error: result.error }));
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* selftune registry status — Show installed entries and version drift.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { readFile } from "node:fs/promises";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
|
|
8
|
+
import { registryRequest } from "./client.js";
|
|
9
|
+
|
|
10
|
+
export async function cliMain() {
|
|
11
|
+
const statePath = join(process.env.HOME || "~", ".selftune", "registry-state.json");
|
|
12
|
+
let state: Array<{ entryId: string; name: string; versionHash: string; installPath: string }>;
|
|
13
|
+
try {
|
|
14
|
+
state = JSON.parse(await readFile(statePath, "utf-8"));
|
|
15
|
+
} catch {
|
|
16
|
+
console.log(JSON.stringify({ message: "No registry installations found." }));
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (state.length === 0) {
|
|
21
|
+
console.log(JSON.stringify({ message: "No registry installations found." }));
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const syncResult = await registryRequest<{
|
|
26
|
+
entries: Array<{
|
|
27
|
+
entry_id: string;
|
|
28
|
+
name: string;
|
|
29
|
+
has_update: boolean;
|
|
30
|
+
latest_version: string;
|
|
31
|
+
current_version: string;
|
|
32
|
+
}>;
|
|
33
|
+
}>("POST", "/sync", {
|
|
34
|
+
body: {
|
|
35
|
+
installations: state.map((s) => ({
|
|
36
|
+
entry_id: s.entryId,
|
|
37
|
+
current_version_hash: s.versionHash,
|
|
38
|
+
})),
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
if (!syncResult.success) {
|
|
43
|
+
console.error(JSON.stringify({ error: syncResult.error }));
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const entries = syncResult.data?.entries || [];
|
|
48
|
+
const table = entries.map((e) => ({
|
|
49
|
+
name: e.name,
|
|
50
|
+
installed: e.current_version,
|
|
51
|
+
latest: e.latest_version,
|
|
52
|
+
status: e.has_update ? "behind" : "up-to-date",
|
|
53
|
+
}));
|
|
54
|
+
|
|
55
|
+
console.log(
|
|
56
|
+
JSON.stringify({
|
|
57
|
+
installations: table,
|
|
58
|
+
total: state.length,
|
|
59
|
+
updates_available: entries.filter((e) => e.has_update).length,
|
|
60
|
+
}),
|
|
61
|
+
);
|
|
62
|
+
}
|