@asterism-app/sdk 0.0.0-stage → 0.47.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/LICENSE +9 -0
- package/README.md +20 -2
- package/docs/data-and-lifecycle.md +77 -0
- package/docs/getting-started.md +37 -0
- package/docs/manual.md +34 -0
- package/docs/minimal-plugin.md +33 -0
- package/docs/project-structure.md +27 -0
- package/docs/resources-and-tools.md +29 -0
- package/docs/views-and-settings.md +35 -0
- package/examples/author.ts +158 -0
- package/examples/consume-usage.ts +29 -0
- package/examples/content-author.ts +29 -0
- package/examples/content-contract.ts +10 -0
- package/examples/content-renderer.ts +26 -0
- package/examples/counter/docs/usage.md +9 -0
- package/examples/counter/package.json +27 -0
- package/examples/counter/src/contracts.ts +31 -0
- package/examples/counter/src/panel.module.css +2 -0
- package/examples/counter/src/renderer.tsx +69 -0
- package/examples/counter/src/runtime.ts +43 -0
- package/examples/counter/src/storage.ts +4 -0
- package/examples/counter/src/styles.d.ts +4 -0
- package/examples/counter/tsconfig.json +16 -0
- package/examples/counter-contract.ts +18 -0
- package/examples/data-codecs.ts +18 -0
- package/examples/events.ts +37 -0
- package/examples/i18n.ts +18 -0
- package/examples/model-completion.ts +15 -0
- package/examples/model-input.ts +16 -0
- package/examples/plugin-calls.ts +22 -0
- package/examples/provider-usage-headers.ts +20 -0
- package/examples/provider-usage.ts +34 -0
- package/examples/renderer.ts +47 -0
- package/examples/session-resources.ts +29 -0
- package/examples/settings-contract.ts +8 -0
- package/examples/settings-renderer.ts +30 -0
- package/examples/settings-runtime.ts +11 -0
- package/examples/storage.ts +26 -0
- package/examples/tool-renderer.ts +47 -0
- package/examples/web-provider.ts +27 -0
- package/package.json +39 -4
- package/src/calls.ts +27 -0
- package/src/card.ts +7 -0
- package/src/channels.ts +20 -0
- package/src/commands.ts +28 -0
- package/src/config.ts +25 -0
- package/src/content-runtime.ts +11 -0
- package/src/content.ts +6 -0
- package/src/contracts.ts +29 -0
- package/src/data-encoding.ts +14 -0
- package/src/error-details.ts +78 -0
- package/src/events.ts +91 -0
- package/src/i18n.ts +58 -0
- package/src/json.ts +4 -0
- package/src/model-cost-estimate.ts +49 -0
- package/src/model-cost.ts +44 -0
- package/src/model-input.ts +19 -0
- package/src/model-media.ts +11 -0
- package/src/model-records.ts +56 -0
- package/src/model-selection.ts +34 -0
- package/src/models.ts +137 -0
- package/src/module-contract.ts +44 -0
- package/src/objects.ts +89 -0
- package/src/plugin.ts +58 -0
- package/src/presentation.ts +21 -0
- package/src/provider-usage.ts +87 -0
- package/src/records.ts +60 -0
- package/src/renderer-calls.ts +11 -0
- package/src/renderer-card.ts +29 -0
- package/src/renderer-content.ts +9 -0
- package/src/renderer-creation.ts +83 -0
- package/src/renderer-i18n.ts +49 -0
- package/src/renderer-island.ts +66 -0
- package/src/renderer-records.ts +49 -0
- package/src/renderer-services.ts +75 -0
- package/src/renderer-tools.ts +103 -0
- package/src/renderer-view-state.ts +33 -0
- package/src/renderer.ts +158 -0
- package/src/runtime-calls.ts +20 -0
- package/src/runtime-channels.ts +24 -0
- package/src/runtime-contracts.ts +188 -0
- package/src/runtime-providers.ts +60 -0
- package/src/runtime-resources.ts +117 -0
- package/src/runtime-tools.ts +31 -0
- package/src/runtime-web.ts +36 -0
- package/src/runtime.ts +44 -0
- package/src/tool-result.ts +45 -0
- package/src/tool-validation.ts +14 -0
- package/src/web.ts +75 -0
- package/src/workspace.ts +43 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Chien Zhang
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
6
|
+
|
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,21 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Asterism SDK
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The SDK defines the shared language between Asterism and plugins: contracts, host capabilities, and contributions.
|
|
4
|
+
|
|
5
|
+
Pure declarations can be shared across execution environments. Backend interfaces support work and persistence; frontend interfaces support presentation and interaction.
|
|
6
|
+
|
|
7
|
+
The SDK describes generic mechanisms. Agent conversation, naming, and other domain behavior belong to the plugin that implements them.
|
|
8
|
+
|
|
9
|
+
Public interfaces should express a capability an author needs. Internal decomposition is not itself a reason to expose another abstraction.
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
Install the testing release with `npm install --save-dev --save-exact @asterism-app/sdk@next`. Declare `@asterism-app/sdk` as a peer dependency with `"*"` in the plugin package.
|
|
14
|
+
|
|
15
|
+
The package ships TypeScript source. Asterism compiles plugin entries and supplies its own SDK instance at runtime. Use declarations compatible with the target application; installing a newer SDK does not update the application's APIs.
|
|
16
|
+
|
|
17
|
+
## Authoring material
|
|
18
|
+
|
|
19
|
+
The source package includes `docs/` for plugin development guides and `examples/` for public API usage. `examples/counter/` is a complete starter package; the other examples are focused fragments to compose into a plugin. The application's manual exposes these directories directly.
|
|
20
|
+
|
|
21
|
+
Examples participate in repository type checking. Internal compile-time counterexamples live in `typechecks/` and are excluded from package contents and the manual. Built-in domain guides and examples belong to `@asterism-app/builtin-api`.
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Manage data and lifecycle
|
|
2
|
+
|
|
3
|
+
Choose storage from the meaning of the data. Latest values, historical facts, immutable bytes, and running processes have different lifetimes.
|
|
4
|
+
|
|
5
|
+
## Separate current state from history
|
|
6
|
+
|
|
7
|
+
Current data records the latest session or object value. Historical records preserve facts needed to understand or reconstruct past work.
|
|
8
|
+
|
|
9
|
+
Saving a current value does not automatically create history. Moving to an earlier history position does not restore that value. A resource that supports restoration defines the facts and operations that make restoration meaningful.
|
|
10
|
+
|
|
11
|
+
Plugin-wide private data belongs to the plugin. Temporary interface choices belong to a view or an object-retained view scope, according to how long they should survive.
|
|
12
|
+
|
|
13
|
+
## Encode data at boundaries
|
|
14
|
+
|
|
15
|
+
Data schemas describe the values used by plugin code and how they are decoded from stored or transported JSON. Writers, resource creation, tool results, and presentations accept the decoded value and encode it before storage or publication. Typed readers and views decode the saved value.
|
|
16
|
+
|
|
17
|
+
Use an ordinary Zod schema when both representations are the same. Use a synchronous `z.codec` when they differ. Both the domain value and its encoding remain JSON-compatible. One-way `.transform()` belongs in input preparation or an explicit conversion before writing; Zod cannot encode it in reverse.
|
|
18
|
+
|
|
19
|
+
Resource creation returns a card title and plugin-owned `data`. State can be any JSON value, including arrays, scalars, or `null`; object fields such as `id`, `type`, and `title` retain their business meaning. The host stores resource identity and card title separately. Renaming a card changes its host title, and creating resources from a template gives them new host identities while preserving the encoded plugin state.
|
|
20
|
+
|
|
21
|
+
Configuration defaults are complete decoded values. Reading an older configuration may fill absent fields through ordinary `.default()` declarations. Configuration updates receive decoded values, and saving an unchanged value preserves a correctly defined codec's domain value. The host handles encoding of configuration files and frontend responses.
|
|
22
|
+
|
|
23
|
+
Codecs own their conversion semantics. Verify meaningful round trips when supplying custom encoders, decoders, or normalization callbacks. Tool results reserve `null` for absent output, so a non-null tool value needs a non-null encoding.
|
|
24
|
+
|
|
25
|
+
## Reuse resources in templates
|
|
26
|
+
|
|
27
|
+
A resource opts into data-copy templates with `template: { current: [Settings, Content] }` in its implementation. The list contains existing data contracts owned by the resource's plugin. An empty list copies only the resource's creation state. Resources without this declaration remain available for ordinary use and do not appear in the template picker.
|
|
28
|
+
|
|
29
|
+
Templates retain encoded creation state and the listed current values that exist at save time. Unlisted current data, history, private files, and running instances are excluded. Instantiation creates new host identities and remaps host links; it preserves domain values and content references without rewriting embedded resource IDs or duplicating external services. Creation callbacks are not rerun, and ordinary presence lifetimes apply to the new objects.
|
|
30
|
+
|
|
31
|
+
Opt in when those copied values are reusable under new identities. For example, a note can retain its body, and an agent can retain its configuration while leaving submission receipts and drafts behind. Resources needing external cloning, reference mapping, or private-state reconstruction need an explicit domain operation. Historical observation does not perform restoration.
|
|
32
|
+
|
|
33
|
+
The installed resource must still support templates and decode all saved state and selected current data when the template is used. Unsupported or invalid saved entries fail explicitly before a new session is created.
|
|
34
|
+
|
|
35
|
+
## Commit and observe
|
|
36
|
+
|
|
37
|
+
Use the host's bound read and execution capabilities. Complete network and file processing outside synchronous transactions, then commit the resulting state.
|
|
38
|
+
|
|
39
|
+
A historical snapshot fixes what is observed. A later write acts in the current execution context. Holding a snapshot does not redirect later writes into the past.
|
|
40
|
+
|
|
41
|
+
Transient events announce change. A consumer needing current state establishes its subscription and reads the current value; later notifications prompt another read.
|
|
42
|
+
|
|
43
|
+
Typed calls describe request and response values with ordinary Zod schemas. The caller supplies the input schema's input; the implementation receives its parsed output, including defaults. The implementation returns JSON accepted by the output schema, and the caller receives that schema's parsed output. These types are inferred from the shared declaration, so a conversion such as string to number does not require separate type annotations.
|
|
44
|
+
|
|
45
|
+
## Retain file content
|
|
46
|
+
|
|
47
|
+
Stage immutable bytes through the content service, then store their reference in persistent data or a record that owns them.
|
|
48
|
+
|
|
49
|
+
A reference identifies content. Copying its identifier does not by itself grant access or retain the bytes. Pass it through the supported operation scope so ownership remains explicit.
|
|
50
|
+
|
|
51
|
+
Retention follows references in the JSON actually saved. Preserve content references in that encoding when their bytes must remain available; synthesizing a reference during decoding does not retain content or grant access.
|
|
52
|
+
|
|
53
|
+
## End asynchronous work
|
|
54
|
+
|
|
55
|
+
Bind shared services to the plugin lifetime and object-owned instances to the resource lifetime. Register cleanup when creating the owned work.
|
|
56
|
+
|
|
57
|
+
Cancellation requests that work stop. Wait for actual completion before releasing what that work still uses.
|
|
58
|
+
|
|
59
|
+
Timeline-dependent operations need the host's activity tracking so history changes and object removal can respect active work.
|
|
60
|
+
|
|
61
|
+
## Evolve stored data
|
|
62
|
+
|
|
63
|
+
Persistent file formats need explicit preparation when they change. Keep that preparation independent of ordinary service startup.
|
|
64
|
+
|
|
65
|
+
Historical records retain their original payloads. Supporting an older shape through decoding is different from rewriting the original historical fact.
|
|
66
|
+
|
|
67
|
+
A codec can accept old and current payload shapes while encoding new writes in the current shape:
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
const current = z.strictObject({ text: z.string() });
|
|
71
|
+
const textSchema = z.codec(z.union([z.string(), current]), current, {
|
|
72
|
+
decode: value => typeof value === "string" ? { text: value } : value,
|
|
73
|
+
encode: value => value,
|
|
74
|
+
});
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
This schema reads an old string as a current object. Plugin code writes the object; historical reads leave the original string untouched.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Prepare for plugin development
|
|
2
|
+
|
|
3
|
+
A plugin adds resources, tools, views, settings, or services through Asterism's public software development kit (SDK). Development includes preparing a compatible package, verifying its capabilities and lifecycle in the application, and checking the packaged result before sharing it.
|
|
4
|
+
|
|
5
|
+
## Prepare a project
|
|
6
|
+
|
|
7
|
+
Use a TypeScript package and the public SDK compatible with the application you intend to run. A package declaration identifies the plugin and its backend, frontend, and optional storage entries.
|
|
8
|
+
|
|
9
|
+
Set the plugin's release version in `package.json.version`. That version identifies distributed releases and updates; it does not affect loading or execution. The runtime definition declares its namespace and behavior. Keep ordinary runtime libraries in `dependencies`; npm or another package manager installs them using its usual dependency and lockfile rules.
|
|
10
|
+
|
|
11
|
+
Declare host-provided packages such as `@asterism-app/sdk` in `peerDependencies` with `"*"`. The running plugin always uses the host's instance, so a narrower range adds maintenance without changing behavior. Install a version of the SDK compatible with the target application in `devDependencies` for editor support and type checking. Extensions to built-in features can use the same arrangement for `@asterism-app/builtin-api`.
|
|
12
|
+
|
|
13
|
+
The npm `next` tag identifies the current testing release. Install it with `npm install --save-dev --save-exact @asterism-app/sdk@next`; add `@asterism-app/builtin-api@next` when using built-in domain APIs. Saving an exact development version keeps the declarations stable until an intentional update. The packages include TypeScript source, guides, and examples. Installing a newer development package does not update the APIs provided by the running application.
|
|
14
|
+
|
|
15
|
+
Asterism binds shared public imports to the active host or plugin provider. Dependency ranges do not reject loading; missing modules, missing exports, invalid contributions, and initialization errors produce loading failures. Check the APIs your plugin actually uses with TypeScript and exercise them in the target application.
|
|
16
|
+
|
|
17
|
+
The `asterism` field declares a namespaced `id` and the needed `runtime`, `renderer`, and `storage` entry paths, relative to the package root. Runtime and renderer entries default-export their SDK plugin definitions. The runtime namespace must match the manifest ID.
|
|
18
|
+
|
|
19
|
+
Keep shared declarations separate from executable backend and frontend code. Backend work can use local system capabilities; frontend code works through the host's view and communication facilities.
|
|
20
|
+
|
|
21
|
+
Start with one capability that demonstrates the plugin's purpose. Add further parts when that capability needs them rather than creating an empty framework in advance.
|
|
22
|
+
|
|
23
|
+
## Load and verify
|
|
24
|
+
|
|
25
|
+
Prepare the package and its dependencies, run its type checks, then add its local package directory in Asterism's plugin settings. Restart to load changes.
|
|
26
|
+
|
|
27
|
+
The host compiles TypeScript, TSX, and imported CSS at startup. A separate JavaScript build is optional. Generate CSS with the plugin's own toolchain first when using Tailwind, Sass, or another preprocessor. Source changes take effect after restart.
|
|
28
|
+
|
|
29
|
+
Check the reported loading state and exercise the capability. Verify its normal result, meaningful errors, and cleanup when an object disappears or the application closes.
|
|
30
|
+
|
|
31
|
+
Type checking establishes interface consistency. Actual loading and use establish whether the package works in the intended host.
|
|
32
|
+
|
|
33
|
+
## Prepare for sharing
|
|
34
|
+
|
|
35
|
+
Include the entries, assets, and dependencies the package needs. Preserve imports of shared public contracts so the host and consumers use the same declarations.
|
|
36
|
+
|
|
37
|
+
Test the resulting package in an installed application. Development files available on the author's machine should not be necessary to use the distributed plugin.
|
package/docs/manual.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Expose plugin documentation
|
|
2
|
+
|
|
3
|
+
A plugin can make its documentation and examples available through Asterism's Manual resource. The manual exposes the declared directories directly. The same files remain usable in an editor or another development environment.
|
|
4
|
+
|
|
5
|
+
## Declare directories
|
|
6
|
+
|
|
7
|
+
Add a `manual` array to the plugin's existing `asterism` manifest:
|
|
8
|
+
|
|
9
|
+
```json
|
|
10
|
+
{
|
|
11
|
+
"asterism": {
|
|
12
|
+
"id": "example.bridge",
|
|
13
|
+
"manual": ["docs", "examples"]
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Each entry names an existing package-relative subdirectory using `/` separators. Entries must be normalized, must not overlap, and must resolve inside the package. Include those directories in the package's distributed files. A documentation declaration does not require a runtime entry.
|
|
19
|
+
|
|
20
|
+
Declare only directories intended for readers. Source examples can be exposed alongside prose; internal test fixtures and credentials belong outside the declared directories.
|
|
21
|
+
|
|
22
|
+
## Read through the manual
|
|
23
|
+
|
|
24
|
+
When the plugin loads successfully, its directories appear under `plugins/<plugin-id>/`. For this example, an article at `docs/usage.md` appears as `plugins/example.bridge/docs/usage.md`. The manual supports directory listing, file discovery, content search, and reading selected lines.
|
|
25
|
+
|
|
26
|
+
The manual reads current files at their original locations. It creates no copied document tree or index. Source declarations follow the loaded package set: changes to declarations, enabling, disabling, or removing a plugin take effect after restart. Plugins with package, startup, or storage failures do not contribute documents.
|
|
27
|
+
|
|
28
|
+
A search at a virtual parent covers the directories below it. Globs apply within each selected physical search directory. Results include the complete virtual path, so identical filenames in different plugins remain distinguishable. Reading a file or following an explicitly selected symbolic link remains within the declared directory.
|
|
29
|
+
|
|
30
|
+
## Write independent articles
|
|
31
|
+
|
|
32
|
+
Each article should explain its purpose, relevant concepts, prerequisites, and working method without depending on a reading sequence. Keep the context needed to understand the topic in the article itself. Use examples to clarify operations and leave complete interface definitions to the public types.
|
|
33
|
+
|
|
34
|
+
Maintain documentation with the domain that owns its meaning. Generic host capabilities belong to the SDK; a plugin's resource behavior and domain extensions belong to that plugin or its public API package. Documentation is reference material and does not become a system instruction merely by appearing in the manual.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Build a small plugin
|
|
2
|
+
|
|
3
|
+
A counter plugin demonstrates the complete path from shared declarations to backend behavior and a resource card. The SDK includes it as an ordinary package directory under `examples/counter`, with source files, a package manifest, and a TypeScript configuration. Its code is the maintained example; this guide explains how to use and adapt it.
|
|
4
|
+
|
|
5
|
+
## Prepare the package
|
|
6
|
+
|
|
7
|
+
Copy the counter directory to a writable development location. Its development dependency selects the SDK version used by the example. Use an Asterism application that provides those APIs. The SDK peer dependency describes a host-provided package, and the host does not use its version range to gate loading. React, React Intl, and Zod are also shared public dependencies supplied by the host when the plugin runs.
|
|
8
|
+
|
|
9
|
+
Run `npm install` and `npm run typecheck` in the copied directory. The host compiles the TypeScript, TSX, and imported CSS entries, so a separate JavaScript build is optional. Add the package directory in Asterism's plugin settings and restart the application to load it.
|
|
10
|
+
|
|
11
|
+
## Follow the data
|
|
12
|
+
|
|
13
|
+
Shared declarations identify the counter, its private current value, projected view, change record, creation entry, increment action, read tool, settings, and typed call. Backend and frontend code import the same declarations. Copying a declaration under the same name does not establish shared runtime identity.
|
|
14
|
+
|
|
15
|
+
Creation establishes the object. Incrementing reads the configured step, saves the current value, and appends a historical change in one synchronous commit. The example keeps the latest counter value when browsing history; it does not implement restoration. Network and file work belongs outside synchronous commits.
|
|
16
|
+
|
|
17
|
+
The card reads projected data and invokes the public increment action. Each button owns its pending state and displays failures. Actions return receipts that the view checks; typed calls and settings saves reject on failure. A settings page edits the step and reads it back through the backend call.
|
|
18
|
+
|
|
19
|
+
The model tool reads the same counter value through its resource implementation. Registering the tool does not grant access: an agent needs a link to the counter and a tool policy that allows the operation.
|
|
20
|
+
|
|
21
|
+
## Adapt the package
|
|
22
|
+
|
|
23
|
+
Choose a distinct plugin identity before using the example as a new plugin. Keep shared declarations independent of backend and frontend implementations. Add resource behavior through actions and expose an operation as a model tool when the model needs to call it.
|
|
24
|
+
|
|
25
|
+
Imported CSS Modules keep the counter's class names local. Host theme variables provide colors. The storage entry declares the managed configuration format; add consecutive migrations when that format changes. Credentials belong in the secret service, and retained file content uses the content service.
|
|
26
|
+
|
|
27
|
+
Object-owned external connections belong to the resource's attachment lifetime. Register their cleanup when opening them. Shared services belong to the plugin lifetime. Managed tasks own asynchronous work and pass cancellation to their I/O.
|
|
28
|
+
|
|
29
|
+
## Verify the result
|
|
30
|
+
|
|
31
|
+
Open the plugin settings page, change the step, and read it back. Create a counter, increment it, and verify the card and history. Link the counter to an agent and use its read tool.
|
|
32
|
+
|
|
33
|
+
After edits, run type checking and restart Asterism. Verify invalid input, failure messages, cleanup, and reopening saved objects. Type checking establishes interface consistency; loading and using the package establishes its behavior in the host.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Organize a plugin
|
|
2
|
+
|
|
3
|
+
A plugin is the unit of installation, identity, settings, and lifetime. Its internal modules group capabilities that share that ownership.
|
|
4
|
+
|
|
5
|
+
## Separate responsibilities
|
|
6
|
+
|
|
7
|
+
Shared contracts describe transferable data and operations. Backend implementations perform work. Frontend views present state and collect input.
|
|
8
|
+
|
|
9
|
+
Keep shared declarations independent of backend-only or frontend-only libraries. A view reaches its backend through typed communication rather than importing the backend implementation.
|
|
10
|
+
|
|
11
|
+
Group a capability's declarations, implementation, and presentation so they remain understandable together. Compose capabilities at the plugin entry through explicit dependencies.
|
|
12
|
+
|
|
13
|
+
## Use public extension points
|
|
14
|
+
|
|
15
|
+
The general SDK supplies host services and contribution interfaces. Built-in domain APIs expose extensions specific to existing features, such as Agent interaction.
|
|
16
|
+
|
|
17
|
+
Use a domain extension when adding to that feature. A plugin with its own resource can define its own behavior and presentation.
|
|
18
|
+
|
|
19
|
+
## Share contracts and services
|
|
20
|
+
|
|
21
|
+
A public package can expose declarations or services for other plugins. Consumers declare ordinary package dependencies in `package.json` and import the original public values. The host derives initialization and cleanup dependencies from those imports, including imports through intermediate packages.
|
|
22
|
+
|
|
23
|
+
Expose shared values through `./public` in the package's standard exports. Public imports bind to the enabled provider's actual exports, including imports through intermediate npm packages. The host uses the import graph for initialization order and reports missing exports or unavailable providers. Package versions and dependency ranges remain package-management metadata; local development copies do not replace the active provider.
|
|
24
|
+
|
|
25
|
+
A shared declaration has runtime identity as well as a name. Copying it under the same name does not establish shared identity. Backend and frontend environments are separate even when they use the same contract.
|
|
26
|
+
|
|
27
|
+
Shared service initialization and cleanup belong to the provider. A consumer should depend on the capability it needs rather than the provider's internal organization.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Add resources and tools
|
|
2
|
+
|
|
3
|
+
A resource represents an object with identity and lifetime. A tool represents a model-callable operation. They can evolve independently.
|
|
4
|
+
|
|
5
|
+
## Add an object
|
|
6
|
+
|
|
7
|
+
Use a resource for something that belongs in a session and can be inspected or operated on. Define how it is created, what state it owns, and what information it presents.
|
|
8
|
+
|
|
9
|
+
The resource's lifetime follows its presence in the session. A hidden card does not imply that its backend instance should stop.
|
|
10
|
+
|
|
11
|
+
Creation supplies initial state. Actions change the object through typed application or plugin operations. Views present the resulting state.
|
|
12
|
+
|
|
13
|
+
Resource titles belong to session history. Use the session's rename operation with a resource reference to change a title; the host appends the event and reconstructs names along each history path. Tool sessions accept a resource ID within the request's captured links. Existing snapshots keep their captured titles.
|
|
14
|
+
|
|
15
|
+
Use a session snapshot to query the resources directly linked to a receiver. Results contain resource references, titles, and public data on that snapshot's timeline path. Reverse links and links through another resource are independent. For external event handling, such as forwarding network messages to linked agents, read a fresh snapshot for each event. Session observers can also query their supplied snapshot after commits or cursor changes. The query exposes relationships without granting access to private state or expanding a model request's operation scope.
|
|
16
|
+
|
|
17
|
+
## Add a model operation
|
|
18
|
+
|
|
19
|
+
Use a resource tool when an operation targets a linked object. A shared tool can have implementations for different resource types, each with an appropriate internal target and result.
|
|
20
|
+
|
|
21
|
+
Use a top-level tool for operations on the execution context or shared resource lifecycle, such as creation and renaming. The host supplies caller identity and execution scope.
|
|
22
|
+
|
|
23
|
+
Describe the operation's purpose and observable behavior. Parameters should explain business meaning that names and types alone do not express. Results should distinguish known success, failure, cancellation, partial output, and uncertain effects.
|
|
24
|
+
|
|
25
|
+
## Connect other interactions
|
|
26
|
+
|
|
27
|
+
Typed calls connect request and response between services or views. Events announce transient changes. Object channels support sustained data such as an interactive screen.
|
|
28
|
+
|
|
29
|
+
An action or service call is not automatically a model tool. Expose it deliberately when the model needs that operation.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Build views and settings
|
|
2
|
+
|
|
3
|
+
Views present state and accept input. Backend services own the work, results, and resource lifetime.
|
|
4
|
+
|
|
5
|
+
## Present objects and operations
|
|
6
|
+
|
|
7
|
+
A resource card shows the object's content. A tool view shows one operation, including generated input, execution progress, and the final outcome.
|
|
8
|
+
|
|
9
|
+
Input previews can be incomplete. Displaying arguments does not establish that execution has begun. A final result should reflect what actually happened.
|
|
10
|
+
|
|
11
|
+
Use host presentation facilities for the surrounding card or operation. Keep domain content within the view rather than rebuilding application navigation and containment.
|
|
12
|
+
|
|
13
|
+
## Style plugin views
|
|
14
|
+
|
|
15
|
+
Import CSS from the renderer entry or its components. Asterism builds the imported styles and loads them before frontend preparation and view registration. CSS references to bundled images and fonts keep their relative paths. Style loading failures appear as frontend plugin failures.
|
|
16
|
+
|
|
17
|
+
Use CSS Modules (`.module.css`) for component-local class names, importing the generated names into the component. Plain CSS shares the application's document, so global selectors and resets affect other views. Host theme variables can supply colors and spacing without redefining the application theme.
|
|
18
|
+
|
|
19
|
+
Tailwind and other preprocessors belong to the plugin's build setup. Generate ordinary CSS and import that output; the host does not process the plugin's Tailwind or PostCSS configuration. Utility classes used only by an external plugin need its own generated CSS.
|
|
20
|
+
|
|
21
|
+
Styles remain loaded while the plugin frontend is active, including when individual cards unmount. Failed initialization releases that plugin's stylesheet; ending the frontend lifetime releases all loaded plugin styles.
|
|
22
|
+
|
|
23
|
+
## Add settings
|
|
24
|
+
|
|
25
|
+
A plugin can have a settings page independently of sessions and resources. The page owns its edit drafts and pending interactions; the plugin service owns shared configuration.
|
|
26
|
+
|
|
27
|
+
Managed configuration suits schema-validated settings. Other plugin data can use a storage model appropriate to its purpose.
|
|
28
|
+
|
|
29
|
+
Closing a page releases its view subscriptions. It does not end every service the plugin uses.
|
|
30
|
+
|
|
31
|
+
## Write interface text
|
|
32
|
+
|
|
33
|
+
Plain text is valid. Message declarations and translation catalogs support plugins that need localized interfaces.
|
|
34
|
+
|
|
35
|
+
Keep protocol identifiers, code, and raw diagnostics distinct from interface explanations. UI language changes should not reset drafts or running work.
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { sample, Counter, Count, CounterView, CountChanged, CreateCounter, ChangeCount, CounterUpdates, counterCard } from "./counter-contract.ts";
|
|
2
|
+
/** Independent examples of session records, resource behavior, and model execution. */
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { pluginNamespace, defineAction, resourceCall, defineRecord, defineTool, defineImplementation, modelRunRecordSchema, type ModelItem } from "@asterism-app/sdk/contracts";
|
|
5
|
+
import {
|
|
6
|
+
defineModule, definePlugin, implementCreation, implementAction, implementPreparer, implementResource, implementTool, implementChannel,
|
|
7
|
+
type PluginContext, type ObjectReader, type ObjectExecutor, type SessionObserver, type SessionSnapshot, type ResourceRef,
|
|
8
|
+
} from "@asterism-app/sdk/runtime";
|
|
9
|
+
|
|
10
|
+
/** Scenario one: uses only session events and lifecycles, without resources, models, or Agents. */
|
|
11
|
+
const journal = pluginNamespace("example.journal");
|
|
12
|
+
const Annotation = defineRecord(journal.name("annotation"), {
|
|
13
|
+
schema: z.strictObject({ text: z.string() }),
|
|
14
|
+
});
|
|
15
|
+
export const journalPlugin = definePlugin({
|
|
16
|
+
namespace: journal, modules: [defineModule(plugin => ({
|
|
17
|
+
records: [Annotation],
|
|
18
|
+
observers: [{
|
|
19
|
+
id: "observe-annotations",
|
|
20
|
+
committed({ records }) {
|
|
21
|
+
for (const record of records) {
|
|
22
|
+
const entry = Annotation.decode(record);
|
|
23
|
+
if (entry?.status === "known") plugin.log.info("Annotation committed.");
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
cursorChanged({ snapshot, session }) {
|
|
27
|
+
const annotation = snapshot.records.last(Annotation, { scope: "timeline" });
|
|
28
|
+
if (annotation?.status !== "known") return;
|
|
29
|
+
session.commit(writer => { writer.set(CurrentSummary, { text: annotation.data.text }); });
|
|
30
|
+
},
|
|
31
|
+
} satisfies SessionObserver],
|
|
32
|
+
}))],
|
|
33
|
+
});
|
|
34
|
+
export function annotate(plugin: PluginContext, sessionId: string, text: string) {
|
|
35
|
+
plugin.session(sessionId).commit(writer => { writer.append(Annotation, { text }); });
|
|
36
|
+
}
|
|
37
|
+
/** Locate the latest typed record by scope and contract; leave unknown for domain consumers to handle. */
|
|
38
|
+
export function latestAnnotation(plugin: PluginContext, sessionId: string) {
|
|
39
|
+
return plugin.session(sessionId).snapshot().records.last(Annotation, { scope: "session" });
|
|
40
|
+
}
|
|
41
|
+
const CurrentSummary = journal.data("summary", z.strictObject({ text: z.string() }));
|
|
42
|
+
/** Current session data does not enter the timeline; it is stored separately from object data and private plugin databases. */
|
|
43
|
+
export function saveSummary(plugin: PluginContext, sessionId: string, text: string) {
|
|
44
|
+
const session = plugin.session(sessionId);
|
|
45
|
+
session.commit(writer => { writer.set(CurrentSummary, { text }); });
|
|
46
|
+
return session.get(CurrentSummary);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Scenario two: creation, state, events, actions, and tools for an independent resource, without model capabilities. */
|
|
50
|
+
const counterSettings = z.strictObject({
|
|
51
|
+
maxChange: z.number().int().min(1).max(100).meta({
|
|
52
|
+
title: "Maximum change", description: "Largest absolute increment accepted by a counter action.",
|
|
53
|
+
}),
|
|
54
|
+
}).meta({ title: "Counter settings" });
|
|
55
|
+
const CounterConfig = sample.config(counterSettings, { maxChange: 10 });
|
|
56
|
+
const Read = defineTool({
|
|
57
|
+
id: "example.counter/read_counter", name: "read_counter", description: "Read the current counter value.", guidelines: [],
|
|
58
|
+
argsSchema: z.strictObject({}).describe("No arguments are required."),
|
|
59
|
+
});
|
|
60
|
+
const CounterRead = defineImplementation({
|
|
61
|
+
...sample.data("read", Count.schema.describe("The current integer value.")),
|
|
62
|
+
resource: Counter, tool: Read,
|
|
63
|
+
targetSchema: z.null().describe("Pass null to select this counter."),
|
|
64
|
+
effect: "read", execution: "parallel",
|
|
65
|
+
});
|
|
66
|
+
function currentCount(object: ObjectReader) {
|
|
67
|
+
const value = object.get(Count);
|
|
68
|
+
if (value === null) throw new Error("Counter state is unavailable.");
|
|
69
|
+
return value;
|
|
70
|
+
}
|
|
71
|
+
/** Feature modules receive only their own settings read capability; configuration declarations remain at the assembly site. */
|
|
72
|
+
function counterChange(limit: () => number) {
|
|
73
|
+
return implementAction(ChangeCount, {
|
|
74
|
+
prepare(input) {
|
|
75
|
+
return Math.abs(input.amount) > limit()
|
|
76
|
+
? { action: "reject", code: "change_too_large", message: "Change exceeds the limit." }
|
|
77
|
+
: { action: "accept" };
|
|
78
|
+
},
|
|
79
|
+
async execute(input, object) {
|
|
80
|
+
object.commit(writer => {
|
|
81
|
+
const value = currentCount(object).value + input.amount;
|
|
82
|
+
writer.set(Count, { value });
|
|
83
|
+
writer.append(CountChanged, { value });
|
|
84
|
+
});
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
export const counterPlugin = definePlugin({
|
|
89
|
+
namespace: sample, modules: [defineModule(plugin => {
|
|
90
|
+
const settings = plugin.config(CounterConfig);
|
|
91
|
+
return {
|
|
92
|
+
resources: [implementResource(Counter, CounterView, {
|
|
93
|
+
card: counterCard,
|
|
94
|
+
records: [CountChanged],
|
|
95
|
+
creation: implementCreation(CreateCounter, {
|
|
96
|
+
create(input, object) {
|
|
97
|
+
object.commit(writer => { writer.set(Count, { value: 0 }); });
|
|
98
|
+
return { title: input.title, data: {} };
|
|
99
|
+
},
|
|
100
|
+
}),
|
|
101
|
+
actions: [counterChange(() => settings.get().maxChange)],
|
|
102
|
+
project(_data, object) { return currentCount(object); },
|
|
103
|
+
async inspect(_data, object) { return currentCount(object); },
|
|
104
|
+
})],
|
|
105
|
+
tools: [implementTool(CounterRead, {
|
|
106
|
+
async call(_data, _target, _args, context) {
|
|
107
|
+
context.signal.throwIfAborted();
|
|
108
|
+
return { kind: "succeeded", data: currentCount(context.object) };
|
|
109
|
+
},
|
|
110
|
+
})],
|
|
111
|
+
// The channel follows the object's lifetime: the host aborts signal when the object is removed or disappears after rewinding.
|
|
112
|
+
channels: [implementChannel(CounterUpdates, channel => {
|
|
113
|
+
channel.onMessage(() => channel.send(currentCount(channel.object)));
|
|
114
|
+
const timer = setInterval(() => channel.send(currentCount(channel.object)), 1000);
|
|
115
|
+
channel.signal.addEventListener("abort", () => clearInterval(timer), { once: true });
|
|
116
|
+
})],
|
|
117
|
+
};
|
|
118
|
+
})],
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
/** Scenario three: the plugin's own Agent drives the loop, with context projected from the timeline each time. */
|
|
122
|
+
const research = pluginNamespace("example.research");
|
|
123
|
+
const Execution = defineRecord(research.name("execution"), { schema: modelRunRecordSchema });
|
|
124
|
+
export const modelRecordsModule = defineModule(() => ({ records: [Execution] }));
|
|
125
|
+
/** The domain plugin constructs the projection from its own input/execution events; it is not stored in Agent or run. */
|
|
126
|
+
export async function modelWork(plugin: PluginContext, object: ObjectExecutor, signal: AbortSignal,
|
|
127
|
+
projectOwnContext: (snapshot: SessionSnapshot, object: ResourceRef) => ModelItem[]) {
|
|
128
|
+
for (let request = 0; request < 8; request++) {
|
|
129
|
+
const snapshot = object.session.snapshot();
|
|
130
|
+
const result = await plugin.models.run({
|
|
131
|
+
object: object.ref, requestParentId: snapshot.position.nodeId, target: { role: "default" },
|
|
132
|
+
system: { blocks: [{ id: research.name("instruction"), kind: "system", from: research.id, text: "Inspect linked materials and report findings." }] },
|
|
133
|
+
messages: projectOwnContext(snapshot, object.ref), cache: { expectHit: request > 0 }, signal,
|
|
134
|
+
observer: { event(event) {
|
|
135
|
+
if (event.type === "progress") return;
|
|
136
|
+
object.commit(writer => { writer.append(Execution, modelRunRecordSchema.parse(event)); });
|
|
137
|
+
} },
|
|
138
|
+
});
|
|
139
|
+
if (result.reason !== "completed" || result.tools.length === 0) return result;
|
|
140
|
+
}
|
|
141
|
+
throw new Error("The model request limit was reached.");
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** External preparers also infer input from the action contract they hold, without redeclaring the schema. */
|
|
145
|
+
export const counterLimit = implementPreparer(ChangeCount, {
|
|
146
|
+
id: "clamp-change",
|
|
147
|
+
prepare(input) { return { action: "replace", input: { amount: Math.max(-10, Math.min(10, input.amount)) } }; },
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
/** Invocation and replacement receive raw strings; preparation and execution receive numbers transformed by the schema. */
|
|
151
|
+
export const FromText = defineAction(Counter, { id: sample.name("from-text"),
|
|
152
|
+
schema: z.string().regex(/^\d+$/u).transform(Number),
|
|
153
|
+
});
|
|
154
|
+
export const parsedAction = implementAction(FromText, {
|
|
155
|
+
prepare(value) { return value > 10 ? { action: "replace", input: "10" } : { action: "accept" }; },
|
|
156
|
+
async execute(value, object) { object.commit(writer => { writer.set(Count, { value }); }); },
|
|
157
|
+
});
|
|
158
|
+
export const parsedInput = resourceCall(FromText, "5");
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { StreamRequest, UsageItem } from "@asterism-app/sdk/contracts";
|
|
2
|
+
import type { PluginContext } from "@asterism-app/sdk/runtime";
|
|
3
|
+
|
|
4
|
+
/** Example consumer: actively queries before a request and parses response headers against the current catalog for the response's connection. */
|
|
5
|
+
export async function* streamWithUsage(plugin: PluginContext, connectionId: string, request: StreamRequest,
|
|
6
|
+
receive: (items: UsageItem[]) => void) {
|
|
7
|
+
const connection = plugin.modelProviders.get(connectionId);
|
|
8
|
+
const usage = connection?.enabled && connection.usage ? plugin.usageProviders.get(connection.usage) : null;
|
|
9
|
+
if (connection && usage && usage.source !== "headers") {
|
|
10
|
+
const credential = await plugin.modelProviders.credential(connection.id, { signal: request.signal });
|
|
11
|
+
const items = await plugin.usages.query(usage.id, { connection: { id: connection.id, endpoint: connection.baseUrl }, credential, signal: request.signal });
|
|
12
|
+
receive(items);
|
|
13
|
+
}
|
|
14
|
+
for await (const event of plugin.models.stream(request)) {
|
|
15
|
+
const response = event.type === "finish" ? event.response : null;
|
|
16
|
+
if (response) {
|
|
17
|
+
const current = plugin.modelProviders.get(response.connection.id);
|
|
18
|
+
const parser = current?.enabled && current.usage ? plugin.usageProviders.get(current.usage) : null;
|
|
19
|
+
if (parser?.source === "headers") {
|
|
20
|
+
try {
|
|
21
|
+
const items = plugin.usages.parseHeaders(parser.id, { connection: { id: response.connection.id, endpoint: response.endpoint },
|
|
22
|
+
model: response.model, status: response.status, receivedAt: response.receivedAt, headers: response.headers });
|
|
23
|
+
if (items !== null) receive(items);
|
|
24
|
+
} catch { plugin.log.warn("Usage response parsing failed."); }
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
yield event;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { defineModule, definePlugin, implementAction, implementCreation, implementResource } from "@asterism-app/sdk/runtime";
|
|
2
|
+
import { CreateDocument, Document, DocumentContent, Imported, ReplaceDocument, files } from "./content-contract.ts";
|
|
3
|
+
|
|
4
|
+
/** The session owns content, and plugins only interpret business formats; this example does not access host source code or private paths. */
|
|
5
|
+
export default definePlugin({ namespace: files, modules: [defineModule(() => ({
|
|
6
|
+
resources: [implementResource(Document, DocumentContent, {
|
|
7
|
+
records: [Imported],
|
|
8
|
+
creation: implementCreation(CreateDocument, {
|
|
9
|
+
async prepare(input, { content }) {
|
|
10
|
+
// Format checks can read a bounded range or use open to stream large files.
|
|
11
|
+
await content.read(input.file, { length: 512 });
|
|
12
|
+
return { action: "accept" };
|
|
13
|
+
},
|
|
14
|
+
create(input, object) {
|
|
15
|
+
object.commit(writer => {
|
|
16
|
+
writer.set(DocumentContent, input);
|
|
17
|
+
// This example explicitly retains the originally imported content; resources that store only the current value can omit this event.
|
|
18
|
+
writer.append(Imported, input);
|
|
19
|
+
});
|
|
20
|
+
return { title: input.name, data: {} };
|
|
21
|
+
},
|
|
22
|
+
}),
|
|
23
|
+
actions: [implementAction(ReplaceDocument, {
|
|
24
|
+
async execute(input, object) { object.commit(writer => { writer.set(DocumentContent, input); }); },
|
|
25
|
+
})],
|
|
26
|
+
project: (_data, object) => object.get(DocumentContent)!,
|
|
27
|
+
inspect: async (_data, object) => object.get(DocumentContent),
|
|
28
|
+
})],
|
|
29
|
+
}))] });
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { contentRefSchema, defineAction, defineCreation, defineRecord, pluginNamespace } from "@asterism-app/sdk/contracts";
|
|
3
|
+
|
|
4
|
+
/** Content identities can be embedded in ordinary contracts; authors need not declare a separate list of persistent attachments. */
|
|
5
|
+
export const files = pluginNamespace("example.content");
|
|
6
|
+
export const Document = files.resource("document", z.object({}));
|
|
7
|
+
export const DocumentContent = files.data("content", z.object({ file: contentRefSchema, name: z.string() }));
|
|
8
|
+
export const Imported = defineRecord(files.name("imported"), { schema: DocumentContent.schema });
|
|
9
|
+
export const CreateDocument = defineCreation(Document, { id: files.name("create"), schema: DocumentContent.schema });
|
|
10
|
+
export const ReplaceDocument = defineAction(Document, { id: files.name("replace"), schema: DocumentContent.schema });
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { creationEntry, type RendererObjectContext } from "@asterism-app/sdk/renderer";
|
|
2
|
+
import { CreateDocument, ReplaceDocument } from "./content-contract.ts";
|
|
3
|
+
|
|
4
|
+
/** This entry point accepts dropped files (with or without local paths) and clipboard images on the workbench. */
|
|
5
|
+
export const importDocument = creationEntry(CreateDocument, {
|
|
6
|
+
id: "example.content/import", label: "Import document",
|
|
7
|
+
triggers: { drop: {
|
|
8
|
+
accepts: { files: {} },
|
|
9
|
+
async toInput(item, content) {
|
|
10
|
+
if (item.kind !== "file" || item.directory) return null;
|
|
11
|
+
return { file: await content.stage(item.file), name: item.name };
|
|
12
|
+
},
|
|
13
|
+
} },
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
/** The host closes the draft after a successful command; failure preserves it for retry, and abandoning it calls close. */
|
|
17
|
+
export function replacementInput(object: RendererObjectContext) {
|
|
18
|
+
const draft = object.input(ReplaceDocument);
|
|
19
|
+
return {
|
|
20
|
+
async submit(file: File) {
|
|
21
|
+
const ref = await draft.stage(file);
|
|
22
|
+
return object.invoke(ReplaceDocument, { file: ref, name: file.name }, { content: draft });
|
|
23
|
+
},
|
|
24
|
+
cancel: () => draft.close(),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Use the counter plugin
|
|
2
|
+
|
|
3
|
+
The counter plugin adds a resource that stores an integer, a card for incrementing it, and a read operation for agents. It also has a settings page for choosing the increment step.
|
|
4
|
+
|
|
5
|
+
Create a Counter from the workbench creation menu. Its initial value is zero. Use Increment on the card to add the configured step. In plugin settings, change the step or read it back from the backend.
|
|
6
|
+
|
|
7
|
+
Link the counter resource to an agent to make its read operation available under that agent's tool policy. The operation reports the current value.
|
|
8
|
+
|
|
9
|
+
Incrementing saves the current value and records the change in history. Browsing an earlier history position does not restore the counter value. Removing the resource removes its current workbench presence while preserving its history.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "asterism-example-counter",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": { "typecheck": "tsc --noEmit" },
|
|
7
|
+
"peerDependencies": {
|
|
8
|
+
"@asterism-app/sdk": "*",
|
|
9
|
+
"react": "^19.3.0",
|
|
10
|
+
"react-intl": "^12.1.4",
|
|
11
|
+
"zod": "^4.6.5"
|
|
12
|
+
},
|
|
13
|
+
"devDependencies": {
|
|
14
|
+
"@asterism-app/sdk": "0.47.0",
|
|
15
|
+
"typescript": "^7.0.2",
|
|
16
|
+
"@types/node": "^26.6.1",
|
|
17
|
+
"@types/react": "^19.3.0"
|
|
18
|
+
},
|
|
19
|
+
"asterism": {
|
|
20
|
+
"id": "example.counter",
|
|
21
|
+
"displayName": "Example counter",
|
|
22
|
+
"runtime": "src/runtime.ts",
|
|
23
|
+
"renderer": "src/renderer.tsx",
|
|
24
|
+
"storage": "src/storage.ts",
|
|
25
|
+
"manual": ["docs"]
|
|
26
|
+
}
|
|
27
|
+
}
|