@elinpf/dsh-ops-access 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Invariant companion for @elinpf/dsh-ops-access.
3
+ *
4
+ * @module @elinpf/dsh-ops-access/invariant
5
+ */
6
+ /** Cordis companion plugin name. */
7
+ declare const name = "ops-access-invariant";
8
+ /** Service required before the companion can reserve package ownership. */
9
+ declare const inject: string[];
10
+ /**
11
+ * Register this package's invariant companion.
12
+ * @param ctx - Context carrying the invariant service.
13
+ * @returns a promise resolving after registration.
14
+ */
15
+ declare const apply: (ctx: any) => Promise<void>;
16
+ export { apply, inject, name };
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Invariant companion for @elinpf/dsh-ops-access.
3
+ *
4
+ * @module @elinpf/dsh-ops-access/invariant
5
+ */
6
+ const PACKAGE_NAME = '@elinpf/dsh-ops-access';
7
+ /** Cordis companion plugin name. */
8
+ const name = 'ops-access-invariant';
9
+ /** Service required before the companion can reserve package ownership. */
10
+ const inject = ['invariants'];
11
+ /**
12
+ * No runtime invariant: this package owns no session event types and no
13
+ * projection — its durable state is the YAML registry file, which is
14
+ * re-read, re-parsed, and re-validated on EVERY call (no cache to drift),
15
+ * and writes are validated against the provider schema before they land.
16
+ * The in-memory state (provider/broker registrations) is fiber-scoped and
17
+ * torn down by effect disposal, so there is nothing to guard at runtime.
18
+ */
19
+ const install = () => { };
20
+ /**
21
+ * Register this package's invariant companion.
22
+ * @param ctx - Context carrying the invariant service.
23
+ * @returns a promise resolving after registration.
24
+ */
25
+ const apply = async (ctx) => {
26
+ ctx.invariants.register(PACKAGE_NAME, install);
27
+ };
28
+ export { apply, inject, name };
@@ -0,0 +1,59 @@
1
+ /**
2
+ * dsh-access mention encoding — the structured form of an `@` pick on an
3
+ * access profile, mirroring session-reference's `dsh-session:` pattern.
4
+ *
5
+ * A mention is a host-neutral Markdown span `@[kind/name](dsh-access:<payload>)`
6
+ * where the payload is base64url(JSON [kind, name]). The composer draft shows
7
+ * the label; on submit the full mention travels in the message text; the
8
+ * preset-plane `agent/pre-step` listener (src/index.ts) parses it back out,
9
+ * rewrites the text to a readable `@kind/name`, and injects the profiles'
10
+ * envelope context.
11
+ *
12
+ * Host-side only — the browser never encodes or parses these; the remote
13
+ * hands it ready-made mention strings.
14
+ *
15
+ * @module @elinpf/dsh-ops-access/mention
16
+ */
17
+ /** URI scheme reserved for ops access-profile references. */
18
+ export declare const ACCESS_REFERENCE_SCHEME = "dsh-access:";
19
+ /** One referenced access profile. */
20
+ export interface AccessReference {
21
+ kind: string;
22
+ name: string;
23
+ }
24
+ /** A parsed reference with its display label, in first-appearance order. */
25
+ export interface ParsedAccessReference extends AccessReference {
26
+ label: string;
27
+ }
28
+ /** Result of extracting mentions from plain text. */
29
+ export interface ParsedAccessReferenceText {
30
+ /** Text with opaque mention spans replaced by readable `@label`. */
31
+ text: string;
32
+ /** Structured references in first-appearance order (not deduplicated). */
33
+ references: ParsedAccessReference[];
34
+ }
35
+ /**
36
+ * Encode a profile reference as a canonical lossless URI.
37
+ * @param reference - kind + name of the profile.
38
+ * @returns canonical `dsh-access:` URI.
39
+ */
40
+ export declare function encodeAccessReferenceUri(reference: AccessReference): string;
41
+ /**
42
+ * Decode and canonicalize one access-reference URI.
43
+ * @param uri - complete canonical URI.
44
+ * @returns decoded kind + name.
45
+ */
46
+ export declare function decodeAccessReferenceUri(uri: string): AccessReference;
47
+ /**
48
+ * Render the Markdown mention the composer inserts into the draft.
49
+ * @param reference - kind + name of the profile.
50
+ * @returns escaped `@[kind/name](uri)` mention.
51
+ */
52
+ export declare function formatAccessMention(reference: AccessReference): string;
53
+ /**
54
+ * Extract Markdown mentions and bare canonical URIs from one text value,
55
+ * replacing them with readable `@label` spans.
56
+ * @param text - host text to normalize.
57
+ * @returns readable text and structured references in appearance order.
58
+ */
59
+ export declare function parseAccessReferenceText(text: string): ParsedAccessReferenceText;
package/lib/mention.js ADDED
@@ -0,0 +1,93 @@
1
+ /**
2
+ * dsh-access mention encoding — the structured form of an `@` pick on an
3
+ * access profile, mirroring session-reference's `dsh-session:` pattern.
4
+ *
5
+ * A mention is a host-neutral Markdown span `@[kind/name](dsh-access:<payload>)`
6
+ * where the payload is base64url(JSON [kind, name]). The composer draft shows
7
+ * the label; on submit the full mention travels in the message text; the
8
+ * preset-plane `agent/pre-step` listener (src/index.ts) parses it back out,
9
+ * rewrites the text to a readable `@kind/name`, and injects the profiles'
10
+ * envelope context.
11
+ *
12
+ * Host-side only — the browser never encodes or parses these; the remote
13
+ * hands it ready-made mention strings.
14
+ *
15
+ * @module @elinpf/dsh-ops-access/mention
16
+ */
17
+ /** URI scheme reserved for ops access-profile references. */
18
+ export const ACCESS_REFERENCE_SCHEME = 'dsh-access:';
19
+ /**
20
+ * Encode a profile reference as a canonical lossless URI.
21
+ * @param reference - kind + name of the profile.
22
+ * @returns canonical `dsh-access:` URI.
23
+ */
24
+ export function encodeAccessReferenceUri(reference) {
25
+ const payload = Buffer.from(JSON.stringify([reference.kind, reference.name]), 'utf8').toString('base64url');
26
+ return `${ACCESS_REFERENCE_SCHEME}${payload}`;
27
+ }
28
+ /**
29
+ * Decode and canonicalize one access-reference URI.
30
+ * @param uri - complete canonical URI.
31
+ * @returns decoded kind + name.
32
+ */
33
+ export function decodeAccessReferenceUri(uri) {
34
+ if (!uri.startsWith(ACCESS_REFERENCE_SCHEME)) {
35
+ throw invalidUri(uri);
36
+ }
37
+ const payload = uri.slice(ACCESS_REFERENCE_SCHEME.length);
38
+ if (!/^[A-Za-z0-9_-]+$/.test(payload))
39
+ throw invalidUri(uri);
40
+ try {
41
+ const parsed = JSON.parse(Buffer.from(payload, 'base64url').toString('utf8'));
42
+ if (!Array.isArray(parsed) || parsed.length !== 2
43
+ || typeof parsed[0] !== 'string' || typeof parsed[1] !== 'string') {
44
+ throw new TypeError('decoded payload is not a [kind, name] pair');
45
+ }
46
+ const reference = { kind: parsed[0], name: parsed[1] };
47
+ if (encodeAccessReferenceUri(reference) !== uri)
48
+ throw new TypeError('URI is not canonical');
49
+ return reference;
50
+ }
51
+ catch (error) {
52
+ throw invalidUri(uri, error);
53
+ }
54
+ }
55
+ /**
56
+ * Render the Markdown mention the composer inserts into the draft.
57
+ * @param reference - kind + name of the profile.
58
+ * @returns escaped `@[kind/name](uri)` mention.
59
+ */
60
+ export function formatAccessMention(reference) {
61
+ const label = escapeLabel(`${reference.kind}/${reference.name}`);
62
+ return `@[${label}](${encodeAccessReferenceUri(reference)})`;
63
+ }
64
+ /**
65
+ * Extract Markdown mentions and bare canonical URIs from one text value,
66
+ * replacing them with readable `@label` spans.
67
+ * @param text - host text to normalize.
68
+ * @returns readable text and structured references in appearance order.
69
+ */
70
+ export function parseAccessReferenceText(text) {
71
+ const references = [];
72
+ const pattern = /@\[((?:\\.|[^\\\]])*)\]\((dsh-access:[^\s)]*)\)|(dsh-access:[A-Za-z0-9_-]+)/gu;
73
+ const rendered = text.replace(pattern, (_match, rawLabel, markdownUri, bareUri) => {
74
+ const uri = markdownUri ?? bareUri;
75
+ /* v8 ignore next -- the two-alternative regex always captures exactly one URI group */
76
+ if (uri === undefined)
77
+ throw new Error('access reference URI is missing');
78
+ const reference = decodeAccessReferenceUri(uri);
79
+ const label = rawLabel === undefined ? `${reference.kind}/${reference.name}` : unescapeLabel(rawLabel);
80
+ references.push({ ...reference, label });
81
+ return `@${label}`;
82
+ });
83
+ return { text: rendered, references };
84
+ }
85
+ function escapeLabel(label) {
86
+ return label.replace(/[\\\]]/gu, match => `\\${match}`);
87
+ }
88
+ function unescapeLabel(label) {
89
+ return label.replace(/\\(.)/gu, '$1');
90
+ }
91
+ function invalidUri(uri, cause) {
92
+ return new Error(`invalid access reference URI ${JSON.stringify(uri)}`, cause === undefined ? undefined : { cause });
93
+ }
package/lib/types.d.ts ADDED
@@ -0,0 +1,268 @@
1
+ /**
2
+ * Type definitions for the ops-access capability seam.
3
+ *
4
+ * Types only — every runtime value (the `Config` schema, `expandHome`,
5
+ * `registerAccessProvider`/`registerAccessBroker`, the plugin itself) lives
6
+ * in index.ts.
7
+ *
8
+ * @module @elinpf/dsh-ops-access/types
9
+ */
10
+ import type { ZodType } from 'zod';
11
+ export type { Config } from './index.js';
12
+ /** A credential-kind provider: the zod schema for its entries plus an optional processing step. */
13
+ export interface AccessProvider {
14
+ /** Credential kind, matching the registry section name ('k8s' | 'ceph' | 'ssh' | ...). */
15
+ kind: string;
16
+ /** Zod schema for one registry entry of this kind (excluding name and the envelope fields). */
17
+ schema: ZodType;
18
+ /** Optional post-validation processing (e.g. `~` expansion). Input is the schema-validated entry. */
19
+ process?(entry: unknown, name: string): Record<string, unknown>;
20
+ /**
21
+ * One-line human doc of the entry's fields, surfaced by `help()` so the
22
+ * agent can learn how to write a registry entry for this kind. Keep it to
23
+ * the fields themselves, e.g. "kubeconfig: path to the kubeconfig file (~
24
+ * is expanded)". The schema is the machine contract; this is its prose.
25
+ */
26
+ fieldsDoc?: string;
27
+ /**
28
+ * How to DERIVE a read-only (ro) credential from the rw one for this kind
29
+ * (the kubectl/ceph command sequence), including the naming convention for
30
+ * the derived account. Surfaced by help(); the register_access tool points
31
+ * the agent at it. Prose, not code — the agent executes the recipe with
32
+ * judgment; exact commands drift with infrastructure versions.
33
+ */
34
+ derivationDoc?: string;
35
+ /**
36
+ * Field names whose values are file PATHS pointing to credential material
37
+ * (e.g. kubeconfig, ceph.conf, keyring, SSH private key). When the admin
38
+ * UI receives CONTENT for these fields (instead of a path), it writes the
39
+ * content to a managed file and stores the resulting path in the registry.
40
+ * Fields not listed here are inline values stored as-is (e.g. ssh host, user, port).
41
+ */
42
+ fileFields?: string[];
43
+ /**
44
+ * Save-time validator for a file field's pasted CONTENT, run before the
45
+ * content is written to disk (the admin UI route and the register_access
46
+ * tool share this check). Return an error message to reject the write,
47
+ * nothing to accept. May be ASYNC — a provider may run a real local
48
+ * parser (ssh passes the paste through ssh-keygen -y, the same parser ssh
49
+ * runs at connection time). Receives the content AFTER provider-declared
50
+ * normalization (normalizeTrailingNewline), i.e. exactly the bytes that
51
+ * will land on disk. Keep it structural — format and shape only (a ceph
52
+ * keyring has an indented, base64-decodable key line; a kubeconfig parses
53
+ * as YAML with clusters/contexts/users), never connectivity or value
54
+ * judgments. Catching paste corruption here beats a cryptic CLI parse
55
+ * error at use time.
56
+ */
57
+ validateContent?: (field: string, content: string) => string | null | undefined | Promise<string | null | undefined>;
58
+ /**
59
+ * When true, file-field content is normalized to end with exactly one
60
+ * trailing newline BEFORE validation and write. PEM/armored formats
61
+ * require the END line newline-terminated, and pastes / model transcripts
62
+ * routinely drop that last byte (2026-08-27: a registered ssh key failed
63
+ * in libcrypto at first use over exactly one missing newline).
64
+ */
65
+ normalizeTrailingNewline?: boolean;
66
+ /**
67
+ * Capability probe (ticket 10): verify the credential's REAL
68
+ * permissions against the claimed tier. Core runs it at save time,
69
+ * after validation (credential files are on disk by then), and stores
70
+ * the result as a `probe` key beside the tier in the registry —
71
+ * surfaced by listAll / list_access / the admin UI. Receives the
72
+ * schema-validated, provider-processed fields. A probe must be
73
+ * READ-ONLY against the infrastructure (k8s runs a can-i matrix;
74
+ * ceph re-reads `auth get` caps). Providers that cannot probe (ssh —
75
+ * there is no read-only shell to test) omit the hook; their tiers
76
+ * stay unprobed. Probe failures degrade to 'unverifiable' — they
77
+ * never reject the write.
78
+ */
79
+ probe?: (fields: Record<string, unknown>, tier: 'ro' | 'rw') => Promise<{
80
+ status: ProbeState['status'];
81
+ detail?: string;
82
+ }>;
83
+ }
84
+ /** A resolved access profile: envelope fields plus the provider-processed type-specific fields. */
85
+ export interface AccessProfile {
86
+ kind: string;
87
+ /** The entry's registry key — its stable id (paths, mentions, grants). */
88
+ name: string;
89
+ /**
90
+ * The tier this resolve actually served ('rw' only via a broker grant).
91
+ * Consumers use it to label credential references, e.g. the shell-tool
92
+ * factory's <id@tier:field> display tokens.
93
+ */
94
+ tier: 'ro' | 'rw';
95
+ /** Display label from the envelope's `name` field (editable, not an identity). */
96
+ displayName?: string;
97
+ description?: string;
98
+ environment?: string;
99
+ /** Type-specific fields after provider schema validation and process. */
100
+ fields: Record<string, unknown>;
101
+ }
102
+ /**
103
+ * Envelope fields common to every entry (not provider-specific). The entry's
104
+ * registry key is its stable, human-readable **id** (used in file paths,
105
+ * mentions, tool calls, grants, and relation references); `name` is a
106
+ * freely editable display label — changing it touches nothing on disk.
107
+ */
108
+ export interface EntryEnvelope {
109
+ /** Display name — modifiable, never referenced by id-based machinery. */
110
+ name?: string;
111
+ description?: string;
112
+ environment?: string;
113
+ }
114
+ /**
115
+ * Capability-probe outcome for one tier (ticket 10): whether the stored
116
+ * credential's REAL permissions match its claimed tier, measured at save
117
+ * time. 'verified' = claims match reality; 'mismatch' = reality
118
+ * contradicts the tier (an admin credential sitting in the ro slot is
119
+ * the classic case); 'unverifiable' = the probe could not run (binary
120
+ * missing, cluster unreachable) — never a write rejection.
121
+ */
122
+ export interface ProbeState {
123
+ status: 'verified' | 'mismatch' | 'unverifiable';
124
+ detail?: string;
125
+ /** ISO timestamp of the probe run. */
126
+ probedAt: string;
127
+ }
128
+ /** Validation status of one entry in one tier — `error` carries the zod reason, never field values. */
129
+ export interface AdminTierStatus {
130
+ ok: boolean;
131
+ error?: string;
132
+ /** Capability-probe outcome recorded at save time, when the provider probes. */
133
+ probe?: ProbeState;
134
+ }
135
+ /** One entry in the merged admin view: envelope + per-tier validation status, never fields. */
136
+ export interface AdminEntry {
137
+ kind: string;
138
+ name: string;
139
+ envelope: EntryEnvelope;
140
+ tiers: {
141
+ ro: AdminTierStatus;
142
+ rw: AdminTierStatus;
143
+ };
144
+ }
145
+ /** One registered credential kind: its JSON Schema (from `zod.toJSONSchema`) and optional field docs. */
146
+ export interface KindDescriptor {
147
+ kind: string;
148
+ jsonSchema: Record<string, unknown>;
149
+ fieldsDoc?: string;
150
+ /** Field names that are file paths (content is managed by the admin UI). */
151
+ fileFields?: string[];
152
+ }
153
+ /**
154
+ * Minimal caller-agent identity consulted by the broker. dsh's `Agent` (whose
155
+ * `id` is the session id) satisfies this structurally; core takes a narrow
156
+ * dependency on purpose — the broker is a pure decision function and never
157
+ * touches the rest of the agent.
158
+ */
159
+ export interface AccessAgent {
160
+ /** Session id (`exec.agent.id`). Grants are keyed by this. */
161
+ readonly id: string;
162
+ }
163
+ /**
164
+ * A broker's decision for one resolve call.
165
+ * - `'ro'` — serve the profile's `ro` tier from the registry
166
+ * - `'rw'` — serve the profile's `rw` tier from the registry
167
+ * - `{ deny }` — refuse; core throws the broker's message verbatim (the
168
+ * broker owns the guidance, e.g. pointing at `request_access`).
169
+ */
170
+ export type AccessBrokerDecision = 'ro' | 'rw' | {
171
+ deny: string;
172
+ };
173
+ /**
174
+ * The pure decision function a gate registers. Receives only kind, profile
175
+ * name, and the caller agent — never credential fields. Once a broker is
176
+ * registered, resolve consults it on EVERY call; `agent` is `undefined` for
177
+ * system-internal calls, and the no-agent ruling belongs to the broker (core
178
+ * does not answer policy on its behalf). Without a registered broker, resolve
179
+ * is unchanged from the broker-less behavior (ro).
180
+ */
181
+ export type AccessBroker = (kind: string, name: string, agent: AccessAgent | undefined) => AccessBrokerDecision;
182
+ /** The ops access handle exposed via ctx.get('opsAccess'). */
183
+ export interface OpsAccess {
184
+ /** Register a credential-kind provider. Throws if the kind is already registered. Returns a disposer. */
185
+ register(provider: AccessProvider): () => void;
186
+ /**
187
+ * Register an access broker (the gate). At most one broker is active; a later
188
+ * registration replaces an earlier one. Returns a disposer. Without a
189
+ * registered broker, resolve is unchanged from the broker-less behavior.
190
+ */
191
+ registerBroker(broker: AccessBroker): () => void;
192
+ /**
193
+ * Whether resolving this profile from the given tier would succeed right
194
+ * now: the entry exists AND passes the provider schema. Never returns
195
+ * fields, never consults the broker — the gate uses it to reject
196
+ * undeliverable requests BEFORE bothering the human approver.
197
+ *
198
+ * Returns `{ ok: true }` when the entry exists and validates. Returns
199
+ * `{ ok: false, error }` when the entry exists but fails provider-schema
200
+ * validation — `error` is built from the zod issue paths and messages
201
+ * (never raw field values). Returns `{ ok: false }` (no `error`) for
202
+ * structural failures: unknown kind, missing file, unparseable file, or
203
+ * missing entry.
204
+ */
205
+ canResolve(kind: string, name: string, tier: 'ro' | 'rw'): Promise<AdminTierStatus>;
206
+ /**
207
+ * Resolve one profile by kind and name. Throws on unknown kind, unknown
208
+ * name, or invalid entry. When a broker is registered it is consulted on
209
+ * every call — including calls without an `agent` (the broker owns the
210
+ * no-agent ruling) — and decides whether the rw profile is served. Without
211
+ * a broker the ro profile (from `registryFile`) is served, byte-for-byte
212
+ * as before.
213
+ */
214
+ resolve(kind: string, name: string, agent?: AccessAgent): Promise<AccessProfile>;
215
+ /** List all profiles across all registered kinds. Sections without a registered provider are skipped. */
216
+ list(): Promise<AccessProfile[]>;
217
+ /**
218
+ * Write (upsert) one tier of one entry in the registry. Reads the file,
219
+ * sets entry[tier] to the given fields, validates the tier via the provider
220
+ * schema (buildProfile), and writes back. A validation failure throws and
221
+ * leaves the file untouched. Creates the file if it does not exist.
222
+ * The envelope is applied field-by-field: omitted fields are preserved,
223
+ * empty-string fields are deleted, so updating one tier never clobbers
224
+ * the existing envelope but the operator can still clear a field.
225
+ */
226
+ writeEntry(kind: string, name: string, tier: 'ro' | 'rw', fields: Record<string, unknown>, envelope?: EntryEnvelope): Promise<void>;
227
+ /**
228
+ * Delete one tier of one entry from the registry. Returns true when the
229
+ * tier was deleted, false when the file, entry, or tier did not exist.
230
+ * When the entry's last tier is removed the whole entry is dropped, and
231
+ * the tier's managed credential files are removed with it.
232
+ */
233
+ deleteEntry(kind: string, name: string, tier: 'ro' | 'rw'): Promise<boolean>;
234
+ /**
235
+ * List all entries across both tiers, merged by kind/name. Each entry
236
+ * carries its envelope (from whichever tier has it) and per-tier validation
237
+ * status via canResolve — never fields.
238
+ */
239
+ listAll(): Promise<AdminEntry[]>;
240
+ /**
241
+ * Read back one entry's NON-file fields and envelope for editing. Returns
242
+ * null when the entry or file does not exist. File fields (credential
243
+ * content) are write-only after save: never returned, not even as paths —
244
+ * only their set status in `fileFields`. The caller (the admin UI) is a
245
+ * human operator, not the agent — non-file values are connection params,
246
+ * never secret material.
247
+ */
248
+ getEntry(kind: string, name: string, tier: 'ro' | 'rw'): Promise<{
249
+ fields: Record<string, unknown>;
250
+ fileFields: Record<string, boolean>;
251
+ displayName?: string;
252
+ description?: string;
253
+ environment?: string;
254
+ } | null>;
255
+ /**
256
+ * List all registered credential kinds with their JSON Schema (serialized
257
+ * via `zod.toJSONSchema(provider.schema)`) and optional field docs.
258
+ * Unregistered kinds do not appear.
259
+ */
260
+ listKinds(): KindDescriptor[];
261
+ /**
262
+ * The registry management doc: file location, format, envelope fields, and
263
+ * every registered kind's field doc. Progressive disclosure — the agent
264
+ * pulls this when it needs to edit the registry; nothing sits in the
265
+ * system prompt.
266
+ */
267
+ help(): string;
268
+ }
package/lib/types.js ADDED
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Type definitions for the ops-access capability seam.
3
+ *
4
+ * Types only — every runtime value (the `Config` schema, `expandHome`,
5
+ * `registerAccessProvider`/`registerAccessBroker`, the plugin itself) lives
6
+ * in index.ts.
7
+ *
8
+ * @module @elinpf/dsh-ops-access/types
9
+ */
10
+ export {};
package/package.json ADDED
@@ -0,0 +1,73 @@
1
+ {
2
+ "name": "@elinpf/dsh-ops-access",
3
+ "version": "0.1.0",
4
+ "description": "Ops access capability seam — owns the YAML credential registry and exposes ctx.opsAccess (resolve/list/register) to provider plugins.",
5
+ "type": "module",
6
+ "main": "lib/index.js",
7
+ "types": "lib/index.d.ts",
8
+ "files": [
9
+ "lib/index.js",
10
+ "lib/invariant.js",
11
+ "lib/types.js",
12
+ "lib/mention.js",
13
+ "lib/**/*.d.ts",
14
+ "cordis.patch.yml"
15
+ ],
16
+ "dsh": {
17
+ "bundle": {
18
+ "patch": "./cordis.patch.yml"
19
+ }
20
+ },
21
+ "dependencies": {
22
+ "@deepseek-ai/dsh-tools": "^0.0.1-rc.1",
23
+ "@deepseek-ai/schemastery": "^3.18.1",
24
+ "yaml": "^2.7.0",
25
+ "zod": "^4.4.3"
26
+ },
27
+ "peerDependencies": {
28
+ "@deepseek-ai/cordis": "^4.0.1",
29
+ "@deepseek-ai/dsh-invariants": "^0.0.1-rc.1",
30
+ "@deepseek-ai/dsh-llm": "^0.1.0-rc.8"
31
+ },
32
+ "devDependencies": {
33
+ "@deepseek-ai/dsh-attachment": "0.0.1-rc.5",
34
+ "@deepseek-ai/dsh-brand": "0.0.1-rc.5",
35
+ "@deepseek-ai/dsh-invariants": "0.0.1-rc.5",
36
+ "@deepseek-ai/dsh-scope": "0.0.1-rc.5",
37
+ "@deepseek-ai/dsh-timeout": "0.0.1-rc.5",
38
+ "@deepseek-ai/cordis": "4.0.1",
39
+ "@types/node": "^22.0.0",
40
+ "typescript": "^5.4.0",
41
+ "vitest": "^4.1.11",
42
+ "@deepseek-ai/dsh-llm": "0.1.0-rc.8",
43
+ "@deepseek-ai/dsh-tools": "0.0.1-rc.1"
44
+ },
45
+ "license": "MIT",
46
+ "exports": {
47
+ ".": {
48
+ "types": "./lib/index.d.ts",
49
+ "default": "./lib/index.js"
50
+ },
51
+ "./invariant": {
52
+ "types": "./lib/invariant.d.ts",
53
+ "default": "./lib/invariant.js"
54
+ },
55
+ "./types": {
56
+ "types": "./lib/types.d.ts",
57
+ "default": "./lib/types.js"
58
+ },
59
+ "./mention": {
60
+ "types": "./lib/mention.d.ts",
61
+ "default": "./lib/mention.js"
62
+ },
63
+ "./package.json": "./package.json"
64
+ },
65
+ "publishConfig": {
66
+ "access": "public"
67
+ },
68
+ "scripts": {
69
+ "build": "tsc",
70
+ "typecheck": "tsc --noEmit",
71
+ "test": "vitest run"
72
+ }
73
+ }