|
@@ -0,0 +1,123 @@
|
|
|
|
|
+/*
|
|
|
|
|
+Copyright 2026 LocoStack.
|
|
|
|
|
+
|
|
|
|
|
+Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
|
+you may not use this file except in compliance with the License.
|
|
|
|
|
+You may obtain a copy of the License at
|
|
|
|
|
+
|
|
|
|
|
+ http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
+
|
|
|
|
|
+Unless required by applicable law or agreed to in writing, software
|
|
|
|
|
+distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
|
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
|
+See the License for the specific language governing permissions and
|
|
|
|
|
+limitations under the License.
|
|
|
|
|
+*/
|
|
|
|
|
+
|
|
|
|
|
+package v1alpha1
|
|
|
|
|
+
|
|
|
|
|
+import (
|
|
|
|
|
+ "strings"
|
|
|
|
|
+
|
|
|
|
|
+ corev1 "k8s.io/api/core/v1"
|
|
|
|
|
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
|
|
|
+)
|
|
|
|
|
+
|
|
|
|
|
+type SkillFileEntry struct {
|
|
|
|
|
+ // path is the relative path of the file within the skill.
|
|
|
|
|
+ Path string `json:"path"`
|
|
|
|
|
+
|
|
|
|
|
+ // content is the content of the file.
|
|
|
|
|
+ Content string `json:"content"`
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// SkillSpec defines the desired state of Skill
|
|
|
|
|
+type SkillSpec struct {
|
|
|
|
|
+ // stackRef associates this tool with a stack.
|
|
|
|
|
+ // +optional
|
|
|
|
|
+ StackRef *corev1.LocalObjectReference `json:"stackRef,omitempty"`
|
|
|
|
|
+
|
|
|
|
|
+ // skillName is the name of the skill, part of the "SKILL.md" file.
|
|
|
|
|
+ // +kubebuilder:validation:Required
|
|
|
|
|
+ SkillName string `json:"skillName"`
|
|
|
|
|
+
|
|
|
|
|
+ // description provides a brief summary of the skill, part of the "SKILL.md" file.
|
|
|
|
|
+ // +kubebuilder:validation:Required
|
|
|
|
|
+ Description string `json:"description"`
|
|
|
|
|
+
|
|
|
|
|
+ // body contains the main content of the skill, part of the "SKILL.md" file.
|
|
|
|
|
+ // +kubebuilder:validation:Required
|
|
|
|
|
+ Body string `json:"body"`
|
|
|
|
|
+
|
|
|
|
|
+ // files is the list of files that will be used by the skill.
|
|
|
|
|
+ // scripts, references, assets, ...
|
|
|
|
|
+ // +optional
|
|
|
|
|
+ Files []SkillFileEntry `json:"files,omitempty"`
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// SkillConfigMapKey maps a skill file's relative path (e.g. "scripts/foo.py") to a
|
|
|
|
|
+// value that satisfies the Kubernetes ConfigMap key charset ([-._a-zA-Z0-9]+), since
|
|
|
|
|
+// paths may contain "/" which the API rejects as a key. "-" is escaped to "--" first
|
|
|
|
|
+// so the "/" -> "-" substitution stays reversible, meaning distinct paths never collide.
|
|
|
|
|
+func SkillConfigMapKey(path string) string {
|
|
|
|
|
+ escaped := strings.ReplaceAll(path, "-", "--")
|
|
|
|
|
+ return strings.ReplaceAll(escaped, "/", "-")
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// SkillFilePathsAnnotation is the annotation on a Skill's ConfigMap holding a
|
|
|
|
|
+// JSON-encoded map of SkillConfigMapKey(path) -> original relative path. Consumers
|
|
|
|
|
+// mounting the ConfigMap read this to lay the files back out as a real directory tree,
|
|
|
|
|
+// without needing to re-fetch the Skill or recompute SkillConfigMapKey themselves.
|
|
|
|
|
+const SkillFilePathsAnnotation = "locostack.com/skill-file-paths"
|
|
|
|
|
+
|
|
|
|
|
+// SkillStatus defines the observed state of Skill.
|
|
|
|
|
+type SkillStatus struct {
|
|
|
|
|
+ // files is the number of files contained in the skill.
|
|
|
|
|
+ // +optional
|
|
|
|
|
+ Files int32 `json:"files,omitempty"`
|
|
|
|
|
+
|
|
|
|
|
+ // observedGeneration is the .metadata.generation last reconciled.
|
|
|
|
|
+ // +optional
|
|
|
|
|
+ ObservedGeneration int64 `json:"observedGeneration,omitempty"`
|
|
|
|
|
+
|
|
|
|
|
+ // The status of each condition is one of True, False, or Unknown.
|
|
|
|
|
+ // +listType=map
|
|
|
|
|
+ // +listMapKey=type
|
|
|
|
|
+ // +optional
|
|
|
|
|
+ Conditions []metav1.Condition `json:"conditions,omitempty"`
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// +kubebuilder:object:root=true
|
|
|
|
|
+// +kubebuilder:subresource:status
|
|
|
|
|
+// +kubebuilder:resource:shortName=skl
|
|
|
|
|
+// +kubebuilder:printcolumn:name="Files",type=integer,JSONPath=".status.files"
|
|
|
|
|
+
|
|
|
|
|
+// Skill is the Schema for the skills API
|
|
|
|
|
+type Skill struct {
|
|
|
|
|
+ metav1.TypeMeta `json:",inline"`
|
|
|
|
|
+
|
|
|
|
|
+ // metadata is a standard object metadata
|
|
|
|
|
+ // +optional
|
|
|
|
|
+ metav1.ObjectMeta `json:"metadata,omitzero"`
|
|
|
|
|
+
|
|
|
|
|
+ // spec defines the desired state of Skill
|
|
|
|
|
+ // +required
|
|
|
|
|
+ Spec SkillSpec `json:"spec"`
|
|
|
|
|
+
|
|
|
|
|
+ // status defines the observed state of Skill
|
|
|
|
|
+ // +optional
|
|
|
|
|
+ Status SkillStatus `json:"status,omitzero"`
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// +kubebuilder:object:root=true
|
|
|
|
|
+
|
|
|
|
|
+// SkillList contains a list of Skill
|
|
|
|
|
+type SkillList struct {
|
|
|
|
|
+ metav1.TypeMeta `json:",inline"`
|
|
|
|
|
+ metav1.ListMeta `json:"metadata,omitzero"`
|
|
|
|
|
+ Items []Skill `json:"items"`
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func init() {
|
|
|
|
|
+ SchemeBuilder.Register(&Skill{}, &SkillList{})
|
|
|
|
|
+}
|