kennel-plugins 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,67 @@
1
+ import { FolderPluginManifest, FolderPluginDetection } from './types.js';
2
+ export { FolderPluginDataset, FolderPluginPayload, FolderPluginPayloadError } from './types.js';
3
+
4
+ /**
5
+ * Context the host passes to a plugin's server entry. The host performs the
6
+ * authorized folder resolution and root containment before calling any
7
+ * plugin, so a plugin id or subpath is never an access grant.
8
+ */
9
+ type FolderPluginContext = {
10
+ /** Absolute authorized folder path, already realpath-resolved by the host. */
11
+ folder: string;
12
+ /**
13
+ * Folders connected to the viewed folder by repository links, resolved by
14
+ * the host (same relation graph that powers linked notes). Plugins may
15
+ * read read-only content there (notes, tasks, instructions) but never
16
+ * write. Empty for remote, restricted, or machine-root hosts.
17
+ */
18
+ linkedFolders?: string[];
19
+ };
20
+ /**
21
+ * Payload a plugin returns from its data handler. The host serializes it to
22
+ * the client. `unavailable` lets a direct plugin URL render an explicit
23
+ * empty state without blocking core folder navigation.
24
+ */
25
+ type FolderPluginData = {
26
+ kind: "available";
27
+ data: unknown;
28
+ } | {
29
+ kind: "unavailable";
30
+ };
31
+ /**
32
+ * Node-side entry for a trusted folder plugin: an id/label manifest plus
33
+ * content detection and a generic list/detail data handler. Plugins may not
34
+ * touch the host shell; the host owns routing, layout, and dispatch.
35
+ */
36
+ /**
37
+ * Thrown by a plugin to signal a server-side failure (like a preview read
38
+ * error). The host maps this to an HTTP 5xx so it can never be mistaken for
39
+ * an empty but healthy result. All other thrown errors in a handler map to a
40
+ * not-found (4xx) response.
41
+ */
42
+ declare class FolderPluginFailure extends Error {
43
+ constructor(message: string);
44
+ }
45
+ /**
46
+ * Files a plugin wants scaffolding to create, paths relative to the
47
+ * authorized folder root (containment is enforced by the scaffolder).
48
+ */
49
+ type FolderPluginInitFile = {
50
+ path: string;
51
+ content: string;
52
+ };
53
+ type FolderPluginInitResult = {
54
+ files: FolderPluginInitFile[];
55
+ };
56
+ type FolderPluginServer = FolderPluginManifest & {
57
+ detect(context: FolderPluginContext): Promise<FolderPluginDetection>;
58
+ handle(context: FolderPluginContext, subpath: string): Promise<FolderPluginData>;
59
+ /**
60
+ * Optional starter content scaffolding (`kennel init` / host init
61
+ * commands). Returns files to create; the host skips files that already
62
+ * exist unless forced. Absent for read-only views with no content file.
63
+ */
64
+ init?(context: FolderPluginContext): Promise<FolderPluginInitResult>;
65
+ };
66
+
67
+ export { type FolderPluginContext, type FolderPluginData, FolderPluginDetection, FolderPluginFailure, type FolderPluginInitFile, type FolderPluginInitResult, type FolderPluginServer };
package/dist/server.js ADDED
@@ -0,0 +1,7 @@
1
+ import {
2
+ FolderPluginFailure
3
+ } from "./chunk-AX4A4GYF.js";
4
+ import "./chunk-MLKGABMK.js";
5
+ export {
6
+ FolderPluginFailure
7
+ };
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Shared subpath matching for folder plugins: a detail subpath selects one
3
+ * item across every discovered source, first match wins. Plugins with a
4
+ * richer address scheme (tasks' `<folderRef>@<id>` qualified ids) parse
5
+ * their own scheme first, then fall back to this helper for bare ids so
6
+ * not-found semantics stay consistent across plugins.
7
+ */
8
+ type SubpathMatch<Source, Item> = {
9
+ /** The full source object the caller supplied, kept intact for reuse. */
10
+ source: Source;
11
+ item: Item;
12
+ };
13
+ /**
14
+ * Resolve a detail subpath to one item across the given sources. A source
15
+ * earlier in the list wins over a later one, mirroring the "first source
16
+ * that contains it wins" behavior of the reference plugins. Returns
17
+ * undefined when no item matches; callers render their not-found state.
18
+ */
19
+ declare function matchSubpath<Source, Item>(sources: Source[], subpath: string, itemsOf: (source: Source) => Iterable<Item>, matches: (item: Item, id: string) => boolean): SubpathMatch<Source, Item> | undefined;
20
+
21
+ export { type SubpathMatch, matchSubpath };
@@ -0,0 +1,16 @@
1
+ import "./chunk-MLKGABMK.js";
2
+
3
+ // src/subpath.ts
4
+ function matchSubpath(sources, subpath, itemsOf, matches) {
5
+ const id = subpath.trim();
6
+ if (!id) return void 0;
7
+ for (const source of sources) {
8
+ for (const item of itemsOf(source)) {
9
+ if (matches(item, id)) return { item, source };
10
+ }
11
+ }
12
+ return void 0;
13
+ }
14
+ export {
15
+ matchSubpath
16
+ };
@@ -0,0 +1,81 @@
1
+ type FolderPluginId = string;
2
+ /**
3
+ * One precise problem found in plugin content, shared by every plugin's
4
+ * validation functions. `path` is JSON-pointer-ish (`tasks[2].status`) or
5
+ * `#` for file-level problems.
6
+ */
7
+ type ContentIssue = {
8
+ path: string;
9
+ message: string;
10
+ /** Advisory problem; absent means a hard error. Warnings never
11
+ * invalidate content — the file still detects as valid. */
12
+ severity?: "warning";
13
+ };
14
+ type FolderPluginManifest = {
15
+ /** Stable identifier used in routes, URLs, and registries. Never a URL segment turned into an import path. */
16
+ id: FolderPluginId;
17
+ /** Navigation label shown in the folder shell. */
18
+ label: string;
19
+ };
20
+ /**
21
+ * Result of a server-side content check. `available` reports an optional
22
+ * count so the navigation item can show a number. `unavailable` means the
23
+ * folder has no plugin content; `error` means detection failed and is not
24
+ * proof of absence. The detect call never returns full content payloads.
25
+ */
26
+ type FolderPluginDetection = {
27
+ count?: number;
28
+ status: "available";
29
+ } | {
30
+ status: "unavailable";
31
+ } | {
32
+ error: string;
33
+ status: "error";
34
+ };
35
+ /**
36
+ * Decoded-dataset payload: the plugin's typed dataset for the view plus
37
+ * optional advisory warnings. Extra fields (like the owning folder) are
38
+ * allowed; the index signature keeps host serialization flexible without
39
+ * weakening the required `status` check.
40
+ */
41
+ type FolderPluginDataset<T> = {
42
+ /** The plugin's decoded dataset for the view. */
43
+ dataset: T;
44
+ status: "available";
45
+ /** Advisory problems; they never invalidate the dataset. */
46
+ warnings?: ContentIssue[];
47
+ } & {
48
+ [key: string]: unknown;
49
+ };
50
+ /**
51
+ * In-band error payload: precise issues or a message, never a throw. The
52
+ * client renders the issue list (the shared `ContentIssues` panel) so a
53
+ * broken content file shows exactly which fields are wrong.
54
+ */
55
+ type FolderPluginPayloadError = {
56
+ /** Per-field issues for invalid content; empty for a not-found detail. */
57
+ issues?: ContentIssue[];
58
+ /** Human message for a not-found detail subpath. */
59
+ message?: string;
60
+ status: "error";
61
+ } & {
62
+ [key: string]: unknown;
63
+ };
64
+ /**
65
+ * Typed payload a plugin returns from its data handler. The `kind`/`status`
66
+ * pairing is the contract: a handler never throws for content problems — it
67
+ * returns `{ kind: "available", data: { status: "error", issues } }` in
68
+ * band, or `{ kind: "unavailable" }` when the folder has no content at all.
69
+ * The host-side `FolderPluginData` stays the loose type for tolerance; new
70
+ * plugins type their handler as `FolderPluginPayload<T>`.
71
+ */
72
+ type FolderPluginPayload<T> = {
73
+ data: FolderPluginDataset<T> | FolderPluginPayloadError;
74
+ kind: "available";
75
+ } | {
76
+ kind: "unavailable";
77
+ };
78
+ /** True when the decoded data is an in-band error payload. */
79
+ declare function isPluginPayloadError(data: unknown): data is FolderPluginPayloadError;
80
+
81
+ export { type ContentIssue, type FolderPluginDataset, type FolderPluginDetection, type FolderPluginId, type FolderPluginManifest, type FolderPluginPayload, type FolderPluginPayloadError, isPluginPayloadError };
package/dist/types.js ADDED
@@ -0,0 +1,7 @@
1
+ import {
2
+ isPluginPayloadError
3
+ } from "./chunk-YLSYYX2L.js";
4
+ import "./chunk-MLKGABMK.js";
5
+ export {
6
+ isPluginPayloadError
7
+ };
@@ -0,0 +1,30 @@
1
+ import { ContentIssue } from './types.js';
2
+
3
+ /**
4
+ * Result of validating a content source: a typed document, or a list of
5
+ * precise per-field issues. Parsing never throws — invalid JSON becomes an
6
+ * issue with the parser's position detail so callers can render it directly.
7
+ */
8
+ type Validated<T> = {
9
+ ok: true;
10
+ document: T;
11
+ } | {
12
+ ok: false;
13
+ issues: ContentIssue[];
14
+ };
15
+ /**
16
+ * Shared "validation function" primitive for folder plugins: parse a JSON
17
+ * source string, run the plugin's `validate` rules, and return either the
18
+ * typed document or structured issues. This is what every plugin's
19
+ * `src/parse.ts` should build on so detection, data loading, client rendering
20
+ * and tests all share one rule set.
21
+ */
22
+ declare function parseJsonSource<T>(source: string, validate: (value: unknown) => ContentIssue[]): Validated<T>;
23
+ /**
24
+ * Compact one-line summary of validation issues for nav badges and error
25
+ * headers: the single issue when there is one, otherwise a count plus the
26
+ * first problem.
27
+ */
28
+ declare function issueSummary(issues: ContentIssue[], file?: string): string;
29
+
30
+ export { ContentIssue, type Validated, issueSummary, parseJsonSource };
@@ -0,0 +1,29 @@
1
+ import "./chunk-MLKGABMK.js";
2
+
3
+ // src/validation.ts
4
+ function parseJsonSource(source, validate) {
5
+ let value;
6
+ try {
7
+ value = JSON.parse(source);
8
+ } catch (error) {
9
+ const detail = error instanceof Error && error.message ? error.message : "could not be parsed";
10
+ return {
11
+ ok: false,
12
+ issues: [{ path: "#", message: `not valid JSON: ${detail}` }]
13
+ };
14
+ }
15
+ const issues = validate(value);
16
+ if (issues.length > 0) return { ok: false, issues };
17
+ return { ok: true, document: value };
18
+ }
19
+ function issueSummary(issues, file = "content file") {
20
+ const first = issues[0];
21
+ if (!first) return `${file} is valid`;
22
+ const label = first.path && first.path !== "#" ? first.path : file;
23
+ if (issues.length === 1) return `${label}: ${first.message}`;
24
+ return `${issues.length} problems found in ${file} (first: ${label} \u2014 ${first.message})`;
25
+ }
26
+ export {
27
+ issueSummary,
28
+ parseJsonSource
29
+ };
package/package.json CHANGED
@@ -1,6 +1,104 @@
1
1
  {
2
+ "$schema": "https://json.schemastore.org/package.json",
2
3
  "name": "kennel-plugins",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
4
+ "version": "0.1.1",
5
+ "description": "Small shared contract for trusted folder view plugins in the Kennel web viewer.",
6
+ "type": "module",
7
+ "license": "MIT",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/raggle-ai/kennel.git",
11
+ "directory": "packages/plugins"
12
+ },
13
+ "publishConfig": {
14
+ "registry": "https://registry.npmjs.org",
15
+ "access": "public"
16
+ },
17
+ "files": [
18
+ "dist"
19
+ ],
20
+ "sideEffects": false,
21
+ "prettier": "@raggle-ai/prettier-config",
22
+ "exports": {
23
+ ".": {
24
+ "types": "./dist/index.d.ts",
25
+ "import": "./dist/index.js"
26
+ },
27
+ "./client": {
28
+ "types": "./dist/client.d.ts",
29
+ "import": "./dist/client.js"
30
+ },
31
+ "./endpoint": {
32
+ "types": "./dist/endpoint.d.ts",
33
+ "import": "./dist/endpoint.js"
34
+ },
35
+ "./page": {
36
+ "types": "./dist/page.d.ts",
37
+ "import": "./dist/page.js"
38
+ },
39
+ "./server": {
40
+ "types": "./dist/server.d.ts",
41
+ "import": "./dist/server.js"
42
+ },
43
+ "./subpath": {
44
+ "types": "./dist/subpath.d.ts",
45
+ "import": "./dist/subpath.js"
46
+ },
47
+ "./types": {
48
+ "types": "./dist/types.d.ts",
49
+ "import": "./dist/types.js"
50
+ },
51
+ "./validation": {
52
+ "types": "./dist/validation.d.ts",
53
+ "import": "./dist/validation.js"
54
+ }
55
+ },
56
+ "scripts": {
57
+ "build": "tsup",
58
+ "prepack": "bun run build",
59
+ "test": "bun test test/*.test.mjs",
60
+ "typecheck": "tsc --noEmit",
61
+ "format": "prettier . --check --cache --cache-location .cache/.prettiercache --ignore-path ../../.prettierignore",
62
+ "format:fix": "prettier . --write --cache --cache-location .cache/.prettiercache --ignore-path ../../.prettierignore",
63
+ "lint": "eslint . --cache --cache-location .cache/.eslintcache",
64
+ "lint:fix": "eslint . --fix --cache --cache-location .cache/.eslintcache",
65
+ "clean": "git clean -xdf dist node_modules .cache .turbo/cache"
66
+ },
67
+ "dependencies": {
68
+ "class-variance-authority": "^0.7.1",
69
+ "clsx": "^2.1.1",
70
+ "cmdk": "^1.1.1",
71
+ "lucide-react": "^0.562.0",
72
+ "next-themes": "^0.4.6",
73
+ "radix-ui": "^1.6.7",
74
+ "sonner": "^2.0.7",
75
+ "tailwind-merge": "^3.4.0"
76
+ },
77
+ "peerDependencies": {
78
+ "react": ">=18.3.0",
79
+ "react-dom": ">=18.3.0"
80
+ },
81
+ "devDependencies": {
82
+ "@eslint/compat": "catalog:eslint",
83
+ "@eslint/eslintrc": "catalog:eslint",
84
+ "@ianvs/prettier-plugin-sort-imports": "catalog:prettier",
85
+ "@prettier/plugin-oxc": "catalog:prettier",
86
+ "@raggle-ai/eslint-config": "workspace:*",
87
+ "@raggle-ai/prettier-config": "workspace:*",
88
+ "@raggle-ai/tsconfig": "workspace:*",
89
+ "@raggle-ai/ui": "workspace:*",
90
+ "kennel-client": "workspace:*",
91
+ "@types/node": "catalog:",
92
+ "@types/react": "catalog:react19",
93
+ "eslint": "catalog:eslint",
94
+ "eslint-plugin-unused-imports": "catalog:eslint",
95
+ "prettier": "catalog:prettier",
96
+ "prettier-plugin-classnames": "catalog:prettier",
97
+ "prettier-plugin-merge": "catalog:prettier",
98
+ "prettier-plugin-tailwindcss": "catalog:prettier",
99
+ "react": "catalog:react19",
100
+ "tsup": "8.5.1",
101
+ "typescript": "catalog:",
102
+ "typescript-eslint": "catalog:eslint"
103
+ }
104
+ }
package/README.md DELETED
@@ -1,3 +0,0 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.