feat(docs): restructure with PHP/Go framework sections

- Add auto-discovery sidebar with nested directory support
- Create packages index with search and grid layout
- Move framework docs to packages/php/
- Update nav: Guide | PHP | Go | Packages | Security

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Snider 2026-01-29 10:47:50 +00:00
parent 6b6612af83
commit 632bca9111
90 changed files with 39470 additions and 2587 deletions

View file

@ -0,0 +1,275 @@
import {
useMediaQuery
} from "./chunk-F7UC4YNX.js";
import {
computed,
ref,
shallowRef,
watch
} from "./chunk-XKDLJUKD.js";
// node_modules/vitepress/dist/client/theme-default/index.js
import "/Users/snider/Code/host-uk/core-php/node_modules/vitepress/dist/client/theme-default/styles/fonts.css";
// node_modules/vitepress/dist/client/theme-default/without-fonts.js
import "/Users/snider/Code/host-uk/core-php/node_modules/vitepress/dist/client/theme-default/styles/vars.css";
import "/Users/snider/Code/host-uk/core-php/node_modules/vitepress/dist/client/theme-default/styles/base.css";
import "/Users/snider/Code/host-uk/core-php/node_modules/vitepress/dist/client/theme-default/styles/icons.css";
import "/Users/snider/Code/host-uk/core-php/node_modules/vitepress/dist/client/theme-default/styles/utils.css";
import "/Users/snider/Code/host-uk/core-php/node_modules/vitepress/dist/client/theme-default/styles/components/custom-block.css";
import "/Users/snider/Code/host-uk/core-php/node_modules/vitepress/dist/client/theme-default/styles/components/vp-code.css";
import "/Users/snider/Code/host-uk/core-php/node_modules/vitepress/dist/client/theme-default/styles/components/vp-code-group.css";
import "/Users/snider/Code/host-uk/core-php/node_modules/vitepress/dist/client/theme-default/styles/components/vp-doc.css";
import "/Users/snider/Code/host-uk/core-php/node_modules/vitepress/dist/client/theme-default/styles/components/vp-sponsor.css";
import VPBadge from "/Users/snider/Code/host-uk/core-php/node_modules/vitepress/dist/client/theme-default/components/VPBadge.vue";
import Layout from "/Users/snider/Code/host-uk/core-php/node_modules/vitepress/dist/client/theme-default/Layout.vue";
import { default as default2 } from "/Users/snider/Code/host-uk/core-php/node_modules/vitepress/dist/client/theme-default/components/VPBadge.vue";
import { default as default3 } from "/Users/snider/Code/host-uk/core-php/node_modules/vitepress/dist/client/theme-default/components/VPButton.vue";
import { default as default4 } from "/Users/snider/Code/host-uk/core-php/node_modules/vitepress/dist/client/theme-default/components/VPDocAsideSponsors.vue";
import { default as default5 } from "/Users/snider/Code/host-uk/core-php/node_modules/vitepress/dist/client/theme-default/components/VPFeatures.vue";
import { default as default6 } from "/Users/snider/Code/host-uk/core-php/node_modules/vitepress/dist/client/theme-default/components/VPHomeContent.vue";
import { default as default7 } from "/Users/snider/Code/host-uk/core-php/node_modules/vitepress/dist/client/theme-default/components/VPHomeFeatures.vue";
import { default as default8 } from "/Users/snider/Code/host-uk/core-php/node_modules/vitepress/dist/client/theme-default/components/VPHomeHero.vue";
import { default as default9 } from "/Users/snider/Code/host-uk/core-php/node_modules/vitepress/dist/client/theme-default/components/VPHomeSponsors.vue";
import { default as default10 } from "/Users/snider/Code/host-uk/core-php/node_modules/vitepress/dist/client/theme-default/components/VPImage.vue";
import { default as default11 } from "/Users/snider/Code/host-uk/core-php/node_modules/vitepress/dist/client/theme-default/components/VPLink.vue";
import { default as default12 } from "/Users/snider/Code/host-uk/core-php/node_modules/vitepress/dist/client/theme-default/components/VPNavBarSearch.vue";
import { default as default13 } from "/Users/snider/Code/host-uk/core-php/node_modules/vitepress/dist/client/theme-default/components/VPSocialLink.vue";
import { default as default14 } from "/Users/snider/Code/host-uk/core-php/node_modules/vitepress/dist/client/theme-default/components/VPSocialLinks.vue";
import { default as default15 } from "/Users/snider/Code/host-uk/core-php/node_modules/vitepress/dist/client/theme-default/components/VPSponsors.vue";
import { default as default16 } from "/Users/snider/Code/host-uk/core-php/node_modules/vitepress/dist/client/theme-default/components/VPTeamMembers.vue";
import { default as default17 } from "/Users/snider/Code/host-uk/core-php/node_modules/vitepress/dist/client/theme-default/components/VPTeamPage.vue";
import { default as default18 } from "/Users/snider/Code/host-uk/core-php/node_modules/vitepress/dist/client/theme-default/components/VPTeamPageSection.vue";
import { default as default19 } from "/Users/snider/Code/host-uk/core-php/node_modules/vitepress/dist/client/theme-default/components/VPTeamPageTitle.vue";
// node_modules/vitepress/dist/client/theme-default/composables/local-nav.js
import { onContentUpdated } from "vitepress";
// node_modules/vitepress/dist/client/theme-default/composables/outline.js
import { getScrollOffset } from "vitepress";
// node_modules/vitepress/dist/client/theme-default/support/utils.js
import { withBase } from "vitepress";
// node_modules/vitepress/dist/client/theme-default/composables/data.js
import { useData as useData$ } from "vitepress";
var useData = useData$;
// node_modules/vitepress/dist/client/theme-default/support/utils.js
function ensureStartingSlash(path) {
return path.startsWith("/") ? path : `/${path}`;
}
// node_modules/vitepress/dist/client/theme-default/support/sidebar.js
function getSidebar(_sidebar, path) {
if (Array.isArray(_sidebar))
return addBase(_sidebar);
if (_sidebar == null)
return [];
path = ensureStartingSlash(path);
const dir = Object.keys(_sidebar).sort((a, b) => {
return b.split("/").length - a.split("/").length;
}).find((dir2) => {
return path.startsWith(ensureStartingSlash(dir2));
});
const sidebar = dir ? _sidebar[dir] : [];
return Array.isArray(sidebar) ? addBase(sidebar) : addBase(sidebar.items, sidebar.base);
}
function getSidebarGroups(sidebar) {
const groups = [];
let lastGroupIndex = 0;
for (const index in sidebar) {
const item = sidebar[index];
if (item.items) {
lastGroupIndex = groups.push(item);
continue;
}
if (!groups[lastGroupIndex]) {
groups.push({ items: [] });
}
groups[lastGroupIndex].items.push(item);
}
return groups;
}
function addBase(items, _base) {
return [...items].map((_item) => {
const item = { ..._item };
const base = item.base || _base;
if (base && item.link)
item.link = base + item.link;
if (item.items)
item.items = addBase(item.items, base);
return item;
});
}
// node_modules/vitepress/dist/client/theme-default/composables/sidebar.js
function useSidebar() {
const { frontmatter, page, theme: theme2 } = useData();
const is960 = useMediaQuery("(min-width: 960px)");
const isOpen = ref(false);
const _sidebar = computed(() => {
const sidebarConfig = theme2.value.sidebar;
const relativePath = page.value.relativePath;
return sidebarConfig ? getSidebar(sidebarConfig, relativePath) : [];
});
const sidebar = ref(_sidebar.value);
watch(_sidebar, (next, prev) => {
if (JSON.stringify(next) !== JSON.stringify(prev))
sidebar.value = _sidebar.value;
});
const hasSidebar = computed(() => {
return frontmatter.value.sidebar !== false && sidebar.value.length > 0 && frontmatter.value.layout !== "home";
});
const leftAside = computed(() => {
if (hasAside)
return frontmatter.value.aside == null ? theme2.value.aside === "left" : frontmatter.value.aside === "left";
return false;
});
const hasAside = computed(() => {
if (frontmatter.value.layout === "home")
return false;
if (frontmatter.value.aside != null)
return !!frontmatter.value.aside;
return theme2.value.aside !== false;
});
const isSidebarEnabled = computed(() => hasSidebar.value && is960.value);
const sidebarGroups = computed(() => {
return hasSidebar.value ? getSidebarGroups(sidebar.value) : [];
});
function open() {
isOpen.value = true;
}
function close() {
isOpen.value = false;
}
function toggle() {
isOpen.value ? close() : open();
}
return {
isOpen,
sidebar,
sidebarGroups,
hasSidebar,
hasAside,
leftAside,
isSidebarEnabled,
open,
close,
toggle
};
}
// node_modules/vitepress/dist/client/theme-default/composables/outline.js
var ignoreRE = /\b(?:VPBadge|header-anchor|footnote-ref|ignore-header)\b/;
var resolvedHeaders = [];
function getHeaders(range) {
const headers = [
...document.querySelectorAll(".VPDoc :where(h1,h2,h3,h4,h5,h6)")
].filter((el) => el.id && el.hasChildNodes()).map((el) => {
const level = Number(el.tagName[1]);
return {
element: el,
title: serializeHeader(el),
link: "#" + el.id,
level
};
});
return resolveHeaders(headers, range);
}
function serializeHeader(h) {
let ret = "";
for (const node of h.childNodes) {
if (node.nodeType === 1) {
if (ignoreRE.test(node.className))
continue;
ret += node.textContent;
} else if (node.nodeType === 3) {
ret += node.textContent;
}
}
return ret.trim();
}
function resolveHeaders(headers, range) {
if (range === false) {
return [];
}
const levelsRange = (typeof range === "object" && !Array.isArray(range) ? range.level : range) || 2;
const [high, low] = typeof levelsRange === "number" ? [levelsRange, levelsRange] : levelsRange === "deep" ? [2, 6] : levelsRange;
return buildTree(headers, high, low);
}
function buildTree(data, min, max) {
resolvedHeaders.length = 0;
const result = [];
const stack = [];
data.forEach((item) => {
const node = { ...item, children: [] };
let parent = stack[stack.length - 1];
while (parent && parent.level >= node.level) {
stack.pop();
parent = stack[stack.length - 1];
}
if (node.element.classList.contains("ignore-header") || parent && "shouldIgnore" in parent) {
stack.push({ level: node.level, shouldIgnore: true });
return;
}
if (node.level > max || node.level < min)
return;
resolvedHeaders.push({ element: node.element, link: node.link });
if (parent)
parent.children.push(node);
else
result.push(node);
stack.push(node);
});
return result;
}
// node_modules/vitepress/dist/client/theme-default/composables/local-nav.js
function useLocalNav() {
const { theme: theme2, frontmatter } = useData();
const headers = shallowRef([]);
const hasLocalNav = computed(() => {
return headers.value.length > 0;
});
onContentUpdated(() => {
headers.value = getHeaders(frontmatter.value.outline ?? theme2.value.outline);
});
return {
headers,
hasLocalNav
};
}
// node_modules/vitepress/dist/client/theme-default/without-fonts.js
var theme = {
Layout,
enhanceApp: ({ app }) => {
app.component("Badge", VPBadge);
}
};
var without_fonts_default = theme;
export {
default2 as VPBadge,
default3 as VPButton,
default4 as VPDocAsideSponsors,
default5 as VPFeatures,
default6 as VPHomeContent,
default7 as VPHomeFeatures,
default8 as VPHomeHero,
default9 as VPHomeSponsors,
default10 as VPImage,
default11 as VPLink,
default12 as VPNavBarSearch,
default13 as VPSocialLink,
default14 as VPSocialLinks,
default15 as VPSponsors,
default16 as VPTeamMembers,
default17 as VPTeamPage,
default18 as VPTeamPageSection,
default19 as VPTeamPageTitle,
without_fonts_default as default,
useLocalNav,
useSidebar
};
//# sourceMappingURL=@theme_index.js.map

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,58 @@
{
"hash": "8c5df599",
"configHash": "2f534d72",
"lockfileHash": "35f249c3",
"browserHash": "e197bf0b",
"optimized": {
"vue": {
"src": "../../../../node_modules/vue/dist/vue.runtime.esm-bundler.js",
"file": "vue.js",
"fileHash": "111bcec5",
"needsInterop": false
},
"vitepress > @vue/devtools-api": {
"src": "../../../../node_modules/@vue/devtools-api/dist/index.js",
"file": "vitepress___@vue_devtools-api.js",
"fileHash": "2778d6d4",
"needsInterop": false
},
"vitepress > @vueuse/core": {
"src": "../../../../node_modules/@vueuse/core/index.mjs",
"file": "vitepress___@vueuse_core.js",
"fileHash": "566d05f6",
"needsInterop": false
},
"vitepress > @vueuse/integrations/useFocusTrap": {
"src": "../../../../node_modules/@vueuse/integrations/useFocusTrap.mjs",
"file": "vitepress___@vueuse_integrations_useFocusTrap.js",
"fileHash": "9d67edf0",
"needsInterop": false
},
"vitepress > mark.js/src/vanilla.js": {
"src": "../../../../node_modules/mark.js/src/vanilla.js",
"file": "vitepress___mark__js_src_vanilla__js.js",
"fileHash": "4a70a960",
"needsInterop": false
},
"vitepress > minisearch": {
"src": "../../../../node_modules/minisearch/dist/es/index.js",
"file": "vitepress___minisearch.js",
"fileHash": "0fdf6780",
"needsInterop": false
},
"@theme/index": {
"src": "../../../../node_modules/vitepress/dist/client/theme-default/index.js",
"file": "@theme_index.js",
"fileHash": "4de14278",
"needsInterop": false
}
},
"chunks": {
"chunk-F7UC4YNX": {
"file": "chunk-F7UC4YNX.js"
},
"chunk-XKDLJUKD": {
"file": "chunk-XKDLJUKD.js"
}
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,3 @@
{
"type": "module"
}

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,583 @@
import {
DefaultMagicKeysAliasMap,
StorageSerializers,
TransitionPresets,
assert,
breakpointsAntDesign,
breakpointsBootstrapV5,
breakpointsElement,
breakpointsMasterCss,
breakpointsPrimeFlex,
breakpointsQuasar,
breakpointsSematic,
breakpointsTailwind,
breakpointsVuetify,
breakpointsVuetifyV2,
breakpointsVuetifyV3,
bypassFilter,
camelize,
clamp,
cloneFnJSON,
computedAsync,
computedEager,
computedInject,
computedWithControl,
containsProp,
controlledRef,
createEventHook,
createFetch,
createFilterWrapper,
createGlobalState,
createInjectionState,
createRef,
createReusableTemplate,
createSharedComposable,
createSingletonPromise,
createTemplatePromise,
createUnrefFn,
customStorageEventName,
debounceFilter,
defaultDocument,
defaultLocation,
defaultNavigator,
defaultWindow,
executeTransition,
extendRef,
formatDate,
formatTimeAgo,
get,
getLifeCycleTarget,
getSSRHandler,
hasOwn,
hyphenate,
identity,
increaseWithUnit,
injectLocal,
invoke,
isClient,
isDef,
isDefined,
isIOS,
isObject,
isWorker,
makeDestructurable,
mapGamepadToXbox360Controller,
noop,
normalizeDate,
notNullish,
now,
objectEntries,
objectOmit,
objectPick,
onClickOutside,
onElementRemoval,
onKeyDown,
onKeyPressed,
onKeyStroke,
onKeyUp,
onLongPress,
onStartTyping,
pausableFilter,
promiseTimeout,
provideLocal,
provideSSRWidth,
pxValue,
rand,
reactify,
reactifyObject,
reactiveComputed,
reactiveOmit,
reactivePick,
refAutoReset,
refDebounced,
refDefault,
refThrottled,
refWithControl,
resolveRef,
resolveUnref,
set,
setSSRHandler,
syncRef,
syncRefs,
templateRef,
throttleFilter,
timestamp,
toArray,
toReactive,
toRef,
toRefs,
toValue,
tryOnBeforeMount,
tryOnBeforeUnmount,
tryOnMounted,
tryOnScopeDispose,
tryOnUnmounted,
unrefElement,
until,
useActiveElement,
useAnimate,
useArrayDifference,
useArrayEvery,
useArrayFilter,
useArrayFind,
useArrayFindIndex,
useArrayFindLast,
useArrayIncludes,
useArrayJoin,
useArrayMap,
useArrayReduce,
useArraySome,
useArrayUnique,
useAsyncQueue,
useAsyncState,
useBase64,
useBattery,
useBluetooth,
useBreakpoints,
useBroadcastChannel,
useBrowserLocation,
useCached,
useClipboard,
useClipboardItems,
useCloned,
useColorMode,
useConfirmDialog,
useCountdown,
useCounter,
useCssVar,
useCurrentElement,
useCycleList,
useDark,
useDateFormat,
useDebounceFn,
useDebouncedRefHistory,
useDeviceMotion,
useDeviceOrientation,
useDevicePixelRatio,
useDevicesList,
useDisplayMedia,
useDocumentVisibility,
useDraggable,
useDropZone,
useElementBounding,
useElementByPoint,
useElementHover,
useElementSize,
useElementVisibility,
useEventBus,
useEventListener,
useEventSource,
useEyeDropper,
useFavicon,
useFetch,
useFileDialog,
useFileSystemAccess,
useFocus,
useFocusWithin,
useFps,
useFullscreen,
useGamepad,
useGeolocation,
useIdle,
useImage,
useInfiniteScroll,
useIntersectionObserver,
useInterval,
useIntervalFn,
useKeyModifier,
useLastChanged,
useLocalStorage,
useMagicKeys,
useManualRefHistory,
useMediaControls,
useMediaQuery,
useMemoize,
useMemory,
useMounted,
useMouse,
useMouseInElement,
useMousePressed,
useMutationObserver,
useNavigatorLanguage,
useNetwork,
useNow,
useObjectUrl,
useOffsetPagination,
useOnline,
usePageLeave,
useParallax,
useParentElement,
usePerformanceObserver,
usePermission,
usePointer,
usePointerLock,
usePointerSwipe,
usePreferredColorScheme,
usePreferredContrast,
usePreferredDark,
usePreferredLanguages,
usePreferredReducedMotion,
usePreferredReducedTransparency,
usePrevious,
useRafFn,
useRefHistory,
useResizeObserver,
useSSRWidth,
useScreenOrientation,
useScreenSafeArea,
useScriptTag,
useScroll,
useScrollLock,
useSessionStorage,
useShare,
useSorted,
useSpeechRecognition,
useSpeechSynthesis,
useStepper,
useStorage,
useStorageAsync,
useStyleTag,
useSupported,
useSwipe,
useTemplateRefsList,
useTextDirection,
useTextSelection,
useTextareaAutosize,
useThrottleFn,
useThrottledRefHistory,
useTimeAgo,
useTimeout,
useTimeoutFn,
useTimeoutPoll,
useTimestamp,
useTitle,
useToNumber,
useToString,
useToggle,
useTransition,
useUrlSearchParams,
useUserMedia,
useVModel,
useVModels,
useVibrate,
useVirtualList,
useWakeLock,
useWebNotification,
useWebSocket,
useWebWorker,
useWebWorkerFn,
useWindowFocus,
useWindowScroll,
useWindowSize,
watchArray,
watchAtMost,
watchDebounced,
watchDeep,
watchIgnorable,
watchImmediate,
watchOnce,
watchPausable,
watchThrottled,
watchTriggerable,
watchWithFilter,
whenever
} from "./chunk-F7UC4YNX.js";
import "./chunk-XKDLJUKD.js";
export {
DefaultMagicKeysAliasMap,
StorageSerializers,
TransitionPresets,
assert,
computedAsync as asyncComputed,
refAutoReset as autoResetRef,
breakpointsAntDesign,
breakpointsBootstrapV5,
breakpointsElement,
breakpointsMasterCss,
breakpointsPrimeFlex,
breakpointsQuasar,
breakpointsSematic,
breakpointsTailwind,
breakpointsVuetify,
breakpointsVuetifyV2,
breakpointsVuetifyV3,
bypassFilter,
camelize,
clamp,
cloneFnJSON,
computedAsync,
computedEager,
computedInject,
computedWithControl,
containsProp,
computedWithControl as controlledComputed,
controlledRef,
createEventHook,
createFetch,
createFilterWrapper,
createGlobalState,
createInjectionState,
reactify as createReactiveFn,
createRef,
createReusableTemplate,
createSharedComposable,
createSingletonPromise,
createTemplatePromise,
createUnrefFn,
customStorageEventName,
debounceFilter,
refDebounced as debouncedRef,
watchDebounced as debouncedWatch,
defaultDocument,
defaultLocation,
defaultNavigator,
defaultWindow,
computedEager as eagerComputed,
executeTransition,
extendRef,
formatDate,
formatTimeAgo,
get,
getLifeCycleTarget,
getSSRHandler,
hasOwn,
hyphenate,
identity,
watchIgnorable as ignorableWatch,
increaseWithUnit,
injectLocal,
invoke,
isClient,
isDef,
isDefined,
isIOS,
isObject,
isWorker,
makeDestructurable,
mapGamepadToXbox360Controller,
noop,
normalizeDate,
notNullish,
now,
objectEntries,
objectOmit,
objectPick,
onClickOutside,
onElementRemoval,
onKeyDown,
onKeyPressed,
onKeyStroke,
onKeyUp,
onLongPress,
onStartTyping,
pausableFilter,
watchPausable as pausableWatch,
promiseTimeout,
provideLocal,
provideSSRWidth,
pxValue,
rand,
reactify,
reactifyObject,
reactiveComputed,
reactiveOmit,
reactivePick,
refAutoReset,
refDebounced,
refDefault,
refThrottled,
refWithControl,
resolveRef,
resolveUnref,
set,
setSSRHandler,
syncRef,
syncRefs,
templateRef,
throttleFilter,
refThrottled as throttledRef,
watchThrottled as throttledWatch,
timestamp,
toArray,
toReactive,
toRef,
toRefs,
toValue,
tryOnBeforeMount,
tryOnBeforeUnmount,
tryOnMounted,
tryOnScopeDispose,
tryOnUnmounted,
unrefElement,
until,
useActiveElement,
useAnimate,
useArrayDifference,
useArrayEvery,
useArrayFilter,
useArrayFind,
useArrayFindIndex,
useArrayFindLast,
useArrayIncludes,
useArrayJoin,
useArrayMap,
useArrayReduce,
useArraySome,
useArrayUnique,
useAsyncQueue,
useAsyncState,
useBase64,
useBattery,
useBluetooth,
useBreakpoints,
useBroadcastChannel,
useBrowserLocation,
useCached,
useClipboard,
useClipboardItems,
useCloned,
useColorMode,
useConfirmDialog,
useCountdown,
useCounter,
useCssVar,
useCurrentElement,
useCycleList,
useDark,
useDateFormat,
refDebounced as useDebounce,
useDebounceFn,
useDebouncedRefHistory,
useDeviceMotion,
useDeviceOrientation,
useDevicePixelRatio,
useDevicesList,
useDisplayMedia,
useDocumentVisibility,
useDraggable,
useDropZone,
useElementBounding,
useElementByPoint,
useElementHover,
useElementSize,
useElementVisibility,
useEventBus,
useEventListener,
useEventSource,
useEyeDropper,
useFavicon,
useFetch,
useFileDialog,
useFileSystemAccess,
useFocus,
useFocusWithin,
useFps,
useFullscreen,
useGamepad,
useGeolocation,
useIdle,
useImage,
useInfiniteScroll,
useIntersectionObserver,
useInterval,
useIntervalFn,
useKeyModifier,
useLastChanged,
useLocalStorage,
useMagicKeys,
useManualRefHistory,
useMediaControls,
useMediaQuery,
useMemoize,
useMemory,
useMounted,
useMouse,
useMouseInElement,
useMousePressed,
useMutationObserver,
useNavigatorLanguage,
useNetwork,
useNow,
useObjectUrl,
useOffsetPagination,
useOnline,
usePageLeave,
useParallax,
useParentElement,
usePerformanceObserver,
usePermission,
usePointer,
usePointerLock,
usePointerSwipe,
usePreferredColorScheme,
usePreferredContrast,
usePreferredDark,
usePreferredLanguages,
usePreferredReducedMotion,
usePreferredReducedTransparency,
usePrevious,
useRafFn,
useRefHistory,
useResizeObserver,
useSSRWidth,
useScreenOrientation,
useScreenSafeArea,
useScriptTag,
useScroll,
useScrollLock,
useSessionStorage,
useShare,
useSorted,
useSpeechRecognition,
useSpeechSynthesis,
useStepper,
useStorage,
useStorageAsync,
useStyleTag,
useSupported,
useSwipe,
useTemplateRefsList,
useTextDirection,
useTextSelection,
useTextareaAutosize,
refThrottled as useThrottle,
useThrottleFn,
useThrottledRefHistory,
useTimeAgo,
useTimeout,
useTimeoutFn,
useTimeoutPoll,
useTimestamp,
useTitle,
useToNumber,
useToString,
useToggle,
useTransition,
useUrlSearchParams,
useUserMedia,
useVModel,
useVModels,
useVibrate,
useVirtualList,
useWakeLock,
useWebNotification,
useWebSocket,
useWebWorker,
useWebWorkerFn,
useWindowFocus,
useWindowScroll,
useWindowSize,
watchArray,
watchAtMost,
watchDebounced,
watchDeep,
watchIgnorable,
watchImmediate,
watchOnce,
watchPausable,
watchThrottled,
watchTriggerable,
watchWithFilter,
whenever
};
//# sourceMappingURL=vitepress___@vueuse_core.js.map

View file

@ -0,0 +1,7 @@
{
"version": 3,
"sources": [],
"sourcesContent": [],
"mappings": "",
"names": []
}

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

347
docs/.vitepress/cache/deps/vue.js vendored Normal file
View file

@ -0,0 +1,347 @@
import {
BaseTransition,
BaseTransitionPropsValidators,
Comment,
DeprecationTypes,
EffectScope,
ErrorCodes,
ErrorTypeStrings,
Fragment,
KeepAlive,
ReactiveEffect,
Static,
Suspense,
Teleport,
Text,
TrackOpTypes,
Transition,
TransitionGroup,
TriggerOpTypes,
VueElement,
assertNumber,
callWithAsyncErrorHandling,
callWithErrorHandling,
camelize,
capitalize,
cloneVNode,
compatUtils,
compile,
computed,
createApp,
createBaseVNode,
createBlock,
createCommentVNode,
createElementBlock,
createHydrationRenderer,
createPropsRestProxy,
createRenderer,
createSSRApp,
createSlots,
createStaticVNode,
createTextVNode,
createVNode,
customRef,
defineAsyncComponent,
defineComponent,
defineCustomElement,
defineEmits,
defineExpose,
defineModel,
defineOptions,
defineProps,
defineSSRCustomElement,
defineSlots,
devtools,
effect,
effectScope,
getCurrentInstance,
getCurrentScope,
getCurrentWatcher,
getTransitionRawChildren,
guardReactiveProps,
h,
handleError,
hasInjectionContext,
hydrate,
hydrateOnIdle,
hydrateOnInteraction,
hydrateOnMediaQuery,
hydrateOnVisible,
initCustomFormatter,
initDirectivesForSSR,
inject,
isMemoSame,
isProxy,
isReactive,
isReadonly,
isRef,
isRuntimeOnly,
isShallow,
isVNode,
markRaw,
mergeDefaults,
mergeModels,
mergeProps,
nextTick,
nodeOps,
normalizeClass,
normalizeProps,
normalizeStyle,
onActivated,
onBeforeMount,
onBeforeUnmount,
onBeforeUpdate,
onDeactivated,
onErrorCaptured,
onMounted,
onRenderTracked,
onRenderTriggered,
onScopeDispose,
onServerPrefetch,
onUnmounted,
onUpdated,
onWatcherCleanup,
openBlock,
patchProp,
popScopeId,
provide,
proxyRefs,
pushScopeId,
queuePostFlushCb,
reactive,
readonly,
ref,
registerRuntimeCompiler,
render,
renderList,
renderSlot,
resolveComponent,
resolveDirective,
resolveDynamicComponent,
resolveFilter,
resolveTransitionHooks,
setBlockTracking,
setDevtoolsHook,
setTransitionHooks,
shallowReactive,
shallowReadonly,
shallowRef,
ssrContextKey,
ssrUtils,
stop,
toDisplayString,
toHandlerKey,
toHandlers,
toRaw,
toRef,
toRefs,
toValue,
transformVNodeArgs,
triggerRef,
unref,
useAttrs,
useCssModule,
useCssVars,
useHost,
useId,
useModel,
useSSRContext,
useShadowRoot,
useSlots,
useTemplateRef,
useTransitionState,
vModelCheckbox,
vModelDynamic,
vModelRadio,
vModelSelect,
vModelText,
vShow,
version,
warn,
watch,
watchEffect,
watchPostEffect,
watchSyncEffect,
withAsyncContext,
withCtx,
withDefaults,
withDirectives,
withKeys,
withMemo,
withModifiers,
withScopeId
} from "./chunk-XKDLJUKD.js";
export {
BaseTransition,
BaseTransitionPropsValidators,
Comment,
DeprecationTypes,
EffectScope,
ErrorCodes,
ErrorTypeStrings,
Fragment,
KeepAlive,
ReactiveEffect,
Static,
Suspense,
Teleport,
Text,
TrackOpTypes,
Transition,
TransitionGroup,
TriggerOpTypes,
VueElement,
assertNumber,
callWithAsyncErrorHandling,
callWithErrorHandling,
camelize,
capitalize,
cloneVNode,
compatUtils,
compile,
computed,
createApp,
createBlock,
createCommentVNode,
createElementBlock,
createBaseVNode as createElementVNode,
createHydrationRenderer,
createPropsRestProxy,
createRenderer,
createSSRApp,
createSlots,
createStaticVNode,
createTextVNode,
createVNode,
customRef,
defineAsyncComponent,
defineComponent,
defineCustomElement,
defineEmits,
defineExpose,
defineModel,
defineOptions,
defineProps,
defineSSRCustomElement,
defineSlots,
devtools,
effect,
effectScope,
getCurrentInstance,
getCurrentScope,
getCurrentWatcher,
getTransitionRawChildren,
guardReactiveProps,
h,
handleError,
hasInjectionContext,
hydrate,
hydrateOnIdle,
hydrateOnInteraction,
hydrateOnMediaQuery,
hydrateOnVisible,
initCustomFormatter,
initDirectivesForSSR,
inject,
isMemoSame,
isProxy,
isReactive,
isReadonly,
isRef,
isRuntimeOnly,
isShallow,
isVNode,
markRaw,
mergeDefaults,
mergeModels,
mergeProps,
nextTick,
nodeOps,
normalizeClass,
normalizeProps,
normalizeStyle,
onActivated,
onBeforeMount,
onBeforeUnmount,
onBeforeUpdate,
onDeactivated,
onErrorCaptured,
onMounted,
onRenderTracked,
onRenderTriggered,
onScopeDispose,
onServerPrefetch,
onUnmounted,
onUpdated,
onWatcherCleanup,
openBlock,
patchProp,
popScopeId,
provide,
proxyRefs,
pushScopeId,
queuePostFlushCb,
reactive,
readonly,
ref,
registerRuntimeCompiler,
render,
renderList,
renderSlot,
resolveComponent,
resolveDirective,
resolveDynamicComponent,
resolveFilter,
resolveTransitionHooks,
setBlockTracking,
setDevtoolsHook,
setTransitionHooks,
shallowReactive,
shallowReadonly,
shallowRef,
ssrContextKey,
ssrUtils,
stop,
toDisplayString,
toHandlerKey,
toHandlers,
toRaw,
toRef,
toRefs,
toValue,
transformVNodeArgs,
triggerRef,
unref,
useAttrs,
useCssModule,
useCssVars,
useHost,
useId,
useModel,
useSSRContext,
useShadowRoot,
useSlots,
useTemplateRef,
useTransitionState,
vModelCheckbox,
vModelDynamic,
vModelRadio,
vModelSelect,
vModelText,
vShow,
version,
warn,
watch,
watchEffect,
watchPostEffect,
watchSyncEffect,
withAsyncContext,
withCtx,
withDefaults,
withDirectives,
withKeys,
withMemo,
withModifiers,
withScopeId
};
//# sourceMappingURL=vue.js.map

7
docs/.vitepress/cache/deps/vue.js.map vendored Normal file
View file

@ -0,0 +1,7 @@
{
"version": 3,
"sources": [],
"sourcesContent": [],
"mappings": "",
"names": []
}

View file

@ -1,32 +1,49 @@
import { defineConfig } from 'vitepress'
import { fileURLToPath } from 'url'
import path from 'path'
import { getPackagesSidebar, getPackagesNav } from './sidebar.js'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
const docsDir = path.resolve(__dirname, '..')
// Auto-discover packages
const packagesSidebar = getPackagesSidebar(docsDir)
const packagesNav = getPackagesNav(docsDir)
// Separate PHP/Go from ecosystem packages for nav
const phpNav = packagesNav.find(p => p.link === '/packages/php/')
const goNav = packagesNav.find(p => p.link === '/packages/go/')
const ecosystemNav = packagesNav.filter(p =>
p.link !== '/packages/php/' && p.link !== '/packages/go/'
)
export default defineConfig({
title: 'Core PHP Framework',
description: 'Modular monolith framework for Laravel',
title: 'Host UK',
description: 'Native application frameworks for PHP and Go',
base: '/',
ignoreDeadLinks: [
// Ignore localhost links
/^https?:\/\/localhost/,
// Old monorepo changelog paths (packages now in separate repos)
/\/packages\/core-(php|admin|api|mcp)\/changelog/,
// Old paths during migration
/\/packages\/core/,
/\/core\//,
/\/architecture\//,
/\/patterns-guide\//,
// Security pages not yet created
/\/security\/(api-authentication|rate-limiting|workspace-isolation|sql-validation|gdpr)/,
// Package pages not yet created
/\/packages\/admin\/(tables|security|hlcrf|activity)/,
/\/packages\/api\/(openapi|analytics|alerts|logging)/,
/\/packages\/mcp\/commerce/,
/\/packages\/core\/(services|seeders|security|email-shield|action-gate|i18n)/,
// Architecture pages not yet created
/\/architecture\/(custom-events|performance)/,
// Patterns pages not yet created
/\/patterns-guide\/(multi-tenancy|workspace-caching|search|admin-menus|services|repositories|responsive-design|factories|webhooks)/,
/\/packages\/php\/(services|seeders|security|email-shield|action-gate|i18n)/,
// Other pages not yet created
/\/testing\//,
/\/contributing/,
/\/guide\/testing/,
// Ignore relative changelog paths
/\.\/packages\//,
// Go docs - relative paths
/\.\.\/configuration/,
/\.\.\/examples/,
],
themeConfig: {
@ -34,17 +51,20 @@ export default defineConfig({
nav: [
{ text: 'Guide', link: '/guide/getting-started' },
{ text: 'Patterns', link: '/patterns-guide/actions' },
{
text: 'PHP',
link: '/packages/php/',
activeMatch: '/packages/php/'
},
{
text: 'Go',
link: '/packages/go/',
activeMatch: '/packages/go/'
},
{
text: 'Packages',
items: [
{ text: 'Core', link: '/packages/core/' },
{ text: 'Admin', link: '/packages/admin/' },
{ text: 'API', link: '/packages/api/' },
{ text: 'MCP', link: '/packages/mcp/' }
]
items: ecosystemNav
},
{ text: 'API', link: '/api/authentication' },
{ text: 'Security', link: '/security/overview' },
{
text: 'v1.0',
@ -69,107 +89,16 @@ export default defineConfig({
}
],
'/architecture/': [
// Packages index
'/packages/': [
{
text: 'Architecture',
items: [
{ text: 'Lifecycle Events', link: '/architecture/lifecycle-events' },
{ text: 'Module System', link: '/architecture/module-system' },
{ text: 'Lazy Loading', link: '/architecture/lazy-loading' },
{ text: 'Multi-Tenancy', link: '/architecture/multi-tenancy' },
{ text: 'Custom Events', link: '/architecture/custom-events' },
{ text: 'Performance', link: '/architecture/performance' }
]
text: 'Packages',
items: packagesNav.map(p => ({ text: p.text, link: p.link }))
}
],
'/patterns-guide/': [
{
text: 'Patterns',
items: [
{ text: 'Actions', link: '/patterns-guide/actions' },
{ text: 'Activity Logging', link: '/patterns-guide/activity-logging' },
{ text: 'Services', link: '/patterns-guide/services' },
{ text: 'Repositories', link: '/patterns-guide/repositories' },
{ text: 'Seeders', link: '/patterns-guide/seeders' },
{ text: 'HLCRF Layouts', link: '/patterns-guide/hlcrf' }
]
}
],
'/packages/core/': [
{
text: 'Core Package',
items: [
{ text: 'Overview', link: '/packages/core/' },
{ text: 'Module System', link: '/packages/core/modules' },
{ text: 'Multi-Tenancy', link: '/packages/core/tenancy' },
{ text: 'CDN Integration', link: '/packages/core/cdn' },
{ text: 'Actions', link: '/packages/core/actions' },
{ text: 'Lifecycle Events', link: '/packages/core/events' },
{ text: 'Configuration', link: '/packages/core/configuration' },
{ text: 'Activity Logging', link: '/packages/core/activity' },
{ text: 'Media Processing', link: '/packages/core/media' },
{ text: 'Search', link: '/packages/core/search' },
{ text: 'SEO Tools', link: '/packages/core/seo' },
{ text: 'Service Contracts', link: '/packages/core/service-contracts' },
{ text: 'Seeder System', link: '/packages/core/seeder-system' }
]
}
],
'/packages/admin/': [
{
text: 'Admin Package',
items: [
{ text: 'Overview', link: '/packages/admin/' },
{ text: 'Form Components', link: '/packages/admin/forms' },
{ text: 'Livewire Modals', link: '/packages/admin/modals' },
{ text: 'Global Search', link: '/packages/admin/search' },
{ text: 'Admin Menus', link: '/packages/admin/menus' },
{ text: 'Authorization', link: '/packages/admin/authorization' },
{ text: 'UI Components', link: '/packages/admin/components' },
{ text: 'Creating Admin Panels', link: '/packages/admin/creating-admin-panels' },
{ text: 'HLCRF Deep Dive', link: '/packages/admin/hlcrf-deep-dive' },
{ text: 'Components Reference', link: '/packages/admin/components-reference' }
]
}
],
'/packages/api/': [
{
text: 'API Package',
items: [
{ text: 'Overview', link: '/packages/api/' },
{ text: 'Authentication', link: '/packages/api/authentication' },
{ text: 'Webhooks', link: '/packages/api/webhooks' },
{ text: 'Rate Limiting', link: '/packages/api/rate-limiting' },
{ text: 'Scopes', link: '/packages/api/scopes' },
{ text: 'Documentation', link: '/packages/api/documentation' },
{ text: 'Building REST APIs', link: '/packages/api/building-rest-apis' },
{ text: 'Webhook Integration', link: '/packages/api/webhook-integration' },
{ text: 'Endpoints Reference', link: '/packages/api/endpoints-reference' }
]
}
],
'/packages/mcp/': [
{
text: 'MCP Package',
items: [
{ text: 'Overview', link: '/packages/mcp/' },
{ text: 'Query Database', link: '/packages/mcp/query-database' },
{ text: 'Creating Tools', link: '/packages/mcp/tools' },
{ text: 'Security', link: '/packages/mcp/security' },
{ text: 'Workspace Context', link: '/packages/mcp/workspace' },
{ text: 'Analytics', link: '/packages/mcp/analytics' },
{ text: 'Usage Quotas', link: '/packages/mcp/quotas' },
{ text: 'Creating MCP Tools', link: '/packages/mcp/creating-mcp-tools' },
{ text: 'SQL Security', link: '/packages/mcp/sql-security' },
{ text: 'Tools Reference', link: '/packages/mcp/tools-reference' }
]
}
],
// Auto-discovered package sidebars (php, go, admin, api, mcp, etc.)
...packagesSidebar,
'/security/': [
{
@ -196,7 +125,7 @@ export default defineConfig({
},
socialLinks: [
{ icon: 'github', link: 'https://github.com/host-uk/core-php' }
{ icon: 'github', link: 'https://github.com/host-uk' }
],
footer: {

178
docs/.vitepress/sidebar.js Normal file
View file

@ -0,0 +1,178 @@
import fs from 'fs'
import path from 'path'
import matter from 'gray-matter'
// Auto-discover packages from docs/packages/
// Each package folder should have an index.md
//
// Frontmatter options:
// title: "Page Title" - Used in sidebar
// sidebarTitle: "Short Title" - Override for sidebar (optional)
// order: 10 - Sort order (lower = first)
// collapsed: true - Start group collapsed (for directories)
export function getPackagesSidebar(docsDir) {
const packagesDir = path.join(docsDir, 'packages')
if (!fs.existsSync(packagesDir)) {
return {}
}
const sidebar = {}
const packages = fs.readdirSync(packagesDir, { withFileTypes: true })
.filter(d => d.isDirectory())
.map(d => d.name)
.sort()
for (const pkg of packages) {
const pkgDir = path.join(packagesDir, pkg)
// Build sidebar tree recursively
const items = buildSidebarItems(pkgDir, `/packages/${pkg}`)
if (items.length === 0) continue
// Get package title from index.md
let packageTitle = formatTitle(pkg)
const indexPath = path.join(pkgDir, 'index.md')
if (fs.existsSync(indexPath)) {
const content = fs.readFileSync(indexPath, 'utf-8')
const { data } = matter(content)
if (data.title) {
packageTitle = data.title
} else {
const h1Match = content.match(/^#\s+(.+)$/m)
if (h1Match) packageTitle = h1Match[1]
}
}
sidebar[`/packages/${pkg}/`] = [
{
text: packageTitle,
items: items
}
]
}
return sidebar
}
// Recursively build sidebar items for a directory
function buildSidebarItems(dir, urlBase) {
const entries = fs.readdirSync(dir, { withFileTypes: true })
const items = []
// Process files first, then directories
const files = entries.filter(e => !e.isDirectory() && e.name.endsWith('.md'))
const dirs = entries.filter(e => e.isDirectory())
// Add markdown files
for (const file of files) {
const filePath = path.join(dir, file.name)
const content = fs.readFileSync(filePath, 'utf-8')
const { data } = matter(content)
let title = data.sidebarTitle || data.title
if (!title) {
const h1Match = content.match(/^#\s+(.+)$/m)
title = h1Match ? h1Match[1] : formatTitle(file.name.replace('.md', ''))
}
const isIndex = file.name === 'index.md'
items.push({
file: file.name,
text: isIndex ? 'Overview' : title,
link: isIndex ? `${urlBase}/` : `${urlBase}/${file.name.replace('.md', '')}`,
order: data.order ?? (isIndex ? -1 : 100)
})
}
// Add subdirectories as collapsed groups
for (const subdir of dirs) {
const subdirPath = path.join(dir, subdir.name)
const subdirUrl = `${urlBase}/${subdir.name}`
const subItems = buildSidebarItems(subdirPath, subdirUrl)
if (subItems.length === 0) continue
// Check for index.md in subdir for title/order
let groupTitle = formatTitle(subdir.name)
let groupOrder = 200
let collapsed = true
const indexPath = path.join(subdirPath, 'index.md')
if (fs.existsSync(indexPath)) {
const content = fs.readFileSync(indexPath, 'utf-8')
const { data } = matter(content)
if (data.sidebarTitle || data.title) {
groupTitle = data.sidebarTitle || data.title
} else {
const h1Match = content.match(/^#\s+(.+)$/m)
if (h1Match) groupTitle = h1Match[1]
}
if (data.order !== undefined) groupOrder = data.order
if (data.collapsed !== undefined) collapsed = data.collapsed
}
items.push({
text: groupTitle,
collapsed: collapsed,
items: subItems,
order: groupOrder
})
}
// Sort by order, then alphabetically
items.sort((a, b) => {
const orderA = a.order ?? 100
const orderB = b.order ?? 100
if (orderA !== orderB) return orderA - orderB
return a.text.localeCompare(b.text)
})
// Remove order from final output
return items.map(({ order, file, ...item }) => item)
}
// Get nav items for packages dropdown
export function getPackagesNav(docsDir) {
const packagesDir = path.join(docsDir, 'packages')
if (!fs.existsSync(packagesDir)) {
return []
}
return fs.readdirSync(packagesDir, { withFileTypes: true })
.filter(d => d.isDirectory())
.filter(d => fs.existsSync(path.join(packagesDir, d.name, 'index.md')))
.map(d => {
const indexPath = path.join(packagesDir, d.name, 'index.md')
const content = fs.readFileSync(indexPath, 'utf-8')
const { data } = matter(content)
let title = data.navTitle || data.title
if (!title) {
const h1Match = content.match(/^#\s+(.+)$/m)
title = h1Match ? h1Match[1] : formatTitle(d.name)
}
return {
text: title,
link: `/packages/${d.name}/`,
order: data.navOrder ?? 100
}
})
.sort((a, b) => {
if (a.order !== b.order) return a.order - b.order
return a.text.localeCompare(b.text)
})
.map(({ text, link }) => ({ text, link }))
}
// Convert kebab-case to Title Case
function formatTitle(str) {
return str
.split('-')
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
.join(' ')
}

View file

@ -1,603 +0,0 @@
# Admin Package
The Admin package provides a complete admin panel with Livewire modals, form components, global search, and an extensible menu system.
## Installation
```bash
composer require host-uk/core-admin
```
## Features
### Admin Menu System
Extensible navigation menu with automatic discovery:
```php
<?php
namespace Mod\Blog;
use Core\Front\Admin\Contracts\AdminMenuProvider;
use Core\Front\Admin\Support\MenuItemBuilder;
class BlogMenuProvider implements AdminMenuProvider
{
public function register(): array
{
return [
MenuItemBuilder::make('Blog')
->icon('newspaper')
->priority(30)
->children([
MenuItemBuilder::make('Posts')
->route('admin.blog.posts.index')
->icon('document-text'),
MenuItemBuilder::make('Categories')
->route('admin.blog.categories.index')
->icon('folder'),
])
->build(),
];
}
}
```
Register in your module's Boot.php:
```php
public function onAdmin(AdminPanelBooting $event): void
{
$event->menu(new BlogMenuProvider());
}
```
[Learn more about Admin Menus →](/patterns-guide/admin-menus)
### Livewire Modals
Full-page modal system for admin interfaces:
```php
<?php
namespace Mod\Blog\View\Modal\Admin;
use Livewire\Component;
class PostEditor extends Component
{
public ?Post $post = null;
public $title;
public $content;
public function mount(?Post $post = null): void
{
$this->post = $post;
$this->title = $post?->title;
$this->content = $post?->content;
}
public function save(): void
{
$validated = $this->validate([
'title' => 'required|max:255',
'content' => 'required',
]);
if ($this->post) {
$this->post->update($validated);
} else {
Post::create($validated);
}
$this->dispatch('post-saved');
$this->closeModal();
}
public function render()
{
return view('blog::admin.post-editor');
}
}
```
Open modals from any admin page:
```blade
<x-button wire:click="$dispatch('openModal', {component: 'blog.post-editor'})">
New Post
</x-button>
<x-button wire:click="$dispatch('openModal', {component: 'blog.post-editor', arguments: {post: {{ $post->id }}}})">
Edit Post
</x-button>
```
### Form Components
Pre-built form components with validation:
```blade
<x-admin::form action="{{ route('admin.posts.store') }}">
<x-admin::form-group
label="Title"
name="title"
required
>
<x-admin::input
name="title"
:value="old('title', $post->title)"
placeholder="Enter post title"
/>
</x-admin::form-group>
<x-admin::form-group
label="Content"
name="content"
required
>
<x-admin::textarea
name="content"
:value="old('content', $post->content)"
rows="10"
/>
</x-admin::form-group>
<x-admin::form-group
label="Category"
name="category_id"
>
<x-admin::select
name="category_id"
:options="$categories"
:selected="old('category_id', $post->category_id)"
/>
</x-admin::form-group>
<x-admin::form-group
label="Published"
name="is_published"
>
<x-admin::toggle
name="is_published"
:checked="old('is_published', $post->is_published)"
/>
</x-admin::form-group>
<div class="flex justify-end space-x-2">
<x-admin::button type="submit" variant="primary">
Save Post
</x-admin::button>
<x-admin::button type="button" variant="secondary" onclick="history.back()">
Cancel
</x-admin::button>
</div>
</x-admin::form>
```
### Global Search
Search across all admin content:
```php
<?php
namespace Mod\Blog\Search;
use Core\Admin\Search\Contracts\SearchProvider;
use Core\Admin\Search\SearchResult;
use Mod\Blog\Models\Post;
class PostSearchProvider implements SearchProvider
{
public function search(string $query): array
{
return Post::where('title', 'like', "%{$query}%")
->orWhere('content', 'like', "%{$query}%")
->limit(5)
->get()
->map(fn ($post) => new SearchResult(
title: $post->title,
description: $post->excerpt,
url: route('admin.blog.posts.edit', $post),
icon: 'document-text',
type: 'Post',
))
->toArray();
}
public function getSearchableTypes(): array
{
return ['posts'];
}
}
```
Register provider:
```php
// config/core-admin.php
'search' => [
'providers' => [
\Mod\Blog\Search\PostSearchProvider::class,
],
],
```
### Dashboard Widgets
Add widgets to the admin dashboard:
```php
<?php
namespace Mod\Blog\Widgets;
use Livewire\Component;
class PostStatsWidget extends Component
{
public function render()
{
return view('blog::admin.widgets.post-stats', [
'totalPosts' => Post::count(),
'publishedPosts' => Post::published()->count(),
'draftPosts' => Post::draft()->count(),
]);
}
}
```
Register widget:
```php
public function onAdmin(AdminPanelBooting $event): void
{
$event->widget(new PostStatsWidget(), priority: 10);
}
```
### Settings Pages
Add custom settings pages:
```php
<?php
namespace Mod\Blog\Settings;
use Livewire\Component;
class BlogSettings extends Component
{
public $postsPerPage;
public $enableComments;
public function mount(): void
{
$this->postsPerPage = config('blog.posts_per_page', 10);
$this->enableComments = config('blog.comments_enabled', true);
}
public function save(): void
{
ConfigService::set('blog.posts_per_page', $this->postsPerPage);
ConfigService::set('blog.comments_enabled', $this->enableComments);
$this->dispatch('settings-saved');
}
public function render()
{
return view('blog::admin.settings');
}
}
```
Register settings page:
```php
public function onAdmin(AdminPanelBooting $event): void
{
$event->settings('blog', BlogSettings::class);
}
```
## Components Reference
### Input
```blade
<x-admin::input
name="title"
type="text"
:value="$value"
placeholder="Enter title"
required
disabled
readonly
/>
```
### Textarea
```blade
<x-admin::textarea
name="content"
:value="$value"
rows="10"
placeholder="Enter content"
/>
```
### Select
```blade
<x-admin::select
name="category"
:options="[1 => 'Tech', 2 => 'Design']"
:selected="$selectedId"
placeholder="Select category"
/>
```
### Checkbox
```blade
<x-admin::checkbox
name="terms"
:checked="$isChecked"
label="I agree to terms"
/>
```
### Toggle
```blade
<x-admin::toggle
name="is_active"
:checked="$isActive"
label="Active"
/>
```
### Button
```blade
<x-admin::button
type="submit"
variant="primary|secondary|danger"
size="sm|md|lg"
icon="save"
disabled
loading
>
Save Changes
</x-admin::button>
```
### Form Group
```blade
<x-admin::form-group
label="Email"
name="email"
help="We'll never share your email"
error="$errors->first('email')"
required
>
<x-admin::input name="email" type="email" />
</x-admin::form-group>
```
## Layouts
### Admin App Layout
```blade
<x-admin::layout>
<x-slot:header>
<h1>Page Title</h1>
</x-slot>
{{-- Main content --}}
<div class="container mx-auto">
Content here
</div>
</x-admin::layout>
```
### HLCRF Layout
```blade
<x-hlcrf::layout>
<x-hlcrf::header>
Page Header with Actions
</x-hlcrf::header>
<x-hlcrf::left>
Sidebar Navigation
</x-hlcrf::left>
<x-hlcrf::content>
Main Content Area
</x-hlcrf::content>
<x-hlcrf::right>
Contextual Help & Widgets
</x-hlcrf::right>
</x-hlcrf::layout>
```
[Learn more about HLCRF →](/patterns-guide/hlcrf)
## Configuration
```php
// config/core-admin.php
return [
'menu' => [
'cache_enabled' => true,
'cache_ttl' => 3600,
'show_icons' => true,
],
'search' => [
'enabled' => true,
'providers' => [
// Register search providers
],
'max_results' => 10,
],
'livewire' => [
'modal_max_width' => '7xl',
'modal_close_on_escape' => true,
],
'form' => [
'validation_real_time' => true,
'show_required_indicator' => true,
],
];
```
## Styling
Admin package uses Tailwind CSS. Customize theme:
```js
// tailwind.config.js
module.exports = {
theme: {
extend: {
colors: {
admin: {
primary: '#3b82f6',
secondary: '#64748b',
success: '#22c55e',
danger: '#ef4444',
},
},
},
},
};
```
## JavaScript
Admin package includes Alpine.js for interactivity:
```blade
<div x-data="{ open: false }">
<button @click="open = !open">Toggle</button>
<div x-show="open">
Content
</div>
</div>
```
## Testing
### Feature Tests
```php
public function test_can_access_admin_dashboard(): void
{
$user = User::factory()->admin()->create();
$response = $this->actingAs($user)
->get('/admin');
$response->assertStatus(200);
}
public function test_admin_menu_displays_blog_items(): void
{
$user = User::factory()->admin()->create();
$response = $this->actingAs($user)
->get('/admin');
$response->assertSee('Blog');
$response->assertSee('Posts');
$response->assertSee('Categories');
}
```
### Livewire Component Tests
```php
public function test_can_create_post_via_modal(): void
{
Livewire::actingAs($admin)
->test(PostEditor::class)
->set('title', 'Test Post')
->set('content', 'Test content')
->call('save')
->assertDispatched('post-saved');
$this->assertDatabaseHas('posts', [
'title' => 'Test Post',
]);
}
```
## Best Practices
### 1. Use Livewire Modals for CRUD
```php
// ✅ Good - modal UX
<x-button wire:click="$dispatch('openModal', {component: 'post-editor'})">
New Post
</x-button>
// ❌ Bad - full page redirect
<a href="{{ route('admin.posts.create') }}">New Post</a>
```
### 2. Organize Menu Items by Domain
```php
MenuItemBuilder::make('Content')
->children([
MenuItemBuilder::make('Posts')->route('admin.posts.index'),
MenuItemBuilder::make('Pages')->route('admin.pages.index'),
]);
```
### 3. Use Form Components
```blade
{{-- ✅ Good - consistent styling --}}
<x-admin::form-group label="Title" name="title">
<x-admin::input name="title" />
</x-admin::form-group>
{{-- ❌ Bad - custom HTML --}}
<div class="mb-4">
<label>Title</label>
<input type="text" name="title">
</div>
```
## Changelog
See [CHANGELOG.md](https://github.com/host-uk/core-php/blob/main/packages/core-admin/changelog/2026/jan/features.md)
## License
EUPL-1.2
## Learn More
- [HLCRF Layout System →](/patterns-guide/hlcrf)
- [Livewire Documentation](https://livewire.laravel.com)
- [Alpine.js Documentation](https://alpinejs.dev)

View file

@ -1,575 +0,0 @@
# API Package
The API package provides secure REST API functionality with OpenAPI documentation, rate limiting, webhook delivery, and scope-based authorization.
## Installation
```bash
composer require host-uk/core-api
```
## Features
### OpenAPI Documentation
Automatically generated API documentation with Swagger/Scalar/ReDoc interfaces:
```php
<?php
namespace Mod\Blog\Controllers\Api;
use Mod\Blog\Models\Post;
use Core\Api\Documentation\Attributes\ApiTag;
use Core\Api\Documentation\Attributes\ApiParameter;
use Core\Api\Documentation\Attributes\ApiResponse;
#[ApiTag('Posts', 'Blog post management')]
class PostController
{
#[ApiResponse(200, 'Success', Post::class)]
#[ApiResponse(404, 'Post not found')]
public function show(Post $post)
{
return response()->json($post);
}
#[ApiParameter('title', 'string', 'Post title', required: true)]
#[ApiParameter('content', 'string', 'Post content', required: true)]
#[ApiResponse(201, 'Post created', Post::class)]
public function store(Request $request)
{
$post = Post::create($request->validated());
return response()->json($post, 201);
}
}
```
Access documentation:
- Scalar UI: `https://your-app.test/api/docs`
- Swagger UI: `https://your-app.test/api/docs/swagger`
- ReDoc: `https://your-app.test/api/docs/redoc`
- OpenAPI JSON: `https://your-app.test/api/docs/openapi.json`
### Secure API Keys
Bcrypt-hashed API keys with rotation support:
```php
use Mod\Api\Models\ApiKey;
// Create API key
$apiKey = ApiKey::create([
'name' => 'Mobile App',
'workspace_id' => $workspace->id,
'scopes' => ['posts:read', 'posts:write'],
'rate_limit_tier' => 'pro',
]);
// Get plaintext key (only shown once!)
$plaintext = $apiKey->plaintext_key; // sk_live_...
// Verify key
if ($apiKey->verify($plaintext)) {
// Valid key
}
// Rotate key
$newKey = $apiKey->rotate();
```
### Rate Limiting
Tier-based rate limiting with workspace isolation:
```php
// config/core-api.php
'rate_limits' => [
'tiers' => [
'free' => [
'requests' => 1000,
'window' => 60, // minutes
],
'pro' => [
'requests' => 10000,
'window' => 60,
],
'enterprise' => [
'requests' => null, // unlimited
],
],
],
```
Rate limit headers are automatically added:
```
X-RateLimit-Limit: 10000
X-RateLimit-Remaining: 9995
X-RateLimit-Reset: 1640995200
```
### Scope Enforcement
Fine-grained API access control:
```php
// Define scopes in API key
$apiKey = ApiKey::create([
'scopes' => ['posts:read', 'posts:write', 'categories:read'],
]);
// Protect routes with scopes
Route::middleware(['api', 'auth:sanctum', 'scope:posts:write'])
->post('/posts', [PostController::class, 'store']);
// Check scopes in controller
if (! $request->user()->tokenCan('posts:delete')) {
abort(403, 'Insufficient permissions');
}
```
Available scopes:
```php
// config/core-api.php
'scopes' => [
'available' => [
'posts:read',
'posts:write',
'posts:delete',
'categories:read',
'categories:write',
'analytics:read',
'webhooks:manage',
],
],
```
### Webhook Delivery
Reliable webhook delivery with retry logic and signature verification:
```php
use Mod\Api\Models\WebhookEndpoint;
use Mod\Api\Services\WebhookService;
// Register webhook endpoint
$endpoint = WebhookEndpoint::create([
'url' => 'https://customer.com/webhooks',
'events' => ['post.created', 'post.updated'],
'secret' => Str::random(32),
]);
// Dispatch webhook
$webhook = app(WebhookService::class);
$webhook->dispatch('post.created', [
'id' => $post->id,
'title' => $post->title,
'published_at' => $post->published_at,
], $endpoint);
```
### Webhook Signature Verification
Webhooks are signed with HMAC-SHA256:
```php
// Receiving webhooks (customer side)
$signature = $request->header('X-Webhook-Signature');
$timestamp = $request->header('X-Webhook-Timestamp');
$payload = $request->getContent();
$expected = hash_hmac(
'sha256',
$timestamp . '.' . $payload,
$webhookSecret
);
if (! hash_equals($expected, $signature)) {
abort(401, 'Invalid signature');
}
// Check timestamp to prevent replay attacks
if (abs(time() - $timestamp) > 300) {
abort(401, 'Request too old');
}
```
Core PHP provides a helper service:
```php
use Mod\Api\Services\WebhookSignature;
$verifier = app(WebhookSignature::class);
if (! $verifier->verify($request, $webhookSecret)) {
abort(401, 'Invalid signature');
}
```
### Usage Alerts
Monitor API usage and alert on high usage:
```php
// config/core-api.php
'usage_alerts' => [
'enabled' => true,
'thresholds' => [
'warning' => 80, // % of limit
'critical' => 95,
],
],
```
Check usage alerts:
```bash
php artisan api:check-usage-alerts
```
Notifications sent when usage exceeds thresholds:
```php
use Mod\Api\Notifications\HighApiUsageNotification;
// Sent automatically to workspace owners
Mail::to($workspace->owner)
->send(new HighApiUsageNotification($workspace, $usage));
```
## API Routes
Define API routes in your module:
```php
// Mod/Blog/Routes/api.php
<?php
use Illuminate\Support\Facades\Route;
use Mod\Blog\Controllers\Api\PostController;
Route::prefix('v1')->group(function () {
// Public endpoints
Route::get('posts', [PostController::class, 'index']);
Route::get('posts/{post}', [PostController::class, 'show']);
// Protected endpoints
Route::middleware('auth:sanctum')->group(function () {
Route::post('posts', [PostController::class, 'store'])
->middleware('scope:posts:write');
Route::put('posts/{post}', [PostController::class, 'update'])
->middleware('scope:posts:write');
Route::delete('posts/{post}', [PostController::class, 'destroy'])
->middleware('scope:posts:delete');
});
});
```
Register in Boot.php:
```php
public function onApiRoutes(ApiRoutesRegistering $event): void
{
$event->routes(fn () => require __DIR__.'/Routes/api.php');
}
```
## API Resources
Transform models for API responses:
```php
<?php
namespace Mod\Blog\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class PostResource extends JsonResource
{
public function toArray($request): array
{
return [
'id' => $this->id,
'title' => $this->title,
'slug' => $this->slug,
'excerpt' => $this->excerpt,
'content' => $this->when(
$request->user()?->tokenCan('posts:read:full'),
$this->content
),
'published_at' => $this->published_at?->toIso8601String(),
'category' => new CategoryResource($this->whenLoaded('category')),
'author' => new UserResource($this->whenLoaded('author')),
'links' => [
'self' => route('api.posts.show', $this),
'category' => route('api.categories.show', $this->category_id),
],
];
}
}
```
Use in controllers:
```php
public function index()
{
$posts = Post::with('category', 'author')->paginate(20);
return PostResource::collection($posts);
}
public function show(Post $post)
{
return new PostResource($post->load('category', 'author'));
}
```
## Error Handling
Standardized error responses:
```json
{
"message": "The given data was invalid.",
"errors": {
"title": ["The title field is required."],
"content": ["The content field is required."]
}
}
```
Custom error responses:
```php
return response()->json([
'message' => 'Post not found',
'error_code' => 'POST_NOT_FOUND',
], 404);
```
## Pagination
Laravel's pagination is automatically formatted:
```json
{
"data": [
{ "id": 1, "title": "Post 1" },
{ "id": 2, "title": "Post 2" }
],
"links": {
"first": "https://api.example.com/posts?page=1",
"last": "https://api.example.com/posts?page=10",
"prev": null,
"next": "https://api.example.com/posts?page=2"
},
"meta": {
"current_page": 1,
"from": 1,
"last_page": 10,
"per_page": 20,
"to": 20,
"total": 200
}
}
```
## Testing
### Feature Tests
```php
<?php
namespace Tests\Feature\Api;
use Tests\TestCase;
use Mod\Blog\Models\Post;
use Mod\Api\Models\ApiKey;
class PostApiTest extends TestCase
{
public function test_can_list_posts(): void
{
Post::factory()->count(3)->create();
$response = $this->getJson('/api/v1/posts');
$response->assertStatus(200)
->assertJsonCount(3, 'data');
}
public function test_requires_authentication_to_create_post(): void
{
$response = $this->postJson('/api/v1/posts', [
'title' => 'Test Post',
'content' => 'Test content',
]);
$response->assertStatus(401);
}
public function test_can_create_post_with_valid_api_key(): void
{
$apiKey = ApiKey::factory()
->withScopes(['posts:write'])
->create();
$response = $this->withHeaders([
'Authorization' => 'Bearer ' . $apiKey->plaintext_key,
])->postJson('/api/v1/posts', [
'title' => 'Test Post',
'content' => 'Test content',
]);
$response->assertStatus(201)
->assertJsonStructure(['data' => ['id', 'title']]);
}
public function test_enforces_rate_limits(): void
{
$apiKey = ApiKey::factory()
->tier('free')
->create();
// Make requests up to limit
for ($i = 0; $i < 1001; $i++) {
$response = $this->withHeaders([
'Authorization' => 'Bearer ' . $apiKey->plaintext_key,
])->getJson('/api/v1/posts');
}
$response->assertStatus(429); // Too Many Requests
}
}
```
## Configuration
```php
// config/core-api.php
return [
'rate_limits' => [
'tiers' => [
'free' => ['requests' => 1000, 'window' => 60],
'pro' => ['requests' => 10000, 'window' => 60],
'enterprise' => ['requests' => null],
],
'headers_enabled' => true,
],
'api_keys' => [
'hash_algorithm' => 'bcrypt',
'rotation_grace_period' => 86400, // 24 hours
'prefix' => 'sk_',
],
'webhooks' => [
'signature_algorithm' => 'sha256',
'max_retries' => 3,
'retry_delay' => 60,
'timeout' => 10,
'verify_ssl' => true,
],
'documentation' => [
'enabled' => true,
'require_auth' => false,
'title' => 'API Documentation',
'default_ui' => 'scalar',
],
'scopes' => [
'enforce' => true,
'available' => [
'posts:read',
'posts:write',
'posts:delete',
],
],
];
```
## Artisan Commands
```bash
# Check usage alerts
php artisan api:check-usage-alerts
# Rotate API key
php artisan api:rotate-key {key-id}
# Generate API documentation
php artisan api:generate-docs
# Test webhook delivery
php artisan api:test-webhook {endpoint-id}
```
## Best Practices
### 1. Use API Resources
```php
// ✅ Good - consistent formatting
return PostResource::collection($posts);
// ❌ Bad - raw data
return response()->json($posts);
```
### 2. Version Your API
```php
// ✅ Good - versioned routes
Route::prefix('v1')->group(/*...*/);
Route::prefix('v2')->group(/*...*/);
// ❌ Bad - no versioning
Route::prefix('api')->group(/*...*/);
```
### 3. Use Scopes for Authorization
```php
// ✅ Good - granular scopes
Route::middleware('scope:posts:write')->post('/posts', /*...*/);
// ❌ Bad - no scope checking
Route::middleware('auth:sanctum')->post('/posts', /*...*/);
```
### 4. Validate Webhook Signatures
```php
// ✅ Good - verify signatures
if (! WebhookSignature::verify($request, $secret)) {
abort(401);
}
// ❌ Bad - no verification
// Process webhook without checking signature
```
## Changelog
See [CHANGELOG.md](https://github.com/host-uk/core-php/blob/main/packages/core-api/changelog/2026/jan/features.md)
## License
EUPL-1.2
## Learn More
- [API Authentication →](/security/api-authentication)
- [Rate Limiting →](/security/rate-limiting)
- [Webhook Delivery →](/patterns-guide/webhooks)
- [OpenAPI Documentation](https://swagger.io/specification/)

View file

@ -1,587 +0,0 @@
# Core Package
The Core package provides the foundation for the framework including the module system, lifecycle events, multi-tenancy, and shared utilities.
## Installation
```bash
composer require host-uk/core
```
## Features
### Module System
Auto-discover and lazy-load modules based on lifecycle events:
```php
<?php
namespace Mod\Example;
use Core\Events\WebRoutesRegistering;
class Boot
{
public static array $listens = [
WebRoutesRegistering::class => 'onWebRoutes',
];
public function onWebRoutes(WebRoutesRegistering $event): void
{
$event->views('example', __DIR__.'/Views');
$event->routes(fn () => require __DIR__.'/Routes/web.php');
}
}
```
[Learn more about the Module System →](/architecture/module-system)
### Lifecycle Events
Event-driven extension points throughout the framework:
- `WebRoutesRegistering` - Public web routes
- `AdminPanelBooting` - Admin panel initialization
- `ApiRoutesRegistering` - REST API routes
- `ClientRoutesRegistering` - Authenticated client routes
- `ConsoleBooting` - Artisan commands
- `McpToolsRegistering` - MCP tools
- `FrameworkBooted` - Late-stage initialization
[Learn more about Lifecycle Events →](/architecture/lifecycle-events)
### Actions Pattern
Single-purpose business logic classes:
```php
class CreatePost
{
use Action;
public function handle(array $data): Post
{
$post = Post::create($data);
event(new PostCreated($post));
return $post;
}
}
// Usage
$post = CreatePost::run($data);
```
[Learn more about Actions →](/patterns-guide/actions)
### Multi-Tenancy
Workspace-scoped data isolation:
```php
use Core\Mod\Tenant\Concerns\BelongsToWorkspace;
class Post extends Model
{
use BelongsToWorkspace;
}
// Queries automatically scoped to current workspace
$posts = Post::all();
```
[Learn more about Multi-Tenancy →](/architecture/multi-tenancy)
### Activity Logging
Track changes to models with automatic workspace scoping:
```php
use Core\Activity\Concerns\LogsActivity;
class Post extends Model
{
use LogsActivity;
protected array $activityLogAttributes = ['title', 'status'];
}
// Changes logged automatically
$post->update(['title' => 'New Title']);
// Retrieve activity
$activity = Activity::forSubject($post)->get();
```
[Learn more about Activity Logging →](/patterns-guide/activity-logging)
### Seeder Discovery
Automatic seeder discovery with dependency ordering:
```php
use Core\Database\Seeders\Attributes\SeederPriority;
use Core\Database\Seeders\Attributes\SeederAfter;
#[SeederPriority(50)]
#[SeederAfter(WorkspaceSeeder::class)]
class PostSeeder extends Seeder
{
public function run(): void
{
Post::factory()->count(20)->create();
}
}
```
[Learn more about Seeders →](/patterns-guide/seeders)
### Configuration Management
Multi-profile configuration with versioning:
```php
use Core\Config\ConfigService;
$config = app(ConfigService::class);
// Set configuration
$config->set('api.rate_limit', 10000, $profile);
// Get configuration
$rateLimit = $config->get('api.rate_limit', $profile);
// Export configuration
php artisan config:export production
// Import configuration
php artisan config:import production.json
```
### CDN Integration
Unified CDN interface for BunnyCDN and Cloudflare:
```php
use Core\Cdn\Facades\Cdn;
// Generate CDN URL
$url = Cdn::url('images/photo.jpg');
// Store file to CDN
$path = Cdn::store($uploadedFile, 'media');
// Delete from CDN
Cdn::delete($path);
// Purge cache
Cdn::purge('images/*');
```
### Security Headers
Configurable security headers with CSP support:
```php
// config/core.php
'security_headers' => [
'csp' => [
'directives' => [
'default-src' => ["'self'"],
'script-src' => ["'self'", "'nonce'"],
'style-src' => ["'self'", "'unsafe-inline'"],
],
],
'hsts' => [
'enabled' => true,
'max_age' => 31536000,
],
],
```
### Email Shield
Disposable email detection and validation:
```php
use Core\Mail\EmailShield;
$shield = app(EmailShield::class);
$result = $shield->validate('user@example.com');
if (! $result->isValid) {
// Email is disposable, has syntax errors, etc.
return back()->withErrors(['email' => $result->reason]);
}
```
### Media Processing
Image optimization and responsive images:
```php
use Core\Media\Image\ImageOptimizer;
$optimizer = app(ImageOptimizer::class);
// Optimize image
$optimized = $optimizer->optimize('path/to/image.jpg');
// Generate responsive variants
$variants = $optimizer->generateVariants($image, [
'thumbnail' => ['width' => 150, 'height' => 150],
'medium' => ['width' => 640],
'large' => ['width' => 1024],
]);
```
### Search
Unified search interface across modules:
```php
use Core\Search\Unified;
$search = app(Unified::class);
$results = $search->search('query', [
'types' => ['posts', 'pages'],
'limit' => 10,
]);
foreach ($results as $result) {
echo $result->title;
echo $result->url;
}
```
### SEO Tools
SEO metadata generation and sitemap:
```php
use Core\Seo\SeoMetadata;
$seo = app(SeoMetadata::class);
$seo->setTitle('Page Title')
->setDescription('Page description')
->setCanonicalUrl('https://example.com/page')
->setOgImage('https://example.com/og-image.jpg');
// Generate in view
{!! $seo->render() !!}
// Sitemap generation
php artisan seo:generate-sitemap
```
## Artisan Commands
### Module Management
```bash
# Create new module
php artisan make:mod Blog
# Create website module
php artisan make:website Marketing
# Create plugin module
php artisan make:plug Stripe
```
### Configuration
```bash
# Export configuration
php artisan config:export production
# Import configuration
php artisan config:import production.json --profile=production
# Show configuration versions
php artisan config:version --profile=production
```
### Activity Logs
```bash
# Prune old activity logs
php artisan activity:prune --days=90
```
### Email Shield
```bash
# Prune email shield statistics
php artisan email-shield:prune --days=30
```
### SEO
```bash
# Generate sitemap
php artisan seo:generate-sitemap
# Audit canonical URLs
php artisan seo:audit-canonical
# Test structured data
php artisan seo:test-structured-data --url=/blog/post-slug
```
### Storage
```bash
# Warm cache
php artisan cache:warm
# Offload files to CDN
php artisan storage:offload --disk=public
```
## Configuration
### Core Configuration
```php
// config/core.php
return [
'module_paths' => [
app_path('Core'),
app_path('Mod'),
app_path('Plug'),
],
'modules' => [
'auto_discover' => true,
'cache_enabled' => true,
],
'seeders' => [
'auto_discover' => true,
'paths' => [
'Mod/*/Database/Seeders',
'Core/*/Database/Seeders',
],
],
'activity' => [
'enabled' => true,
'retention_days' => 90,
'log_ip_address' => false,
],
'workspace_cache' => [
'enabled' => true,
'ttl' => 3600,
'use_tags' => true,
],
];
```
[View full configuration options →](/guide/configuration#core-configuration)
## Testing
### Feature Tests
```php
<?php
namespace Tests\Feature;
use Tests\TestCase;
use Mod\Blog\Actions\CreatePost;
class CreatePostTest extends TestCase
{
public function test_creates_post(): void
{
$post = CreatePost::run([
'title' => 'Test Post',
'content' => 'Test content',
]);
$this->assertDatabaseHas('posts', [
'title' => 'Test Post',
]);
}
}
```
### Unit Tests
```php
<?php
namespace Tests\Unit;
use Tests\TestCase;
use Core\Module\ModuleScanner;
class ModuleScannerTest extends TestCase
{
public function test_discovers_modules(): void
{
$scanner = new ModuleScanner();
$modules = $scanner->scan([app_path('Mod')]);
$this->assertNotEmpty($modules);
$this->assertArrayHasKey('listens', $modules[0]);
}
}
```
## Database
### Migrations
Core package includes migrations for:
- `activity_log` - Activity logging
- `config_keys` - Configuration keys
- `config_values` - Configuration values
- `config_profiles` - Configuration profiles
- `config_versions` - Configuration versioning
- `email_shield_stats` - Email validation statistics
- `workspaces` - Multi-tenant workspaces
- `workspace_users` - User-workspace relationships
Run migrations:
```bash
php artisan migrate
```
## Events
Core package dispatches these events:
### Lifecycle Events
- `Core\Events\WebRoutesRegistering`
- `Core\Events\AdminPanelBooting`
- `Core\Events\ApiRoutesRegistering`
- `Core\Events\ClientRoutesRegistering`
- `Core\Events\ConsoleBooting`
- `Core\Events\McpToolsRegistering`
- `Core\Events\FrameworkBooted`
### Configuration Events
- `Core\Config\Events\ConfigChanged`
- `Core\Config\Events\ConfigInvalidated`
### Activity Events
- `Core\Activity\Events\ActivityLogged`
## Middleware
### Multi-Tenancy
- `Core\Mod\Tenant\Middleware\RequireWorkspaceContext` - Ensure workspace is set
### Security
- `Core\Headers\SecurityHeaders` - Apply security headers
- `Core\Bouncer\BlocklistService` - IP blocklist
- `Core\Bouncer\Gate\ActionGateMiddleware` - Action authorization
## Service Providers
Register Core package in `config/app.php`:
```php
'providers' => [
// ...
Core\CoreServiceProvider::class,
],
```
Or use auto-discovery (Laravel 11+).
## Helpers
### Global Helpers
```php
// Get current workspace
$workspace = workspace();
// Create activity log
activity()
->performedOn($model)
->log('action');
// Generate CDN URL
$url = cdn_url('path/to/asset.jpg');
// Get CSP nonce
$nonce = csp_nonce();
```
## Best Practices
### 1. Use Actions for Business Logic
```php
// ✅ Good
$post = CreatePost::run($data);
// ❌ Bad
$post = Post::create($data);
event(new PostCreated($post));
Cache::forget('posts');
```
### 2. Log Activity for Audit Trail
```php
class Post extends Model
{
use LogsActivity;
protected array $activityLogAttributes = ['title', 'status', 'published_at'];
}
```
### 3. Use Workspace Scoping
```php
class Post extends Model
{
use BelongsToWorkspace;
}
```
### 4. Leverage Module System
```php
// Create focused modules with clear boundaries
Mod/Blog/
Mod/Commerce/
Mod/Analytics/
```
## Changelog
See [CHANGELOG.md](https://github.com/host-uk/core-php/blob/main/packages/core-php/changelog/2026/jan/features.md)
## License
EUPL-1.2
## Learn More
- [Module System →](/architecture/module-system)
- [Lifecycle Events →](/architecture/lifecycle-events)
- [Actions Pattern →](/patterns-guide/actions)
- [Multi-Tenancy →](/architecture/multi-tenancy)
- [Activity Logging →](/patterns-guide/activity-logging)

View file

@ -0,0 +1,171 @@
# core build
Build Go, Wails, Docker, and LinuxKit projects with automatic project detection.
## Usage
```bash
core build [flags]
```
## Flags
| Flag | Description |
|------|-------------|
| `--type` | Project type: `go`, `wails`, `docker`, `linuxkit` (auto-detected) |
| `--targets` | Build targets: `linux/amd64,darwin/arm64,windows/amd64` |
| `--output` | Output directory (default: `dist`) |
| `--ci` | CI mode - non-interactive, fail fast |
| `--image` | Docker image name (for docker builds) |
| `--no-sign` | Skip code signing |
| `--notarize` | Enable macOS notarization (requires Apple credentials) |
## Examples
### Go Project
```bash
# Auto-detect and build
core build
# Build for specific platforms
core build --targets linux/amd64,linux/arm64,darwin/arm64
# CI mode
core build --ci
```
### Wails Project
```bash
# Build Wails desktop app
core build --type wails
# Build for all desktop platforms
core build --type wails --targets darwin/amd64,darwin/arm64,windows/amd64,linux/amd64
```
### Docker Image
```bash
# Build Docker image
core build --type docker
# With custom image name
core build --type docker --image ghcr.io/myorg/myapp
```
### LinuxKit Image
```bash
# Build LinuxKit ISO
core build --type linuxkit
```
## Project Detection
Core automatically detects project type based on files:
| Files | Type |
|-------|------|
| `wails.json` | Wails |
| `go.mod` | Go |
| `Dockerfile` | Docker |
| `composer.json` | PHP |
| `package.json` | Node |
## Output
Build artifacts are placed in `dist/` by default:
```
dist/
├── myapp-linux-amd64.tar.gz
├── myapp-linux-arm64.tar.gz
├── myapp-darwin-amd64.tar.gz
├── myapp-darwin-arm64.tar.gz
├── myapp-windows-amd64.zip
└── CHECKSUMS.txt
```
## Configuration
Optional `.core/build.yaml`:
```yaml
version: 1
project:
name: myapp
binary: myapp
build:
main: ./cmd/myapp
ldflags:
- -s -w
- -X main.version={{.Version}}
targets:
- os: linux
arch: amd64
- os: linux
arch: arm64
- os: darwin
arch: arm64
sign:
enabled: true
gpg:
key: $GPG_KEY_ID
macos:
identity: "Developer ID Application: Your Name (TEAM_ID)"
notarize: false
apple_id: $APPLE_ID
team_id: $APPLE_TEAM_ID
app_password: $APPLE_APP_PASSWORD
```
## Code Signing
Core supports GPG signing for checksums and native code signing for macOS.
### GPG Signing
Signs `CHECKSUMS.txt` with a detached ASCII signature (`.asc`):
```bash
# Build with GPG signing (default if key configured)
core build
# Skip signing
core build --no-sign
```
Users can verify:
```bash
gpg --verify CHECKSUMS.txt.asc CHECKSUMS.txt
sha256sum -c CHECKSUMS.txt
```
### macOS Code Signing
Signs Darwin binaries with your Developer ID and optionally notarizes with Apple:
```bash
# Build with codesign (automatic if identity configured)
core build
# Build with notarization (takes 1-5 minutes)
core build --notarize
```
### Environment Variables
| Variable | Purpose |
|----------|---------|
| `GPG_KEY_ID` | GPG key ID or fingerprint |
| `CODESIGN_IDENTITY` | macOS Developer ID (fallback) |
| `APPLE_ID` | Apple account email |
| `APPLE_TEAM_ID` | Apple Developer Team ID |
| `APPLE_APP_PASSWORD` | App-specific password for notarization |

325
docs/packages/go/cmd/dev.md Normal file
View file

@ -0,0 +1,325 @@
# core dev
Portable development environment with 100+ embedded tools.
## Overview
Core DevOps provides a sandboxed, immutable development environment based on LinuxKit. It includes AI tools (Claude, Gemini), runtimes (Go, Node, PHP, Python, Rust), and infrastructure tools (Docker, Kubernetes, Terraform).
## Commands
| Command | Description |
|---------|-------------|
| `install` | Download the core-devops image for your platform |
| `boot` | Start the development environment |
| `stop` | Stop the running environment |
| `status` | Show environment status |
| `shell` | Open a shell in the environment |
| `serve` | Mount project and start dev server |
| `test` | Run tests inside the environment |
| `claude` | Start sandboxed Claude session |
| `update` | Update to latest image |
## Quick Start
```bash
# First time setup
core dev install
core dev boot
# Open shell
core dev shell
# Or mount current project and serve
core dev serve
```
## dev install
Download the core-devops image for your platform.
```bash
core dev install [flags]
```
### Flags
| Flag | Description |
|------|-------------|
| `--source` | Image source: `github`, `registry`, `cdn` (default: auto) |
| `--force` | Force re-download even if exists |
### Examples
```bash
# Download image (auto-detects platform)
core dev install
# Force re-download
core dev install --force
```
## dev boot
Start the development environment.
```bash
core dev boot [flags]
```
### Flags
| Flag | Description |
|------|-------------|
| `--memory` | Memory allocation in MB (default: 4096) |
| `--cpus` | Number of CPUs (default: 4) |
| `--name` | Container name (default: core-dev) |
### Examples
```bash
# Start with defaults
core dev boot
# More resources
core dev boot --memory 8192 --cpus 8
```
## dev shell
Open a shell in the running environment.
```bash
core dev shell [flags]
```
### Flags
| Flag | Description |
|------|-------------|
| `--console` | Use serial console instead of SSH |
### Examples
```bash
# SSH into environment
core dev shell
# Serial console (for debugging)
core dev shell --console
```
## dev serve
Mount current directory and start the appropriate dev server.
```bash
core dev serve [flags]
```
### Flags
| Flag | Description |
|------|-------------|
| `--port` | Port to expose (default: 8000) |
| `--path` | Subdirectory to serve |
### Auto-Detection
| Project | Server Command |
|---------|---------------|
| Laravel (`artisan`) | `php artisan octane:start` |
| Node (`package.json` with `dev` script) | `npm run dev` |
| PHP (`composer.json`) | `frankenphp php-server` |
| Other | `python -m http.server` |
### Examples
```bash
# Auto-detect and serve
core dev serve
# Custom port
core dev serve --port 3000
```
## dev test
Run tests inside the environment.
```bash
core dev test [flags] [-- custom command]
```
### Flags
| Flag | Description |
|------|-------------|
| `--unit` | Run only unit tests |
### Test Detection
Core auto-detects the test framework:
1. `.core/test.yaml` - Custom config
2. `composer.json``composer test`
3. `package.json``npm test`
4. `go.mod``go test ./...`
5. `pytest.ini``pytest`
6. `Taskfile.yaml``task test`
### Examples
```bash
# Auto-detect and run tests
core dev test
# Custom command
core dev test -- go test -v ./pkg/...
```
### Test Configuration
Create `.core/test.yaml` for custom test setup:
```yaml
version: 1
commands:
- name: unit
run: vendor/bin/pest --parallel
- name: types
run: vendor/bin/phpstan analyse
- name: lint
run: vendor/bin/pint --test
env:
APP_ENV: testing
DB_CONNECTION: sqlite
```
## dev claude
Start a sandboxed Claude session with your project mounted.
```bash
core dev claude [flags]
```
### Flags
| Flag | Description |
|------|-------------|
| `--no-auth` | Clean session without host credentials |
| `--auth` | Selective auth forwarding (e.g., `gh,anthropic`) |
### What Gets Forwarded
By default, these are forwarded to the sandbox:
- `~/.anthropic/` or `ANTHROPIC_API_KEY`
- `~/.config/gh/` (GitHub CLI auth)
- SSH agent
- Git config (name, email)
### Examples
```bash
# Full auth forwarding (default)
core dev claude
# Clean sandbox
core dev claude --no-auth
# Only GitHub auth
core dev claude --auth=gh
```
### Why Use This?
- **Immutable base** - Reset anytime with `core dev boot --fresh`
- **Safe experimentation** - Claude can install packages, make mistakes
- **Host system untouched** - All changes stay in the sandbox
- **Real credentials** - Can still push code, create PRs
- **Full tooling** - 100+ tools available in the image
## dev status
Show the current state of the development environment.
```bash
core dev status
```
Output includes:
- Running/stopped state
- Resource usage (CPU, memory)
- Exposed ports
- Mounted directories
## dev update
Check for and download newer images.
```bash
core dev update [flags]
```
### Flags
| Flag | Description |
|------|-------------|
| `--force` | Force download even if up to date |
## Embedded Tools
The core-devops image includes 100+ tools:
| Category | Tools |
|----------|-------|
| **AI/LLM** | claude, gemini, aider, ollama, llm |
| **VCS** | git, gh, glab, lazygit, delta, git-lfs |
| **Runtimes** | frankenphp, node, bun, deno, go, python3, rustc |
| **Package Mgrs** | composer, npm, pnpm, yarn, pip, uv, cargo |
| **Build** | task, make, just, nx, turbo |
| **Linting** | pint, phpstan, prettier, eslint, biome, golangci-lint, ruff |
| **Testing** | phpunit, pest, vitest, playwright, k6 |
| **Infra** | docker, kubectl, k9s, helm, terraform, ansible |
| **Databases** | sqlite3, mysql, psql, redis-cli, mongosh, usql |
| **HTTP/Net** | curl, httpie, xh, websocat, grpcurl, mkcert, ngrok |
| **Data** | jq, yq, fx, gron, miller, dasel |
| **Security** | age, sops, cosign, trivy, trufflehog, vault |
| **Files** | fd, rg, fzf, bat, eza, tree, zoxide, broot |
| **Editors** | nvim, helix, micro |
## Configuration
Global config in `~/.core/config.yaml`:
```yaml
version: 1
images:
source: auto # auto | github | registry | cdn
cdn:
url: https://images.example.com/core-devops
github:
repo: host-uk/core-images
registry:
image: ghcr.io/host-uk/core-devops
```
## Image Storage
Images are stored in `~/.core/images/`:
```
~/.core/
├── config.yaml
└── images/
├── core-devops-darwin-arm64.qcow2
├── core-devops-linux-amd64.qcow2
└── manifest.json
```

View file

@ -0,0 +1,110 @@
# core docs
Documentation management across repositories.
## Usage
```bash
core docs <command> [flags]
```
## Commands
| Command | Description |
|---------|-------------|
| `list` | List documentation across repos |
| `sync` | Sync documentation to output directory |
## docs list
Show documentation coverage across all repos.
```bash
core docs list [flags]
```
### Flags
| Flag | Description |
|------|-------------|
| `--registry` | Path to repos.yaml |
### Output
```
Repo README CLAUDE CHANGELOG docs/
──────────────────────────────────────────────────────────────────────
core ✓ ✓ — 12 files
core-php ✓ ✓ ✓ 8 files
core-images ✓ — — —
Coverage: 3 with docs, 0 without
```
## docs sync
Sync documentation from all repos to an output directory.
```bash
core docs sync [flags]
```
### Flags
| Flag | Description |
|------|-------------|
| `--registry` | Path to repos.yaml |
| `--output` | Output directory (default: ./docs-build) |
| `--dry-run` | Show what would be synced |
### Output Structure
```
docs-build/
└── packages/
├── core/
│ ├── index.md # from README.md
│ ├── claude.md # from CLAUDE.md
│ ├── changelog.md # from CHANGELOG.md
│ ├── build.md # from docs/build.md
│ └── ...
└── core-php/
├── index.md
└── ...
```
### Example
```bash
# Preview what will be synced
core docs sync --dry-run
# Sync to default output
core docs sync
# Sync to custom directory
core docs sync --output ./site/content
```
## What Gets Synced
For each repo, the following files are collected:
| Source | Destination |
|--------|-------------|
| `README.md` | `index.md` |
| `CLAUDE.md` | `claude.md` |
| `CHANGELOG.md` | `changelog.md` |
| `docs/*.md` | `*.md` |
## Integration with core.help
The synced docs are used to build https://core.help:
1. Run `core docs sync --output ../core-php/docs/packages`
2. VitePress builds the combined documentation
3. Deploy to core.help
## See Also
- [Configuration](../configuration.md) - Project configuration

View file

@ -0,0 +1,75 @@
# core doctor
Check your development environment for required tools and configuration.
## Usage
```bash
core doctor
```
## What It Checks
### Required Tools
| Tool | Purpose |
|------|---------|
| `git` | Version control |
| `go` | Go compiler |
| `gh` | GitHub CLI |
### Optional Tools
| Tool | Purpose |
|------|---------|
| `node` | Node.js runtime |
| `docker` | Container runtime |
| `wails` | Desktop app framework |
| `qemu` | VM runtime for LinuxKit |
| `gpg` | Code signing |
| `codesign` | macOS signing (macOS only) |
### Configuration
- Git user name and email
- GitHub CLI authentication
- Go workspace setup
## Output
```
Core Doctor
===========
Required:
[OK] git 2.43.0
[OK] go 1.23.0
[OK] gh 2.40.0
Optional:
[OK] node 20.10.0
[OK] docker 24.0.7
[--] wails (not installed)
[OK] qemu 8.2.0
[OK] gpg 2.4.3
[OK] codesign (available)
Configuration:
[OK] git user.name: Your Name
[OK] git user.email: you@example.com
[OK] gh auth status: Logged in
All checks passed!
```
## Exit Codes
| Code | Meaning |
|------|---------|
| 0 | All required checks passed |
| 1 | One or more required checks failed |
## See Also
- [setup command](setup.md) - Clone repos from registry
- [dev install](dev.md) - Install development environment

138
docs/packages/go/cmd/php.md Normal file
View file

@ -0,0 +1,138 @@
# core php
Laravel/PHP development environment with FrankenPHP, Vite, Horizon, Reverb, and Redis.
## Commands
| Command | Description |
|---------|-------------|
| `core php dev` | Start development environment |
| `core php test` | Run PHPUnit/Pest tests |
| `core php fmt` | Format with Laravel Pint |
| `core php analyse` | Static analysis with PHPStan |
| `core php build` | Build production container |
| `core php deploy` | Deploy to Coolify |
## Development Environment
```bash
# Start all services
core php dev
```
This starts:
- FrankenPHP/Octane (HTTP server)
- Vite dev server (frontend)
- Laravel Horizon (queues)
- Laravel Reverb (WebSockets)
- Redis
```bash
# View unified logs
core php logs
# Stop all services
core php stop
```
## Testing
```bash
# Run tests
core php test
# Parallel testing
core php test --parallel
# With coverage
core php test --coverage
```
## Code Quality
```bash
# Format code
core php fmt
# Static analysis
core php analyse
# Run both
core php fmt && core php analyse
```
## Building
```bash
# Build Docker container
core php build
# Build LinuxKit image
core php build --type linuxkit
# Run production locally
core php serve --production
```
## Deployment
```bash
# Deploy to Coolify
core php deploy
# Deploy to staging
core php deploy --staging
# Check deployment status
core php deploy:status
# Rollback
core php deploy:rollback
```
## Package Management
Link local packages for development:
```bash
# Link a local package
core php packages link ../my-package
# Update linked packages
core php packages update
# Unlink
core php packages unlink my-package
```
## SSL/HTTPS
Local SSL with mkcert:
```bash
# Auto-configured with core php dev
# Uses mkcert for trusted local certificates
```
## Configuration
Optional `.core/php.yaml`:
```yaml
version: 1
dev:
domain: myapp.test
ssl: true
services:
- frankenphp
- vite
- horizon
- reverb
- redis
deploy:
coolify:
server: https://coolify.example.com
project: my-project
```

View file

@ -0,0 +1,201 @@
# core release
Build and publish releases to GitHub, npm, Homebrew, Scoop, AUR, Chocolatey, Docker, and LinuxKit.
## Usage
```bash
core release [flags]
```
## Flags
| Flag | Description |
|------|-------------|
| `--dry-run` | Preview what would be published |
| `--version` | Override version (default: git tag) |
| `--target` | Release target: `sdk` for SDK-only release |
| `--draft` | Create release as draft |
| `--prerelease` | Mark release as prerelease |
## Quick Start
```bash
# Initialize release config
core release init
# Preview release
core release --dry-run
# Release
core release
# SDK-only release
core release --target sdk
```
## SDK Release
Generate SDKs without building binaries:
```bash
# Generate SDKs with version from git tag
core release --target sdk
# Explicit version
core release --target sdk --version v1.2.3
# Preview
core release --target sdk --dry-run
```
This will:
1. Determine version from git tags (or `--version` flag)
2. Run breaking change detection if configured
3. Generate SDKs for all configured languages
4. Output to `sdk/` directory
See [SDK commands](sdk.md) for more details.
## Publishers
### GitHub Releases
Uploads artifacts and changelog to GitHub Releases.
```yaml
publishers:
- type: github
prerelease: false
draft: false
```
### npm
Publishes binary wrapper that downloads correct platform binary.
```yaml
publishers:
- type: npm
package: "@myorg/myapp"
access: public
```
Requires `NPM_TOKEN` environment variable.
### Homebrew
Generates formula and commits to tap repository.
```yaml
publishers:
- type: homebrew
tap: myorg/homebrew-tap
formula: myapp # optional, defaults to project name
```
For official Homebrew PR:
```yaml
publishers:
- type: homebrew
tap: myorg/homebrew-tap
official:
enabled: true
output: dist/homebrew
```
### Scoop
Generates manifest and commits to bucket repository.
```yaml
publishers:
- type: scoop
bucket: myorg/scoop-bucket
```
### AUR
Generates PKGBUILD and pushes to AUR.
```yaml
publishers:
- type: aur
maintainer: "Your Name <email@example.com>"
```
### Chocolatey
Generates NuSpec and optionally pushes to Chocolatey.
```yaml
publishers:
- type: chocolatey
push: false # generate only
```
Set `push: true` and `CHOCOLATEY_API_KEY` to publish.
### Docker
Builds and pushes multi-arch Docker images.
```yaml
publishers:
- type: docker
registry: ghcr.io
image: myorg/myapp
platforms:
- linux/amd64
- linux/arm64
tags:
- latest
- "{{.Version}}"
```
### LinuxKit
Builds immutable LinuxKit images.
```yaml
publishers:
- type: linuxkit
config: .core/linuxkit/server.yml
formats:
- iso
- qcow2
- docker # Immutable container
platforms:
- linux/amd64
- linux/arm64
```
## Full Example
See [examples/full-release.yaml](examples/full-release.yaml) for a complete configuration.
## Changelog
Changelog is auto-generated from conventional commits:
```
feat: Add new feature → Features
fix: Fix bug → Bug Fixes
perf: Improve performance → Performance
refactor: Refactor code → Refactoring
```
Configure in `.core/release.yaml`:
```yaml
changelog:
include:
- feat
- fix
- perf
exclude:
- chore
- docs
- test
```

132
docs/packages/go/cmd/run.md Normal file
View file

@ -0,0 +1,132 @@
# core run
Run LinuxKit images with qemu or hyperkit.
## Usage
```bash
core run <image> [flags]
```
## Flags
| Flag | Description |
|------|-------------|
| `-d, --detach` | Run in background |
| `--cpus` | Number of CPUs (default: 1) |
| `--mem` | Memory in MB (default: 1024) |
| `--disk` | Disk size (default: none) |
| `--template` | Use built-in template |
## Examples
### Run ISO Image
```bash
# Run LinuxKit ISO
core run server.iso
# With more resources
core run server.iso --cpus 2 --mem 2048
# Detached mode
core run server.iso -d
```
### Run from Template
```bash
# List available templates
core templates
# Run template
core run --template core-dev
```
### Formats
Supported image formats:
- `.iso` - Bootable ISO
- `.qcow2` - QEMU disk image
- `.raw` - Raw disk image
- `.vmdk` - VMware disk
## Container Management
```bash
# List running containers
core ps
# View logs
core logs <id>
# Follow logs
core logs -f <id>
# Execute command
core exec <id> <command>
# Stop container
core stop <id>
```
## Templates
Built-in templates in `.core/linuxkit/`:
| Template | Description |
|----------|-------------|
| `core-dev` | Development environment |
| `server-php` | FrankenPHP server |
### Custom Templates
Create `.core/linuxkit/mytemplate.yml`:
```yaml
kernel:
image: linuxkit/kernel:6.6
cmdline: "console=tty0"
init:
- linuxkit/init:latest
- linuxkit/runc:latest
- linuxkit/containerd:latest
services:
- name: myservice
image: myorg/myservice:latest
files:
- path: /etc/myconfig
contents: |
key: value
```
Then run:
```bash
core run --template mytemplate
```
## Networking
LinuxKit VMs get their own network namespace. Port forwarding:
```bash
# Forward port 8080
core run server.iso -p 8080:80
# Multiple ports
core run server.iso -p 8080:80 -p 8443:443
```
## Disk Persistence
```bash
# Create persistent disk
core run server.iso --disk 10G
# Attach existing disk
core run server.iso --disk /path/to/disk.qcow2
```

188
docs/packages/go/cmd/sdk.md Normal file
View file

@ -0,0 +1,188 @@
# core sdk
Generate typed API clients from OpenAPI specifications.
## Usage
```bash
core sdk <command> [flags]
```
## Commands
| Command | Description |
|---------|-------------|
| `generate` | Generate SDKs from OpenAPI spec |
| `validate` | Validate OpenAPI spec |
| `diff` | Check for breaking API changes |
## sdk generate
Generate typed API clients for multiple languages.
```bash
core sdk generate [flags]
```
### Flags
| Flag | Description |
|------|-------------|
| `--spec` | Path to OpenAPI spec file (auto-detected) |
| `--lang` | Generate only this language |
### Examples
```bash
# Generate all configured SDKs
core sdk generate
# Generate only TypeScript SDK
core sdk generate --lang typescript
# Use specific spec file
core sdk generate --spec api/openapi.yaml
```
### Supported Languages
| Language | Generator |
|----------|-----------|
| TypeScript | openapi-generator (typescript-fetch) |
| Python | openapi-generator (python) |
| Go | openapi-generator (go) |
| PHP | openapi-generator (php) |
## sdk validate
Validate an OpenAPI specification file.
```bash
core sdk validate [flags]
```
### Flags
| Flag | Description |
|------|-------------|
| `--spec` | Path to OpenAPI spec file (auto-detected) |
### Examples
```bash
# Validate detected spec
core sdk validate
# Validate specific file
core sdk validate --spec api/openapi.yaml
```
## sdk diff
Check for breaking changes between API versions.
```bash
core sdk diff [flags]
```
### Flags
| Flag | Description |
|------|-------------|
| `--base` | Base spec version (git tag or file path) |
| `--spec` | Current spec file (auto-detected) |
### Examples
```bash
# Compare against previous release
core sdk diff --base v1.0.0
# Compare two files
core sdk diff --base old-api.yaml --spec new-api.yaml
```
### Breaking Changes Detected
- Removed endpoints
- Changed parameter types
- Removed required fields
- Changed response types
## Release Integration
Generate SDKs as part of the release process:
```bash
# Generate SDKs for release
core release --target sdk
# With explicit version
core release --target sdk --version v1.2.3
# Preview what would be generated
core release --target sdk --dry-run
```
See [release command](release.md) for full details.
## Configuration
Configure SDK generation in `.core/release.yaml`:
```yaml
sdk:
# OpenAPI spec path (auto-detected if not set)
spec: api/openapi.yaml
# Languages to generate
languages:
- typescript
- python
- go
- php
# Output directory
output: sdk
# Package naming
package:
name: my-api-sdk
# Breaking change detection
diff:
enabled: true
fail_on_breaking: false # Warn but continue
```
## Spec Auto-Detection
Core looks for OpenAPI specs in this order:
1. Path specified in config (`sdk.spec`)
2. `openapi.yaml` / `openapi.json`
3. `api/openapi.yaml` / `api/openapi.json`
4. `docs/openapi.yaml` / `docs/openapi.json`
5. Laravel Scramble endpoint (`/docs/api.json`)
## Output Structure
Generated SDKs are placed in language-specific directories:
```
sdk/
├── typescript/
│ ├── src/
│ ├── package.json
│ └── tsconfig.json
├── python/
│ ├── my_api_sdk/
│ ├── setup.py
│ └── requirements.txt
├── go/
│ ├── client.go
│ └── go.mod
└── php/
├── src/
└── composer.json
```

View file

@ -0,0 +1,112 @@
# core search & install
Search GitHub for repositories and install them locally.
## core search
Search GitHub for repositories matching a pattern.
```bash
core search <pattern> [flags]
```
### Flags
| Flag | Description |
|------|-------------|
| `--org` | Search within a specific organization |
| `--limit` | Maximum results (default: 10) |
| `--language` | Filter by programming language |
### Examples
```bash
# Search by pattern
core search "cli tool"
# Search within organization
core search --org host-uk
# Search with language filter
core search --org host-uk --language go
# Search all core-* repos
core search "core-" --org host-uk
```
### Output
```
Found 5 repositories:
host-uk/core
Go CLI for the host-uk ecosystem
★ 42 Go Updated 2 hours ago
host-uk/core-php
PHP/Laravel packages for Core
★ 18 PHP Updated 1 day ago
host-uk/core-images
Docker and LinuxKit images
★ 8 Dockerfile Updated 3 days ago
```
## core install
Clone a repository from GitHub.
```bash
core install <repo> [flags]
```
### Flags
| Flag | Description |
|------|-------------|
| `--path` | Destination directory (default: current dir) |
| `--branch` | Clone specific branch |
| `--depth` | Shallow clone depth |
### Examples
```bash
# Install by full name
core install host-uk/core
# Install to specific path
core install host-uk/core --path ~/Code/host-uk
# Install specific branch
core install host-uk/core --branch dev
# Shallow clone
core install host-uk/core --depth 1
```
### Authentication
Uses GitHub CLI (`gh`) authentication. Ensure you're logged in:
```bash
gh auth status
gh auth login # if not authenticated
```
## Workflow Example
```bash
# Find repositories
core search --org host-uk
# Install one
core install host-uk/core-php --path ~/Code/host-uk
# Check setup
core doctor
```
## See Also
- [setup command](setup.md) - Clone all repos from registry
- [doctor command](doctor.md) - Check environment

View file

@ -0,0 +1,94 @@
# core setup
Clone all repositories from the registry.
## Usage
```bash
core setup [flags]
```
## Flags
| Flag | Description |
|------|-------------|
| `--registry` | Path to repos.yaml |
| `--path` | Base directory for cloning (default: current dir) |
| `--ssh` | Use SSH URLs instead of HTTPS |
## Examples
```bash
# Clone all repos from registry
core setup
# Clone to specific directory
core setup --path ~/Code/host-uk
# Use SSH for cloning
core setup --ssh
```
## Registry Format
The registry file (`repos.yaml`) defines repositories:
```yaml
repos:
- name: core
url: https://github.com/host-uk/core
description: Go CLI for the host-uk ecosystem
- name: core-php
url: https://github.com/host-uk/core-php
description: PHP/Laravel packages
- name: core-images
url: https://github.com/host-uk/core-images
description: Docker and LinuxKit images
- name: core-api
url: https://github.com/host-uk/core-api
description: API service
```
## Output
```
Setting up host-uk workspace...
Cloning repositories:
[1/4] core............... ✓
[2/4] core-php........... ✓
[3/4] core-images........ ✓
[4/4] core-api........... ✓
Done! 4 repositories cloned to ~/Code/host-uk
```
## Finding Registry
Core looks for `repos.yaml` in:
1. Current directory
2. Parent directories (up to 5 levels)
3. `~/.core/repos.yaml`
## After Setup
```bash
# Check health of all repos
core health
# Pull latest changes
core pull --all
# Check CI status
core ci
```
## See Also
- [work commands](work.md) - Multi-repo operations
- [search command](search.md) - Find repos on GitHub
- [install command](search.md) - Clone individual repos

View file

@ -0,0 +1,117 @@
# core templates
Manage LinuxKit templates for container images.
## Usage
```bash
core templates [command]
```
## Commands
| Command | Description |
|---------|-------------|
| `list` | List available templates |
| `show` | Show template details |
## templates list
List all available LinuxKit templates.
```bash
core templates list
```
### Output
```
Available Templates:
core-dev
Full development environment with 100+ tools
Platforms: linux/amd64, linux/arm64
server-php
FrankenPHP production server
Platforms: linux/amd64, linux/arm64
edge-node
Minimal edge deployment
Platforms: linux/amd64, linux/arm64
```
## templates show
Show details of a specific template.
```bash
core templates show <name>
```
### Example
```bash
core templates show core-dev
```
Output:
```
Template: core-dev
Description: Full development environment with 100+ tools
Platforms:
- linux/amd64
- linux/arm64
Formats:
- iso
- qcow2
Services:
- sshd
- docker
- frankenphp
Size: ~1.8GB
```
## Template Locations
Templates are searched in order:
1. `.core/linuxkit/` - Project-specific
2. `~/.core/templates/` - User templates
3. Built-in templates
## Creating Templates
Create a LinuxKit YAML in `.core/linuxkit/`:
```yaml
# .core/linuxkit/myserver.yml
kernel:
image: linuxkit/kernel:5.15
cmdline: "console=tty0"
init:
- linuxkit/init:v1.0.0
services:
- name: sshd
image: linuxkit/sshd:v1.0.0
- name: myapp
image: ghcr.io/myorg/myapp:latest
```
Run with:
```bash
core run --template myserver
```
## See Also
- [run command](run.md) - Run LinuxKit images
- [build command](build.md) - Build LinuxKit images

View file

@ -0,0 +1,160 @@
# core work
Multi-repo git operations for managing the host-uk organization.
## Overview
The `work` command and related commands (`health`, `issues`, `reviews`, `commit`, `push`, `pull`, `impact`, `ci`) help manage multiple repositories in the host-uk ecosystem simultaneously.
## Commands
| Command | Description |
|---------|-------------|
| `core work` | Multi-repo git operations |
| `core health` | Quick health check across all repos |
| `core issues` | List open issues across all repos |
| `core reviews` | List PRs needing review |
| `core commit` | Claude-assisted commits across repos |
| `core push` | Push commits across all repos |
| `core pull` | Pull updates across all repos |
| `core impact` | Show impact of changing a repo |
| `core ci` | Check CI status across all repos |
## core health
Quick health check showing status of all repos.
```bash
core health
```
Output shows:
- Git status (clean/dirty)
- Current branch
- Commits ahead/behind remote
- CI status
## core issues
List open issues across all repositories.
```bash
core issues [flags]
```
### Flags
| Flag | Description |
|------|-------------|
| `--assignee` | Filter by assignee |
| `--label` | Filter by label |
| `--limit` | Max issues per repo |
## core reviews
List pull requests needing review.
```bash
core reviews [flags]
```
Shows PRs where:
- You are a requested reviewer
- PR is open and not draft
- CI is passing
## core commit
Create commits across repos with Claude assistance.
```bash
core commit [flags]
```
### Flags
| Flag | Description |
|------|-------------|
| `--message` | Commit message (auto-generated if not provided) |
| `--all` | Commit in all dirty repos |
Claude analyzes changes and suggests conventional commit messages.
## core push
Push commits across all repos.
```bash
core push [flags]
```
### Flags
| Flag | Description |
|------|-------------|
| `--all` | Push all repos with unpushed commits |
| `--force` | Force push (use with caution) |
## core pull
Pull updates across all repos.
```bash
core pull [flags]
```
### Flags
| Flag | Description |
|------|-------------|
| `--all` | Pull all repos |
| `--rebase` | Rebase instead of merge |
## core impact
Show the impact of changing a repository.
```bash
core impact <repo>
```
Shows:
- Dependent repos
- Reverse dependencies
- Potential breaking changes
## core ci
Check CI status across all repos.
```bash
core ci [flags]
```
### Flags
| Flag | Description |
|------|-------------|
| `--watch` | Watch for status changes |
| `--failing` | Show only failing repos |
## Registry
These commands use `repos.yaml` to know which repos to manage:
```yaml
repos:
- name: core
path: ./core
url: https://github.com/host-uk/core
- name: core-php
path: ./core-php
url: https://github.com/host-uk/core-php
```
Use `core setup` to clone all repos from the registry.
## See Also
- [setup command](setup.md) - Clone repos from registry
- [search command](search.md) - Find and install repos

View file

@ -0,0 +1,223 @@
# Configuration
Core uses `.core/` directory for project configuration.
## Directory Structure
```
.core/
├── release.yaml # Release configuration
├── build.yaml # Build configuration (optional)
├── php.yaml # PHP configuration (optional)
└── linuxkit/ # LinuxKit templates
├── server.yml
└── dev.yml
```
## release.yaml
Full release configuration reference:
```yaml
version: 1
project:
name: myapp
repository: myorg/myapp
build:
targets:
- os: linux
arch: amd64
- os: linux
arch: arm64
- os: darwin
arch: amd64
- os: darwin
arch: arm64
- os: windows
arch: amd64
publishers:
# GitHub Releases (required - others reference these artifacts)
- type: github
prerelease: false
draft: false
# npm binary wrapper
- type: npm
package: "@myorg/myapp"
access: public # or "restricted"
# Homebrew formula
- type: homebrew
tap: myorg/homebrew-tap
formula: myapp
official:
enabled: false
output: dist/homebrew
# Scoop manifest (Windows)
- type: scoop
bucket: myorg/scoop-bucket
official:
enabled: false
output: dist/scoop
# AUR (Arch Linux)
- type: aur
maintainer: "Name <email>"
# Chocolatey (Windows)
- type: chocolatey
push: false # true to publish
# Docker multi-arch
- type: docker
registry: ghcr.io
image: myorg/myapp
dockerfile: Dockerfile
platforms:
- linux/amd64
- linux/arm64
tags:
- latest
- "{{.Version}}"
build_args:
VERSION: "{{.Version}}"
# LinuxKit images
- type: linuxkit
config: .core/linuxkit/server.yml
formats:
- iso
- qcow2
- docker
platforms:
- linux/amd64
- linux/arm64
changelog:
include:
- feat
- fix
- perf
- refactor
exclude:
- chore
- docs
- style
- test
- ci
```
## build.yaml
Optional build configuration:
```yaml
version: 1
project:
name: myapp
binary: myapp
build:
main: ./cmd/myapp
env:
CGO_ENABLED: "0"
flags:
- -trimpath
ldflags:
- -s -w
- -X main.version={{.Version}}
- -X main.commit={{.Commit}}
targets:
- os: linux
arch: amd64
- os: darwin
arch: arm64
```
## php.yaml
PHP/Laravel configuration:
```yaml
version: 1
dev:
domain: myapp.test
ssl: true
port: 8000
services:
- frankenphp
- vite
- horizon
- reverb
- redis
test:
parallel: true
coverage: false
deploy:
coolify:
server: https://coolify.example.com
project: my-project
environment: production
```
## LinuxKit Templates
LinuxKit YAML configuration:
```yaml
kernel:
image: linuxkit/kernel:6.6
cmdline: "console=tty0 console=ttyS0"
init:
- linuxkit/init:latest
- linuxkit/runc:latest
- linuxkit/containerd:latest
- linuxkit/ca-certificates:latest
onboot:
- name: sysctl
image: linuxkit/sysctl:latest
services:
- name: dhcpcd
image: linuxkit/dhcpcd:latest
- name: sshd
image: linuxkit/sshd:latest
- name: myapp
image: myorg/myapp:latest
capabilities:
- CAP_NET_BIND_SERVICE
files:
- path: /etc/myapp/config.yaml
contents: |
server:
port: 8080
```
## Environment Variables
| Variable | Description |
|----------|-------------|
| `GITHUB_TOKEN` | GitHub authentication (via gh CLI) |
| `NPM_TOKEN` | npm publish token |
| `CHOCOLATEY_API_KEY` | Chocolatey publish key |
| `COOLIFY_TOKEN` | Coolify deployment token |
## Defaults
If no configuration exists, sensible defaults are used:
- **Targets**: linux/amd64, linux/arm64, darwin/amd64, darwin/arm64, windows/amd64
- **Publishers**: GitHub only
- **Changelog**: feat, fix, perf, refactor included

View file

@ -0,0 +1,134 @@
# Architecture
Core follows a modular, service-based architecture designed for maintainability and testability.
## Overview
```
┌─────────────────────────────────────────────────────────┐
│ Wails Application │
├─────────────────────────────────────────────────────────┤
│ Core │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Display │ │ WebView │ │ MCP │ │ Config │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Crypt │ │ I18n │ │ IO │ │Workspace │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
├─────────────────────────────────────────────────────────┤
│ Plugin System │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Plugin A │ │ Plugin B │ │ Plugin C │ │
│ └──────────┘ └──────────┘ └──────────┘ │
└─────────────────────────────────────────────────────────┘
```
## Core Container
The `Core` struct is the central service container:
```go
type Core struct {
services map[string]any // Service registry
actions []ActionHandler // IPC handlers
Features *Features // Feature flags
servicesLocked bool // Prevent late registration
}
```
### Service Registration
Services are registered using factory functions:
```go
core.New(
core.WithService(display.NewService), // Auto-discovered name
core.WithName("custom", myFactory), // Explicit name
)
```
### Service Retrieval
Type-safe service retrieval:
```go
// Returns error if not found
svc, err := core.ServiceFor[*display.Service](c, "display")
// Panics if not found (use in init code)
svc := core.MustServiceFor[*display.Service](c, "display")
```
## Service Lifecycle
Services can implement lifecycle interfaces:
```go
// Called when app starts
type Startable interface {
OnStartup(ctx context.Context) error
}
// Called when app shuts down
type Stoppable interface {
OnShutdown(ctx context.Context) error
}
```
## IPC / Actions
Services communicate via the action system:
```go
// Register a handler
c.RegisterAction(func(c *core.Core, msg core.Message) error {
if msg.Type == "my-action" {
// Handle message
}
return nil
})
// Send a message
c.ACTION(core.Message{
Type: "my-action",
Data: map[string]any{"key": "value"},
})
```
## Frontend Bindings
Wails generates TypeScript bindings automatically:
```typescript
// Auto-generated from Go service
import { ShowNotification } from '@bindings/display/service';
await ShowNotification({
title: "Hello",
message: "From TypeScript!"
});
```
## Package Structure
```
pkg/
├── core/ # Core container and interfaces
├── display/ # Window, tray, dialogs, clipboard
├── webview/ # JS execution, DOM, screenshots
├── mcp/ # Model Context Protocol server
├── config/ # Configuration persistence
├── crypt/ # Encryption and signing
├── i18n/ # Internationalization
├── io/ # File system helpers
├── workspace/ # Project management
├── plugin/ # Plugin system
└── module/ # Module system
```
## Design Principles
1. **Dependency Injection**: Services receive dependencies via constructor
2. **Interface Segregation**: Small, focused interfaces
3. **Testability**: All services are mockable
4. **No Globals**: State contained in Core instance

View file

@ -0,0 +1,122 @@
# Config Service
The Config service (`pkg/config`) provides unified configuration management with automatic persistence, feature flags, and XDG-compliant directory paths.
## Features
- JSON configuration with auto-save
- Feature flag management
- XDG Base Directory support
- Struct serialization helpers
- Type-safe get/set operations
## Basic Usage
```go
import "github.com/Snider/Core/pkg/config"
// Standalone usage
cfg, err := config.New()
if err != nil {
log.Fatal(err)
}
// With Core framework
c, _ := core.New(
core.WithService(config.Register),
)
cfg := core.MustServiceFor[*config.Service](c, "config")
```
## Get & Set Values
```go
// Set a value (auto-saves)
err := cfg.Set("language", "fr")
// Get a value
var lang string
err := cfg.Get("language", &lang)
```
Available configuration keys:
| Key | Type | Description |
|-----|------|-------------|
| `language` | string | UI language code |
| `default_route` | string | Default navigation route |
| `configDir` | string | Config files directory |
| `dataDir` | string | Data files directory |
| `cacheDir` | string | Cache directory |
| `workspaceDir` | string | Workspaces directory |
## Feature Flags
```go
// Enable a feature
cfg.EnableFeature("dark_mode")
// Check if enabled
if cfg.IsFeatureEnabled("dark_mode") {
// Apply dark theme
}
// Disable a feature
cfg.DisableFeature("dark_mode")
```
## Struct Serialization
Store complex data structures in separate JSON files:
```go
type UserPrefs struct {
Theme string `json:"theme"`
Notifications bool `json:"notifications"`
}
// Save struct to config/user_prefs.json
prefs := UserPrefs{Theme: "dark", Notifications: true}
err := cfg.SaveStruct("user_prefs", prefs)
// Load struct from file
var loaded UserPrefs
err := cfg.LoadStruct("user_prefs", &loaded)
```
## Directory Paths
The service automatically creates XDG-compliant directories:
```go
// Access directory paths
fmt.Println(cfg.ConfigDir) // ~/.config/lethean or ~/lethean/config
fmt.Println(cfg.DataDir) // Data storage
fmt.Println(cfg.CacheDir) // Cache files
fmt.Println(cfg.WorkspaceDir) // User workspaces
```
## Manual Save
Changes are auto-saved, but you can save explicitly:
```go
err := cfg.Save()
```
## Frontend Usage (TypeScript)
```typescript
import { Get, Set, IsFeatureEnabled } from '@bindings/config/service';
// Get configuration
const lang = await Get("language");
// Set configuration
await Set("default_route", "/dashboard");
// Check feature flag
if (await IsFeatureEnabled("dark_mode")) {
applyDarkTheme();
}
```

View file

@ -0,0 +1,312 @@
# Core API Reference
Complete API reference for the Core framework (`pkg/core`).
## Core Struct
The central application container.
### Creation
```go
func New(opts ...Option) (*Core, error)
```
Creates a new Core instance with the specified options.
### Methods
#### Service Access
```go
func ServiceFor[T any](c *Core, name string) (T, error)
```
Retrieves a service by name with type safety.
```go
func MustServiceFor[T any](c *Core, name string) T
```
Retrieves a service by name, panics if not found or wrong type.
#### Actions
```go
func (c *Core) ACTION(msg Message) error
```
Broadcasts a message to all registered action handlers.
```go
func (c *Core) RegisterAction(handler func(*Core, Message) error)
```
Registers an action handler.
#### Service Registration
```go
func (c *Core) AddService(name string, svc any) error
```
Manually adds a service to the registry.
#### Config Access
```go
func (c *Core) Config() *config.Service
```
Returns the config service if registered.
## Options
### WithService
```go
func WithService(factory ServiceFactory) Option
```
Registers a service using its factory function.
```go
c, _ := core.New(
core.WithService(config.Register),
core.WithService(display.NewService),
)
```
### WithName
```go
func WithName(name string, factory ServiceFactory) Option
```
Registers a service with an explicit name.
```go
c, _ := core.New(
core.WithName("mydb", database.NewService),
)
```
### WithAssets
```go
func WithAssets(assets embed.FS) Option
```
Sets embedded assets for the application.
### WithServiceLock
```go
func WithServiceLock() Option
```
Prevents late service registration after initialization.
## ServiceFactory
```go
type ServiceFactory func(c *Core) (any, error)
```
Factory function signature for service creation.
## Message
```go
type Message interface{}
```
Messages can be any type. Common patterns:
```go
// Map-based message
c.ACTION(map[string]any{
"action": "user.created",
"id": "123",
})
// Typed message
type UserCreated struct {
ID string
Email string
}
c.ACTION(UserCreated{ID: "123", Email: "user@example.com"})
```
## ServiceRuntime
Generic helper for services that need Core access.
```go
type ServiceRuntime[T any] struct {
core *Core
options T
}
```
### Creation
```go
func NewServiceRuntime[T any](c *Core, opts T) *ServiceRuntime[T]
```
### Methods
```go
func (r *ServiceRuntime[T]) Core() *Core
func (r *ServiceRuntime[T]) Options() T
func (r *ServiceRuntime[T]) Config() *config.Service
```
### Usage
```go
type MyOptions struct {
Timeout time.Duration
}
type MyService struct {
*core.ServiceRuntime[MyOptions]
}
func NewMyService(c *core.Core) (any, error) {
opts := MyOptions{Timeout: 30 * time.Second}
return &MyService{
ServiceRuntime: core.NewServiceRuntime(c, opts),
}, nil
}
func (s *MyService) DoSomething() {
timeout := s.Options().Timeout
cfg := s.Config()
// ...
}
```
## Lifecycle Interfaces
### Startable
```go
type Startable interface {
OnStartup(ctx context.Context) error
}
```
Implement for initialization on app start.
### Stoppable
```go
type Stoppable interface {
OnShutdown(ctx context.Context) error
}
```
Implement for cleanup on app shutdown.
### IPC Handler
```go
type IPCHandler interface {
HandleIPCEvents(c *Core, msg Message) error
}
```
Automatically registered when using `WithService`.
## Built-in Actions
### ActionServiceStartup
```go
type ActionServiceStartup struct{}
```
Sent to all services when application starts.
### ActionServiceShutdown
```go
type ActionServiceShutdown struct{}
```
Sent to all services when application shuts down.
## Error Helpers
```go
func E(service, operation string, err error) error
```
Creates a contextual error with service and operation info.
```go
if err != nil {
return core.E("myservice", "Connect", err)
}
// Error: myservice.Connect: connection refused
```
## Complete Example
```go
package main
import (
"context"
"github.com/Snider/Core/pkg/core"
"github.com/Snider/Core/pkg/config"
)
type MyService struct {
*core.ServiceRuntime[struct{}]
data string
}
func NewMyService(c *core.Core) (any, error) {
return &MyService{
ServiceRuntime: core.NewServiceRuntime(c, struct{}{}),
data: "initialized",
}, nil
}
func (s *MyService) OnStartup(ctx context.Context) error {
// Startup logic
return nil
}
func (s *MyService) OnShutdown(ctx context.Context) error {
// Cleanup logic
return nil
}
func (s *MyService) HandleIPCEvents(c *core.Core, msg core.Message) error {
switch m := msg.(type) {
case map[string]any:
if m["action"] == "myservice.update" {
s.data = m["data"].(string)
}
}
return nil
}
func main() {
c, err := core.New(
core.WithService(config.Register),
core.WithService(NewMyService),
core.WithServiceLock(),
)
if err != nil {
panic(err)
}
svc := core.MustServiceFor[*MyService](c, "main")
_ = svc
}
```

View file

@ -0,0 +1,133 @@
# Crypt Service
The Crypt service (`pkg/crypt`) provides cryptographic utilities including hashing, checksums, RSA encryption, and PGP operations.
## Features
- Multiple hash algorithms (SHA512, SHA256, SHA1, MD5)
- Checksum functions (Fletcher, Luhn)
- RSA key generation and encryption
- PGP encryption, signing, and verification
- Symmetric PGP encryption
## Basic Usage
```go
import "github.com/Snider/Core/pkg/crypt"
// Standalone usage
crypto, err := crypt.New()
// With Core framework
c, _ := core.New(
core.WithService(crypt.Register),
)
crypto := core.MustServiceFor[*crypt.Service](c, "crypt")
```
## Hashing
```go
// Available algorithms: SHA512, SHA256, SHA1, MD5, LTHN
hash := crypto.Hash(crypt.SHA256, "hello world")
// Check if string is valid hash algorithm
isValid := crypto.IsHashAlgo("sha256")
```
## Checksums
```go
// Luhn validation (credit card numbers)
isValid := crypto.Luhn("4532015112830366")
// Fletcher checksums
f16 := crypto.Fletcher16("data")
f32 := crypto.Fletcher32("data")
f64 := crypto.Fletcher64("data")
```
## RSA Encryption
```go
// Generate key pair (2048 or 4096 bits recommended)
publicKey, privateKey, err := crypto.GenerateRSAKeyPair(2048)
// Encrypt with public key
ciphertext, err := crypto.EncryptRSA(publicKey, "secret message")
// Decrypt with private key
plaintext, err := crypto.DecryptRSA(privateKey, ciphertext)
```
## PGP Encryption
### Key Generation
```go
// Generate PGP key pair
publicKey, privateKey, err := crypto.GeneratePGPKeyPair(
"User Name",
"user@example.com",
"Key comment",
)
```
### Asymmetric Encryption
```go
// Encrypt for recipient
ciphertext, err := crypto.EncryptPGPToString(recipientPublicKey, "secret data")
// Decrypt with private key
plaintext, err := crypto.DecryptPGP(privateKey, ciphertext)
```
### Symmetric Encryption
```go
var buf bytes.Buffer
err := crypto.SymmetricallyEncryptPGP(&buf, "data", "passphrase")
```
### Signing & Verification
```go
// Sign data
signature, err := crypto.SignPGP(privateKey, "data to sign")
// Verify signature
err := crypto.VerifyPGP(publicKey, "data to sign", signature)
if err != nil {
// Signature invalid
}
```
## Hash Types
| Constant | Algorithm |
|----------|-----------|
| `crypt.SHA512` | SHA-512 |
| `crypt.SHA256` | SHA-256 |
| `crypt.SHA1` | SHA-1 |
| `crypt.MD5` | MD5 |
| `crypt.LTHN` | Custom LTHN hash |
## Frontend Usage (TypeScript)
```typescript
import {
Hash,
GenerateRSAKeyPair,
EncryptRSA,
DecryptRSA
} from '@bindings/crypt/service';
// Hash data
const hash = await Hash("SHA256", "hello world");
// RSA encryption
const { publicKey, privateKey } = await GenerateRSAKeyPair(2048);
const encrypted = await EncryptRSA(publicKey, "secret");
const decrypted = await DecryptRSA(privateKey, encrypted);
```

View file

@ -0,0 +1,352 @@
# Display API Reference
Complete API reference for the Display service (`pkg/display`).
## Service Creation
```go
func NewService(c *core.Core) (any, error)
```
## Window Management
### CreateWindow
```go
func (s *Service) CreateWindow(opts CreateWindowOptions) (*WindowInfo, error)
```
Creates a new window with the specified options.
```go
type CreateWindowOptions struct {
Name string
Title string
URL string
X int
Y int
Width int
Height int
}
```
### CloseWindow
```go
func (s *Service) CloseWindow(name string) error
```
### GetWindowInfo
```go
func (s *Service) GetWindowInfo(name string) (*WindowInfo, error)
```
Returns:
```go
type WindowInfo struct {
Name string
Title string
X int
Y int
Width int
Height int
IsVisible bool
IsFocused bool
IsMaximized bool
IsMinimized bool
}
```
### ListWindowInfos
```go
func (s *Service) ListWindowInfos() []*WindowInfo
```
### Window Position & Size
```go
func (s *Service) SetWindowPosition(name string, x, y int) error
func (s *Service) SetWindowSize(name string, width, height int) error
func (s *Service) SetWindowBounds(name string, x, y, width, height int) error
```
### Window State
```go
func (s *Service) MaximizeWindow(name string) error
func (s *Service) MinimizeWindow(name string) error
func (s *Service) RestoreWindow(name string) error
func (s *Service) FocusWindow(name string) error
func (s *Service) SetWindowFullscreen(name string, fullscreen bool) error
func (s *Service) SetWindowAlwaysOnTop(name string, onTop bool) error
func (s *Service) SetWindowVisibility(name string, visible bool) error
```
### Window Title
```go
func (s *Service) SetWindowTitle(name, title string) error
func (s *Service) GetWindowTitle(name string) (string, error)
```
### Window Background
```go
func (s *Service) SetWindowBackgroundColour(name string, r, g, b, a uint8) error
```
### Focus
```go
func (s *Service) GetFocusedWindow() string
```
## Screen Management
### GetScreens
```go
func (s *Service) GetScreens() []*Screen
```
Returns:
```go
type Screen struct {
ID string
Name string
X int
Y int
Width int
Height int
ScaleFactor float64
IsPrimary bool
}
```
### GetScreen
```go
func (s *Service) GetScreen(id string) (*Screen, error)
```
### GetPrimaryScreen
```go
func (s *Service) GetPrimaryScreen() (*Screen, error)
```
### GetScreenAtPoint
```go
func (s *Service) GetScreenAtPoint(x, y int) (*Screen, error)
```
### GetScreenForWindow
```go
func (s *Service) GetScreenForWindow(name string) (*Screen, error)
```
### GetWorkAreas
```go
func (s *Service) GetWorkAreas() []*WorkArea
```
Returns usable screen space (excluding dock/taskbar).
## Layout Management
### SaveLayout / RestoreLayout
```go
func (s *Service) SaveLayout(name string) error
func (s *Service) RestoreLayout(name string) error
func (s *Service) ListLayouts() []string
func (s *Service) DeleteLayout(name string) error
func (s *Service) GetLayout(name string) *Layout
```
### TileWindows
```go
func (s *Service) TileWindows(mode TileMode, windows []string) error
```
Tile modes:
```go
const (
TileModeLeft TileMode = "left"
TileModeRight TileMode = "right"
TileModeGrid TileMode = "grid"
TileModeQuadrants TileMode = "quadrants"
)
```
### SnapWindow
```go
func (s *Service) SnapWindow(name string, position SnapPosition) error
```
Snap positions:
```go
const (
SnapPositionLeft SnapPosition = "left"
SnapPositionRight SnapPosition = "right"
SnapPositionTop SnapPosition = "top"
SnapPositionBottom SnapPosition = "bottom"
SnapPositionTopLeft SnapPosition = "top-left"
SnapPositionTopRight SnapPosition = "top-right"
SnapPositionBottomLeft SnapPosition = "bottom-left"
SnapPositionBottomRight SnapPosition = "bottom-right"
)
```
### StackWindows
```go
func (s *Service) StackWindows(windows []string, offsetX, offsetY int) error
```
### ApplyWorkflowLayout
```go
func (s *Service) ApplyWorkflowLayout(workflow WorkflowType) error
```
Workflow types:
```go
const (
WorkflowCoding WorkflowType = "coding"
WorkflowDebugging WorkflowType = "debugging"
WorkflowPresenting WorkflowType = "presenting"
)
```
## Dialogs
### File Dialogs
```go
func (s *Service) OpenSingleFileDialog(opts OpenFileOptions) (string, error)
func (s *Service) OpenFileDialog(opts OpenFileOptions) ([]string, error)
func (s *Service) SaveFileDialog(opts SaveFileOptions) (string, error)
func (s *Service) OpenDirectoryDialog(opts OpenDirectoryOptions) (string, error)
```
Options:
```go
type OpenFileOptions struct {
Title string
DefaultDirectory string
AllowMultiple bool
Filters []FileFilter
}
type SaveFileOptions struct {
Title string
DefaultDirectory string
DefaultFilename string
Filters []FileFilter
}
type FileFilter struct {
DisplayName string
Pattern string // e.g., "*.png;*.jpg"
}
```
### ConfirmDialog
```go
func (s *Service) ConfirmDialog(title, message string) (bool, error)
```
### PromptDialog
```go
func (s *Service) PromptDialog(title, message string) (string, bool, error)
```
## System Tray
```go
func (s *Service) SetTrayIcon(icon []byte) error
func (s *Service) SetTrayTooltip(tooltip string) error
func (s *Service) SetTrayLabel(label string) error
func (s *Service) SetTrayMenu(items []TrayMenuItem) error
func (s *Service) GetTrayInfo() map[string]any
```
Menu item:
```go
type TrayMenuItem struct {
Label string
ActionID string
IsSeparator bool
}
```
## Clipboard
```go
func (s *Service) ReadClipboard() (string, error)
func (s *Service) WriteClipboard(text string) error
func (s *Service) HasClipboard() bool
func (s *Service) ClearClipboard() error
```
## Notifications
```go
func (s *Service) ShowNotification(opts NotificationOptions) error
func (s *Service) ShowInfoNotification(title, message string) error
func (s *Service) ShowWarningNotification(title, message string) error
func (s *Service) ShowErrorNotification(title, message string) error
func (s *Service) RequestNotificationPermission() (bool, error)
func (s *Service) CheckNotificationPermission() (bool, error)
```
Options:
```go
type NotificationOptions struct {
ID string
Title string
Message string
Subtitle string
}
```
## Theme
```go
func (s *Service) GetTheme() *Theme
func (s *Service) GetSystemTheme() string
```
Returns:
```go
type Theme struct {
IsDark bool
}
```
## Events
```go
func (s *Service) GetEventManager() *EventManager
```
The EventManager handles WebSocket connections for real-time events.

View file

@ -0,0 +1,54 @@
---
title: e
---
# Service: `e`
Package e provides a standardized error handling mechanism for the Core library.
It allows for wrapping errors with contextual information, making it easier to
trace the origin of an error and provide meaningful feedback.
The design of this package is influenced by the need for a simple, yet powerful
way to handle errors that can occur in different layers of the application,
from low-level file operations to high-level service interactions.
The key features of this package are:
- Error wrapping: The Op and an optional Msg field provide context about
where and why an error occurred.
- Stack traces: By wrapping errors, we can build a logical stack trace
that is more informative than a raw stack trace.
- Consistent error handling: Encourages a uniform approach to error
handling across the entire codebase.
## Types
### `type Error`
`Error` represents a standardized error with operational context.
```go
type Error struct {
// Op is the operation being performed, e.g., "config.Load".
Op string
// Msg is a human-readable message explaining the error.
Msg string
// Err is the underlying error that was wrapped.
Err error
}
```
#### Methods
- `Error() string`: Error returns the string representation of the error.
- `Unwrap() error`: Unwrap provides compatibility for Go's errors.Is and errors.As functions.
## Functions
- `E(op, msg string, err error) error`: E is a helper function to create a new Error.
This is the primary way to create errors that will be consumed by the system. For example:
```go
return e.E("config.Load", "failed to load config file", err)
```
The `op` parameter should be in the format of `package.function` or `service.method`. The `msg` parameter should be a human-readable message that can be displayed to the user. The `err` parameter is the underlying error that is being wrapped.

View file

@ -0,0 +1,152 @@
# Help Service
The Help service (`pkg/help`) provides an embeddable documentation system that displays MkDocs-based help content in a dedicated window.
## Features
- Embedded help content (MkDocs static site)
- Context-sensitive help navigation
- Works with or without Display service
- Multiple content sources (embedded, filesystem, custom)
## Basic Usage
```go
import "github.com/Snider/Core/pkg/help"
// Create with default embedded content
helpService, err := help.New(help.Options{})
// Initialize with core dependencies
helpService.Init(coreInstance, displayService)
```
## Showing Help
```go
// Show main help window
err := helpService.Show()
// Show specific section
err := helpService.ShowAt("getting-started")
err := helpService.ShowAt("api/config")
```
## Options
```go
type Options struct {
Source string // Path to help content directory
Assets fs.FS // Custom filesystem for assets
}
```
### Default Embedded Content
```go
// Uses embedded MkDocs site
helpService, _ := help.New(help.Options{})
```
### Custom Directory
```go
// Use local directory
helpService, _ := help.New(help.Options{
Source: "/path/to/docs/site",
})
```
### Custom Filesystem
```go
//go:embed docs/*
var docsFS embed.FS
helpService, _ := help.New(help.Options{
Assets: docsFS,
})
```
## Integration with Core
The help service can work standalone or integrated with Core:
### With Display Service
When Display service is available, help opens through the IPC action system:
```go
// Automatically uses display.open_window action
helpService.Init(core, displayService)
helpService.Show()
```
### Without Display Service
Falls back to direct Wails window creation:
```go
// Creates window directly via Wails
helpService.Init(core, nil)
helpService.Show()
```
## Lifecycle
```go
// Called on application startup
err := helpService.ServiceStartup(ctx)
```
## Building Help Content
Help content is a static MkDocs site. To update:
1. Edit documentation in `docs/` directory
2. Build with MkDocs:
```bash
mkdocs build
```
3. The built site goes to `pkg/help/public/`
4. Content is embedded at compile time
## Frontend Usage (TypeScript)
```typescript
import { Show, ShowAt } from '@bindings/help/service';
// Open help window
await Show();
// Open specific section
await ShowAt("configuration");
await ShowAt("api/display");
```
## Help Window Options
The help window opens with default settings:
| Property | Value |
|----------|-------|
| Title | "Help" |
| Width | 800px |
| Height | 600px |
## IPC Action
When using Display service, help triggers this action:
```go
{
"action": "display.open_window",
"name": "help",
"options": {
"Title": "Help",
"Width": 800,
"Height": 600,
"URL": "/#anchor", // When using ShowAt
},
}
```

View file

@ -0,0 +1,130 @@
# I18n Service
The I18n service (`pkg/i18n`) provides internationalization and localization support with automatic language detection and template-based translations.
## Features
- JSON-based locale files
- Embedded locale bundles
- Automatic language detection from environment
- Template variable interpolation
- BCP 47 language tag support
## Basic Usage
```go
import "github.com/Snider/Core/pkg/i18n"
// Create service (defaults to English)
i18n, err := i18n.New()
if err != nil {
log.Fatal(err)
}
```
## Setting Language
```go
// Set language using BCP 47 tag
err := i18n.SetLanguage("fr")
err := i18n.SetLanguage("en-US")
err := i18n.SetLanguage("zh-Hans")
```
## Translating Messages
```go
// Simple translation
msg := i18n.Translate("welcome_message")
// With template data
msg := i18n.Translate("greeting", map[string]string{
"Name": "John",
})
// Template: "Hello, {{.Name}}!"
// Result: "Hello, John!"
```
## Available Languages
```go
// Get list of available language codes
langs := i18n.AvailableLanguages()
// Returns: ["en", "es", "fr", "de", ...]
```
## Get All Messages
```go
// Get all translations for a language
messages, err := i18n.GetAllMessages("en")
for key, value := range messages {
fmt.Printf("%s: %s\n", key, value)
}
```
## Locale File Format
Locale files are JSON stored in `locales/` directory:
```json
// locales/en.json
{
"welcome": "Welcome to the application",
"greeting": "Hello, {{.Name}}!",
"items_count": {
"one": "{{.Count}} item",
"other": "{{.Count}} items"
}
}
```
## Adding New Languages
1. Create a new JSON file in `pkg/i18n/locales/`:
```
locales/es.json
```
2. Add translations:
```json
{
"welcome": "Bienvenido a la aplicación",
"greeting": "¡Hola, {{.Name}}!"
}
```
3. The service automatically loads embedded locales at startup.
## Language Detection
The service can detect system language from the `LANG` environment variable:
```go
// Automatic detection happens internally
// LANG=fr_FR.UTF-8 -> French
// LANG=de_DE.UTF-8 -> German
```
## Frontend Usage (TypeScript)
```typescript
import {
SetLanguage,
Translate,
AvailableLanguages,
GetAllMessages
} from '@bindings/i18n/service';
// Set language
await SetLanguage("fr");
// Translate
const welcome = await Translate("welcome_message");
// Get available languages for a selector
const languages = await AvailableLanguages();
// Load all messages for client-side caching
const messages = await GetAllMessages("en");
```

View file

@ -0,0 +1,76 @@
# Installation
## Prerequisites
### Go 1.22+
```bash
# macOS
brew install go
# Linux
sudo apt install golang-go
# Windows - download from https://go.dev/dl/
```
### Wails v3
```bash
go install github.com/wailsapp/wails/v3/cmd/wails3@latest
```
### Task (Build Automation)
```bash
# macOS
brew install go-task
# Linux
sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d
# Windows
choco install go-task
```
## Install Core
```bash
go get github.com/Snider/Core@latest
```
## Verify Installation
```bash
# Check Go
go version
# Check Wails
wails3 version
# Check Task
task --version
```
## IDE Setup
### VS Code
Install the Go extension and configure:
```json
{
"go.useLanguageServer": true,
"gopls": {
"ui.semanticTokens": true
}
}
```
### GoLand / IntelliJ
Go support is built-in. Enable the Wails plugin for additional features.
## Next Steps
Continue to [Quick Start](quickstart.md) to create your first application.

View file

@ -0,0 +1,165 @@
# IO Service
The IO package (`pkg/io`) provides a unified interface for file operations across different storage backends (local filesystem, S3, SFTP, etc.).
## Features
- Abstract `Medium` interface for storage backends
- Local filesystem implementation
- Copy between different mediums
- Mock implementation for testing
## Medium Interface
All storage backends implement the `Medium` interface:
```go
type Medium interface {
Read(path string) (string, error)
Write(path, content string) error
EnsureDir(path string) error
IsFile(path string) bool
FileGet(path string) (string, error)
FileSet(path, content string) error
}
```
## Local Filesystem
```go
import (
"github.com/Snider/Core/pkg/io"
"github.com/Snider/Core/pkg/io/local"
)
// Pre-initialized global medium (root = "/")
content, err := io.Local.Read("/etc/hosts")
// Create sandboxed medium
medium, err := local.New("/app/data")
content, err := medium.Read("config.json") // Reads /app/data/config.json
```
## Basic Operations
```go
// Read file
content, err := medium.Read("path/to/file.txt")
// Write file
err := medium.Write("path/to/file.txt", "content")
// Check if file exists
if medium.IsFile("config.json") {
// File exists
}
// Ensure directory exists
err := medium.EnsureDir("path/to/dir")
// Convenience methods
content, err := medium.FileGet("file.txt")
err := medium.FileSet("file.txt", "content")
```
## Helper Functions
Package-level functions that work with any Medium:
```go
// Read from medium
content, err := io.Read(medium, "file.txt")
// Write to medium
err := io.Write(medium, "file.txt", "content")
// Ensure directory
err := io.EnsureDir(medium, "path/to/dir")
// Check if file
exists := io.IsFile(medium, "file.txt")
```
## Copy Between Mediums
```go
localMedium, _ := local.New("/local/path")
remoteMedium := s3.New(bucket, region) // hypothetical S3 implementation
// Copy from local to remote
err := io.Copy(localMedium, "data.json", remoteMedium, "backup/data.json")
```
## Mock Medium for Testing
```go
import "github.com/Snider/Core/pkg/io"
func TestMyFunction(t *testing.T) {
mock := io.NewMockMedium()
// Pre-populate files
mock.Files["config.json"] = `{"key": "value"}`
mock.Dirs["data"] = true
// Use in tests
myService := NewService(mock)
// Verify writes
err := myService.SaveData("test")
if mock.Files["data/test.json"] != expectedContent {
t.Error("unexpected content")
}
}
```
## Creating Custom Backends
Implement the `Medium` interface for custom storage:
```go
type S3Medium struct {
bucket string
client *s3.Client
}
func (m *S3Medium) Read(path string) (string, error) {
// Implement S3 read
}
func (m *S3Medium) Write(path, content string) error {
// Implement S3 write
}
// ... implement remaining methods
```
## Error Handling
```go
content, err := medium.Read("missing.txt")
if err != nil {
// File not found or read error
log.Printf("Read failed: %v", err)
}
```
## Frontend Usage
The IO package is primarily used server-side. Frontend file operations should use the Display service dialogs or direct API calls:
```typescript
import { OpenFileDialog, SaveFileDialog } from '@bindings/display/service';
// Open file picker
const path = await OpenFileDialog({
title: "Select File",
filters: [{ displayName: "Text", pattern: "*.txt" }]
});
// Save file picker
const savePath = await SaveFileDialog({
title: "Save As",
defaultFilename: "document.txt"
});
```

View file

@ -0,0 +1,119 @@
# IPC & Actions
Core provides an inter-process communication system for services to communicate without tight coupling.
## Message Structure
```go
type Message struct {
Type string // Message type identifier
Data map[string]any // Message payload
Source string // Originating service (optional)
Timestamp time.Time // When message was created
}
```
## Sending Messages
```go
c.ACTION(core.Message{
Type: "user.created",
Data: map[string]any{
"id": "123",
"email": "user@example.com",
},
})
```
## Handling Messages
Register action handlers during service initialization:
```go
func NewNotificationService(c *core.Core) (any, error) {
svc := &NotificationService{}
// Register handler
c.RegisterAction(func(c *core.Core, msg core.Message) error {
return svc.handleAction(msg)
})
return svc, nil
}
func (s *NotificationService) handleAction(msg core.Message) error {
switch msg.Type {
case "user.created":
email := msg.Data["email"].(string)
return s.sendWelcomeEmail(email)
}
return nil
}
```
## Auto-Discovery
Services implementing `HandleIPCEvents` are automatically registered:
```go
type MyService struct{}
// Automatically registered when using WithService
func (s *MyService) HandleIPCEvents(c *core.Core, msg core.Message) error {
// Handle messages
return nil
}
```
## Common Patterns
### Request/Response
```go
// Sender
responseChan := make(chan any)
c.ACTION(core.Message{
Type: "data.request",
Data: map[string]any{
"query": "SELECT * FROM users",
"response": responseChan,
},
})
result := <-responseChan
// Handler
func (s *DataService) handleAction(msg core.Message) error {
if msg.Type == "data.request" {
query := msg.Data["query"].(string)
respChan := msg.Data["response"].(chan any)
result, err := s.execute(query)
if err != nil {
return err
}
respChan <- result
}
return nil
}
```
### Event Broadcasting
```go
// Broadcast to all listeners
c.ACTION(core.Message{
Type: "system.config.changed",
Data: map[string]any{
"key": "theme",
"value": "dark",
},
})
```
## Best Practices
1. **Use namespaced types** - `service.action` format
2. **Keep payloads simple** - Use primitive types when possible
3. **Handle errors** - Return errors from handlers
4. **Document message types** - Create constants for message types

View file

@ -0,0 +1,101 @@
# Service Lifecycle
Core provides lifecycle hooks for services to initialize and clean up resources.
## Lifecycle Interfaces
### Startable
Called when the application starts:
```go
type Startable interface {
OnStartup(ctx context.Context) error
}
```
### Stoppable
Called when the application shuts down:
```go
type Stoppable interface {
OnShutdown(ctx context.Context) error
}
```
## Implementation Example
```go
type DatabaseService struct {
db *sql.DB
}
func (s *DatabaseService) OnStartup(ctx context.Context) error {
db, err := sql.Open("postgres", os.Getenv("DATABASE_URL"))
if err != nil {
return err
}
// Verify connection
if err := db.PingContext(ctx); err != nil {
return err
}
s.db = db
return nil
}
func (s *DatabaseService) OnShutdown(ctx context.Context) error {
if s.db != nil {
return s.db.Close()
}
return nil
}
```
## Lifecycle Order
1. **Registration**: Services registered via `core.New()`
2. **Wails Binding**: Services bound to Wails app
3. **Startup**: `OnStartup()` called for each Startable service
4. **Running**: Application runs
5. **Shutdown**: `OnShutdown()` called for each Stoppable service
## Context Usage
The context passed to lifecycle methods includes:
- Cancellation signal for graceful shutdown
- Deadline for timeout handling
```go
func (s *Service) OnStartup(ctx context.Context) error {
select {
case <-ctx.Done():
return ctx.Err()
case <-s.initialize():
return nil
}
}
```
## Error Handling
If `OnStartup` returns an error, the application will fail to start:
```go
func (s *Service) OnStartup(ctx context.Context) error {
if err := s.validate(); err != nil {
return fmt.Errorf("validation failed: %w", err)
}
return nil
}
```
## Best Practices
1. **Keep startup fast** - Defer heavy initialization
2. **Handle context cancellation** - Support graceful shutdown
3. **Clean up resources** - Always implement OnShutdown for services with resources
4. **Log lifecycle events** - Helps with debugging

View file

@ -0,0 +1,220 @@
# MCP Bridge
The MCP Bridge (`cmd/core-gui/mcp_bridge.go`) connects the Model Context Protocol server with Display, WebView, and WebSocket services.
## Overview
The MCP Bridge provides an HTTP API for AI assistants to interact with the desktop application, enabling:
- Window and screen management
- JavaScript execution in webviews
- DOM interaction (click, type, select)
- Screenshot capture
- File and process management
- Real-time events via WebSocket
## HTTP Endpoints
| Endpoint | Description |
|----------|-------------|
| `GET /health` | Health check |
| `GET /mcp` | Server capabilities |
| `GET /mcp/tools` | List available tools |
| `POST /mcp/call` | Execute a tool |
| `WS /ws` | WebSocket for GUI clients |
| `WS /events` | WebSocket for display events |
## Server Capabilities
```bash
curl http://localhost:9877/mcp
```
Response:
```json
{
"name": "core",
"version": "0.1.0",
"capabilities": {
"webview": true,
"display": true,
"windowControl": true,
"screenControl": true,
"websocket": "ws://localhost:9877/ws",
"events": "ws://localhost:9877/events"
}
}
```
## Tool Categories
### File Operations
| Tool | Description |
|------|-------------|
| `file_read` | Read file contents |
| `file_write` | Write content to file |
| `file_edit` | Edit file by replacing text |
| `file_delete` | Delete a file |
| `file_exists` | Check if file exists |
| `dir_list` | List directory contents |
| `dir_create` | Create directory |
### Window Control
| Tool | Description |
|------|-------------|
| `window_list` | List all windows |
| `window_create` | Create new window |
| `window_close` | Close window |
| `window_position` | Move window |
| `window_size` | Resize window |
| `window_maximize` | Maximize window |
| `window_minimize` | Minimize window |
| `window_focus` | Bring window to front |
### WebView Interaction
| Tool | Description |
|------|-------------|
| `webview_eval` | Execute JavaScript |
| `webview_click` | Click element |
| `webview_type` | Type into element |
| `webview_screenshot` | Capture page |
| `webview_navigate` | Navigate to URL |
| `webview_console` | Get console messages |
### Screen Management
| Tool | Description |
|------|-------------|
| `screen_list` | List all monitors |
| `screen_primary` | Get primary screen |
| `screen_at_point` | Get screen at coordinates |
| `screen_work_areas` | Get usable screen space |
### Layout Management
| Tool | Description |
|------|-------------|
| `layout_save` | Save window arrangement |
| `layout_restore` | Restore saved layout |
| `layout_tile` | Auto-tile windows |
| `layout_snap` | Snap window to edge |
## Calling Tools
```bash
# List windows
curl -X POST http://localhost:9877/mcp/call \
-H "Content-Type: application/json" \
-d '{"tool": "window_list", "params": {}}'
# Move window
curl -X POST http://localhost:9877/mcp/call \
-H "Content-Type: application/json" \
-d '{
"tool": "window_position",
"params": {"name": "main", "x": 100, "y": 100}
}'
# Execute JavaScript
curl -X POST http://localhost:9877/mcp/call \
-H "Content-Type: application/json" \
-d '{
"tool": "webview_eval",
"params": {
"window": "main",
"code": "document.title"
}
}'
# Click element
curl -X POST http://localhost:9877/mcp/call \
-H "Content-Type: application/json" \
-d '{
"tool": "webview_click",
"params": {
"window": "main",
"selector": "#submit-button"
}
}'
# Take screenshot
curl -X POST http://localhost:9877/mcp/call \
-H "Content-Type: application/json" \
-d '{
"tool": "webview_screenshot",
"params": {"window": "main"}
}'
```
## WebSocket Events
Connect to `/events` for real-time display events:
```javascript
const ws = new WebSocket('ws://localhost:9877/events');
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
switch (data.type) {
case 'window.focus':
console.log('Window focused:', data.name);
break;
case 'window.move':
console.log('Window moved:', data.name, data.x, data.y);
break;
case 'theme.change':
console.log('Theme changed:', data.isDark);
break;
}
};
```
Event types:
- `window.focus` - Window received focus
- `window.blur` - Window lost focus
- `window.move` - Window position changed
- `window.resize` - Window size changed
- `window.close` - Window was closed
- `window.create` - New window created
- `theme.change` - System theme changed
- `screen.change` - Screen configuration changed
## Go Integration
```go
import "github.com/Snider/Core/cmd/core-gui"
// Create bridge
bridge := NewMCPBridge(9877, displayService)
// Access services
mcpSvc := bridge.GetMCPService()
webview := bridge.GetWebView()
display := bridge.GetDisplay()
```
## Configuration
The bridge starts automatically on Wails app startup via the `ServiceStartup` lifecycle hook:
```go
func (b *MCPBridge) ServiceStartup(ctx context.Context, options application.ServiceOptions) error {
b.app = application.Get()
b.webview.SetApp(b.app)
go b.startHTTPServer()
return nil
}
```
## Security
The MCP server binds to localhost only by default. For production:
- Consider firewall rules
- Add authentication if needed
- Limit exposed tools

View file

@ -0,0 +1,151 @@
# MCP Service
The MCP service (`pkg/mcp`) implements the [Model Context Protocol](https://modelcontextprotocol.io/), enabling AI assistants like Claude to interact with your application.
## Overview
MCP provides a standardized way for AI tools to:
- Execute operations in your application
- Query application state
- Interact with the UI
- Manage files and processes
## Basic Setup
```go
import "github.com/Snider/Core/pkg/mcp"
// Create standalone MCP server
mcpService := mcp.NewStandaloneWithPort(9877)
// Or integrate with Core
c, _ := core.New(
core.WithService(mcp.NewService),
)
```
## Available Tools
The MCP service exposes numerous tools organized by category:
### File Operations
| Tool | Description |
|------|-------------|
| `file_read` | Read file contents |
| `file_write` | Write content to file |
| `file_edit` | Replace text in file |
| `file_delete` | Delete a file |
| `file_exists` | Check if file exists |
| `dir_list` | List directory contents |
| `dir_create` | Create directory |
### Window Control
| Tool | Description |
|------|-------------|
| `window_list` | List all windows |
| `window_create` | Create new window |
| `window_close` | Close window |
| `window_position` | Move window |
| `window_size` | Resize window |
| `window_maximize` | Maximize window |
| `window_minimize` | Minimize window |
| `window_focus` | Bring to front |
### WebView Interaction
| Tool | Description |
|------|-------------|
| `webview_eval` | Execute JavaScript |
| `webview_click` | Click element |
| `webview_type` | Type into element |
| `webview_screenshot` | Capture page |
| `webview_navigate` | Navigate to URL |
| `webview_console` | Get console logs |
### Process Management
| Tool | Description |
|------|-------------|
| `process_start` | Start a process |
| `process_stop` | Stop a process |
| `process_list` | List running processes |
| `process_output` | Get process output |
## HTTP API
The MCP service exposes an HTTP API:
```bash
# Health check
curl http://localhost:9877/health
# List available tools
curl http://localhost:9877/mcp/tools
# Call a tool
curl -X POST http://localhost:9877/mcp/call \
-H "Content-Type: application/json" \
-d '{"tool": "window_list", "params": {}}'
```
## WebSocket Events
Connect to `/events` for real-time updates:
```javascript
const ws = new WebSocket('ws://localhost:9877/events');
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log('Event:', data.type, data.data);
};
```
## Integration with Display Service
```go
mcpService := mcp.NewStandaloneWithPort(9877)
mcpService.SetDisplay(displayService)
mcpService.SetWebView(webviewService)
```
## Example: Claude Integration
When Claude connects via MCP, it can:
```
User: "Move the settings window to the left side of the screen"
Claude uses: window_position("settings", 0, 100)
```
```
User: "Take a screenshot of the app"
Claude uses: webview_screenshot("main")
```
```
User: "Click the submit button"
Claude uses: webview_click("main", "#submit-btn")
```
## Security Considerations
- MCP server binds to localhost by default
- No authentication (designed for local AI assistants)
- Consider firewall rules for production
## Configuration
```go
// Custom port
mcp.NewStandaloneWithPort(8080)
// With all services
bridge := NewMCPBridge(9877, displayService)
bridge.SetWebView(webviewService)
```

View file

@ -0,0 +1,271 @@
# Module System
The Module system (`pkg/module`) provides a declarative way to register UI menus, routes, and API endpoints using the `.itw3.json` configuration format.
## Features
- Declarative module configuration
- UI menu contributions
- Frontend route registration
- API endpoint declarations
- Multi-context support (developer, retail, miner)
- Binary/daemon management
- Module dependencies
## Module Config Format
Modules are defined using `.itw3.json` files:
```json
{
"code": "wallet",
"type": "core",
"name": "Wallet Manager",
"version": "1.0.0",
"namespace": "finance",
"description": "Cryptocurrency wallet management",
"author": "Your Name",
"contexts": ["default", "retail"],
"menu": [...],
"routes": [...],
"api": [...],
"config": {...}
}
```
## Module Types
| Type | Description |
|------|-------------|
| `core` | Built-in core functionality |
| `app` | External web application |
| `bin` | Binary/daemon wrapper |
## UI Contexts
Modules can target specific UI contexts:
| Context | Description |
|---------|-------------|
| `default` | Standard user interface |
| `developer` | Developer tools and debugging |
| `retail` | Point-of-sale interface |
| `miner` | Mining operations interface |
## Menu Contributions
Add items to the application menu:
```json
{
"menu": [
{
"id": "wallet-send",
"label": "Send Funds",
"icon": "send",
"route": "/wallet/send",
"accelerator": "CmdOrCtrl+Shift+S",
"contexts": ["default", "retail"],
"order": 10
},
{
"id": "wallet-receive",
"label": "Receive",
"icon": "receive",
"route": "/wallet/receive",
"order": 20
},
{
"separator": true
},
{
"id": "wallet-settings",
"label": "Settings",
"action": "wallet.open_settings",
"children": [
{"id": "wallet-backup", "label": "Backup", "action": "wallet.backup"},
{"id": "wallet-restore", "label": "Restore", "action": "wallet.restore"}
]
}
]
}
```
## Route Contributions
Register frontend routes:
```json
{
"routes": [
{
"path": "/wallet",
"component": "wallet-dashboard",
"title": "Wallet",
"icon": "wallet",
"contexts": ["default"]
},
{
"path": "/wallet/send",
"component": "wallet-send-form",
"title": "Send Funds"
}
]
}
```
## API Declarations
Declare API endpoints the module provides:
```json
{
"api": [
{
"method": "GET",
"path": "/balance",
"description": "Get wallet balance"
},
{
"method": "POST",
"path": "/send",
"description": "Send transaction"
}
]
}
```
## Binary Downloads
For `bin` type modules, specify platform binaries:
```json
{
"downloads": {
"app": "https://example.com/wallet-ui.tar.gz",
"x86_64": {
"darwin": {
"url": "https://example.com/wallet-darwin-x64",
"checksum": "sha256:abc123..."
},
"linux": {
"url": "https://example.com/wallet-linux-x64",
"checksum": "sha256:def456..."
},
"windows": {
"url": "https://example.com/wallet-win-x64.exe",
"checksum": "sha256:ghi789..."
}
},
"aarch64": {
"darwin": {
"url": "https://example.com/wallet-darwin-arm64"
}
}
}
}
```
## Web App Configuration
For `app` type modules:
```json
{
"app": {
"url": "https://example.com/wallet-app.tar.gz",
"type": "spa",
"hooks": [
{
"type": "rename",
"from": "dist",
"to": "wallet"
}
]
}
}
```
## Dependencies
Declare module dependencies:
```json
{
"depends": ["core", "crypto"]
}
```
## Using in Go
### Module Registration
```go
import "github.com/Snider/Core/pkg/module"
// Create from config
cfg := module.Config{
Code: "wallet",
Type: module.TypeCore,
Name: "Wallet",
Namespace: "finance",
}
mod := module.Module{
Config: cfg,
Handler: myHandler,
}
```
### Gin Router Integration
```go
type WalletModule struct{}
func (m *WalletModule) RegisterRoutes(group *gin.RouterGroup) {
group.GET("/balance", m.getBalance)
group.POST("/send", m.sendTransaction)
}
// Register with Gin
router := gin.Default()
apiGroup := router.Group("/api/finance/wallet")
walletModule.RegisterRoutes(apiGroup)
```
## Registry Service
The registry manages all modules:
```go
import "github.com/Snider/Core/pkg/module"
registry := module.NewRegistry()
// Register module
registry.Register(walletModule)
// Get module by code
mod := registry.Get("wallet")
// List all modules
modules := registry.List()
// Get modules for context
devModules := registry.ForContext(module.ContextDeveloper)
```
## Built-in Modules
Core provides several built-in modules:
- System information
- Configuration management
- Process management
- File operations
Access via:
```go
builtins := module.BuiltinModules()
```

View file

@ -0,0 +1,175 @@
# GUI Application
The Core GUI (`cmd/core-gui`) is a Wails v3 desktop application that demonstrates the Core framework capabilities with integrated MCP support.
## Features
- Angular frontend with Wails bindings
- MCP HTTP server for AI tool integration
- WebView automation capabilities
- Real-time WebSocket communication
- System tray support
- Multi-window management
## Architecture
```
┌─────────────────────────────────────────┐
│ Angular Frontend │
│ (TypeScript + Wails Bindings) │
└─────────────────┬───────────────────────┘
│ IPC
┌─────────────────┴───────────────────────┐
│ Wails Runtime │
│ (Window, Events, Bindings) │
└─────────────────┬───────────────────────┘
┌─────────────────┴───────────────────────┐
│ MCP Bridge │
│ ┌─────────┬──────────┬─────────────┐ │
│ │ Display │ WebView │ WebSocket │ │
│ │ Service │ Service │ Hub │ │
│ └─────────┴──────────┴─────────────┘ │
└─────────────────────────────────────────┘
```
## Running the GUI
### Development Mode
```bash
# From project root
task gui:dev
# Or directly
cd cmd/core-gui
wails3 dev
```
### Production Build
```bash
task gui:build
```
## Directory Structure
```
cmd/core-gui/
├── main.go # Application entry point
├── mcp_bridge.go # MCP HTTP server and tool handler
├── claude_bridge.go # Claude MCP client (optional)
├── frontend/ # Angular application
│ ├── src/
│ │ ├── app/ # Angular components
│ │ └── lib/ # Shared utilities
│ └── bindings/ # Generated Wails bindings
└── public/ # Static assets
```
## Services Integrated
The GUI integrates several Core services:
| Service | Purpose |
|---------|---------|
| Display | Window management, dialogs, tray |
| WebView | JavaScript execution, DOM interaction |
| MCP | AI tool protocol server |
| WebSocket | Real-time communication |
## Configuration
The application uses the Config service for settings:
```go
// Default settings
DefaultRoute: "/"
Language: "en"
Features: []
```
## Frontend Bindings
Wails generates TypeScript bindings for Go services:
```typescript
import { CreateWindow, ShowNotification } from '@bindings/display/service';
import { Translate, SetLanguage } from '@bindings/i18n/service';
// Create a new window
await CreateWindow({
name: "settings",
title: "Settings",
width: 800,
height: 600
});
// Show notification
await ShowNotification({
title: "Success",
message: "Operation completed!"
});
```
## WebSocket Communication
Connect to the WebSocket endpoint for real-time updates:
```typescript
const ws = new WebSocket('ws://localhost:9877/ws');
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log('Received:', data);
};
ws.send(JSON.stringify({
type: 'ping',
data: {}
}));
```
## System Tray
The application includes system tray support:
```go
// Set tray menu
display.SetTrayMenu([]display.TrayMenuItem{
{Label: "Open", ActionID: "open"},
{Label: "Settings", ActionID: "settings"},
{IsSeparator: true},
{Label: "Quit", ActionID: "quit"},
})
```
## Building for Distribution
### macOS
```bash
task gui:build
# Creates: build/bin/core-gui.app
```
### Windows
```bash
task gui:build
# Creates: build/bin/core-gui.exe
```
### Linux
```bash
task gui:build
# Creates: build/bin/core-gui
```
## Environment Variables
| Variable | Description |
|----------|-------------|
| `MCP_PORT` | MCP server port (default: 9877) |
| `DEBUG` | Enable debug logging |

View file

@ -0,0 +1,172 @@
# Plugin System
The Plugin system (`pkg/plugin`) allows you to extend Core applications with HTTP-based plugins that register routes under `/api/{namespace}/{name}/`.
## Features
- Namespace-based organization
- HTTP handler registration
- Lifecycle hooks (OnRegister, OnUnregister)
- Wails service integration
## Plugin Interface
All plugins implement the `Plugin` interface:
```go
type Plugin interface {
// Name returns the unique identifier for this plugin
Name() string
// Namespace returns the plugin's namespace (e.g., "core", "mining")
Namespace() string
// ServeHTTP handles HTTP requests routed to this plugin
http.Handler
// OnRegister is called when the plugin is registered
OnRegister(ctx context.Context) error
// OnUnregister is called when the plugin is being removed
OnUnregister(ctx context.Context) error
}
```
## Using BasePlugin
For simple plugins, embed `BasePlugin`:
```go
import "github.com/Snider/Core/pkg/plugin"
func NewMyPlugin() *plugin.BasePlugin {
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello from plugin!"))
})
return plugin.NewBasePlugin("myapp", "greeting", handler).
WithDescription("A simple greeting plugin").
WithVersion("1.0.0")
}
```
## Custom Plugin Implementation
For more control, implement the full interface:
```go
type DataPlugin struct {
db *sql.DB
}
func (p *DataPlugin) Name() string { return "data" }
func (p *DataPlugin) Namespace() string { return "myapp" }
func (p *DataPlugin) ServeHTTP(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/users":
p.handleUsers(w, r)
case "/items":
p.handleItems(w, r)
default:
http.NotFound(w, r)
}
}
func (p *DataPlugin) OnRegister(ctx context.Context) error {
// Initialize database connection
db, err := sql.Open("postgres", os.Getenv("DATABASE_URL"))
if err != nil {
return err
}
p.db = db
return nil
}
func (p *DataPlugin) OnUnregister(ctx context.Context) error {
if p.db != nil {
return p.db.Close()
}
return nil
}
```
## Plugin Info
Access plugin metadata:
```go
info := myPlugin.Info()
fmt.Println(info.Name) // "greeting"
fmt.Println(info.Namespace) // "myapp"
fmt.Println(info.Description) // "A simple greeting plugin"
fmt.Println(info.Version) // "1.0.0"
```
## Wails Integration
Register plugins as Wails services:
```go
app := application.New(application.Options{
Services: []application.Service{
application.NewServiceWithOptions(
myPlugin,
plugin.ServiceOptionsForPlugin(myPlugin),
),
},
})
```
## URL Routing
Plugins receive requests at:
```
/api/{namespace}/{name}/{path}
```
Examples:
- `/api/myapp/greeting/` → GreetingPlugin
- `/api/myapp/data/users` → DataPlugin (path: "/users")
- `/api/core/system/health` → SystemPlugin (path: "/health")
## Built-in Plugins
### System Plugin
Located at `pkg/plugin/builtin/system`:
```go
// Provides system information endpoints
/api/core/system/info - Application info
/api/core/system/health - Health check
```
## Plugin Router
The Router manages plugin registration:
```go
import "github.com/Snider/Core/pkg/plugin"
router := plugin.NewRouter()
// Register plugins
router.Register(ctx, myPlugin)
router.Register(ctx, dataPlugin)
// Get all registered plugins
plugins := router.List()
// Unregister a plugin
router.Unregister(ctx, "myapp", "greeting")
```
## Best Practices
1. **Use namespaces** to group related plugins
2. **Implement OnRegister** for initialization that can fail
3. **Implement OnUnregister** to clean up resources
4. **Return meaningful errors** from lifecycle hooks
5. **Use standard HTTP patterns** in ServeHTTP

View file

@ -0,0 +1,128 @@
# Quick Start
Build a simple Core application in 5 minutes.
## Create Project
```bash
mkdir myapp && cd myapp
go mod init myapp
```
## Install Dependencies
```bash
go get github.com/Snider/Core@latest
go get github.com/wailsapp/wails/v3@latest
```
## Create Main File
Create `main.go`:
```go
package main
import (
"context"
"embed"
"log"
"github.com/Snider/Core/pkg/core"
"github.com/Snider/Core/pkg/display"
"github.com/wailsapp/wails/v3/pkg/application"
)
//go:embed all:frontend/dist
var assets embed.FS
func main() {
// Initialize Core with display service
c, err := core.New(
core.WithAssets(assets),
core.WithService(display.NewService),
)
if err != nil {
log.Fatal(err)
}
// Get display service for window creation
displaySvc := core.MustServiceFor[*display.Service](c, "display")
// Create Wails application
app := application.New(application.Options{
Name: "My App",
Assets: application.AssetOptions{
FS: assets,
},
})
// Create main window
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
Title: "My App",
Width: 1200,
Height: 800,
URL: "/",
})
// Register display service with Wails
app.RegisterService(displaySvc)
// Run application
if err := app.Run(); err != nil {
log.Fatal(err)
}
}
```
## Create Frontend
Create a minimal frontend:
```bash
mkdir -p frontend/dist
```
Create `frontend/dist/index.html`:
```html
<!DOCTYPE html>
<html>
<head>
<title>My App</title>
<style>
body {
font-family: system-ui, sans-serif;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
background: #1a1a2e;
color: #eee;
}
</style>
</head>
<body>
<h1>Hello from Core!</h1>
</body>
</html>
```
## Run Development Mode
```bash
wails3 dev
```
## Build for Production
```bash
wails3 build
```
## Next Steps
- [Architecture](architecture.md) - Understand how Core works
- [Display Service](../services/display.md) - Window and dialog management
- [MCP Integration](../services/mcp.md) - AI tool support

View file

@ -0,0 +1,76 @@
---
title: runtime
---
# Service: `runtime`
The `runtime` service provides the main entry point for the application and a helper structure for services to interact with the `Core`.
## Types
### `type Runtime`
`Runtime` is the top-level container that holds the `Core` instance and the Wails application. It serves as the bridge between Wails and the Core framework.
```go
type Runtime struct {
// Core is the central service manager
Core *Core
// app is the Wails application instance
app *application.App
}
```
### `type ServiceRuntime[T any]`
`ServiceRuntime` is a generic helper struct designed to be embedded in service implementations. It provides easy access to the `Core` and service-specific options.
```go
type ServiceRuntime[T any] struct {
core *Core
opts T
}
```
### `type ServiceFactory`
`ServiceFactory` is a function type that creates a service instance.
```go
type ServiceFactory func() (any, error)
```
## Functions
### `func NewRuntime(app *application.App) (*Runtime, error)`
`NewRuntime` creates and wires together all application services using default settings. It is the standard way to initialize the runtime.
### `func NewWithFactories(app *application.App, factories map[string]ServiceFactory) (*Runtime, error)`
`NewWithFactories` creates a new `Runtime` instance using a provided map of service factories. This allows for flexible, dynamic service registration.
### `func NewServiceRuntime[T any](c *Core, opts T) *ServiceRuntime[T]`
`NewServiceRuntime` creates a new `ServiceRuntime` instance. This is typically used in a service's factory or constructor.
## Methods
### `func (r *Runtime) ServiceName() string`
`ServiceName` returns the name of the service ("Core"). This is used by Wails for service identification.
### `func (r *Runtime) ServiceStartup(ctx context.Context, options application.ServiceOptions)`
`ServiceStartup` delegates the startup lifecycle event to the underlying `Core`, which in turn initializes all registered services.
### `func (r *Runtime) ServiceShutdown(ctx context.Context)`
`ServiceShutdown` delegates the shutdown lifecycle event to the underlying `Core`.
### `func (r *ServiceRuntime[T]) Core() *Core`
`Core` returns the central `Core` instance, giving the service access to other services and features.
### `func (r *ServiceRuntime[T]) Config() Config`
`Config` returns the registered `Config` service from the `Core`. It is a convenience method for accessing configuration.

View file

@ -0,0 +1,119 @@
# Services
Services are the building blocks of a Core application. Each service encapsulates a specific domain of functionality.
## Creating a Service
```go
package myservice
import (
"context"
"github.com/Snider/Core/pkg/core"
)
type Service struct {
core *core.Core
}
// Factory function for registration
func NewService(c *core.Core) (any, error) {
return &Service{core: c}, nil
}
// Implement Startable for startup logic
func (s *Service) OnStartup(ctx context.Context) error {
// Initialize resources
return nil
}
// Implement Stoppable for cleanup
func (s *Service) OnShutdown(ctx context.Context) error {
// Release resources
return nil
}
```
## Registering Services
```go
c, err := core.New(
// Auto-discover name from package path
core.WithService(myservice.NewService),
// Explicit name
core.WithName("custom", func(c *core.Core) (any, error) {
return &CustomService{}, nil
}),
)
```
## Retrieving Services
```go
// Safe retrieval with error
svc, err := core.ServiceFor[*myservice.Service](c, "myservice")
if err != nil {
log.Printf("Service not found: %v", err)
}
// Must retrieval (panics if not found)
svc := core.MustServiceFor[*myservice.Service](c, "myservice")
```
## Service Dependencies
Services can depend on other services:
```go
func NewOrderService(c *core.Core) (any, error) {
// Get required dependencies
userSvc := core.MustServiceFor[*user.Service](c, "user")
paymentSvc := core.MustServiceFor[*payment.Service](c, "payment")
return &OrderService{
users: userSvc,
payments: paymentSvc,
}, nil
}
```
!!! warning "Dependency Order"
Register dependencies before services that use them. Core does not automatically resolve dependency order.
## Exposing to Frontend
Services are automatically exposed to the frontend via Wails bindings:
```go
// Go service method
func (s *Service) GetUser(id string) (*User, error) {
return s.db.FindUser(id)
}
```
```typescript
// TypeScript (auto-generated)
import { GetUser } from '@bindings/myservice/service';
const user = await GetUser("123");
```
## Testing Services
```go
func TestMyService(t *testing.T) {
// Create mock core
c, _ := core.New()
// Create service
svc, err := NewService(c)
if err != nil {
t.Fatal(err)
}
// Test methods
result := svc.(*Service).DoSomething()
assert.Equal(t, expected, result)
}
```

View file

@ -0,0 +1,215 @@
# WebView Service
The WebView service (`pkg/webview`) provides programmatic interaction with web content in your application windows.
## Features
- JavaScript execution
- DOM manipulation
- Element interaction (click, type, select)
- Console message capture
- Screenshots
- Network request monitoring
- Performance metrics
## Basic Usage
```go
import "github.com/Snider/Core/pkg/webview"
// Create service
wv := webview.New()
// Set Wails app reference
wv.SetApp(app)
```
## JavaScript Execution
```go
// Execute JavaScript and get result
result, err := wv.ExecJS("main", `
document.title
`)
// Execute complex scripts
result, err := wv.ExecJS("main", `
const items = document.querySelectorAll('.item');
Array.from(items).map(el => el.textContent);
`)
```
## DOM Interaction
### Click Element
```go
err := wv.Click("main", "#submit-button")
err := wv.Click("main", ".nav-link:first-child")
```
### Type Text
```go
err := wv.Type("main", "#search-input", "hello world")
```
### Select Option
```go
err := wv.Select("main", "#country-select", "US")
```
### Check/Uncheck
```go
err := wv.Check("main", "#agree-checkbox", true)
```
### Hover
```go
err := wv.Hover("main", ".dropdown-trigger")
```
### Scroll
```go
// Scroll to element
err := wv.Scroll("main", "#section-3", 0, 0)
// Scroll by coordinates
err := wv.Scroll("main", "", 0, 500)
```
## Element Information
### Query Selector
```go
elements, err := wv.QuerySelector("main", ".list-item")
```
### Element Info
```go
info, err := wv.GetElementInfo("main", "#user-card")
// Returns: tag, id, classes, text, attributes, bounds
```
### Computed Styles
```go
styles, err := wv.GetComputedStyle("main", ".button",
[]string{"color", "background-color", "font-size"})
```
### DOM Tree
```go
tree, err := wv.GetDOMTree("main", 5) // max depth 5
```
## Console Messages
```go
// Setup console listener
wv.SetupConsoleListener()
// Inject capture script
wv.InjectConsoleCapture("main")
// Get messages
messages := wv.GetConsoleMessages("all", 100)
messages := wv.GetConsoleMessages("error", 50)
// Clear buffer
wv.ClearConsole()
// Get errors only
errors := wv.GetErrors(50)
```
## Screenshots
```go
// Full page screenshot (base64 PNG)
data, err := wv.Screenshot("main")
// Element screenshot
data, err := wv.ScreenshotElement("main", "#chart")
// Export as PDF
pdfData, err := wv.ExportToPDF("main", map[string]any{
"margin": 20,
})
```
## Page Information
```go
// Get current URL
url, err := wv.GetURL("main")
// Get page title
title, err := wv.GetTitle("main")
// Get page source
source, err := wv.GetPageSource("main")
// Navigate
err := wv.Navigate("main", "https://example.com")
```
## Network Monitoring
```go
// Inject network interceptor
wv.InjectNetworkInterceptor("main")
// Get captured requests
requests, err := wv.GetNetworkRequests("main", 100)
// Clear request log
wv.ClearNetworkRequests("main")
```
## Performance Metrics
```go
metrics, err := wv.GetPerformance("main")
// Returns: loadTime, domContentLoaded, firstPaint, etc.
```
## Resource Listing
```go
resources, err := wv.GetResources("main")
// Returns: scripts, stylesheets, images, fonts, etc.
```
## Visual Debugging
```go
// Highlight element temporarily
err := wv.Highlight("main", "#target-element", 2000) // 2 seconds
```
## Window Listing
```go
windows := wv.ListWindows()
for _, w := range windows {
fmt.Println(w.Name)
}
```
## Frontend Usage
The WebView service is primarily used server-side for:
- Automated testing
- AI assistant interactions (via MCP)
- Scripted UI interactions
For normal frontend development, use standard DOM APIs directly.

View file

@ -0,0 +1,152 @@
# Workspace Service
The Workspace service (`pkg/workspace`) manages isolated user workspaces with encrypted storage and PGP key pairs.
## Features
- Isolated workspace environments
- PGP key pair generation per workspace
- Encrypted workspace identification
- File operations within workspace context
- Multiple workspace support
## Basic Usage
```go
import "github.com/Snider/Core/pkg/workspace"
// With IO medium (standalone)
medium, _ := local.New("/app/workspaces")
ws, err := workspace.New(medium)
// With Core framework (recommended)
c, _ := core.New(
core.WithService(workspace.Register),
)
ws := core.MustServiceFor[*workspace.Service](c, "workspace")
```
## Creating Workspaces
```go
// Create a new encrypted workspace
workspaceID, err := ws.CreateWorkspace("my-project", "secure-password")
// Returns obfuscated workspace ID
// Workspace structure created:
// workspaces/
// <workspace-id>/
// config/
// log/
// data/
// files/
// keys/
// key.pub (PGP public key)
// key.priv (PGP private key)
```
## Switching Workspaces
```go
// Switch to a workspace
err := ws.SwitchWorkspace(workspaceID)
// Switch to default workspace
err := ws.SwitchWorkspace("default")
```
## Workspace File Operations
```go
// Write file to active workspace
err := ws.WorkspaceFileSet("config/settings.json", jsonData)
// Read file from active workspace
content, err := ws.WorkspaceFileGet("config/settings.json")
```
## Listing Workspaces
```go
// Get all workspace IDs
workspaces := ws.ListWorkspaces()
for _, id := range workspaces {
fmt.Println(id)
}
```
## Active Workspace
```go
// Get current workspace info
active := ws.ActiveWorkspace()
if active != nil {
fmt.Println("Name:", active.Name)
fmt.Println("Path:", active.Path)
}
```
## Workspace Structure
Each workspace contains:
| Directory | Purpose |
|-----------|---------|
| `config/` | Workspace configuration files |
| `log/` | Workspace logs |
| `data/` | Application data |
| `files/` | User files |
| `keys/` | PGP key pair |
## Security Model
Workspaces use a two-level hashing scheme:
1. **Real Name**: Hash of the identifier
2. **Workspace ID**: Hash of `workspace/{real_name}`
This prevents workspace enumeration while allowing consistent access.
## IPC Events
The workspace service responds to IPC messages:
```go
// Switch workspace via IPC
c.ACTION(core.Message{
Type: "workspace.switch_workspace",
Data: map[string]any{
"name": workspaceID,
},
})
```
## Frontend Usage (TypeScript)
```typescript
import {
CreateWorkspace,
SwitchWorkspace,
WorkspaceFileGet,
WorkspaceFileSet,
ListWorkspaces,
ActiveWorkspace
} from '@bindings/workspace/service';
// Create workspace
const wsId = await CreateWorkspace("my-project", "password");
// Switch workspace
await SwitchWorkspace(wsId);
// Read/write files
const config = await WorkspaceFileGet("config/app.json");
await WorkspaceFileSet("config/app.json", JSON.stringify(newConfig));
// List all workspaces
const workspaces = await ListWorkspaces();
// Get active workspace
const active = await ActiveWorkspace();
console.log(`Current: ${active.Name} at ${active.Path}`);
```

123
docs/packages/go/index.md Normal file
View file

@ -0,0 +1,123 @@
# Go Framework
Core is a native application framework for Go, built on Wails v3. It provides dependency injection, service lifecycle management, IPC messaging, and a unified CLI for building, releasing, and deploying applications.
## Installation
```bash
# Go install
go install github.com/host-uk/core/cmd/core@latest
# Or download from releases
curl -fsSL https://github.com/host-uk/core/releases/latest/download/core-$(uname -s | tr '[:upper:]' '[:lower:]')-$(uname -m | sed 's/x86_64/amd64/').tar.gz | tar -xzf - -C /usr/local/bin
```
## Commands
### Build & Release
| Command | Description |
|---------|-------------|
| [`core build`](cmd/build.md) | Build Go, Wails, Docker, and LinuxKit projects |
| [`core release`](cmd/release.md) | Build and publish to GitHub, npm, Homebrew, etc. |
| [`core sdk`](cmd/sdk.md) | Generate and manage API SDKs |
### Containers
| Command | Description |
|---------|-------------|
| [`core run`](cmd/run.md) | Run LinuxKit images with qemu/hyperkit |
| `core ps` | List running containers |
| `core stop` | Stop running containers |
| `core logs` | View container logs |
| `core exec` | Execute commands in containers |
| [`core templates`](cmd/templates.md) | Manage LinuxKit templates |
### Development
| Command | Description |
|---------|-------------|
| [`core dev`](cmd/dev.md) | Portable development environment (100+ tools) |
| [`core php`](cmd/php.md) | Laravel/PHP development tools |
| [`core doctor`](cmd/doctor.md) | Check development environment |
### GitHub & Multi-Repo
| Command | Description |
|---------|-------------|
| [`core search`](cmd/search.md) | Search GitHub for repositories |
| [`core install`](cmd/search.md) | Clone a repository from GitHub |
| [`core setup`](cmd/setup.md) | Clone all repos from registry |
| [`core work`](cmd/work.md) | Multi-repo git operations |
| [`core health`](cmd/work.md) | Quick health check across repos |
| [`core issues`](cmd/work.md) | List open issues across repos |
| [`core reviews`](cmd/work.md) | List PRs needing review |
| [`core ci`](cmd/work.md) | Check CI status across repos |
### Documentation
| Command | Description |
|---------|-------------|
| [`core docs`](cmd/docs.md) | Documentation management |
## Quick Start
```bash
# Build a Go project
core build
# Build for specific targets
core build --targets linux/amd64,darwin/arm64
# Release to GitHub
core release
# Release to multiple package managers
core release # Publishes to all configured targets
# Start PHP dev environment
core php dev
# Run a LinuxKit image
core run server.iso
```
## Configuration
Core uses `.core/` directory for project configuration:
```
.core/
├── release.yaml # Release targets and settings
├── build.yaml # Build configuration (optional)
└── linuxkit/ # LinuxKit templates
└── server.yml
```
## Documentation
### Command Reference
- [Build](cmd/build.md) - Cross-platform builds with code signing
- [Release](cmd/release.md) - Publishing to package managers
- [SDK](cmd/sdk.md) - Generate API clients from OpenAPI
- [Run](cmd/run.md) - Container management
- [Templates](cmd/templates.md) - LinuxKit templates
- [Dev](cmd/dev.md) - Portable development environment
- [PHP](cmd/php.md) - Laravel development
- [Doctor](cmd/doctor.md) - Environment check
- [Search & Install](cmd/search.md) - GitHub integration
- [Setup](cmd/setup.md) - Clone repos from registry
- [Work](cmd/work.md) - Multi-repo operations
- [Docs](cmd/docs.md) - Documentation management
### Reference
- [Configuration](configuration.md) - All config options
- [Examples](examples/) - Sample configurations
## Framework
Core also provides a Go framework for building desktop applications:
- [Framework Overview](framework/overview.md)
- [Services](framework/services.md)
- [Lifecycle](framework/lifecycle.md)

168
docs/packages/index.md Normal file
View file

@ -0,0 +1,168 @@
---
title: Packages
---
<script setup>
import { ref, computed } from 'vue'
const search = ref('')
const packages = [
{
name: 'PHP Framework',
slug: 'php',
description: 'Modular monolith framework for Laravel with lifecycle events, multi-tenancy, and module system',
icon: '🐘',
featured: true
},
{
name: 'Go Framework',
slug: 'go',
description: 'Native desktop application framework with Wails v3, services, lifecycle management, and CLI tools',
icon: '🔷',
featured: true
},
{
name: 'Admin',
slug: 'admin',
description: 'Admin panel with Livewire modals, forms, and global search',
icon: '🎛️'
},
{
name: 'API',
slug: 'api',
description: 'REST API framework with authentication, rate limiting, and webhooks',
icon: '🔌'
},
{
name: 'MCP',
slug: 'mcp',
description: 'Model Context Protocol server for AI agent integration',
icon: '🤖'
}
]
const filtered = computed(() => {
if (!search.value) return packages
const q = search.value.toLowerCase()
return packages.filter(p =>
p.name.toLowerCase().includes(q) ||
p.description.toLowerCase().includes(q)
)
})
const featured = computed(() => filtered.value.filter(p => p.featured))
const ecosystem = computed(() => filtered.value.filter(p => !p.featured))
</script>
<style scoped>
.search-box {
width: 100%;
padding: 12px 16px;
font-size: 16px;
border: 1px solid var(--vp-c-border);
border-radius: 8px;
background: var(--vp-c-bg-soft);
color: var(--vp-c-text-1);
margin-bottom: 24px;
}
.search-box:focus {
outline: none;
border-color: var(--vp-c-brand-1);
}
.search-box::placeholder {
color: var(--vp-c-text-3);
}
.section-title {
font-size: 14px;
font-weight: 600;
color: var(--vp-c-text-2);
text-transform: uppercase;
letter-spacing: 0.05em;
margin-bottom: 12px;
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 16px;
margin-bottom: 32px;
}
.grid-featured {
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
}
.card {
border: 1px solid var(--vp-c-border);
border-radius: 12px;
padding: 20px;
background: var(--vp-c-bg-soft);
transition: all 0.2s ease;
text-decoration: none;
display: block;
}
.card:hover {
border-color: var(--vp-c-brand-1);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
transform: translateY(-2px);
}
.card-featured {
border-color: var(--vp-c-brand-2);
background: linear-gradient(135deg, var(--vp-c-bg-soft) 0%, var(--vp-c-bg) 100%);
}
.card-icon {
font-size: 32px;
margin-bottom: 12px;
}
.card-title {
font-size: 18px;
font-weight: 600;
color: var(--vp-c-text-1);
margin-bottom: 8px;
}
.card-desc {
font-size: 14px;
color: var(--vp-c-text-2);
line-height: 1.5;
}
.no-results {
text-align: center;
color: var(--vp-c-text-3);
padding: 40px;
}
</style>
# Packages
Browse the Host UK package ecosystem.
<input
v-model="search"
type="text"
class="search-box"
placeholder="Search packages..."
/>
<div v-if="featured.length" class="section">
<div class="section-title">Frameworks</div>
<div class="grid grid-featured">
<a v-for="pkg in featured" :key="pkg.slug" :href="`./${pkg.slug}/`" class="card card-featured">
<div class="card-icon">{{ pkg.icon }}</div>
<div class="card-title">{{ pkg.name }}</div>
<div class="card-desc">{{ pkg.description }}</div>
</a>
</div>
</div>
<div v-if="ecosystem.length" class="section">
<div class="section-title">Ecosystem</div>
<div class="grid">
<a v-for="pkg in ecosystem" :key="pkg.slug" :href="`./${pkg.slug}/`" class="card">
<div class="card-icon">{{ pkg.icon }}</div>
<div class="card-title">{{ pkg.name }}</div>
<div class="card-desc">{{ pkg.description }}</div>
</a>
</div>
</div>
<div v-if="!featured.length && !ecosystem.length" class="no-results">
No packages match "{{ search }}"
</div>

View file

@ -1,652 +0,0 @@
# MCP Package
The MCP (Model Context Protocol) package provides AI-powered tools for integrating with Large Language Models. Build custom tools with workspace context security, SQL query validation, usage quotas, and analytics.
## Installation
```bash
composer require host-uk/core-mcp
```
## Features
### Tool Registry
Automatically discover and register MCP tools:
```php
<?php
namespace Mod\Blog\Mcp\Tools;
use Core\Mcp\Tool;
use Core\Mcp\Request;
use Core\Mcp\Response;
use Mod\Blog\Models\Post;
class GetPostTool extends Tool
{
public function name(): string
{
return 'blog_get_post';
}
public function description(): string
{
return 'Retrieve a blog post by ID or slug';
}
public function parameters(): array
{
return [
'post_id' => [
'type' => 'number',
'description' => 'The post ID',
'required' => false,
],
'slug' => [
'type' => 'string',
'description' => 'The post slug',
'required' => false,
],
];
}
public function handle(Request $request): Response
{
$post = $request->input('post_id')
? Post::findOrFail($request->input('post_id'))
: Post::where('slug', $request->input('slug'))->firstOrFail();
return Response::success([
'id' => $post->id,
'title' => $post->title,
'content' => $post->content,
'published_at' => $post->published_at,
]);
}
}
```
Register tools in your module:
```php
public function onMcpTools(McpToolsRegistering $event): void
{
$event->tools([
GetPostTool::class,
CreatePostTool::class,
UpdatePostTool::class,
]);
}
```
### Workspace Context Security
Enforce workspace context for multi-tenant safety:
```php
<?php
namespace Mod\Blog\Mcp\Tools;
use Core\Mcp\Tool;
use Core\Mcp\Concerns\RequiresWorkspaceContext;
class ListPostsTool extends Tool
{
use RequiresWorkspaceContext;
public function handle(Request $request): Response
{
// Workspace context automatically validated
$workspace = $this->workspace();
// Queries automatically scoped to workspace
$posts = Post::latest()->limit(10)->get();
return Response::success([
'posts' => $posts->map(fn ($post) => [
'id' => $post->id,
'title' => $post->title,
'published_at' => $post->published_at,
]),
]);
}
}
```
If workspace context is missing or invalid, the tool automatically throws `MissingWorkspaceContextException`.
### SQL Query Validation
Secure database querying with multi-layer validation:
```php
<?php
namespace Core\Mcp\Tools;
use Core\Mcp\Tool;
use Core\Mcp\Request;
use Core\Mcp\Response;
use Core\Mcp\Services\SqlQueryValidator;
class QueryDatabaseTool extends Tool
{
use RequiresWorkspaceContext;
public function __construct(
private SqlQueryValidator $validator,
) {}
public function handle(Request $request): Response
{
$query = $request->input('query');
// Validate query against:
// - Blocked keywords (INSERT, UPDATE, DELETE, etc.)
// - Blocked tables (users, api_keys, etc.)
// - SQL injection patterns
// - Whitelist (if enabled)
$this->validator->validate($query);
$results = DB::connection('mcp_readonly')
->select($query);
return Response::success([
'rows' => $results,
'count' => count($results),
]);
}
}
```
Configuration:
```php
// config/core-mcp.php
'database' => [
'validation' => [
'enabled' => true,
'blocked_keywords' => ['INSERT', 'UPDATE', 'DELETE', 'DROP', 'TRUNCATE'],
'blocked_tables' => ['users', 'api_keys', 'password_resets'],
'whitelist_enabled' => false,
],
],
```
### EXPLAIN Query Analysis
Analyze query performance:
```php
$tool = new QueryDatabaseTool();
$response = $tool->handle(new Request([
'query' => 'SELECT * FROM posts WHERE category_id = 1',
'explain' => true,
]));
// Returns:
[
'explain' => [
'type' => 'ref',
'possible_keys' => 'category_id_index',
'key' => 'category_id_index',
'rows' => 42,
],
'analysis' => [
'efficient' => true,
'warnings' => [],
'recommendations' => [
'Consider adding LIMIT clause for large result sets',
],
],
]
```
### Tool Dependencies
Declare tool dependencies:
```php
<?php
namespace Mod\Blog\Mcp\Tools;
use Core\Mcp\Tool;
use Core\Mcp\Dependencies\HasDependencies;
use Core\Mcp\Dependencies\ToolDependency;
class PublishPostTool extends Tool
{
use HasDependencies;
public function dependencies(): array
{
return [
ToolDependency::make('blog_get_post')
->description('Required to fetch post before publishing'),
ToolDependency::make('notifications_send')
->optional()
->description('Send notifications when post is published'),
];
}
public function handle(Request $request): Response
{
// Dependencies validated before execution
$post = $this->callTool('blog_get_post', [
'post_id' => $request->input('post_id'),
]);
$post->update(['published_at' => now()]);
// Optional dependency
if ($this->hasTool('notifications_send')) {
$this->callTool('notifications_send', [
'type' => 'post_published',
'post_id' => $post->id,
]);
}
return Response::success($post);
}
}
```
### Usage Quotas
Per-workspace usage limits:
```php
// config/core-mcp.php
'quotas' => [
'enabled' => true,
'tiers' => [
'free' => [
'daily_calls' => 100,
'monthly_calls' => 2000,
],
'pro' => [
'daily_calls' => 1000,
'monthly_calls' => 25000,
],
'enterprise' => [
'daily_calls' => null, // unlimited
'monthly_calls' => null,
],
],
],
```
Quota enforcement is automatic via middleware:
```php
// Applied automatically to MCP routes
Route::middleware(CheckMcpQuota::class)->group(/*...*/);
```
Check quota status:
```php
use Core\Mcp\Services\McpQuotaService;
$quota = app(McpQuotaService::class);
$usage = $quota->getUsage($workspace);
// ['daily' => 42, 'monthly' => 1250]
$remaining = $quota->getRemaining($workspace);
// ['daily' => 58, 'monthly' => 750]
$isExceeded = $quota->isExceeded($workspace);
// false
```
### Tool Analytics
Track tool usage and performance:
```php
use Core\Mcp\Services\ToolAnalyticsService;
$analytics = app(ToolAnalyticsService::class);
// Get tool statistics
$stats = $analytics->getToolStats('blog_get_post', $workspace);
// ToolStats {
// total_calls: 1234,
// success_rate: 98.5,
// avg_duration_ms: 45.2,
// error_count: 19,
// }
// Get top tools
$topTools = $analytics->getTopTools($workspace, limit: 10);
// Get recent errors
$errors = $analytics->getRecentErrors($workspace, limit: 20);
```
View analytics in admin panel:
```
/admin/mcp/analytics
/admin/mcp/analytics/{tool}
```
### MCP Playground
Interactive tool testing interface:
```
/admin/mcp/playground
```
Features:
- Tool browser with search
- Parameter editor with validation
- Real-time response preview
- Workspace context switcher
- Request history
## Tool Patterns
### Read-Only Tools
```php
class GetPostsTool extends Tool
{
use RequiresWorkspaceContext;
public function handle(Request $request): Response
{
$posts = Post::query()
->when($request->input('category_id'), fn ($q, $id) =>
$q->where('category_id', $id)
)
->latest()
->limit($request->input('limit', 10))
->get();
return Response::success(['posts' => $posts]);
}
}
```
### Mutation Tools
```php
class CreatePostTool extends Tool
{
use RequiresWorkspaceContext;
public function parameters(): array
{
return [
'title' => ['type' => 'string', 'required' => true],
'content' => ['type' => 'string', 'required' => true],
'category_id' => ['type' => 'number', 'required' => false],
];
}
public function handle(Request $request): Response
{
$validated = $request->validate([
'title' => 'required|max:255',
'content' => 'required',
'category_id' => 'nullable|exists:categories,id',
]);
$post = Post::create($validated);
return Response::success($post);
}
}
```
### Async Tools
```php
class GeneratePostContentTool extends Tool
{
public function handle(Request $request): Response
{
// Queue long-running task
$job = GenerateContentJob::dispatch(
$request->input('topic'),
$request->input('style')
);
return Response::accepted([
'job_id' => $job->id,
'status_url' => route('api.jobs.status', $job->id),
]);
}
}
```
## Error Handling
```php
class GetPostTool extends Tool
{
public function handle(Request $request): Response
{
try {
$post = Post::findOrFail($request->input('post_id'));
return Response::success($post);
} catch (ModelNotFoundException $e) {
return Response::error(
'Post not found',
code: 'POST_NOT_FOUND',
status: 404
);
} catch (\Exception $e) {
return Response::error(
'Failed to fetch post',
code: 'INTERNAL_ERROR',
status: 500,
details: app()->environment('local') ? $e->getMessage() : null
);
}
}
}
```
## Testing
### Tool Tests
```php
<?php
namespace Tests\Feature\Mcp;
use Tests\TestCase;
use Mod\Blog\Models\Post;
use Mod\Blog\Mcp\Tools\GetPostTool;
use Core\Mcp\Request;
class GetPostToolTest extends TestCase
{
public function test_retrieves_post_by_id(): void
{
$post = Post::factory()->create();
$tool = new GetPostTool();
$response = $tool->handle(new Request([
'post_id' => $post->id,
]));
$this->assertTrue($response->isSuccess());
$this->assertEquals($post->id, $response->data['id']);
}
public function test_requires_workspace_context(): void
{
$this->expectException(MissingWorkspaceContextException::class);
// No workspace context set
app()->forgetInstance('current.workspace');
$tool = new GetPostTool();
$tool->handle(new Request(['post_id' => 1]));
}
public function test_respects_workspace_isolation(): void
{
$workspace1 = Workspace::factory()->create();
$workspace2 = Workspace::factory()->create();
$post = Post::factory()->for($workspace1)->create();
// Set context to workspace2
app()->instance('current.workspace', $workspace2);
$tool = new GetPostTool();
$response = $tool->handle(new Request([
'post_id' => $post->id,
]));
$this->assertTrue($response->isError());
$this->assertEquals(404, $response->status);
}
}
```
## Configuration
```php
// config/core-mcp.php
return [
'tools' => [
'auto_discover' => true,
'paths' => [
'Mod/*/Mcp/Tools',
'Core/Mcp/Tools',
],
],
'database' => [
'connection' => 'mcp_readonly',
'validation' => [
'enabled' => true,
'blocked_keywords' => ['INSERT', 'UPDATE', 'DELETE'],
'blocked_tables' => ['users', 'api_keys'],
],
],
'workspace_context' => [
'required' => true,
'validation' => [
'verify_existence' => true,
'check_suspension' => true,
],
],
'analytics' => [
'enabled' => true,
'retention_days' => 90,
],
'quotas' => [
'enabled' => true,
'tiers' => [/*...*/],
],
];
```
## Artisan Commands
```bash
# List registered tools
php artisan mcp:tools
# Test tool execution
php artisan mcp:test blog_get_post --post_id=1
# Prune old metrics
php artisan mcp:prune-metrics --days=90
# Check quota usage
php artisan mcp:quota-status {workspace-id}
# Export tool definitions
php artisan mcp:export-tools --format=json
```
## Best Practices
### 1. Use Workspace Context
```php
// ✅ Good - workspace security
class ListPostsTool extends Tool
{
use RequiresWorkspaceContext;
}
// ❌ Bad - no workspace isolation
class ListPostsTool extends Tool { }
```
### 2. Validate SQL Queries
```php
// ✅ Good - validated queries
$this->validator->validate($query);
DB::select($query);
// ❌ Bad - raw queries
DB::select($userInput); // SQL injection risk!
```
### 3. Use Read-Only Connections
```php
// ✅ Good - read-only connection
DB::connection('mcp_readonly')->select($query);
// ❌ Bad - default connection with write access
DB::select($query);
```
### 4. Track Analytics
```php
// ✅ Good - analytics tracked automatically
// Just implement the tool, framework handles tracking
// ❌ Bad - manual tracking (not needed)
```
### 5. Declare Dependencies
```php
// ✅ Good - explicit dependencies
public function dependencies(): array
{
return [
ToolDependency::make('prerequisite_tool'),
];
}
```
## Changelog
See [CHANGELOG.md](https://github.com/host-uk/core-php/blob/main/packages/core-mcp/changelog/2026/jan/features.md)
## License
EUPL-1.2
## Learn More
- [Workspace Security →](/security/workspace-isolation)
- [SQL Injection Prevention →](/security/sql-validation)
- [Model Context Protocol Specification](https://modelcontextprotocol.io)

View file

@ -177,5 +177,5 @@ class CreatePostTest extends TestCase
```
## Learn More
- [Lifecycle Events →](/packages/core/events)
- [Module System →](/packages/core/modules)
- [Lifecycle Events →](/core/events)
- [Module System →](/core/modules)

View file

@ -527,5 +527,5 @@ class ActivityTest extends TestCase
## Learn More
- [Multi-Tenancy →](/packages/core/tenancy)
- [Multi-Tenancy →](/core/tenancy)
- [GDPR Compliance →](/security/overview)

View file

@ -394,6 +394,6 @@ $exists = Cdn::exists('path/file.jpg');
## Learn More
- [Media Processing →](/packages/core/media)
- [Media Processing →](/core/media)
- [Storage Configuration →](/guide/configuration#storage)
- [Asset Pipeline →](/packages/core/media#asset-pipeline)
- [Asset Pipeline →](/core/media#asset-pipeline)

View file

@ -470,5 +470,5 @@ class ConfigTest extends TestCase
## Learn More
- [Module System →](/packages/core/modules)
- [Multi-Tenancy →](/packages/core/tenancy)
- [Module System →](/core/modules)
- [Multi-Tenancy →](/core/tenancy)

View file

@ -415,6 +415,6 @@ Enable event logging:
## Learn More
- [Module System →](/packages/core/modules)
- [Actions Pattern →](/packages/core/actions)
- [Multi-Tenancy →](/packages/core/tenancy)
- [Module System →](/core/modules)
- [Actions Pattern →](/core/actions)
- [Multi-Tenancy →](/core/tenancy)

View file

@ -1,6 +1,6 @@
# Core Package
# PHP Framework
The Core package provides the foundation for the framework including the module system, lifecycle events, multi-tenancy, and shared utilities.
The PHP framework provides the foundation for Host UK applications including the module system, lifecycle events, multi-tenancy, and shared utilities.
## Installation
@ -35,42 +35,42 @@ class Boot
### Foundation
- **[Module System](/packages/core/modules)** - Auto-discover and lazy-load modules based on lifecycle events
- **[Lifecycle Events](/packages/core/events)** - Event-driven extension points throughout the framework
- **[Actions Pattern](/packages/core/actions)** - Single-purpose business logic classes
- **[Service Discovery](/packages/core/services)** - Automatic service registration and dependency management
- **[Module System](/core/modules)** - Auto-discover and lazy-load modules based on lifecycle events
- **[Lifecycle Events](/core/events)** - Event-driven extension points throughout the framework
- **[Actions Pattern](/core/actions)** - Single-purpose business logic classes
- **[Service Discovery](/core/services)** - Automatic service registration and dependency management
### Multi-Tenancy
- **[Workspaces & Namespaces](/packages/core/tenancy)** - Workspace and namespace scoping for data isolation
- **[Workspace Caching](/packages/core/tenancy#workspace-caching)** - Isolated cache management per workspace
- **[Context Resolution](/packages/core/tenancy#context-resolution)** - Automatic workspace/namespace detection
- **[Workspaces & Namespaces](/core/tenancy)** - Workspace and namespace scoping for data isolation
- **[Workspace Caching](/core/tenancy#workspace-caching)** - Isolated cache management per workspace
- **[Context Resolution](/core/tenancy#context-resolution)** - Automatic workspace/namespace detection
### Data & Storage
- **[Configuration Management](/packages/core/configuration)** - Multi-profile configuration with versioning and export/import
- **[Activity Logging](/packages/core/activity)** - Track changes to models with automatic workspace scoping
- **[Seeder Discovery](/packages/core/seeders)** - Automatic seeder discovery with dependency ordering
- **[CDN Integration](/packages/core/cdn)** - Unified CDN interface for BunnyCDN and Cloudflare
- **[Configuration Management](/core/configuration)** - Multi-profile configuration with versioning and export/import
- **[Activity Logging](/core/activity)** - Track changes to models with automatic workspace scoping
- **[Seeder Discovery](/core/seeders)** - Automatic seeder discovery with dependency ordering
- **[CDN Integration](/core/cdn)** - Unified CDN interface for BunnyCDN and Cloudflare
### Content & Media
- **[Media Processing](/packages/core/media)** - Image optimization, responsive images, and thumbnails
- **[Search](/packages/core/search)** - Unified search interface across modules with analytics
- **[SEO Tools](/packages/core/seo)** - SEO metadata generation, sitemaps, and structured data
- **[Media Processing](/core/media)** - Image optimization, responsive images, and thumbnails
- **[Search](/core/search)** - Unified search interface across modules with analytics
- **[SEO Tools](/core/seo)** - SEO metadata generation, sitemaps, and structured data
### Security
- **[Security Headers](/packages/core/security)** - Configurable security headers with CSP support
- **[Email Shield](/packages/core/email-shield)** - Disposable email detection and validation
- **[Action Gate](/packages/core/action-gate)** - Permission-based action authorization
- **[Blocklist Service](/packages/core/security#blocklist)** - IP blocklist and rate limiting
- **[Security Headers](/core/security)** - Configurable security headers with CSP support
- **[Email Shield](/core/email-shield)** - Disposable email detection and validation
- **[Action Gate](/core/action-gate)** - Permission-based action authorization
- **[Blocklist Service](/core/security#blocklist)** - IP blocklist and rate limiting
### Utilities
- **[Input Sanitization](/packages/core/security#sanitization)** - XSS protection and input cleaning
- **[Encryption](/packages/core/security#encryption)** - Additional encryption utilities (HadesEncrypt)
- **[Translation Memory](/packages/core/i18n)** - Translation management with fuzzy matching and ICU support
- **[Input Sanitization](/core/security#sanitization)** - XSS protection and input cleaning
- **[Encryption](/core/security#encryption)** - Additional encryption utilities (HadesEncrypt)
- **[Translation Memory](/core/i18n)** - Translation management with fuzzy matching and ICU support
## Architecture
@ -160,7 +160,7 @@ Core package dispatches these lifecycle events:
- `Core\Events\McpToolsRegistering` - MCP tools
- `Core\Events\FrameworkBooted` - Late-stage initialization
[Learn more about Lifecycle Events →](/packages/core/events)
[Learn more about Lifecycle Events →](/core/events)
## Middleware
@ -266,8 +266,8 @@ EUPL-1.2
## Learn More
- [Module System →](/packages/core/modules)
- [Lifecycle Events →](/packages/core/events)
- [Multi-Tenancy →](/packages/core/tenancy)
- [Configuration →](/packages/core/configuration)
- [Activity Logging →](/packages/core/activity)
- [Module System →](/core/modules)
- [Lifecycle Events →](/core/events)
- [Multi-Tenancy →](/core/tenancy)
- [Configuration →](/core/configuration)
- [Activity Logging →](/core/activity)

View file

@ -502,5 +502,5 @@ class MediaTest extends TestCase
## Learn More
- [CDN Integration →](/packages/core/cdn)
- [Configuration →](/packages/core/configuration)
- [CDN Integration →](/core/cdn)
- [Configuration →](/core/configuration)

View file

@ -482,7 +482,7 @@ public function store(Request $request)
## Learn More
- [Lifecycle Events →](/packages/core/events)
- [Actions Pattern →](/packages/core/actions)
- [Service Discovery →](/packages/core/services)
- [Lifecycle Events →](/core/events)
- [Actions Pattern →](/core/actions)
- [Service Discovery →](/core/services)
- [Architecture Overview →](/architecture/module-system)

View file

@ -603,5 +603,5 @@ class SearchTest extends TestCase
## Learn More
- [Configuration →](/packages/core/configuration)
- [Configuration →](/core/configuration)
- [Global Search →](/packages/admin/search)

View file

@ -608,6 +608,6 @@ class PackageSeeder extends Seeder { }
## Learn More
- [Module System](/packages/core/modules)
- [Service Contracts](/packages/core/service-contracts)
- [Configuration](/packages/core/configuration)
- [Module System](/core/modules)
- [Service Contracts](/core/service-contracts)
- [Configuration](/core/configuration)

View file

@ -496,5 +496,5 @@ class SeoTest extends TestCase
## Learn More
- [Configuration →](/packages/core/configuration)
- [Media Processing →](/packages/core/media)
- [Configuration →](/core/configuration)
- [Media Processing →](/core/media)

View file

@ -505,6 +505,6 @@ return [
## Learn More
- [Module System](/packages/core/modules)
- [Lifecycle Events](/packages/core/events)
- [Seeder System](/packages/core/seeder-system)
- [Module System](/core/modules)
- [Lifecycle Events](/core/events)
- [Seeder System](/core/seeder-system)

119
package-lock.json generated
View file

@ -7,6 +7,7 @@
"devDependencies": {
"@tailwindcss/vite": "^4.1.18",
"autoprefixer": "^10.4.20",
"gray-matter": "^4.0.3",
"laravel-vite-plugin": "^1.2.0",
"postcss": "^8.4.47",
"tailwindcss": "^4.0.0",
@ -1955,6 +1956,16 @@
"node": ">= 14.0.0"
}
},
"node_modules/argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
"dev": true,
"license": "MIT",
"dependencies": {
"sprintf-js": "~1.0.2"
}
},
"node_modules/autoprefixer": {
"version": "10.4.23",
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.23.tgz",
@ -2261,6 +2272,20 @@
"node": ">=6"
}
},
"node_modules/esprima": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
"dev": true,
"license": "BSD-2-Clause",
"bin": {
"esparse": "bin/esparse.js",
"esvalidate": "bin/esvalidate.js"
},
"engines": {
"node": ">=4"
}
},
"node_modules/estree-walker": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
@ -2268,6 +2293,19 @@
"dev": true,
"license": "MIT"
},
"node_modules/extend-shallow": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
"integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==",
"dev": true,
"license": "MIT",
"dependencies": {
"is-extendable": "^0.1.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/fdir": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
@ -2332,6 +2370,22 @@
"dev": true,
"license": "ISC"
},
"node_modules/gray-matter": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz",
"integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"js-yaml": "^3.13.1",
"kind-of": "^6.0.2",
"section-matter": "^1.0.0",
"strip-bom-string": "^1.0.0"
},
"engines": {
"node": ">=6.0"
}
},
"node_modules/hast-util-to-html": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz",
@ -2388,6 +2442,16 @@
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/is-extendable": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
"integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-what": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/is-what/-/is-what-5.5.0.tgz",
@ -2411,6 +2475,30 @@
"jiti": "lib/jiti-cli.mjs"
}
},
"node_modules/js-yaml": {
"version": "3.14.2",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz",
"integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==",
"dev": true,
"license": "MIT",
"dependencies": {
"argparse": "^1.0.7",
"esprima": "^4.0.0"
},
"bin": {
"js-yaml": "bin/js-yaml.js"
}
},
"node_modules/kind-of": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
"integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/laravel-vite-plugin": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-1.3.0.tgz",
@ -3049,6 +3137,20 @@
"license": "MIT",
"peer": true
},
"node_modules/section-matter": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz",
"integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==",
"dev": true,
"license": "MIT",
"dependencies": {
"extend-shallow": "^2.0.1",
"kind-of": "^6.0.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/shiki": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/shiki/-/shiki-2.5.0.tgz",
@ -3097,6 +3199,13 @@
"node": ">=0.10.0"
}
},
"node_modules/sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
"dev": true,
"license": "BSD-3-Clause"
},
"node_modules/stringify-entities": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz",
@ -3112,6 +3221,16 @@
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/strip-bom-string": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz",
"integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/superjson": {
"version": "2.2.6",
"resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.6.tgz",

View file

@ -11,6 +11,7 @@
"devDependencies": {
"@tailwindcss/vite": "^4.1.18",
"autoprefixer": "^10.4.20",
"gray-matter": "^4.0.3",
"laravel-vite-plugin": "^1.2.0",
"postcss": "^8.4.47",
"tailwindcss": "^4.0.0",