apps 0.8.3 → 1.0.0-beta.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 +51 -166
- package/dist/authoring/index.d.ts +205 -0
- package/dist/authoring/index.js +26 -0
- package/dist/index-8wmxmfhc.js +90 -0
- package/dist/index-dh4z81es.js +10 -0
- package/dist/index-mcjwntr2.js +13 -0
- package/dist/index-wfngzvv2.js +228 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +26 -0
- package/dist/manifest/collect.d.ts +64 -0
- package/dist/manifest/contract.d.ts +55 -0
- package/dist/manifest/contract.js +8 -0
- package/dist/manifest/index.d.ts +18 -0
- package/dist/manifest/index.js +15 -0
- package/dist/runner/index.d.ts +4 -0
- package/dist/runner/index.js +61 -0
- package/dist/runner/protocol.d.ts +28 -0
- package/dist/runner/protocol.js +6 -0
- package/dist/workerd/index.d.ts +2 -0
- package/dist/workerd/index.js +3 -0
- package/package.json +49 -36
- package/src/authoring/index.ts +340 -0
- package/src/index.ts +9 -0
- package/src/manifest/collect.ts +198 -0
- package/src/manifest/contract.ts +55 -0
- package/src/manifest/index.ts +224 -0
- package/src/runner/index.ts +117 -0
- package/src/runner/protocol.ts +41 -0
- package/src/workerd/index.ts +2 -0
- package/.npmignore +0 -1
- package/bin/apps +0 -36
- package/bin/build +0 -14
- package/bin/doc +0 -16
- package/dist/app.js +0 -9773
- package/dist/app.min.js +0 -5
- package/dist/schema.html +0 -119
- package/dist/schema.md +0 -156
- package/index.html +0 -6
- package/index.js +0 -115
- package/lib/addons.js +0 -67
- package/schema.js +0 -100
- package/templates/app.mustache.html +0 -36
- package/templates/build.mustache.html +0 -13
- package/templates/schema.mustache.html +0 -20
- package/test/addons.js +0 -172
- package/test/cli.js +0 -53
- package/test/fixtures/app.json +0 -32
- package/test/index.js +0 -277
- package/test/mocha.opts +0 -2
package/README.md
CHANGED
|
@@ -1,168 +1,53 @@
|
|
|
1
1
|
# apps
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
Options:
|
|
55
|
-
|
|
56
|
-
-h, --help output usage information
|
|
57
|
-
-V, --version output the version number
|
|
58
|
-
|
|
59
|
-
Use 'app <command> --help' to get more information about a specific command.
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
## Class Methods
|
|
63
|
-
|
|
64
|
-
### App.new(payload)
|
|
65
|
-
|
|
66
|
-
Instantiate with a JSON filename:
|
|
67
|
-
|
|
68
|
-
```js
|
|
69
|
-
var app = App.new(__dirname + "/path/to/app.json")
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
Instantiate with a JSON string:
|
|
73
|
-
|
|
74
|
-
```js
|
|
75
|
-
var json = "{name: \"small-sharp-tool\", description: \"This app does one little thing, and does it well.\"}"
|
|
76
|
-
var app = App.new(json)
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
Instantiate with a JavaScript object:
|
|
80
|
-
|
|
81
|
-
```js
|
|
82
|
-
var app = App.new({
|
|
83
|
-
name: "small-sharp-tool",
|
|
84
|
-
description: "This app does one little thing, and does it well."
|
|
85
|
-
})
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
### App.fetch(url, callback)
|
|
89
|
-
|
|
90
|
-
You can fetch app manifests straight from GitHub. The
|
|
91
|
-
[github-raw-cors-proxy](https://github.com/zeke/github-raw-cors-proxy) service is used
|
|
92
|
-
to make the `app.json` file downloadable from browsers.
|
|
93
|
-
|
|
94
|
-
`url` can be a fully qualified GitHub URL, or a shorthand `user/repo` string:
|
|
95
|
-
|
|
96
|
-
```js
|
|
97
|
-
App.fetch('zeke/harp-slideshow-template', function(err, manifest) {
|
|
98
|
-
console.log(err, manifest)
|
|
99
|
-
})
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
### App.example
|
|
103
|
-
|
|
104
|
-
Generates an example manifest from example content in the [schema](/schema.js).
|
|
105
|
-
|
|
106
|
-
```js
|
|
107
|
-
App.example
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
## Instance Methods
|
|
111
|
-
|
|
112
|
-
### app.valid
|
|
113
|
-
|
|
114
|
-
A getter method that validates the app manifest and returns `true` or `false`
|
|
115
|
-
|
|
116
|
-
### app.errors
|
|
117
|
-
|
|
118
|
-
Returns `null` if app manifest is valid.
|
|
119
|
-
|
|
120
|
-
Returns an array of error objects if invalid:
|
|
121
|
-
|
|
122
|
-
```js
|
|
123
|
-
[
|
|
124
|
-
{property: "name", message: "is required"},
|
|
125
|
-
{property: "website", message: "is not a valid url"}
|
|
126
|
-
]
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
### app.toJSON()
|
|
130
|
-
|
|
131
|
-
Return a pretty JSON string representation of the manifest, without any superfluous properties.
|
|
132
|
-
|
|
133
|
-
### app.getAddonPrices(callback)
|
|
134
|
-
|
|
135
|
-
Fetch pricing data about the app's required addons by hitting the Heroku Platform API.
|
|
136
|
-
|
|
137
|
-
## Tests
|
|
138
|
-
|
|
139
|
-
```
|
|
140
|
-
npm install
|
|
141
|
-
npm test
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
## Bundle
|
|
145
|
-
|
|
146
|
-
To prepare a browser-ready bundle, run the following:
|
|
147
|
-
|
|
148
|
-
```sh
|
|
149
|
-
npm run build
|
|
150
|
-
|
|
151
|
-
# Wrote dist/app.js
|
|
152
|
-
# Wrote dist/app.min.js
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
## Docs
|
|
156
|
-
|
|
157
|
-
Genarate human-friendly docs from the schema
|
|
158
|
-
|
|
159
|
-
```sh
|
|
160
|
-
npm run docs
|
|
161
|
-
|
|
162
|
-
# Wrote dist/schema.md
|
|
163
|
-
# Wrote dist/schema.html
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
## License
|
|
167
|
-
|
|
168
|
-
MIT
|
|
3
|
+
Author executor apps in plain TypeScript: declare the services a tool talks
|
|
4
|
+
to, the auth they need, and the tools themselves. The executor platform loads,
|
|
5
|
+
validates, and serves them; credentials never enter your handler — each
|
|
6
|
+
requirement arrives as a capability that spends them for you.
|
|
7
|
+
|
|
8
|
+
> `1.0.0-beta.x` is a new package under this name, unrelated to `0.x`.
|
|
9
|
+
|
|
10
|
+
```ts
|
|
11
|
+
// app.ts
|
|
12
|
+
import { defineApp, secrets, service } from "apps";
|
|
13
|
+
|
|
14
|
+
export const github = service({
|
|
15
|
+
origin: "api.github.com",
|
|
16
|
+
label: "GitHub",
|
|
17
|
+
auth: {
|
|
18
|
+
pat: secrets({
|
|
19
|
+
label: "Personal access token",
|
|
20
|
+
help: "Create a fine-grained token at github.com/settings/tokens.",
|
|
21
|
+
fields: { token: { label: "Token", secret: true } },
|
|
22
|
+
}),
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
export default defineApp({
|
|
27
|
+
key: "github",
|
|
28
|
+
label: "GitHub",
|
|
29
|
+
description: "Repositories, issues, and pull requests.",
|
|
30
|
+
});
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
// tools/get-repo.ts
|
|
35
|
+
import { defineTool } from "apps";
|
|
36
|
+
import { z } from "zod";
|
|
37
|
+
import { github } from "../app";
|
|
38
|
+
|
|
39
|
+
export default defineTool({
|
|
40
|
+
description: "Fetch a repository",
|
|
41
|
+
input: z.object({ owner: z.string(), repo: z.string() }),
|
|
42
|
+
requirements: { gh: github },
|
|
43
|
+
handler: async ({ owner, repo }, ctx) => {
|
|
44
|
+
const response = await ctx.gh.fetch(`/repos/${owner}/${repo}`);
|
|
45
|
+
return response.json();
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
The root export is pure and synchronous — no effect dependency, no node
|
|
51
|
+
builtins — so authored files run unmodified anywhere, including sandboxed
|
|
52
|
+
workers. Host machinery lives behind subpaths (`apps/manifest`,
|
|
53
|
+
`apps/runner`) and requires `effect`.
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Synchronous authoring primitives imported by app.ts and tools/*.ts.
|
|
3
|
+
* Authored modules record plain claims without I/O or user context; the
|
|
4
|
+
* manifest loader owns execution, validation, and effects.
|
|
5
|
+
*/
|
|
6
|
+
export interface SecretsMethod<F extends Readonly<Record<string, SecretField>> = Readonly<Record<string, SecretField>>> {
|
|
7
|
+
readonly kind: "secrets";
|
|
8
|
+
readonly label: string;
|
|
9
|
+
/** Markdown shown under this auth method on the connect page, for example:
|
|
10
|
+
* "Create a fine-grained token at github.com/settings/tokens." */
|
|
11
|
+
readonly help?: string;
|
|
12
|
+
readonly fields: F;
|
|
13
|
+
}
|
|
14
|
+
export interface SecretField {
|
|
15
|
+
readonly label: string;
|
|
16
|
+
/** Form masking. Stated, never guessed from the field name. */
|
|
17
|
+
readonly secret: boolean;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* An oauth auth method is a secrets-shaped auth method with acquisition strategies
|
|
21
|
+
* layered on: the material contract is always {token, refresh_token?,
|
|
22
|
+
* expires_at?}, and the host fills it by the best strategy it can offer
|
|
23
|
+
* — authorization-code ceremony, device-code ceremony, or paste. Paste
|
|
24
|
+
* is the always-on floor (a pasted token spends identically), so the
|
|
25
|
+
* auth method works on any deployment; ceremony is progressive enhancement.
|
|
26
|
+
* Handlers cannot tell how material arrived.
|
|
27
|
+
*/
|
|
28
|
+
export interface OAuthMethod {
|
|
29
|
+
readonly kind: "oauth";
|
|
30
|
+
readonly label: string;
|
|
31
|
+
/** Markdown shown under this auth method on the connect page, for example:
|
|
32
|
+
* "Create a fine-grained token at github.com/settings/tokens." */
|
|
33
|
+
readonly help?: string;
|
|
34
|
+
/** Endpoints only when the service publishes no well-knowns —
|
|
35
|
+
* discovery (RFC 9728 + AS metadata) outranks these when present.
|
|
36
|
+
* Authors state facts about the service, never about the client. */
|
|
37
|
+
readonly authorizationEndpoint?: string;
|
|
38
|
+
readonly tokenEndpoint?: string;
|
|
39
|
+
readonly scopes?: readonly string[];
|
|
40
|
+
/** Set false only when a user-pasted token genuinely cannot work
|
|
41
|
+
* (audience-bound tokens, DPoP). Stated, never inferred. */
|
|
42
|
+
readonly paste?: false;
|
|
43
|
+
}
|
|
44
|
+
export type AuthMethod = SecretsMethod | OAuthMethod;
|
|
45
|
+
export declare const secrets: <const F extends Readonly<Record<string, SecretField>>>(opts: {
|
|
46
|
+
readonly label: string;
|
|
47
|
+
/** Markdown shown under this auth method on the connect page, for example:
|
|
48
|
+
* "Create a fine-grained token at github.com/settings/tokens." */
|
|
49
|
+
readonly help?: string;
|
|
50
|
+
readonly fields: F;
|
|
51
|
+
}) => SecretsMethod<F>;
|
|
52
|
+
export declare const oauth: (opts: {
|
|
53
|
+
readonly label: string;
|
|
54
|
+
/** Markdown shown under this auth method on the connect page, for example:
|
|
55
|
+
* "Create a fine-grained token at github.com/settings/tokens." */
|
|
56
|
+
readonly help?: string;
|
|
57
|
+
readonly authorizationEndpoint?: string;
|
|
58
|
+
readonly tokenEndpoint?: string;
|
|
59
|
+
readonly scopes?: readonly string[];
|
|
60
|
+
readonly paste?: false;
|
|
61
|
+
}) => OAuthMethod;
|
|
62
|
+
export interface ServiceDecl {
|
|
63
|
+
/** Canonical origin, possibly templated: "https://{region}.posthog.com". */
|
|
64
|
+
readonly origin: string;
|
|
65
|
+
/** Hosts beyond the origin the credential legitimately travels to. */
|
|
66
|
+
readonly reach?: readonly string[];
|
|
67
|
+
/** Connection-scoped non-secret connect-form values; keys may fill
|
|
68
|
+
* origin templates. */
|
|
69
|
+
readonly params?: Readonly<Record<string, ParamSpec>>;
|
|
70
|
+
/** Named auth methods. Key = the grant name tools switch on. */
|
|
71
|
+
readonly auth?: Readonly<Record<string, AuthMethod>>;
|
|
72
|
+
}
|
|
73
|
+
export interface ParamSpec {
|
|
74
|
+
readonly label: string;
|
|
75
|
+
readonly options?: readonly string[];
|
|
76
|
+
}
|
|
77
|
+
/** What the manifest pipeline reads off a requirement value. */
|
|
78
|
+
export interface RequirementSpec {
|
|
79
|
+
readonly service: ServiceDecl;
|
|
80
|
+
readonly arity: "one" | "optional" | "many";
|
|
81
|
+
readonly grant?: string;
|
|
82
|
+
readonly description?: string;
|
|
83
|
+
}
|
|
84
|
+
declare const RequirementKey: unique symbol;
|
|
85
|
+
/**
|
|
86
|
+
* A requirement value in a tool's `requirements` record. A Service IS
|
|
87
|
+
* a requirement (bare value = arity "one"); .optional()/.array()
|
|
88
|
+
* return terminal Requirements so nonsense chains do not typecheck.
|
|
89
|
+
*/
|
|
90
|
+
export interface Requirement<D extends ServiceDecl = ServiceDecl> {
|
|
91
|
+
readonly [RequirementKey]: RequirementSpec;
|
|
92
|
+
describe(text: string): Requirement<D>;
|
|
93
|
+
}
|
|
94
|
+
export interface Service<D extends ServiceDecl = ServiceDecl> extends Requirement<D> {
|
|
95
|
+
readonly decl: D;
|
|
96
|
+
optional(): Requirement<D>;
|
|
97
|
+
array(): Requirement<D>;
|
|
98
|
+
/** Pin one auth method: this tool supports only the named grant. */
|
|
99
|
+
grant(name: keyof NonNullable<D["auth"]> & string): Service<D>;
|
|
100
|
+
describe(text: string): Service<D>;
|
|
101
|
+
}
|
|
102
|
+
/** A publish-time authoring problem accumulated without interrupting defines. */
|
|
103
|
+
export interface Problem {
|
|
104
|
+
readonly message: string;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* A dynamic tool-set claim: tools discovered from a live surface (an
|
|
108
|
+
* MCP server's tool list, an OpenAPI document) rather than authored one
|
|
109
|
+
* file at a time. The claim is recorded at publish time; discovery runs
|
|
110
|
+
* later, per connection — an app with zero static tools and one claim
|
|
111
|
+
* saves green, and listing never blocks on an upstream.
|
|
112
|
+
*/
|
|
113
|
+
export type DynamicClaimSpec = {
|
|
114
|
+
readonly kind: "mcp";
|
|
115
|
+
readonly requirement: RequirementSpec;
|
|
116
|
+
/** MCP endpoint URL. Absent = the service origin at its root path. */
|
|
117
|
+
readonly endpoint?: string;
|
|
118
|
+
} | {
|
|
119
|
+
readonly kind: "openapi";
|
|
120
|
+
readonly requirement: RequirementSpec;
|
|
121
|
+
/** OpenAPI document: an absolute URL or an app-relative path. */
|
|
122
|
+
readonly spec: string;
|
|
123
|
+
};
|
|
124
|
+
declare const DynamicKey: unique symbol;
|
|
125
|
+
/** The value fromMCP/fromOpenAPI return: an opaque claim handle a tool
|
|
126
|
+
* file default-exports. The spec is undefined when the claim was
|
|
127
|
+
* invalid (a problem was recorded instead). */
|
|
128
|
+
export interface DynamicToolSet {
|
|
129
|
+
readonly [DynamicKey]: DynamicClaimSpec | undefined;
|
|
130
|
+
}
|
|
131
|
+
/** A registered dynamic claim and its source attribution. @internal */
|
|
132
|
+
export interface RegisteredDynamic {
|
|
133
|
+
readonly spec: DynamicClaimSpec;
|
|
134
|
+
readonly file: string;
|
|
135
|
+
}
|
|
136
|
+
/** A registered service declaration and its source attribution. @internal */
|
|
137
|
+
export interface RegisteredService {
|
|
138
|
+
readonly decl: ServiceDecl;
|
|
139
|
+
readonly file: string;
|
|
140
|
+
}
|
|
141
|
+
/** A registered tool declaration and its source attribution. @internal */
|
|
142
|
+
export interface RegisteredTool {
|
|
143
|
+
readonly spec: ToolSpec;
|
|
144
|
+
readonly file: string;
|
|
145
|
+
}
|
|
146
|
+
/** Mutable claim registry populated while authored modules execute. @internal */
|
|
147
|
+
export interface Registry {
|
|
148
|
+
app: AppManifest | undefined;
|
|
149
|
+
readonly services: Map<string, RegisteredService>;
|
|
150
|
+
readonly tools: Map<string, RegisteredTool>;
|
|
151
|
+
readonly dynamic: Map<string, RegisteredDynamic>;
|
|
152
|
+
readonly problems: Problem[];
|
|
153
|
+
readonly toolFiles: Map<string, {
|
|
154
|
+
definitions: number;
|
|
155
|
+
hasUnnamedTool: boolean;
|
|
156
|
+
}>;
|
|
157
|
+
}
|
|
158
|
+
/** Creates an isolated empty authoring registry. @internal */
|
|
159
|
+
export declare const newRegistry: () => Registry;
|
|
160
|
+
/** Swaps the registry receiving subsequent authoring claims. @internal */
|
|
161
|
+
export declare const setActiveRegistry: (registry: Registry) => Registry;
|
|
162
|
+
/** Sets source-file attribution for subsequent authoring claims. @internal */
|
|
163
|
+
export declare const __enterFile: (path: string) => void;
|
|
164
|
+
/** Clears source-file attribution after an authored module import. @internal */
|
|
165
|
+
export declare const __leaveFile: () => void;
|
|
166
|
+
export declare const service: <const D extends ServiceDecl>(decl: D) => Service<D>;
|
|
167
|
+
export declare const requirementSpecOf: (value: unknown) => RequirementSpec | undefined;
|
|
168
|
+
export interface AppManifest {
|
|
169
|
+
readonly kind: "executor-app";
|
|
170
|
+
readonly key: string;
|
|
171
|
+
readonly label: string;
|
|
172
|
+
readonly description: string;
|
|
173
|
+
}
|
|
174
|
+
export declare const defineApp: (manifest: {
|
|
175
|
+
readonly key: string;
|
|
176
|
+
readonly label: string;
|
|
177
|
+
readonly description: string;
|
|
178
|
+
}) => AppManifest;
|
|
179
|
+
/** Any Standard Schema (zod preferred) or a raw JSON-schema object. */
|
|
180
|
+
export type SchemaLike = object;
|
|
181
|
+
export interface ToolAnnotations {
|
|
182
|
+
readonly readOnly?: boolean;
|
|
183
|
+
readonly destructive?: boolean;
|
|
184
|
+
/** Facts, not flow: shortfall surfaces as the scope challenge. */
|
|
185
|
+
readonly scopes?: readonly string[];
|
|
186
|
+
}
|
|
187
|
+
export interface ToolSpec {
|
|
188
|
+
/** Address segment. Single-tool files may omit it (filename). */
|
|
189
|
+
readonly name?: string;
|
|
190
|
+
readonly description: string;
|
|
191
|
+
readonly requirements?: Readonly<Record<string, Requirement>>;
|
|
192
|
+
readonly input: SchemaLike;
|
|
193
|
+
readonly output?: SchemaLike;
|
|
194
|
+
readonly annotations?: ToolAnnotations;
|
|
195
|
+
handler(input: never, ctx: never): Promise<unknown>;
|
|
196
|
+
}
|
|
197
|
+
declare const ToolKey: unique symbol;
|
|
198
|
+
export interface Tool {
|
|
199
|
+
readonly [ToolKey]: ToolSpec;
|
|
200
|
+
}
|
|
201
|
+
export declare const defineTool: (spec: ToolSpec) => Tool;
|
|
202
|
+
export declare const toolSpecOf: (value: unknown) => ToolSpec | undefined;
|
|
203
|
+
/** A tool file's default export. */
|
|
204
|
+
export type ToolExport = Tool | ReadonlyArray<Tool>;
|
|
205
|
+
export {};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import {
|
|
2
|
+
__enterFile,
|
|
3
|
+
__leaveFile,
|
|
4
|
+
defineApp,
|
|
5
|
+
defineTool,
|
|
6
|
+
newRegistry,
|
|
7
|
+
oauth,
|
|
8
|
+
requirementSpecOf,
|
|
9
|
+
secrets,
|
|
10
|
+
service,
|
|
11
|
+
setActiveRegistry,
|
|
12
|
+
toolSpecOf
|
|
13
|
+
} from "../index-8wmxmfhc.js";
|
|
14
|
+
export {
|
|
15
|
+
toolSpecOf,
|
|
16
|
+
setActiveRegistry,
|
|
17
|
+
service,
|
|
18
|
+
secrets,
|
|
19
|
+
requirementSpecOf,
|
|
20
|
+
oauth,
|
|
21
|
+
newRegistry,
|
|
22
|
+
defineTool,
|
|
23
|
+
defineApp,
|
|
24
|
+
__leaveFile,
|
|
25
|
+
__enterFile
|
|
26
|
+
};
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// src/authoring/index.ts
|
|
2
|
+
var secrets = (opts) => ({ kind: "secrets", ...opts });
|
|
3
|
+
var oauth = (opts) => ({ kind: "oauth", ...opts });
|
|
4
|
+
var RequirementKey = Symbol.for("executor/apps/requirement");
|
|
5
|
+
var DynamicKey = Symbol.for("executor/apps/dynamic");
|
|
6
|
+
var newRegistry = () => ({
|
|
7
|
+
app: undefined,
|
|
8
|
+
services: new Map,
|
|
9
|
+
tools: new Map,
|
|
10
|
+
dynamic: new Map,
|
|
11
|
+
problems: [],
|
|
12
|
+
toolFiles: new Map
|
|
13
|
+
});
|
|
14
|
+
var activeRegistry = newRegistry();
|
|
15
|
+
var currentFile;
|
|
16
|
+
var setActiveRegistry = (registry) => {
|
|
17
|
+
const previous = activeRegistry;
|
|
18
|
+
activeRegistry = registry;
|
|
19
|
+
return previous;
|
|
20
|
+
};
|
|
21
|
+
var __enterFile = (path) => {
|
|
22
|
+
currentFile = path;
|
|
23
|
+
};
|
|
24
|
+
var __leaveFile = () => {
|
|
25
|
+
currentFile = undefined;
|
|
26
|
+
};
|
|
27
|
+
var sourceFileFor = (claim) => {
|
|
28
|
+
if (currentFile !== undefined)
|
|
29
|
+
return currentFile;
|
|
30
|
+
activeRegistry.problems.push({
|
|
31
|
+
message: `${claim} called outside an authored module import`
|
|
32
|
+
});
|
|
33
|
+
return "<unattributed>";
|
|
34
|
+
};
|
|
35
|
+
var makeTerminal = (spec) => ({
|
|
36
|
+
[RequirementKey]: spec,
|
|
37
|
+
describe: (text) => makeTerminal({ ...spec, description: text })
|
|
38
|
+
});
|
|
39
|
+
var service = (decl) => {
|
|
40
|
+
const file = sourceFileFor("service(...)");
|
|
41
|
+
const make = (spec) => ({
|
|
42
|
+
[RequirementKey]: spec,
|
|
43
|
+
decl,
|
|
44
|
+
optional: () => makeTerminal({ ...spec, arity: "optional" }),
|
|
45
|
+
array: () => makeTerminal({ ...spec, arity: "many" }),
|
|
46
|
+
grant: (name) => make({ ...spec, grant: name }),
|
|
47
|
+
describe: (text) => make({ ...spec, description: text })
|
|
48
|
+
});
|
|
49
|
+
const value = make({ service: decl, arity: "one" });
|
|
50
|
+
if (!activeRegistry.services.has(decl.origin))
|
|
51
|
+
activeRegistry.services.set(decl.origin, { decl, file });
|
|
52
|
+
return value;
|
|
53
|
+
};
|
|
54
|
+
var isRequirement = (value) => typeof value === "object" && value !== null && (RequirementKey in value);
|
|
55
|
+
var isService = (value) => isRequirement(value) && ("decl" in value) && ("optional" in value) && ("array" in value) && ("grant" in value);
|
|
56
|
+
var requirementSpecOf = (value) => isService(value) || isRequirement(value) ? value[RequirementKey] : undefined;
|
|
57
|
+
var defineApp = (manifest) => {
|
|
58
|
+
const file = sourceFileFor("defineApp(...)");
|
|
59
|
+
const value = { kind: "executor-app", ...manifest };
|
|
60
|
+
if (activeRegistry.app === undefined)
|
|
61
|
+
activeRegistry.app = value;
|
|
62
|
+
else
|
|
63
|
+
activeRegistry.problems.push({ message: `${file}: duplicate defineApp(...)` });
|
|
64
|
+
return value;
|
|
65
|
+
};
|
|
66
|
+
var ToolKey = Symbol.for("executor/apps/tool");
|
|
67
|
+
var defineTool = (spec) => {
|
|
68
|
+
const file = sourceFileFor("defineTool(...)");
|
|
69
|
+
const value = { [ToolKey]: spec };
|
|
70
|
+
const fileName = file.slice(file.lastIndexOf("/") + 1);
|
|
71
|
+
const stem = fileName.endsWith(".ts") ? fileName.slice(0, -3) : fileName;
|
|
72
|
+
const name = spec.name ?? stem;
|
|
73
|
+
const fileSummary = activeRegistry.toolFiles.get(file);
|
|
74
|
+
activeRegistry.toolFiles.set(file, {
|
|
75
|
+
definitions: (fileSummary?.definitions ?? 0) + 1,
|
|
76
|
+
hasUnnamedTool: fileSummary?.hasUnnamedTool === true || spec.name === undefined
|
|
77
|
+
});
|
|
78
|
+
const existing = activeRegistry.tools.get(name);
|
|
79
|
+
if (existing !== undefined) {
|
|
80
|
+
activeRegistry.problems.push({
|
|
81
|
+
message: `${file}: tool name '${name}' is already defined in ${existing.file}`
|
|
82
|
+
});
|
|
83
|
+
} else
|
|
84
|
+
activeRegistry.tools.set(name, { spec, file });
|
|
85
|
+
return value;
|
|
86
|
+
};
|
|
87
|
+
var isTool = (value) => typeof value === "object" && value !== null && (ToolKey in value);
|
|
88
|
+
var toolSpecOf = (value) => isTool(value) ? value[ToolKey] : undefined;
|
|
89
|
+
|
|
90
|
+
export { secrets, oauth, newRegistry, setActiveRegistry, __enterFile, __leaveFile, service, requirementSpecOf, defineApp, defineTool, toolSpecOf };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// src/manifest/contract.ts
|
|
2
|
+
import { Data, Schema } from "effect";
|
|
3
|
+
var JsonSchemaObject = Schema.Record(Schema.String, Schema.Json);
|
|
4
|
+
|
|
5
|
+
class PublishError extends Data.TaggedError("PublishError") {
|
|
6
|
+
get message() {
|
|
7
|
+
return `publish failed:
|
|
8
|
+
${this.problems.map((problem) => ` - ${problem}`).join(`
|
|
9
|
+
`)}`;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export { JsonSchemaObject, PublishError };
|