@mccaskie-labs/workbench-config 0.1.13 → 0.1.15
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/CHANGELOG.md +13 -0
- package/capabilities/ai.json +51 -4
- package/capabilities/config.json +9 -2
- package/capabilities/email.json +8 -7
- package/dist/capabilities.gen.d.ts +85 -10
- package/dist/capabilities.gen.js +137 -11
- package/dist/capability-schema.d.ts +14 -0
- package/dist/capability-schema.js +14 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# @mccaskie-labs/workbench-config
|
|
2
2
|
|
|
3
|
+
## 0.1.15
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- The `email` manifest's probe ids now match wb-probe's, which already hold stored history and appear in the phase 4 checkpoint: `email.send.inline-attachment` (was `email.send.inline-image`) and `email.send.dev-key-recipient-refused` (was `email.send.dev-recipient-refused`). `email.send.transactional` is no longer in the daily smoke run, so the smoke run sends no daily mail. `email.send.new-sender` (after the Control drill's `gw_sender_add`) is listed.
|
|
8
|
+
- The `config` manifest lists the brand file exports (`BRAND_FILE`, `brandSchema`, `defineBrand`, `resolveBrand`, `BRAND_DEFAULTS`), a rule to keep one `brand.json` per app, and the probe `config.brand-file`.
|
|
9
|
+
|
|
10
|
+
## 0.1.14
|
|
11
|
+
|
|
12
|
+
### Patch Changes
|
|
13
|
+
|
|
14
|
+
- Shared voices (`docs/spec.md` §2.8). `workbench-ai`: `voice` and `spelling` options on `text`, `structured`, `stream` and `countTokens` (a Workbench voice by name, or `{ instructions }` for the app's own), `voice` on a text result, and a writing check with no AI call: `checkWriting`, `writingStops`, `writingNotes`, `writingFixNote`, `paragraphBands`. Paragraph length is a target: quiet up to 1.25 times, a note up to double, a stop only past that. `createFakeAi` records the voice and spelling of each call. `workbench-config`: the `ai` manifest lists the voices, spellings, targets and banned phrases, and three probes. `workbench-ai` now needs `workbench-config` 0.1.14.
|
|
15
|
+
|
|
3
16
|
## 0.1.13
|
|
4
17
|
|
|
5
18
|
### Patch Changes
|
package/capabilities/ai.json
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"status": "built",
|
|
8
8
|
"surfaces": {
|
|
9
9
|
"gateway": ["POST /anthropic/v1/messages", "POST /anthropic/v1/messages/count_tokens"],
|
|
10
|
-
"package": ["createAi", "Ai.text", "Ai.structured", "Ai.stream", "Ai.countTokens", "Ai.budget", "createFakeAi"]
|
|
10
|
+
"package": ["createAi", "Ai.text", "Ai.structured", "Ai.stream", "Ai.countTokens", "Ai.budget", "createFakeAi", "checkWriting", "writingStops", "writingNotes", "writingFixNote"]
|
|
11
11
|
},
|
|
12
12
|
"config": [
|
|
13
13
|
{
|
|
@@ -33,7 +33,43 @@
|
|
|
33
33
|
],
|
|
34
34
|
"explicit_models": ["claude-sonnet-5", "claude-opus-5"],
|
|
35
35
|
"server_tools": ["web_search", "web_fetch", "code_execution"],
|
|
36
|
-
"required_headers": ["X-Workbench-Job"]
|
|
36
|
+
"required_headers": ["X-Workbench-Job"],
|
|
37
|
+
"optional_headers": ["X-Workbench-Voice", "X-Workbench-Spelling"],
|
|
38
|
+
"voices": [
|
|
39
|
+
{ "name": "casual", "paragraph_target": 50, "sentence_target": 18 },
|
|
40
|
+
{ "name": "conversational", "paragraph_target": 60, "sentence_target": 22 },
|
|
41
|
+
{ "name": "plain", "paragraph_target": 60, "sentence_target": 20 },
|
|
42
|
+
{ "name": "formal", "paragraph_target": 80, "sentence_target": 25 },
|
|
43
|
+
{ "name": "strict-formal", "paragraph_target": 80, "sentence_target": 28 }
|
|
44
|
+
],
|
|
45
|
+
"spellings": ["en-AU", "en-PH", "en-GB", "en-US"],
|
|
46
|
+
"writing": {
|
|
47
|
+
"voice_set_version": 1,
|
|
48
|
+
"house_paragraph_target": 80,
|
|
49
|
+
"paragraph_grace": 1.25,
|
|
50
|
+
"paragraph_ceiling": 2,
|
|
51
|
+
"banned_phrases": [
|
|
52
|
+
"delve",
|
|
53
|
+
"it's worth noting",
|
|
54
|
+
"it is worth noting",
|
|
55
|
+
"it's important to note",
|
|
56
|
+
"it is important to note",
|
|
57
|
+
"in today's fast-paced world",
|
|
58
|
+
"navigate the complexities",
|
|
59
|
+
"a testament to",
|
|
60
|
+
"game-changer",
|
|
61
|
+
"game-changing",
|
|
62
|
+
"let's dive in",
|
|
63
|
+
"dive into",
|
|
64
|
+
"rest assured",
|
|
65
|
+
"in conclusion",
|
|
66
|
+
"at the end of the day",
|
|
67
|
+
"the world of",
|
|
68
|
+
"unlock the",
|
|
69
|
+
"embark on",
|
|
70
|
+
"tapestry"
|
|
71
|
+
]
|
|
72
|
+
}
|
|
37
73
|
},
|
|
38
74
|
"limits": [
|
|
39
75
|
{ "id": "ai.retry-attempts", "value": 3, "unit": "attempts", "per": "request", "enforced_by": "gateway" }
|
|
@@ -45,7 +81,8 @@
|
|
|
45
81
|
"rules": [
|
|
46
82
|
{ "id": "ai.no-partial-success", "kind": "must_not", "text": "Treat a refusal or a max_tokens truncation as a successful result.", "enforced_by": "types", "probe": "ai.text.truncation-throws" },
|
|
47
83
|
{ "id": "ai.store-resolved-model", "kind": "must", "text": "Store the resolved model and request id with any output kept.", "enforced_by": "review", "probe": "ai.text.claude-default" },
|
|
48
|
-
{ "id": "ai.aliases-only", "kind": "must", "text": "Ask for a Workbench alias (or an allowlisted explicit id), never an arbitrary model id.", "enforced_by": "gateway", "probe": "ai.model-not-allowed" }
|
|
84
|
+
{ "id": "ai.aliases-only", "kind": "must", "text": "Ask for a Workbench alias (or an allowlisted explicit id), never an arbitrary model id.", "enforced_by": "gateway", "probe": "ai.model-not-allowed" },
|
|
85
|
+
{ "id": "ai.voice-by-name", "kind": "must", "text": "Name a Workbench voice and spelling with the voice and spelling options, so the Gateway adds the text, or bring the app's own voice. Never paste Workbench's voice text into a prompt.", "enforced_by": "gateway", "probe": "ai.voice" }
|
|
49
86
|
],
|
|
50
87
|
"examples": [
|
|
51
88
|
{
|
|
@@ -54,6 +91,13 @@
|
|
|
54
91
|
"lang": "ts",
|
|
55
92
|
"probe": "ai.text.claude-default",
|
|
56
93
|
"code": "const ai = createAi({ key: env.WORKBENCH_GATEWAY_KEY, env: workbenchEnv(env) })\nconst r = await ai.text({ model: 'claude-default', job: 'article.write', prompt })\nawait save({ text: r.text, model: r.model, requestId: r.requestId })"
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"id": "ai.example.voice",
|
|
97
|
+
"title": "Text in a Workbench voice, then checked without AI",
|
|
98
|
+
"lang": "ts",
|
|
99
|
+
"probe": "ai.voice",
|
|
100
|
+
"code": "const r = await ai.text({ model: 'claude-default', job: 'article.write', voice: 'conversational', spelling: 'en-AU', prompt })\nconst issues = checkWriting(r.text, { voice: 'conversational' })\nif (writingStops(issues).length > 0) await retryWith(prompt + writingFixNote(issues))"
|
|
57
101
|
}
|
|
58
102
|
],
|
|
59
103
|
"probes": [
|
|
@@ -65,6 +109,9 @@
|
|
|
65
109
|
{ "id": "ai.text.truncation-throws", "half": "runtime", "expect": "pass", "cost_hint_usd": 0.001 },
|
|
66
110
|
{ "id": "ai.model-not-allowed", "half": "runtime", "expect": "refused_as_expected", "refusal": "model_not_allowed", "cost_hint_usd": 0 },
|
|
67
111
|
{ "id": "ai.count-tokens", "half": "runtime", "expect": "pass", "smoke": true, "cost_hint_usd": 0 },
|
|
68
|
-
{ "id": "ai.budget", "half": "runtime", "expect": "pass", "smoke": true, "cost_hint_usd": 0 }
|
|
112
|
+
{ "id": "ai.budget", "half": "runtime", "expect": "pass", "smoke": true, "cost_hint_usd": 0 },
|
|
113
|
+
{ "id": "ai.voice", "half": "runtime", "expect": "pass", "cost_hint_usd": 0.004 },
|
|
114
|
+
{ "id": "ai.voice.unknown", "half": "runtime", "expect": "refused_as_expected", "refusal": "invalid_request", "cost_hint_usd": 0 },
|
|
115
|
+
{ "id": "ai.voice.cache-read", "half": "runtime", "expect": "pass", "cost_hint_usd": 0.008 }
|
|
69
116
|
]
|
|
70
117
|
}
|
package/capabilities/config.json
CHANGED
|
@@ -17,7 +17,12 @@
|
|
|
17
17
|
"capabilitySchema",
|
|
18
18
|
"capabilities",
|
|
19
19
|
"MANIFEST_VERSION",
|
|
20
|
-
"compareDevVars"
|
|
20
|
+
"compareDevVars",
|
|
21
|
+
"BRAND_FILE",
|
|
22
|
+
"brandSchema",
|
|
23
|
+
"defineBrand",
|
|
24
|
+
"resolveBrand",
|
|
25
|
+
"BRAND_DEFAULTS"
|
|
21
26
|
]
|
|
22
27
|
},
|
|
23
28
|
"config": [
|
|
@@ -36,7 +41,8 @@
|
|
|
36
41
|
{ "id": "config.fail-by-name", "kind": "must", "text": "Fail at startup when a required variable is missing or invalid, naming every bad variable in one error.", "enforced_by": "types", "probe": "config.missing-variable-named" },
|
|
37
42
|
{ "id": "config.never-print-values", "kind": "must_not", "text": "Print, log or include in an error the value of any variable, valid or not.", "enforced_by": "types", "probe": "config.value-never-printed" },
|
|
38
43
|
{ "id": "config.no-node-env", "kind": "must_not", "text": "Decide the environment from NODE_ENV or a default. WORKBENCH_ENV is required and has no fallback.", "enforced_by": "types", "probe": "config.env-required" },
|
|
39
|
-
{ "id": "config.dev-vars-example", "kind": "must", "text": "Keep .dev.vars.example listing every variable .dev.vars has, with no values; check with npx workbench-config check.", "enforced_by": "review", "probe": "config.dev-vars-check" }
|
|
44
|
+
{ "id": "config.dev-vars-example", "kind": "must", "text": "Keep .dev.vars.example listing every variable .dev.vars has, with no values; check with npx workbench-config check.", "enforced_by": "review", "probe": "config.dev-vars-check" },
|
|
45
|
+
{ "id": "config.one-brand-file", "kind": "must", "text": "Keep the app's name, logo, colours and fonts in brand.json at the repo root, read with defineBrand, so emails, PDFs and sign-in mails are branded from one file.", "enforced_by": "types", "probe": "config.brand-file" }
|
|
40
46
|
],
|
|
41
47
|
"examples": [
|
|
42
48
|
{
|
|
@@ -51,6 +57,7 @@
|
|
|
51
57
|
{ "id": "config.missing-variable-named", "half": "runtime", "expect": "pass", "smoke": true, "cost_hint_usd": 0 },
|
|
52
58
|
{ "id": "config.value-never-printed", "half": "runtime", "expect": "pass", "smoke": true, "cost_hint_usd": 0 },
|
|
53
59
|
{ "id": "config.env-required", "half": "runtime", "expect": "pass", "cost_hint_usd": 0 },
|
|
60
|
+
{ "id": "config.brand-file", "half": "runtime", "expect": "pass", "cost_hint_usd": 0, "note": "The app's brand.json passes defineBrand and a bad one is refused with every bad field named" },
|
|
54
61
|
{ "id": "config.dev-vars-check", "half": "runtime", "expect": "pass", "cost_hint_usd": 0, "note": "Manual: run npx workbench-config check in the project" }
|
|
55
62
|
]
|
|
56
63
|
}
|
package/capabilities/email.json
CHANGED
|
@@ -35,10 +35,10 @@
|
|
|
35
35
|
"rules": [
|
|
36
36
|
{ "id": "email.text-always", "kind": "must", "text": "Send a text part with every mail, even when there is HTML.", "enforced_by": "gateway", "probe": "email.send.transactional" },
|
|
37
37
|
{ "id": "email.idempotency-key", "kind": "must", "text": "Give an idempotencyKey to any send a retry could repeat (invites, receipts, resets). A repeat within 24 hours returns the first result and sends nothing.", "enforced_by": "review", "probe": "email.send.idempotent-repeat" },
|
|
38
|
-
{ "id": "email.attachments-as-bytes", "kind": "must", "text": "Pass attachments as bytes (Uint8Array), and give an inline image a contentId that the HTML references as cid:.", "enforced_by": "types", "probe": "email.send.inline-
|
|
38
|
+
{ "id": "email.attachments-as-bytes", "kind": "must", "text": "Pass attachments as bytes (Uint8Array), and give an inline image a contentId that the HTML references as cid:.", "enforced_by": "types", "probe": "email.send.inline-attachment" },
|
|
39
39
|
{ "id": "email.allowed-sender", "kind": "must", "text": "Send only from an address added with gw_sender_add on a domain onboarded with cf_email_sending_onboard.", "enforced_by": "gateway", "probe": "email.send.sender-not-allowed" },
|
|
40
40
|
{ "id": "email.no-capture-outside-dev", "kind": "must_not", "text": "Use the capture transport outside development. createMailer throws when WORKBENCH_ENV is staging or production.", "enforced_by": "types", "probe": "email.capture-refused-in-staging" },
|
|
41
|
-
{ "id": "email.dev-key-allowlist", "kind": "must_not", "text": "Expect a development key to mail anyone but Robert. The Gateway refuses other recipients before the budget.", "enforced_by": "gateway", "probe": "email.send.dev-recipient-refused" },
|
|
41
|
+
{ "id": "email.dev-key-allowlist", "kind": "must_not", "text": "Expect a development key to mail anyone but Robert. The Gateway refuses other recipients before the budget.", "enforced_by": "gateway", "probe": "email.send.dev-key-recipient-refused" },
|
|
42
42
|
{ "id": "email.transactional-only", "kind": "must_not", "text": "Send bulk or marketing mail. type bulk is refused with not_available until Amazon SES is built.", "enforced_by": "gateway", "probe": "email.send.transactional" }
|
|
43
43
|
],
|
|
44
44
|
"examples": [
|
|
@@ -46,16 +46,17 @@
|
|
|
46
46
|
"id": "email.example.send",
|
|
47
47
|
"title": "Send a transactional mail with an inline logo",
|
|
48
48
|
"lang": "ts",
|
|
49
|
-
"probe": "email.send.inline-
|
|
49
|
+
"probe": "email.send.inline-attachment",
|
|
50
50
|
"code": "const env = workbenchEnv(process.env)\nconst mailer = createMailer({ env, transport: selectTransport(env), from: { address: 'hello@mail.example.com', name: 'Example' }, gateway: { key: process.env.WORKBENCH_GATEWAY_KEY! } })\nawait mailer.send({ to: user.email, subject: 'Welcome', text, html: '<img src=\"cid:logo\"> ...', template: 'welcome', idempotencyKey: `welcome:${user.id}`, attachments: [{ filename: 'logo.png', mimeType: 'image/png', bytes: logo, contentId: 'logo' }] })"
|
|
51
51
|
}
|
|
52
52
|
],
|
|
53
53
|
"probes": [
|
|
54
|
-
{ "id": "email.send.transactional", "half": "runtime", "expect": "pass", "
|
|
55
|
-
{ "id": "email.send.inline-
|
|
54
|
+
{ "id": "email.send.transactional", "half": "runtime", "expect": "pass", "cost_hint_usd": 0.00035, "note": "To Robert; check that it arrives. Not in the daily smoke run, so it sends no daily mail" },
|
|
55
|
+
{ "id": "email.send.inline-attachment", "half": "runtime", "expect": "pass", "cost_hint_usd": 0.00035, "note": "Robert checks the image renders" },
|
|
56
56
|
{ "id": "email.send.idempotent-repeat", "half": "runtime", "expect": "pass", "cost_hint_usd": 0.00035, "note": "The repeat answers replayed: true and no second mail arrives" },
|
|
57
|
-
{ "id": "email.send.dev-recipient-refused", "half": "runtime", "expect": "refused_as_expected", "refusal": "recipient_not_allowed", "cost_hint_usd": 0 },
|
|
57
|
+
{ "id": "email.send.dev-key-recipient-refused", "half": "runtime", "expect": "refused_as_expected", "refusal": "recipient_not_allowed", "cost_hint_usd": 0 },
|
|
58
58
|
{ "id": "email.send.sender-not-allowed", "half": "runtime", "expect": "refused_as_expected", "refusal": "sender_not_allowed", "cost_hint_usd": 0 },
|
|
59
|
-
{ "id": "email.capture-refused-in-staging", "half": "runtime", "expect": "pass", "cost_hint_usd": 0, "note": "createMailer with transport capture and WORKBENCH_ENV=staging throws invalid_config" }
|
|
59
|
+
{ "id": "email.capture-refused-in-staging", "half": "runtime", "expect": "pass", "cost_hint_usd": 0, "note": "createMailer with transport capture and WORKBENCH_ENV=staging throws invalid_config" },
|
|
60
|
+
{ "id": "email.send.new-sender", "half": "runtime", "expect": "pass", "cost_hint_usd": 0.00035, "note": "Runs after the Control drill's gw_sender_add step; skipped without it" }
|
|
60
61
|
]
|
|
61
62
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const MANIFEST_VERSION: "
|
|
1
|
+
export declare const MANIFEST_VERSION: "f81423629d34";
|
|
2
2
|
export declare const capabilities: {
|
|
3
3
|
readonly ai: {
|
|
4
4
|
readonly $schema: "./schema.json";
|
|
@@ -9,7 +9,7 @@ export declare const capabilities: {
|
|
|
9
9
|
readonly status: "built";
|
|
10
10
|
readonly surfaces: {
|
|
11
11
|
readonly gateway: readonly ["POST /anthropic/v1/messages", "POST /anthropic/v1/messages/count_tokens"];
|
|
12
|
-
readonly package: readonly ["createAi", "Ai.text", "Ai.structured", "Ai.stream", "Ai.countTokens", "Ai.budget", "createFakeAi"];
|
|
12
|
+
readonly package: readonly ["createAi", "Ai.text", "Ai.structured", "Ai.stream", "Ai.countTokens", "Ai.budget", "createFakeAi", "checkWriting", "writingStops", "writingNotes", "writingFixNote"];
|
|
13
13
|
};
|
|
14
14
|
readonly config: readonly [{
|
|
15
15
|
readonly name: "WORKBENCH_GATEWAY_KEY";
|
|
@@ -41,6 +41,36 @@ export declare const capabilities: {
|
|
|
41
41
|
readonly explicit_models: readonly ["claude-sonnet-5", "claude-opus-5"];
|
|
42
42
|
readonly server_tools: readonly ["web_search", "web_fetch", "code_execution"];
|
|
43
43
|
readonly required_headers: readonly ["X-Workbench-Job"];
|
|
44
|
+
readonly optional_headers: readonly ["X-Workbench-Voice", "X-Workbench-Spelling"];
|
|
45
|
+
readonly voices: readonly [{
|
|
46
|
+
readonly name: "casual";
|
|
47
|
+
readonly paragraph_target: 50;
|
|
48
|
+
readonly sentence_target: 18;
|
|
49
|
+
}, {
|
|
50
|
+
readonly name: "conversational";
|
|
51
|
+
readonly paragraph_target: 60;
|
|
52
|
+
readonly sentence_target: 22;
|
|
53
|
+
}, {
|
|
54
|
+
readonly name: "plain";
|
|
55
|
+
readonly paragraph_target: 60;
|
|
56
|
+
readonly sentence_target: 20;
|
|
57
|
+
}, {
|
|
58
|
+
readonly name: "formal";
|
|
59
|
+
readonly paragraph_target: 80;
|
|
60
|
+
readonly sentence_target: 25;
|
|
61
|
+
}, {
|
|
62
|
+
readonly name: "strict-formal";
|
|
63
|
+
readonly paragraph_target: 80;
|
|
64
|
+
readonly sentence_target: 28;
|
|
65
|
+
}];
|
|
66
|
+
readonly spellings: readonly ["en-AU", "en-PH", "en-GB", "en-US"];
|
|
67
|
+
readonly writing: {
|
|
68
|
+
readonly voice_set_version: 1;
|
|
69
|
+
readonly house_paragraph_target: 80;
|
|
70
|
+
readonly paragraph_grace: 1.25;
|
|
71
|
+
readonly paragraph_ceiling: 2;
|
|
72
|
+
readonly banned_phrases: readonly ["delve", "it's worth noting", "it is worth noting", "it's important to note", "it is important to note", "in today's fast-paced world", "navigate the complexities", "a testament to", "game-changer", "game-changing", "let's dive in", "dive into", "rest assured", "in conclusion", "at the end of the day", "the world of", "unlock the", "embark on", "tapestry"];
|
|
73
|
+
};
|
|
44
74
|
};
|
|
45
75
|
readonly limits: readonly [{
|
|
46
76
|
readonly id: "ai.retry-attempts";
|
|
@@ -78,6 +108,12 @@ export declare const capabilities: {
|
|
|
78
108
|
readonly text: "Ask for a Workbench alias (or an allowlisted explicit id), never an arbitrary model id.";
|
|
79
109
|
readonly enforced_by: "gateway";
|
|
80
110
|
readonly probe: "ai.model-not-allowed";
|
|
111
|
+
}, {
|
|
112
|
+
readonly id: "ai.voice-by-name";
|
|
113
|
+
readonly kind: "must";
|
|
114
|
+
readonly text: "Name a Workbench voice and spelling with the voice and spelling options, so the Gateway adds the text, or bring the app's own voice. Never paste Workbench's voice text into a prompt.";
|
|
115
|
+
readonly enforced_by: "gateway";
|
|
116
|
+
readonly probe: "ai.voice";
|
|
81
117
|
}];
|
|
82
118
|
readonly examples: readonly [{
|
|
83
119
|
readonly id: "ai.example.text";
|
|
@@ -85,6 +121,12 @@ export declare const capabilities: {
|
|
|
85
121
|
readonly lang: "ts";
|
|
86
122
|
readonly code: "const ai = createAi({ key: env.WORKBENCH_GATEWAY_KEY, env: workbenchEnv(env) })\nconst r = await ai.text({ model: 'claude-default', job: 'article.write', prompt })\nawait save({ text: r.text, model: r.model, requestId: r.requestId })";
|
|
87
123
|
readonly probe: "ai.text.claude-default";
|
|
124
|
+
}, {
|
|
125
|
+
readonly id: "ai.example.voice";
|
|
126
|
+
readonly title: "Text in a Workbench voice, then checked without AI";
|
|
127
|
+
readonly lang: "ts";
|
|
128
|
+
readonly code: "const r = await ai.text({ model: 'claude-default', job: 'article.write', voice: 'conversational', spelling: 'en-AU', prompt })\nconst issues = checkWriting(r.text, { voice: 'conversational' })\nif (writingStops(issues).length > 0) await retryWith(prompt + writingFixNote(issues))";
|
|
129
|
+
readonly probe: "ai.voice";
|
|
88
130
|
}];
|
|
89
131
|
readonly probes: readonly [{
|
|
90
132
|
readonly id: "ai.text.claude-default";
|
|
@@ -135,6 +177,22 @@ export declare const capabilities: {
|
|
|
135
177
|
readonly expect: "pass";
|
|
136
178
|
readonly smoke: true;
|
|
137
179
|
readonly cost_hint_usd: 0;
|
|
180
|
+
}, {
|
|
181
|
+
readonly id: "ai.voice";
|
|
182
|
+
readonly half: "runtime";
|
|
183
|
+
readonly expect: "pass";
|
|
184
|
+
readonly cost_hint_usd: 0.004;
|
|
185
|
+
}, {
|
|
186
|
+
readonly id: "ai.voice.unknown";
|
|
187
|
+
readonly half: "runtime";
|
|
188
|
+
readonly expect: "refused_as_expected";
|
|
189
|
+
readonly refusal: "invalid_request";
|
|
190
|
+
readonly cost_hint_usd: 0;
|
|
191
|
+
}, {
|
|
192
|
+
readonly id: "ai.voice.cache-read";
|
|
193
|
+
readonly half: "runtime";
|
|
194
|
+
readonly expect: "pass";
|
|
195
|
+
readonly cost_hint_usd: 0.008;
|
|
138
196
|
}];
|
|
139
197
|
};
|
|
140
198
|
readonly analytics: {
|
|
@@ -217,7 +275,7 @@ export declare const capabilities: {
|
|
|
217
275
|
readonly summary: "Reads WORKBENCH_ENV and the app's own variables and secrets. A missing or invalid variable fails at startup, by name, and no value is ever printed.";
|
|
218
276
|
readonly status: "built";
|
|
219
277
|
readonly surfaces: {
|
|
220
|
-
readonly package: readonly ["workbenchEnv", "defineConfig", "sharedSecret", "workerSecret", "redact", "redactAll", "WorkbenchConfigError", "capabilitySchema", "capabilities", "MANIFEST_VERSION", "compareDevVars"];
|
|
278
|
+
readonly package: readonly ["workbenchEnv", "defineConfig", "sharedSecret", "workerSecret", "redact", "redactAll", "WorkbenchConfigError", "capabilitySchema", "capabilities", "MANIFEST_VERSION", "compareDevVars", "BRAND_FILE", "brandSchema", "defineBrand", "resolveBrand", "BRAND_DEFAULTS"];
|
|
221
279
|
};
|
|
222
280
|
readonly config: readonly [{
|
|
223
281
|
readonly name: "WORKBENCH_ENV";
|
|
@@ -253,6 +311,12 @@ export declare const capabilities: {
|
|
|
253
311
|
readonly text: "Keep .dev.vars.example listing every variable .dev.vars has, with no values; check with npx workbench-config check.";
|
|
254
312
|
readonly enforced_by: "review";
|
|
255
313
|
readonly probe: "config.dev-vars-check";
|
|
314
|
+
}, {
|
|
315
|
+
readonly id: "config.one-brand-file";
|
|
316
|
+
readonly kind: "must";
|
|
317
|
+
readonly text: "Keep the app's name, logo, colours and fonts in brand.json at the repo root, read with defineBrand, so emails, PDFs and sign-in mails are branded from one file.";
|
|
318
|
+
readonly enforced_by: "types";
|
|
319
|
+
readonly probe: "config.brand-file";
|
|
256
320
|
}];
|
|
257
321
|
readonly examples: readonly [{
|
|
258
322
|
readonly id: "config.example.define";
|
|
@@ -278,6 +342,12 @@ export declare const capabilities: {
|
|
|
278
342
|
readonly half: "runtime";
|
|
279
343
|
readonly expect: "pass";
|
|
280
344
|
readonly cost_hint_usd: 0;
|
|
345
|
+
}, {
|
|
346
|
+
readonly id: "config.brand-file";
|
|
347
|
+
readonly half: "runtime";
|
|
348
|
+
readonly expect: "pass";
|
|
349
|
+
readonly cost_hint_usd: 0;
|
|
350
|
+
readonly note: "The app's brand.json passes defineBrand and a bad one is refused with every bad field named";
|
|
281
351
|
}, {
|
|
282
352
|
readonly id: "config.dev-vars-check";
|
|
283
353
|
readonly half: "runtime";
|
|
@@ -552,7 +622,7 @@ export declare const capabilities: {
|
|
|
552
622
|
readonly kind: "must";
|
|
553
623
|
readonly text: "Pass attachments as bytes (Uint8Array), and give an inline image a contentId that the HTML references as cid:.";
|
|
554
624
|
readonly enforced_by: "types";
|
|
555
|
-
readonly probe: "email.send.inline-
|
|
625
|
+
readonly probe: "email.send.inline-attachment";
|
|
556
626
|
}, {
|
|
557
627
|
readonly id: "email.allowed-sender";
|
|
558
628
|
readonly kind: "must";
|
|
@@ -570,7 +640,7 @@ export declare const capabilities: {
|
|
|
570
640
|
readonly kind: "must_not";
|
|
571
641
|
readonly text: "Expect a development key to mail anyone but Robert. The Gateway refuses other recipients before the budget.";
|
|
572
642
|
readonly enforced_by: "gateway";
|
|
573
|
-
readonly probe: "email.send.dev-recipient-refused";
|
|
643
|
+
readonly probe: "email.send.dev-key-recipient-refused";
|
|
574
644
|
}, {
|
|
575
645
|
readonly id: "email.transactional-only";
|
|
576
646
|
readonly kind: "must_not";
|
|
@@ -583,17 +653,16 @@ export declare const capabilities: {
|
|
|
583
653
|
readonly title: "Send a transactional mail with an inline logo";
|
|
584
654
|
readonly lang: "ts";
|
|
585
655
|
readonly code: "const env = workbenchEnv(process.env)\nconst mailer = createMailer({ env, transport: selectTransport(env), from: { address: 'hello@mail.example.com', name: 'Example' }, gateway: { key: process.env.WORKBENCH_GATEWAY_KEY! } })\nawait mailer.send({ to: user.email, subject: 'Welcome', text, html: '<img src=\"cid:logo\"> ...', template: 'welcome', idempotencyKey: `welcome:${user.id}`, attachments: [{ filename: 'logo.png', mimeType: 'image/png', bytes: logo, contentId: 'logo' }] })";
|
|
586
|
-
readonly probe: "email.send.inline-
|
|
656
|
+
readonly probe: "email.send.inline-attachment";
|
|
587
657
|
}];
|
|
588
658
|
readonly probes: readonly [{
|
|
589
659
|
readonly id: "email.send.transactional";
|
|
590
660
|
readonly half: "runtime";
|
|
591
661
|
readonly expect: "pass";
|
|
592
|
-
readonly smoke: true;
|
|
593
662
|
readonly cost_hint_usd: 0.00035;
|
|
594
|
-
readonly note: "To Robert; check that it arrives";
|
|
663
|
+
readonly note: "To Robert; check that it arrives. Not in the daily smoke run, so it sends no daily mail";
|
|
595
664
|
}, {
|
|
596
|
-
readonly id: "email.send.inline-
|
|
665
|
+
readonly id: "email.send.inline-attachment";
|
|
597
666
|
readonly half: "runtime";
|
|
598
667
|
readonly expect: "pass";
|
|
599
668
|
readonly cost_hint_usd: 0.00035;
|
|
@@ -605,7 +674,7 @@ export declare const capabilities: {
|
|
|
605
674
|
readonly cost_hint_usd: 0.00035;
|
|
606
675
|
readonly note: "The repeat answers replayed: true and no second mail arrives";
|
|
607
676
|
}, {
|
|
608
|
-
readonly id: "email.send.dev-recipient-refused";
|
|
677
|
+
readonly id: "email.send.dev-key-recipient-refused";
|
|
609
678
|
readonly half: "runtime";
|
|
610
679
|
readonly expect: "refused_as_expected";
|
|
611
680
|
readonly refusal: "recipient_not_allowed";
|
|
@@ -622,6 +691,12 @@ export declare const capabilities: {
|
|
|
622
691
|
readonly expect: "pass";
|
|
623
692
|
readonly cost_hint_usd: 0;
|
|
624
693
|
readonly note: "createMailer with transport capture and WORKBENCH_ENV=staging throws invalid_config";
|
|
694
|
+
}, {
|
|
695
|
+
readonly id: "email.send.new-sender";
|
|
696
|
+
readonly half: "runtime";
|
|
697
|
+
readonly expect: "pass";
|
|
698
|
+
readonly cost_hint_usd: 0.00035;
|
|
699
|
+
readonly note: "Runs after the Control drill's gw_sender_add step; skipped without it";
|
|
625
700
|
}];
|
|
626
701
|
};
|
|
627
702
|
readonly "email-inbound": {
|
package/dist/capabilities.gen.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Generated by `npm run capabilities:build` from capabilities/*.json. Do not edit.
|
|
2
2
|
// check:capabilities fails in Checks when this file is stale.
|
|
3
|
-
export const MANIFEST_VERSION = "
|
|
3
|
+
export const MANIFEST_VERSION = "f81423629d34";
|
|
4
4
|
export const capabilities = {
|
|
5
5
|
"ai": {
|
|
6
6
|
"$schema": "./schema.json",
|
|
@@ -21,7 +21,11 @@ export const capabilities = {
|
|
|
21
21
|
"Ai.stream",
|
|
22
22
|
"Ai.countTokens",
|
|
23
23
|
"Ai.budget",
|
|
24
|
-
"createFakeAi"
|
|
24
|
+
"createFakeAi",
|
|
25
|
+
"checkWriting",
|
|
26
|
+
"writingStops",
|
|
27
|
+
"writingNotes",
|
|
28
|
+
"writingFixNote"
|
|
25
29
|
]
|
|
26
30
|
},
|
|
27
31
|
"config": [
|
|
@@ -76,7 +80,71 @@ export const capabilities = {
|
|
|
76
80
|
],
|
|
77
81
|
"required_headers": [
|
|
78
82
|
"X-Workbench-Job"
|
|
79
|
-
]
|
|
83
|
+
],
|
|
84
|
+
"optional_headers": [
|
|
85
|
+
"X-Workbench-Voice",
|
|
86
|
+
"X-Workbench-Spelling"
|
|
87
|
+
],
|
|
88
|
+
"voices": [
|
|
89
|
+
{
|
|
90
|
+
"name": "casual",
|
|
91
|
+
"paragraph_target": 50,
|
|
92
|
+
"sentence_target": 18
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"name": "conversational",
|
|
96
|
+
"paragraph_target": 60,
|
|
97
|
+
"sentence_target": 22
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"name": "plain",
|
|
101
|
+
"paragraph_target": 60,
|
|
102
|
+
"sentence_target": 20
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
"name": "formal",
|
|
106
|
+
"paragraph_target": 80,
|
|
107
|
+
"sentence_target": 25
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
"name": "strict-formal",
|
|
111
|
+
"paragraph_target": 80,
|
|
112
|
+
"sentence_target": 28
|
|
113
|
+
}
|
|
114
|
+
],
|
|
115
|
+
"spellings": [
|
|
116
|
+
"en-AU",
|
|
117
|
+
"en-PH",
|
|
118
|
+
"en-GB",
|
|
119
|
+
"en-US"
|
|
120
|
+
],
|
|
121
|
+
"writing": {
|
|
122
|
+
"voice_set_version": 1,
|
|
123
|
+
"house_paragraph_target": 80,
|
|
124
|
+
"paragraph_grace": 1.25,
|
|
125
|
+
"paragraph_ceiling": 2,
|
|
126
|
+
"banned_phrases": [
|
|
127
|
+
"delve",
|
|
128
|
+
"it's worth noting",
|
|
129
|
+
"it is worth noting",
|
|
130
|
+
"it's important to note",
|
|
131
|
+
"it is important to note",
|
|
132
|
+
"in today's fast-paced world",
|
|
133
|
+
"navigate the complexities",
|
|
134
|
+
"a testament to",
|
|
135
|
+
"game-changer",
|
|
136
|
+
"game-changing",
|
|
137
|
+
"let's dive in",
|
|
138
|
+
"dive into",
|
|
139
|
+
"rest assured",
|
|
140
|
+
"in conclusion",
|
|
141
|
+
"at the end of the day",
|
|
142
|
+
"the world of",
|
|
143
|
+
"unlock the",
|
|
144
|
+
"embark on",
|
|
145
|
+
"tapestry"
|
|
146
|
+
]
|
|
147
|
+
}
|
|
80
148
|
},
|
|
81
149
|
"limits": [
|
|
82
150
|
{
|
|
@@ -122,6 +190,13 @@ export const capabilities = {
|
|
|
122
190
|
"text": "Ask for a Workbench alias (or an allowlisted explicit id), never an arbitrary model id.",
|
|
123
191
|
"enforced_by": "gateway",
|
|
124
192
|
"probe": "ai.model-not-allowed"
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
"id": "ai.voice-by-name",
|
|
196
|
+
"kind": "must",
|
|
197
|
+
"text": "Name a Workbench voice and spelling with the voice and spelling options, so the Gateway adds the text, or bring the app's own voice. Never paste Workbench's voice text into a prompt.",
|
|
198
|
+
"enforced_by": "gateway",
|
|
199
|
+
"probe": "ai.voice"
|
|
125
200
|
}
|
|
126
201
|
],
|
|
127
202
|
"examples": [
|
|
@@ -131,6 +206,13 @@ export const capabilities = {
|
|
|
131
206
|
"lang": "ts",
|
|
132
207
|
"code": "const ai = createAi({ key: env.WORKBENCH_GATEWAY_KEY, env: workbenchEnv(env) })\nconst r = await ai.text({ model: 'claude-default', job: 'article.write', prompt })\nawait save({ text: r.text, model: r.model, requestId: r.requestId })",
|
|
133
208
|
"probe": "ai.text.claude-default"
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
"id": "ai.example.voice",
|
|
212
|
+
"title": "Text in a Workbench voice, then checked without AI",
|
|
213
|
+
"lang": "ts",
|
|
214
|
+
"code": "const r = await ai.text({ model: 'claude-default', job: 'article.write', voice: 'conversational', spelling: 'en-AU', prompt })\nconst issues = checkWriting(r.text, { voice: 'conversational' })\nif (writingStops(issues).length > 0) await retryWith(prompt + writingFixNote(issues))",
|
|
215
|
+
"probe": "ai.voice"
|
|
134
216
|
}
|
|
135
217
|
],
|
|
136
218
|
"probes": [
|
|
@@ -191,6 +273,25 @@ export const capabilities = {
|
|
|
191
273
|
"expect": "pass",
|
|
192
274
|
"smoke": true,
|
|
193
275
|
"cost_hint_usd": 0
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
"id": "ai.voice",
|
|
279
|
+
"half": "runtime",
|
|
280
|
+
"expect": "pass",
|
|
281
|
+
"cost_hint_usd": 0.004
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
"id": "ai.voice.unknown",
|
|
285
|
+
"half": "runtime",
|
|
286
|
+
"expect": "refused_as_expected",
|
|
287
|
+
"refusal": "invalid_request",
|
|
288
|
+
"cost_hint_usd": 0
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
"id": "ai.voice.cache-read",
|
|
292
|
+
"half": "runtime",
|
|
293
|
+
"expect": "pass",
|
|
294
|
+
"cost_hint_usd": 0.008
|
|
194
295
|
}
|
|
195
296
|
]
|
|
196
297
|
},
|
|
@@ -311,7 +412,12 @@ export const capabilities = {
|
|
|
311
412
|
"capabilitySchema",
|
|
312
413
|
"capabilities",
|
|
313
414
|
"MANIFEST_VERSION",
|
|
314
|
-
"compareDevVars"
|
|
415
|
+
"compareDevVars",
|
|
416
|
+
"BRAND_FILE",
|
|
417
|
+
"brandSchema",
|
|
418
|
+
"defineBrand",
|
|
419
|
+
"resolveBrand",
|
|
420
|
+
"BRAND_DEFAULTS"
|
|
315
421
|
]
|
|
316
422
|
},
|
|
317
423
|
"config": [
|
|
@@ -362,6 +468,13 @@ export const capabilities = {
|
|
|
362
468
|
"text": "Keep .dev.vars.example listing every variable .dev.vars has, with no values; check with npx workbench-config check.",
|
|
363
469
|
"enforced_by": "review",
|
|
364
470
|
"probe": "config.dev-vars-check"
|
|
471
|
+
},
|
|
472
|
+
{
|
|
473
|
+
"id": "config.one-brand-file",
|
|
474
|
+
"kind": "must",
|
|
475
|
+
"text": "Keep the app's name, logo, colours and fonts in brand.json at the repo root, read with defineBrand, so emails, PDFs and sign-in mails are branded from one file.",
|
|
476
|
+
"enforced_by": "types",
|
|
477
|
+
"probe": "config.brand-file"
|
|
365
478
|
}
|
|
366
479
|
],
|
|
367
480
|
"examples": [
|
|
@@ -394,6 +507,13 @@ export const capabilities = {
|
|
|
394
507
|
"expect": "pass",
|
|
395
508
|
"cost_hint_usd": 0
|
|
396
509
|
},
|
|
510
|
+
{
|
|
511
|
+
"id": "config.brand-file",
|
|
512
|
+
"half": "runtime",
|
|
513
|
+
"expect": "pass",
|
|
514
|
+
"cost_hint_usd": 0,
|
|
515
|
+
"note": "The app's brand.json passes defineBrand and a bad one is refused with every bad field named"
|
|
516
|
+
},
|
|
397
517
|
{
|
|
398
518
|
"id": "config.dev-vars-check",
|
|
399
519
|
"half": "runtime",
|
|
@@ -760,7 +880,7 @@ export const capabilities = {
|
|
|
760
880
|
"kind": "must",
|
|
761
881
|
"text": "Pass attachments as bytes (Uint8Array), and give an inline image a contentId that the HTML references as cid:.",
|
|
762
882
|
"enforced_by": "types",
|
|
763
|
-
"probe": "email.send.inline-
|
|
883
|
+
"probe": "email.send.inline-attachment"
|
|
764
884
|
},
|
|
765
885
|
{
|
|
766
886
|
"id": "email.allowed-sender",
|
|
@@ -781,7 +901,7 @@ export const capabilities = {
|
|
|
781
901
|
"kind": "must_not",
|
|
782
902
|
"text": "Expect a development key to mail anyone but Robert. The Gateway refuses other recipients before the budget.",
|
|
783
903
|
"enforced_by": "gateway",
|
|
784
|
-
"probe": "email.send.dev-recipient-refused"
|
|
904
|
+
"probe": "email.send.dev-key-recipient-refused"
|
|
785
905
|
},
|
|
786
906
|
{
|
|
787
907
|
"id": "email.transactional-only",
|
|
@@ -797,7 +917,7 @@ export const capabilities = {
|
|
|
797
917
|
"title": "Send a transactional mail with an inline logo",
|
|
798
918
|
"lang": "ts",
|
|
799
919
|
"code": "const env = workbenchEnv(process.env)\nconst mailer = createMailer({ env, transport: selectTransport(env), from: { address: 'hello@mail.example.com', name: 'Example' }, gateway: { key: process.env.WORKBENCH_GATEWAY_KEY! } })\nawait mailer.send({ to: user.email, subject: 'Welcome', text, html: '<img src=\"cid:logo\"> ...', template: 'welcome', idempotencyKey: `welcome:${user.id}`, attachments: [{ filename: 'logo.png', mimeType: 'image/png', bytes: logo, contentId: 'logo' }] })",
|
|
800
|
-
"probe": "email.send.inline-
|
|
920
|
+
"probe": "email.send.inline-attachment"
|
|
801
921
|
}
|
|
802
922
|
],
|
|
803
923
|
"probes": [
|
|
@@ -805,12 +925,11 @@ export const capabilities = {
|
|
|
805
925
|
"id": "email.send.transactional",
|
|
806
926
|
"half": "runtime",
|
|
807
927
|
"expect": "pass",
|
|
808
|
-
"smoke": true,
|
|
809
928
|
"cost_hint_usd": 0.00035,
|
|
810
|
-
"note": "To Robert; check that it arrives"
|
|
929
|
+
"note": "To Robert; check that it arrives. Not in the daily smoke run, so it sends no daily mail"
|
|
811
930
|
},
|
|
812
931
|
{
|
|
813
|
-
"id": "email.send.inline-
|
|
932
|
+
"id": "email.send.inline-attachment",
|
|
814
933
|
"half": "runtime",
|
|
815
934
|
"expect": "pass",
|
|
816
935
|
"cost_hint_usd": 0.00035,
|
|
@@ -824,7 +943,7 @@ export const capabilities = {
|
|
|
824
943
|
"note": "The repeat answers replayed: true and no second mail arrives"
|
|
825
944
|
},
|
|
826
945
|
{
|
|
827
|
-
"id": "email.send.dev-recipient-refused",
|
|
946
|
+
"id": "email.send.dev-key-recipient-refused",
|
|
828
947
|
"half": "runtime",
|
|
829
948
|
"expect": "refused_as_expected",
|
|
830
949
|
"refusal": "recipient_not_allowed",
|
|
@@ -843,6 +962,13 @@ export const capabilities = {
|
|
|
843
962
|
"expect": "pass",
|
|
844
963
|
"cost_hint_usd": 0,
|
|
845
964
|
"note": "createMailer with transport capture and WORKBENCH_ENV=staging throws invalid_config"
|
|
965
|
+
},
|
|
966
|
+
{
|
|
967
|
+
"id": "email.send.new-sender",
|
|
968
|
+
"half": "runtime",
|
|
969
|
+
"expect": "pass",
|
|
970
|
+
"cost_hint_usd": 0.00035,
|
|
971
|
+
"note": "Runs after the Control drill's gw_sender_add step; skipped without it"
|
|
846
972
|
}
|
|
847
973
|
]
|
|
848
974
|
},
|
|
@@ -39,6 +39,20 @@ export declare const allowedSchemas: {
|
|
|
39
39
|
explicit_models: z.ZodArray<z.ZodString>;
|
|
40
40
|
server_tools: z.ZodArray<z.ZodString>;
|
|
41
41
|
required_headers: z.ZodArray<z.ZodString>;
|
|
42
|
+
optional_headers: z.ZodArray<z.ZodString>;
|
|
43
|
+
voices: z.ZodArray<z.ZodObject<{
|
|
44
|
+
name: z.ZodString;
|
|
45
|
+
paragraph_target: z.ZodNumber;
|
|
46
|
+
sentence_target: z.ZodNumber;
|
|
47
|
+
}, z.core.$strict>>;
|
|
48
|
+
spellings: z.ZodArray<z.ZodString>;
|
|
49
|
+
writing: z.ZodObject<{
|
|
50
|
+
voice_set_version: z.ZodNumber;
|
|
51
|
+
house_paragraph_target: z.ZodNumber;
|
|
52
|
+
paragraph_grace: z.ZodNumber;
|
|
53
|
+
paragraph_ceiling: z.ZodNumber;
|
|
54
|
+
banned_phrases: z.ZodArray<z.ZodString>;
|
|
55
|
+
}, z.core.$strict>;
|
|
42
56
|
}, z.core.$strict>;
|
|
43
57
|
images: z.ZodObject<{
|
|
44
58
|
aliases: z.ZodArray<z.ZodObject<{
|
|
@@ -99,6 +99,20 @@ export const allowedSchemas = {
|
|
|
99
99
|
explicit_models: z.array(z.string().min(1)),
|
|
100
100
|
server_tools: z.array(z.string().min(1)),
|
|
101
101
|
required_headers: z.array(z.string().min(1)),
|
|
102
|
+
// Voices and spelling (docs/spec.md §2.8). The Gateway holds the text; these are the names and the
|
|
103
|
+
// figures the package's writing check needs, kept here so the two can't drift apart.
|
|
104
|
+
optional_headers: z.array(z.string().min(1)),
|
|
105
|
+
voices: z
|
|
106
|
+
.array(z.strictObject({ name: z.string().regex(/^[a-z][a-z0-9-]{0,39}$/), paragraph_target: z.number().int().positive(), sentence_target: z.number().int().positive() }))
|
|
107
|
+
.min(1),
|
|
108
|
+
spellings: z.array(z.string().regex(/^[a-z]{2,3}-[A-Z]{2}$/)).min(1),
|
|
109
|
+
writing: z.strictObject({
|
|
110
|
+
voice_set_version: z.number().int().positive(),
|
|
111
|
+
house_paragraph_target: z.number().int().positive(),
|
|
112
|
+
paragraph_grace: z.number().min(1),
|
|
113
|
+
paragraph_ceiling: z.number().min(1),
|
|
114
|
+
banned_phrases: z.array(z.string().min(1)).min(1),
|
|
115
|
+
}),
|
|
102
116
|
}),
|
|
103
117
|
images: z.strictObject({
|
|
104
118
|
aliases: z.array(aliasEntry.extend({ provider: z.enum(["google", "openai"]) })).min(1),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mccaskie-labs/workbench-config",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.15",
|
|
4
4
|
"description": "Workbench app configuration: WORKBENCH_ENV, typed variables that fail by name, secrets that are never printed, and the capabilities manifest.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"type": "module",
|