@intentic/extension-manifest 1.223.0 → 1.225.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/README.md +16 -16
- package/dist/bundle.js +2 -2
- package/dist/bundle.js.map +1 -1
- package/dist/manifest.js +5 -5
- package/dist/manifest.js.map +1 -1
- package/dist/mark.js +3 -3
- package/dist/mark.js.map +1 -1
- package/dist/permissions.js +1 -1
- package/dist/permissions.js.map +1 -1
- package/dist/points/automation-templates.d.ts +1 -1
- package/dist/points/automation-templates.d.ts.map +1 -1
- package/dist/points/automation-templates.js +6 -6
- package/dist/points/automation-templates.js.map +1 -1
- package/dist/points/bin.d.ts +1 -1
- package/dist/points/bin.d.ts.map +1 -1
- package/dist/points/bin.js +1 -1
- package/dist/points/bin.js.map +1 -1
- package/dist/points/capabilities.d.ts +1 -1
- package/dist/points/capabilities.d.ts.map +1 -1
- package/dist/points/capabilities.js +13 -13
- package/dist/points/capabilities.js.map +1 -1
- package/dist/points/commands.js +1 -1
- package/dist/points/commands.js.map +1 -1
- package/dist/points/environment.d.ts +1 -1
- package/dist/points/environment.d.ts.map +1 -1
- package/dist/points/environment.js +2 -2
- package/dist/points/environment.js.map +1 -1
- package/dist/points/files.js +2 -2
- package/dist/points/files.js.map +1 -1
- package/dist/points/index.d.ts +7 -7
- package/dist/points/listener.d.ts +1 -1
- package/dist/points/listener.d.ts.map +1 -1
- package/dist/points/listener.js +3 -3
- package/dist/points/listener.js.map +1 -1
- package/dist/points/processes.d.ts +1 -1
- package/dist/points/processes.d.ts.map +1 -1
- package/dist/points/processes.js +1 -1
- package/dist/points/processes.js.map +1 -1
- package/dist/points/settings.js +1 -1
- package/dist/points/settings.js.map +1 -1
- package/dist/points/viewers.d.ts +1 -1
- package/dist/points/viewers.d.ts.map +1 -1
- package/dist/points/viewers.js +3 -3
- package/dist/points/viewers.js.map +1 -1
- package/intentic-extension.schema.json +73 -73
- package/package.json +3 -3
- package/src/bundle.ts +5 -5
- package/src/contribution-point.ts +5 -5
- package/src/json-schema.ts +6 -6
- package/src/manifest.ts +18 -18
- package/src/mark.ts +11 -11
- package/src/permissions.ts +2 -2
- package/src/points/agent.ts +1 -1
- package/src/points/automation-templates.ts +10 -10
- package/src/points/bin.ts +2 -2
- package/src/points/capabilities.ts +38 -38
- package/src/points/commands.ts +4 -4
- package/src/points/documents.ts +2 -2
- package/src/points/environment.ts +2 -2
- package/src/points/files.ts +9 -9
- package/src/points/index.ts +4 -4
- package/src/points/listener.ts +7 -7
- package/src/points/processes.ts +1 -1
- package/src/points/settings.ts +1 -1
- package/src/points/viewers.ts +8 -8
- package/src/powers-diff.ts +5 -5
package/src/bundle.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
/* WHAT A PUBLISHED BUNDLE MAY IMPORT
|
|
1
|
+
/* WHAT A PUBLISHED BUNDLE MAY IMPORT, the loader's side of the manifest contract, stated where both sides can
|
|
2
2
|
* read it. The host fetches an extension's entry bytes and imports them from a blob: URL, which has two hard
|
|
3
3
|
* consequences: a relative import resolves against a blob: URL that was never created (a 404 for a file that
|
|
4
4
|
* exists on disk), and a bare specifier resolves only if the shell's import map publishes it. Both failures are
|
|
5
|
-
* invisible to the author
|
|
5
|
+
* invisible to the author, their own workspace loads the directory live, and fatal for every installer.
|
|
6
6
|
*
|
|
7
7
|
* It lives HERE, beside the manifest schema, because two independent judges have to agree on it: the daemon's
|
|
8
8
|
* readiness check (before an author publishes) and the registry scanner (re-deriving the same answer cold, at
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
export const HOST_PUBLISHED_SPECIFIERS = ["vue", "@intentic/extension-api", "@intentic/extension-ui", "@tanstack/vue-query"] as const;
|
|
15
15
|
|
|
16
16
|
// Every specifier a single-file ESM bundle names: static imports, re-exports, bare side-effect imports, and
|
|
17
|
-
// dynamic import(). A bundle is one file by contract, so a regex over its text is the right instrument
|
|
17
|
+
// dynamic import(). A bundle is one file by contract, so a regex over its text is the right instrument, there
|
|
18
18
|
// is no module graph to walk.
|
|
19
19
|
export const bundleSpecifiers = (source: string): string[] => [
|
|
20
20
|
...new Set([
|
|
@@ -30,12 +30,12 @@ export const bundleProblem = (source: string): string | undefined => {
|
|
|
30
30
|
const specifiers = bundleSpecifiers(source);
|
|
31
31
|
const relative = specifiers.filter((specifier) => specifier.startsWith(".") || specifier.startsWith("/"));
|
|
32
32
|
if (relative.length > 0) {
|
|
33
|
-
return `imports a second file (${relative.join(", ")})
|
|
33
|
+
return `imports a second file (${relative.join(", ")}): a bundle is imported from a blob URL, so nothing relative to it can resolve`;
|
|
34
34
|
}
|
|
35
35
|
const published = new Set<string>(HOST_PUBLISHED_SPECIFIERS);
|
|
36
36
|
const unpublished = specifiers.filter((specifier) => !published.has(specifier));
|
|
37
37
|
if (unpublished.length > 0) {
|
|
38
|
-
return `imports ${unpublished.join(", ")}, which the host does not publish
|
|
38
|
+
return `imports ${unpublished.join(", ")}, which the host does not publish, bundle it in, or use one of: ${HOST_PUBLISHED_SPECIFIERS.join(", ")}`;
|
|
39
39
|
}
|
|
40
40
|
return undefined;
|
|
41
41
|
};
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import type { z } from "zod";
|
|
2
2
|
|
|
3
|
-
/* A CONTRIBUTION POINT, DEFINED ONCE
|
|
3
|
+
/* A CONTRIBUTION POINT, DEFINED ONCE, its key, its shape, and the sentence that explains it to the person
|
|
4
4
|
* writing the manifest, in one object.
|
|
5
5
|
*
|
|
6
6
|
* The three used to live apart, and only two of them lived anywhere at all. The key and the shape sat in one
|
|
7
7
|
* file that every feature had to edit to add anything, in two places (the schema, then the key on `contributes`);
|
|
8
|
-
* the explanation sat in a `//` comment above it, where the extension author
|
|
9
|
-
* for
|
|
8
|
+
* the explanation sat in a `//` comment above it, where the extension author, the only reader it was written
|
|
9
|
+
* for, could never see it. So the manifest was a thing you wrote by copying another extension's and guessing,
|
|
10
10
|
* with a misspelt contribution point dropped in silence rather than named.
|
|
11
11
|
*
|
|
12
12
|
* Binding the description to the schema is what changes that: it rides `z.describe`, so it reaches the generated
|
|
13
13
|
* authoring schema (json-schema.ts) and comes back as hover text in the author's editor. The prose that is for
|
|
14
|
-
* MAINTAINERS
|
|
14
|
+
* MAINTAINERS, why a point exists, what it replaced, what it deliberately does not do, stays a comment in the
|
|
15
15
|
* point's own file, because it is not what someone filling in a field needs to read. */
|
|
16
16
|
export interface ContributionPoint<Name extends string = string, Schema extends z.ZodType = z.ZodType> {
|
|
17
17
|
// The key under `contributes` in intentic-extension.json.
|
|
@@ -20,7 +20,7 @@ export interface ContributionPoint<Name extends string = string, Schema extends
|
|
|
20
20
|
* them, and what the host does with it. It lands verbatim in editor hover text, so a paragraph of rationale
|
|
21
21
|
* here is a paragraph in a tooltip. */
|
|
22
22
|
readonly description: string;
|
|
23
|
-
// The value shape under that key
|
|
23
|
+
// The value shape under that key, an array for a point that takes many entries, the entry itself for a
|
|
24
24
|
// point that takes one. `contributes` makes every one of them optional; a point is never required.
|
|
25
25
|
readonly schema: Schema;
|
|
26
26
|
}
|
package/src/json-schema.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { ExtensionManifestSchema } from "./manifest.js";
|
|
3
3
|
|
|
4
|
-
/* THE AUTHORING SCHEMA
|
|
4
|
+
/* THE AUTHORING SCHEMA, what an editor reads to help someone write `intentic-extension.json`.
|
|
5
5
|
*
|
|
6
6
|
* A manifest was previously written by copying another extension's and guessing. Nothing told the author what a
|
|
7
7
|
* field meant, because every explanation lived in a `//` comment in this package; and nothing told them when
|
|
8
8
|
* they got one wrong, because zod STRIPS unknown keys at every level rather than refusing them. A misspelt
|
|
9
|
-
* `viewers` was not an error
|
|
9
|
+
* `viewers` was not an error, it was a viewer that never appeared, discovered at install, with the manifest
|
|
10
10
|
* parsing perfectly.
|
|
11
11
|
*
|
|
12
12
|
* So: the same points, emitted as JSON Schema, with the descriptions that ride each one (contribution-point.ts)
|
|
@@ -15,7 +15,7 @@ import { ExtensionManifestSchema } from "./manifest.js";
|
|
|
15
15
|
*
|
|
16
16
|
* STRICT HERE, LENIENT AT RUNTIME, and the asymmetry is the point. Authoring is where an unknown key is a typo
|
|
17
17
|
* and should be shouted about. Runtime is where an unknown key is a manifest written for a NEWER host, which an
|
|
18
|
-
* older daemon must go on installing with the point it doesn't understand ignored
|
|
18
|
+
* older daemon must go on installing with the point it doesn't understand ignored, refusing it outright would
|
|
19
19
|
* make every addition to this list a breaking change. */
|
|
20
20
|
|
|
21
21
|
// Where the published copy answers, so `$schema` in a manifest resolves for an author who has installed nothing.
|
|
@@ -23,7 +23,7 @@ export const MANIFEST_SCHEMA_URL = "https://intentic.dev/intentic-extension.sche
|
|
|
23
23
|
|
|
24
24
|
/* `additionalProperties: false` on every object node, so a key nothing declares is flagged where it is typed.
|
|
25
25
|
*
|
|
26
|
-
* Skips a node that already carries `additionalProperties
|
|
26
|
+
* Skips a node that already carries `additionalProperties`, that is a `z.record`, whose whole shape is "any key,
|
|
27
27
|
* this value" (a cli capability's `env`), and pinning it closed would reject every entry it exists to accept. */
|
|
28
28
|
const closeToUnknownKeys = (node: unknown): void => {
|
|
29
29
|
if (Array.isArray(node)) {
|
|
@@ -44,7 +44,7 @@ const closeToUnknownKeys = (node: unknown): void => {
|
|
|
44
44
|
}
|
|
45
45
|
};
|
|
46
46
|
|
|
47
|
-
/* The manifest schema as JSON Schema. `io: "input"` because this describes what an author WRITES
|
|
47
|
+
/* The manifest schema as JSON Schema. `io: "input"` because this describes what an author WRITES, the shape
|
|
48
48
|
* going in, before any refinement or default has been applied to it. */
|
|
49
49
|
export const manifestJsonSchema = (): Record<string, unknown> => {
|
|
50
50
|
const schema = z.toJSONSchema(ExtensionManifestSchema, { unrepresentable: "any", io: "input" }) as Record<string, unknown>;
|
|
@@ -58,5 +58,5 @@ export const manifestJsonSchema = (): Record<string, unknown> => {
|
|
|
58
58
|
};
|
|
59
59
|
|
|
60
60
|
// The committed file's exact bytes, so the generator and the check that guards it cannot disagree about
|
|
61
|
-
// formatting
|
|
61
|
+
// formatting, four spaces and a trailing newline, the repo's shape for a committed generated document.
|
|
62
62
|
export const serializeManifestJsonSchema = (schema: Record<string, unknown>): string => `${JSON.stringify(schema, undefined, 4)}\n`;
|
package/src/manifest.ts
CHANGED
|
@@ -3,25 +3,25 @@ import { MARK_FIELDS } from "./mark.js";
|
|
|
3
3
|
import { contributesSchema } from "./points/index.js";
|
|
4
4
|
|
|
5
5
|
/* The extension manifest: `intentic-extension.json` at the extension repo root (deliberately NOT inside
|
|
6
|
-
* .claude-plugin
|
|
6
|
+
* .claude-plugin/, that directory is Claude Code's namespace with its own semantics). The manifest is the
|
|
7
7
|
* approval surface: the install dialog shows exactly these declared contributions before the owner confirms,
|
|
8
8
|
* and the host refuses runtime registrations (views, commands) whose ids the approved manifest never declared.
|
|
9
9
|
*
|
|
10
|
-
* This file is the ENVELOPE only
|
|
10
|
+
* This file is the ENVELOPE only, who the extension is, which host it needs, what code it ships, how far it
|
|
11
11
|
* may reach. What it may CONTRIBUTE is one file per contribution point under points/, assembled here; see
|
|
12
12
|
* contribution-point.ts for why the description travels with the schema instead of sitting in a comment. */
|
|
13
13
|
|
|
14
14
|
export const ExtensionManifestSchema = z.object({
|
|
15
|
-
/* The authoring schema this manifest is written against
|
|
15
|
+
/* The authoring schema this manifest is written against, editors read it and give the author completion,
|
|
16
16
|
* hover text and a red squiggle on a misspelt key. Declared so it survives the parse rather than being
|
|
17
17
|
* silently stripped, which is what would otherwise happen to the one field an author is most likely to add
|
|
18
18
|
* by hand. Nothing at runtime reads it. */
|
|
19
19
|
$schema: z.string().optional().describe("The authoring schema, for editor completion and validation. Nothing at runtime reads it."),
|
|
20
20
|
publisher: z.string().regex(/^[a-z0-9][a-z0-9-]*$/),
|
|
21
21
|
name: z.string().regex(/^[a-z0-9][a-z0-9-]*$/),
|
|
22
|
-
// The extension's own semver
|
|
23
|
-
version: z.string().min(1).describe("Your own semver
|
|
24
|
-
/* The section this extension sits under in the Sandbox hub's Extensions tab
|
|
22
|
+
// The extension's own semver, display/identity only; the installed code identity is the pinned commit sha.
|
|
23
|
+
version: z.string().min(1).describe("Your own semver, display and identity only. The installed code's identity is the pinned commit sha."),
|
|
24
|
+
/* The section this extension sits under in the Sandbox hub's Extensions tab, a grouping by what it is FOR,
|
|
25
25
|
* declared because it cannot be derived. Nine of the first-party extensions contribute a rail tile, so a
|
|
26
26
|
* grouping read off `contributes` puts more than half the list in one section and says nothing about any of
|
|
27
27
|
* them. Deliberately a loose string, exactly like a connector's `catalog.category`: the vocabulary belongs
|
|
@@ -32,15 +32,15 @@ export const ExtensionManifestSchema = z.object({
|
|
|
32
32
|
.min(1)
|
|
33
33
|
.optional()
|
|
34
34
|
.describe(
|
|
35
|
-
"Which section of the Extensions tab this sits under
|
|
35
|
+
"Which section of the Extensions tab this sits under: a grouping by what it is FOR, which cannot be derived from what it contributes. A section this app has never heard of lands in 'Other' rather than failing to install.",
|
|
36
36
|
),
|
|
37
|
-
/* What the extension is drawn as wherever it is LISTED rather than used
|
|
37
|
+
/* What the extension is drawn as wherever it is LISTED rather than used, the Extensions tab, a registry
|
|
38
38
|
* being browsed, the gallery. Deliberately here and not on a view: `Activation.icon` is the glyph of one
|
|
39
39
|
* rail tile, it only exists once the extension's code has activated in this browser, and nine of the
|
|
40
40
|
* first-party extensions register no view at all. An extension that is switched off, daemon-only, or not
|
|
41
41
|
* yet installed still has to look like something. See MARK_FIELDS. */
|
|
42
42
|
...MARK_FIELDS,
|
|
43
|
-
// Semver range over the host's extension API version (extensionApiVersion)
|
|
43
|
+
// Semver range over the host's extension API version (extensionApiVersion), checked before activation.
|
|
44
44
|
engines: z
|
|
45
45
|
.object({ intentic: z.string().min(1) })
|
|
46
46
|
.describe("A semver range over the host's extension API version, checked before your code is activated."),
|
|
@@ -54,7 +54,7 @@ export const ExtensionManifestSchema = z.object({
|
|
|
54
54
|
.describe(
|
|
55
55
|
"Repo-relative path of your prebuilt single-file ESM bundle, built with `vue` and `@intentic/extension-api` as externals. Absent ⇒ an extension with no UI.",
|
|
56
56
|
),
|
|
57
|
-
/* Repo-relative path of the prebuilt single-file node ESM SERVER bundle
|
|
57
|
+
/* Repo-relative path of the prebuilt single-file node ESM SERVER bundle, the extension's BACKEND half,
|
|
58
58
|
* exporting `activateServer(api, context)`. Loaded by the daemon's backend host (a separate supervised
|
|
59
59
|
* process, so a toggle or a live edit is a host restart rather than a daemon death) and served under the
|
|
60
60
|
* extension's own route namespace `/x/<id>/…`, which the daemon proxies. Self-contained by construction:
|
|
@@ -68,12 +68,12 @@ export const ExtensionManifestSchema = z.object({
|
|
|
68
68
|
.describe(
|
|
69
69
|
"Repo-relative path of your prebuilt single-file node ESM server bundle, exporting `activateServer`. Served under your own route namespace, which the daemon proxies. Nothing is provided at runtime but node builtins, so bundle everything else in. Absent ⇒ no backend.",
|
|
70
70
|
),
|
|
71
|
-
// Declared reach, both halves in one grammar
|
|
72
|
-
// (e.g. "GET /panels", "POST /panels/*/start")
|
|
71
|
+
// Declared reach, both halves in one grammar, "<METHOD> <path-glob>" where `*` matches one path segment
|
|
72
|
+
// (e.g. "GET /panels", "POST /panels/*/start"), so the install dialog, the gate and the usage ledger read
|
|
73
73
|
// one vocabulary.
|
|
74
|
-
// sandbox
|
|
74
|
+
// sandbox, the daemon routes the UI half may call through api.sandbox (the host refuses undeclared
|
|
75
75
|
// ones). An extension's OWN namespace `/x/<its id>/…` needs no entry: its backend is its own.
|
|
76
|
-
// daemon
|
|
76
|
+
// daemon , the daemon routes the SERVER half may call through api.daemon, enforced by the daemon's
|
|
77
77
|
// extension-token grant. Separate from `sandbox` because the halves run as different
|
|
78
78
|
// principals: the UI acts with the owner's session, the backend with a minted per-extension
|
|
79
79
|
// token, and a grant to one must never quietly widen the other.
|
|
@@ -83,22 +83,22 @@ export const ExtensionManifestSchema = z.object({
|
|
|
83
83
|
sandbox: z
|
|
84
84
|
.array(z.string())
|
|
85
85
|
.optional()
|
|
86
|
-
.describe("Daemon routes your UI half may call. Your own backend namespace needs no entry
|
|
86
|
+
.describe("Daemon routes your UI half may call. Your own backend namespace needs no entry: its backend is your own code."),
|
|
87
87
|
daemon: z
|
|
88
88
|
.array(z.string())
|
|
89
89
|
.optional()
|
|
90
90
|
.describe(
|
|
91
|
-
"Daemon routes your SERVER half may call. Separate from `sandbox` because the two halves run as different principals
|
|
91
|
+
"Daemon routes your SERVER half may call. Separate from `sandbox` because the two halves run as different principals: the UI as the owner's session, the backend as a minted per-extension token, so a grant to one must never quietly widen the other.",
|
|
92
92
|
),
|
|
93
93
|
})
|
|
94
94
|
.optional()
|
|
95
95
|
.describe(
|
|
96
|
-
'How far this extension may reach into the daemon, as "<METHOD> <path-glob>" entries where `*` matches one path segment
|
|
96
|
+
'How far this extension may reach into the daemon, as "<METHOD> <path-glob>" entries where `*` matches one path segment: e.g. "GET /panels", "POST /panels/*/start". The install dialog shows these, the host refuses anything undeclared, and the usage ledger records which were actually earned.',
|
|
97
97
|
),
|
|
98
98
|
contributes: contributesSchema.optional(),
|
|
99
99
|
});
|
|
100
100
|
export type ExtensionManifest = z.infer<typeof ExtensionManifestSchema>;
|
|
101
101
|
|
|
102
|
-
// The extension's identity everywhere (capability entries, /ext routes, settings namespaces)
|
|
102
|
+
// The extension's identity everywhere (capability entries, /ext routes, settings namespaces), derived, never
|
|
103
103
|
// declared, so it can't contradict the publisher/name the install dialog showed.
|
|
104
104
|
export const extensionIdOf = (manifest: Pick<ExtensionManifest, "publisher" | "name">): string => `${manifest.publisher}.${manifest.name}`;
|
package/src/mark.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
|
|
3
|
-
/* HOW SOMETHING LOOKS BEFORE ANY OF ITS CODE RUNS
|
|
3
|
+
/* HOW SOMETHING LOOKS BEFORE ANY OF ITS CODE RUNS, the mark a capability card and an extension are drawn
|
|
4
4
|
* with, in ONE shape because one component draws both (<BrandMark>) and a second copy of these two fields is a
|
|
5
5
|
* second answer to what happens when a slug 404s.
|
|
6
6
|
*
|
|
@@ -8,7 +8,7 @@ import { z } from "zod";
|
|
|
8
8
|
* author controls completely, and the only one that can make a grid of unfamiliar names look like a shelf of
|
|
9
9
|
* distinct products rather than a column of identical glyphs. `logo` is a simple-icons slug fetched from a
|
|
10
10
|
* CDN: exactly right for a card standing in for somebody else's product (GitHub, Postgres, Slack), useless
|
|
11
|
-
* for the many things that have no brand in that set, and unreachable in an offline sandbox
|
|
11
|
+
* for the many things that have no brand in that set, and unreachable in an offline sandbox, so it can never
|
|
12
12
|
* be the only tier. `icon` is a name from the host's own bundled vocabulary, which ships in the image, follows
|
|
13
13
|
* the theme and costs no request; it is what carries a first-party extension that has not been drawn yet. What
|
|
14
14
|
* declares none of them is drawn as its initials, so no row is ever blank and no author is obliged to have a
|
|
@@ -16,15 +16,15 @@ import { z } from "zod";
|
|
|
16
16
|
*
|
|
17
17
|
* ART BEATS A BRAND SLUG because they answer different questions. A slug says "this is Slack"; art says "this
|
|
18
18
|
* is mine". An extension that stands in for somebody else's product should declare the slug and no art, and
|
|
19
|
-
* one that is its own thing should declare art
|
|
19
|
+
* one that is its own thing should declare art, but where both arrive, the author's own drawing is the more
|
|
20
20
|
* specific claim and wins.
|
|
21
21
|
*
|
|
22
22
|
* Artwork that will not parse, a slug that fails to load and an icon name this build has never heard of all
|
|
23
|
-
* fall to the tier BELOW rather than to a hole
|
|
23
|
+
* fall to the tier BELOW rather than to a hole, the rule the rail already applies to Activation.icon, here for
|
|
24
24
|
* the surfaces that must draw an extension whose code is not running: one that is switched off, one that is
|
|
25
25
|
* daemon-only, one being read about in a registry before it is installed at all. */
|
|
26
26
|
|
|
27
|
-
/* Big enough for a drawn mark, far too small for a traced photograph
|
|
27
|
+
/* Big enough for a drawn mark, far too small for a traced photograph, which is the line being drawn. This
|
|
28
28
|
* string rides every registry row and every manifest read, so it is a budget as much as a limit: at 4 KB the
|
|
29
29
|
* whole official registry's artwork costs less than one screenshot, and an author who needs more than that is
|
|
30
30
|
* shipping a raster they should be shipping as a brand slug or not at all. */
|
|
@@ -33,24 +33,24 @@ const ART_MAX_BYTES = 4096;
|
|
|
33
33
|
export const MARK_FIELDS = {
|
|
34
34
|
/* THE SVG DOCUMENT ITSELF, not a URL and not base64.
|
|
35
35
|
*
|
|
36
|
-
* A URL would put a stranger's server in the render path of a page listing extensions
|
|
36
|
+
* A URL would put a stranger's server in the render path of a page listing extensions, a fetch that
|
|
37
37
|
* tracks who is browsing what, breaks in an offline sandbox, and 404s long after the row was approved:
|
|
38
38
|
* the three failures the `logo` tier already documents, with none of its excuse. Inline costs one string
|
|
39
39
|
* and always draws.
|
|
40
40
|
*
|
|
41
41
|
* Kept as READABLE TEXT rather than a data URI because of who reads it. The registry's curated file is
|
|
42
42
|
* reviewed by a human before anything is published, and `<rect fill="#5B4FE9"/><circle .../>` can be read
|
|
43
|
-
* in a diff, while base64 is a wall nobody checks
|
|
43
|
+
* in a diff, while base64 is a wall nobody checks, an opaque blob in the one file whose entire purpose is
|
|
44
44
|
* to be checked. The renderer does its own encoding at the point of use.
|
|
45
45
|
*
|
|
46
46
|
* It is drawn INERT (an <img>, never inline in the document), so a hostile registry row cannot script the
|
|
47
|
-
* page it is listed on
|
|
47
|
+
* page it is listed on, see <BrandMark>, which owns that guarantee and the sniff test that enforces it. */
|
|
48
48
|
art: z
|
|
49
49
|
.string()
|
|
50
50
|
.max(ART_MAX_BYTES)
|
|
51
51
|
.optional()
|
|
52
52
|
.describe(
|
|
53
|
-
"This extension's own mark, as a complete SVG document inline
|
|
53
|
+
"This extension's own mark, as a complete SVG document inline: the tier an author controls fully. Give it a viewBox and let it fill its own square edge to edge; it is drawn as the tile, not as a glyph on a plate. Kept as readable SVG text (not base64) so a registry reviewer can see what they are publishing, drawn inert so it cannot script the page, and capped at 4 KB. Anything that does not parse as SVG falls back to `logo`, then `icon`, then initials.",
|
|
54
54
|
),
|
|
55
55
|
// A simple-icons slug (https://cdn.simpleicons.org/<slug>). A "/<hex>" suffix forces a colour for marks
|
|
56
56
|
// that vanish against the surface they land on (github's near-black).
|
|
@@ -58,13 +58,13 @@ export const MARK_FIELDS = {
|
|
|
58
58
|
.string()
|
|
59
59
|
.optional()
|
|
60
60
|
.describe(
|
|
61
|
-
'A simple-icons slug, fetched from a CDN
|
|
61
|
+
'A simple-icons slug, fetched from a CDN: right for standing in for somebody else\'s product. Add a "/<hex>" suffix to force a colour for a mark that vanishes against the surface it lands on. Unreachable in an offline sandbox, so it falls back to `icon`, then to initials.',
|
|
62
62
|
),
|
|
63
63
|
// A name from the host's icon set (@intentic/ui IconName), drawn when no simple-icons slug fits.
|
|
64
64
|
icon: z
|
|
65
65
|
.string()
|
|
66
66
|
.optional()
|
|
67
67
|
.describe(
|
|
68
|
-
"A name from the host's own icon set, drawn when no simple-icons slug fits. It ships in the image, follows the theme and costs no request
|
|
68
|
+
"A name from the host's own icon set, drawn when no simple-icons slug fits. It ships in the image, follows the theme and costs no request: what actually carries a first-party extension. An unknown name falls back to initials rather than to a hole.",
|
|
69
69
|
),
|
|
70
70
|
};
|
package/src/permissions.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* The sandbox-route permission model. An extension declares in its manifest exactly which daemon routes it may
|
|
2
2
|
* reach through `api.sandbox.request/json`, as "<METHOD> <path-glob>" strings where `*` matches exactly one path
|
|
3
|
-
* segment. The host matches every call against these and refuses an undeclared route
|
|
3
|
+
* segment. The host matches every call against these and refuses an undeclared route, so an extension's backend
|
|
4
4
|
* reach is explicit, diffable, and reviewable rather than an ambient client to the whole daemon. */
|
|
5
5
|
|
|
6
6
|
const escapeRegExp = (literal: string): string => literal.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -15,7 +15,7 @@ interface CompiledRoute {
|
|
|
15
15
|
const compile = (entry: string): CompiledRoute => {
|
|
16
16
|
const spaceIndex = entry.indexOf(" ");
|
|
17
17
|
if (spaceIndex < 0) {
|
|
18
|
-
throw new Error(`invalid sandbox permission "${entry}"
|
|
18
|
+
throw new Error(`invalid sandbox permission "${entry}": expected "<METHOD> <path-glob>", e.g. "GET /panels"`);
|
|
19
19
|
}
|
|
20
20
|
const method = entry.slice(0, spaceIndex).trim().toUpperCase();
|
|
21
21
|
const glob = entry.slice(spaceIndex + 1).trim();
|
package/src/points/agent.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { z } from "zod";
|
|
|
2
2
|
import type { ContributionPoint } from "../contribution-point.js";
|
|
3
3
|
|
|
4
4
|
// "This checkout is ALSO a Claude Code plugin": the daemon hands the directory to the Agent SDK's plugin
|
|
5
|
-
// loader, which reads skills/agents/hooks/commands/.mcp.json each turn
|
|
5
|
+
// loader, which reads skills/agents/hooks/commands/.mcp.json each turn, the daemon never parses plugin
|
|
6
6
|
// internals.
|
|
7
7
|
export const AgentContributionSchema = z.object({
|
|
8
8
|
path: z.string().optional().describe("Relative to the extension checkout. Absent ⇒ the checkout root."),
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import type { ContributionPoint } from "../contribution-point.js";
|
|
3
3
|
|
|
4
|
-
/* A STARTING POINT in the automation composer
|
|
4
|
+
/* A STARTING POINT in the automation composer, a trigger, a prompt written for that trigger's payload, and
|
|
5
5
|
* whatever guard or hold makes it safe to leave on. Pure prefill: creating one makes an ordinary automation and
|
|
6
6
|
* the daemon knows nothing about templates afterwards.
|
|
7
7
|
*
|
|
8
8
|
* IT LIVES WITH THE AREA THAT KNOWS THE SERVICE, which is the point of it being a contribution at all. The
|
|
9
|
-
* automation surface used to carry every one of these
|
|
9
|
+
* automation surface used to carry every one of these. Komodo's, Sentry's, Stripe's, CI's, the chore book's,
|
|
10
10
|
* so a pack that gained something worth reacting to could not say so without an edit to a surface it has
|
|
11
11
|
* nothing to do with. A template declared here appears when the pack is installed and its capability connected,
|
|
12
12
|
* and disappears with it.
|
|
@@ -14,22 +14,22 @@ import type { ContributionPoint } from "../contribution-point.js";
|
|
|
14
14
|
* The daemon validates each one against the real trigger schema when it builds the catalogue and drops what
|
|
15
15
|
* does not parse, so a template can never offer a trigger that `upsert` would refuse. */
|
|
16
16
|
export const AutomationTemplateContributionSchema = z.object({
|
|
17
|
-
// Prefills the automation name, and is what "does one of these exist already" is asked by
|
|
17
|
+
// Prefills the automation name, and is what "does one of these exist already" is asked by, so it must be
|
|
18
18
|
// spelled as an automation id, not as prose.
|
|
19
19
|
id: z
|
|
20
20
|
.string()
|
|
21
21
|
.regex(/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/)
|
|
22
|
-
.describe('Prefills the automation name, and is what "does one of these exist already" is asked by
|
|
22
|
+
.describe('Prefills the automation name, and is what "does one of these exist already" is asked by, so spell it as an id, not as prose.'),
|
|
23
23
|
title: z.string().min(1),
|
|
24
24
|
logo: z.string().min(1).optional().describe("A simple-icons slug for the card."),
|
|
25
25
|
icon: z.string().min(1).optional().describe("A name from the host's icon set, drawn when no simple-icons slug fits."),
|
|
26
|
-
// Capability providers that make this template WORK
|
|
26
|
+
// Capability providers that make this template WORK, any one connected is enough (fixing CI rides github
|
|
27
27
|
// or gitlab). Omitted ⇒ nothing to connect, always offered.
|
|
28
28
|
requires: z
|
|
29
29
|
.array(z.string().min(1))
|
|
30
30
|
.optional()
|
|
31
31
|
.describe(
|
|
32
|
-
"Capability providers that make this template work
|
|
32
|
+
"Capability providers that make this template work: any one connected is enough (fixing CI rides github or gitlab). Omitted ⇒ nothing to connect, so it is always offered.",
|
|
33
33
|
),
|
|
34
34
|
// Shaped loosely here and parsed strictly at the merge: the manifest package cannot see the trigger union
|
|
35
35
|
// (the dependency runs the other way), so the daemon is where a declaration meets the real schema.
|
|
@@ -48,7 +48,7 @@ export const AutomationTemplateContributionSchema = z.object({
|
|
|
48
48
|
.string()
|
|
49
49
|
.min(1)
|
|
50
50
|
.optional()
|
|
51
|
-
.describe("A condition that must hold before the turn runs
|
|
51
|
+
.describe("A condition that must hold before the turn runs: what makes a template safe to leave switched on."),
|
|
52
52
|
holdForSeconds: z.number().int().positive().optional().describe("Wait this long and coalesce repeats, rather than firing on every event."),
|
|
53
53
|
prompt: z.string().min(1).describe("The turn this starts. You own the trigger's payload vocabulary, so you own the prompt that reads it."),
|
|
54
54
|
note: z.string().min(1).optional(),
|
|
@@ -63,7 +63,7 @@ export const AutomationTemplateContributionSchema = z.object({
|
|
|
63
63
|
.enum(["create", "configure"])
|
|
64
64
|
.optional()
|
|
65
65
|
.describe(
|
|
66
|
-
"Absent ⇒ it waits in the gallery, where you go once you know what you want. `create` puts a card on the page that makes it, switched off, in one click. `configure` puts one there that opens the dialog prefilled, for a template that cannot work unconfigured. Both are for what a user would never think to go looking for
|
|
66
|
+
"Absent ⇒ it waits in the gallery, where you go once you know what you want. `create` puts a card on the page that makes it, switched off, in one click. `configure` puts one there that opens the dialog prefilled, for a template that cannot work unconfigured. Both are for what a user would never think to go looking for: mark everything as offered and you have rebuilt the gallery with extra steps.",
|
|
67
67
|
),
|
|
68
68
|
// Whether what this makes watches THIS codebase (the chores shelf) rather than the outside world. Declared
|
|
69
69
|
// rather than read off the trigger: a nightly dependency sweep and a nightly Stripe poll are both schedules.
|
|
@@ -71,7 +71,7 @@ export const AutomationTemplateContributionSchema = z.object({
|
|
|
71
71
|
.boolean()
|
|
72
72
|
.optional()
|
|
73
73
|
.describe(
|
|
74
|
-
"Whether what this makes watches THIS codebase rather than the outside world. Declared rather than read off the trigger
|
|
74
|
+
"Whether what this makes watches THIS codebase rather than the outside world. Declared rather than read off the trigger: a nightly dependency sweep and a nightly Stripe poll are both schedules.",
|
|
75
75
|
),
|
|
76
76
|
});
|
|
77
77
|
export type AutomationTemplateContribution = z.infer<typeof AutomationTemplateContributionSchema>;
|
|
@@ -79,6 +79,6 @@ export type AutomationTemplateContribution = z.infer<typeof AutomationTemplateCo
|
|
|
79
79
|
export const automationTemplatesPoint = {
|
|
80
80
|
name: "automationTemplates",
|
|
81
81
|
description:
|
|
82
|
-
"Starting points this pack offers in the automation composer
|
|
82
|
+
"Starting points this pack offers in the automation composer, a trigger, a prompt written for that trigger's payload, and whatever guard makes it safe to leave on. Declared by whoever knows the service rather than by the composer, so they appear when your pack is installed and disappear with it. Pure prefill: creating one makes an ordinary automation.",
|
|
83
83
|
schema: z.array(AutomationTemplateContributionSchema),
|
|
84
84
|
} as const satisfies ContributionPoint;
|
package/src/points/bin.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import type { ContributionPoint } from "../contribution-point.js";
|
|
3
3
|
|
|
4
|
-
// A checkout-relative directory of executables the daemon prepends to the AGENT's PATH each turn
|
|
4
|
+
// A checkout-relative directory of executables the daemon prepends to the AGENT's PATH each turn, how an
|
|
5
5
|
// extension ships a command-line tool for the agent (the CLI-tools path). The files ARE the approved code (they
|
|
6
6
|
// ride the sha-pinned checkout); the daemon only adds the dir to PATH.
|
|
7
7
|
export const binPoint = {
|
|
8
8
|
name: "bin",
|
|
9
9
|
description:
|
|
10
|
-
"A checkout-relative directory of executables the daemon puts on the agent's PATH every turn
|
|
10
|
+
"A checkout-relative directory of executables the daemon puts on the agent's PATH every turn, how you ship the agent a command-line tool. The files are the approved code themselves: they ride the pinned checkout, and the daemon only adds the directory to PATH.",
|
|
11
11
|
schema: z
|
|
12
12
|
.string()
|
|
13
13
|
.min(1)
|