kennel-plugins 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-454D7WI6.js +16332 -0
- package/dist/chunk-AX4A4GYF.js +11 -0
- package/dist/chunk-MLKGABMK.js +9 -0
- package/dist/chunk-STR4VH4C.js +11 -0
- package/dist/chunk-TCV5SJIW.js +171 -0
- package/dist/chunk-YLSYYX2L.js +8 -0
- package/dist/client.d.ts +65 -0
- package/dist/client.js +11 -0
- package/dist/endpoint.d.ts +61 -0
- package/dist/endpoint.js +21 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +51 -0
- package/dist/page.d.ts +204 -0
- package/dist/page.js +23 -0
- package/dist/server.d.ts +67 -0
- package/dist/server.js +7 -0
- package/dist/subpath.d.ts +21 -0
- package/dist/subpath.js +16 -0
- package/dist/types.d.ts +81 -0
- package/dist/types.js +7 -0
- package/dist/validation.d.ts +30 -0
- package/dist/validation.js +29 -0
- package/package.json +102 -4
- package/README.md +0 -3
package/dist/server.d.ts
ADDED
|
@@ -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,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 };
|
package/dist/subpath.js
ADDED
|
@@ -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
|
+
};
|
package/dist/types.d.ts
ADDED
|
@@ -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,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.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
4
|
+
"version": "0.1.0",
|
|
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