@noodleseed/agent-kit 0.94.0 → 0.96.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.
Files changed (104) hide show
  1. package/manifest.json +571 -363
  2. package/package.json +1 -1
  3. package/skills/claude-code/SKILL.md +1 -1
  4. package/skills/claude-code/authoring-mcp-servers/SKILL.md +1 -1
  5. package/skills/claude-code/authoring-mcp-servers/references/authoring-workflow.md +7 -12
  6. package/skills/claude-code/authoring-mcp-servers/references/build-an-mcp-server.md +2 -2
  7. package/skills/claude-code/building-mcp-apps/SKILL.md +1 -1
  8. package/skills/claude-code/building-mcp-apps/references/build-an-mcp-app.md +2 -2
  9. package/skills/claude-code/building-mcp-apps/references/widgets-and-apps.md +1 -1
  10. package/skills/claude-code/connecting-apis-to-mcp/SKILL.md +1 -1
  11. package/skills/claude-code/connecting-apis-to-mcp/references/authoring-workflow.md +7 -12
  12. package/skills/claude-code/connecting-apis-to-mcp/references/connect-an-api.md +2 -2
  13. package/skills/claude-code/creating-product-agent-guides/SKILL.md +1 -1
  14. package/skills/claude-code/debugging-mcp-delivery/SKILL.md +1 -1
  15. package/skills/claude-code/debugging-mcp-delivery/references/verify-and-recover.md +2 -1
  16. package/skills/claude-code/deploying-mcp-services/SKILL.md +1 -1
  17. package/skills/claude-code/designing-mcp-products/SKILL.md +1 -1
  18. package/skills/claude-code/designing-mcp-products/references/authoring-workflow.md +7 -12
  19. package/skills/claude-code/embedding-mcp-assistants/SKILL.md +1 -1
  20. package/skills/claude-code/embedding-mcp-assistants/references/authoring-workflow.md +7 -12
  21. package/skills/claude-code/embedding-mcp-assistants/references/embedded-assistant.md +33 -27
  22. package/skills/claude-code/examples/acme-tasks/README.md +14 -4
  23. package/skills/claude-code/examples/customer-auth/README.md +3 -0
  24. package/skills/claude-code/examples/hello/README.md +6 -2
  25. package/skills/claude-code/examples/stateful-draft/README.md +115 -0
  26. package/skills/claude-code/examples/stateful-draft/noodle.json +5 -0
  27. package/skills/claude-code/examples/stateful-draft/package.json +22 -0
  28. package/skills/claude-code/examples/stateful-draft/site/client.js +44 -0
  29. package/skills/claude-code/examples/stateful-draft/site/demo.mjs +158 -0
  30. package/skills/claude-code/examples/stateful-draft/site/index.html +41 -0
  31. package/skills/claude-code/examples/stateful-draft/src/helpers.ts +6 -0
  32. package/skills/claude-code/examples/stateful-draft/src/server.ts +159 -0
  33. package/skills/claude-code/examples/stateful-draft/src/views/draft-card.tsx +152 -0
  34. package/skills/claude-code/examples/stateful-draft/src/views/widget-style.css +93 -0
  35. package/skills/claude-code/examples/stateful-draft/test/draft-card.test.tsx +88 -0
  36. package/skills/claude-code/examples/stateful-draft/test/server.test.ts +58 -0
  37. package/skills/claude-code/examples/stateful-draft/vitest.config.ts +6 -0
  38. package/skills/claude-code/examples/weather/README.md +4 -0
  39. package/skills/claude-code/executing-noodle-plans/SKILL.md +1 -1
  40. package/skills/claude-code/publishing-mcp-integrations/SKILL.md +1 -1
  41. package/skills/claude-code/references/authoring-workflow.md +7 -12
  42. package/skills/claude-code/references/build-an-mcp-app.md +2 -2
  43. package/skills/claude-code/references/build-an-mcp-server.md +2 -2
  44. package/skills/claude-code/references/connect-an-api.md +2 -2
  45. package/skills/claude-code/references/embedded-assistant.md +33 -27
  46. package/skills/claude-code/references/examples.md +1 -1
  47. package/skills/claude-code/references/verify-and-recover.md +2 -1
  48. package/skills/claude-code/references/widgets-and-apps.md +1 -1
  49. package/skills/claude-code/reporting-noodle-feedback/SKILL.md +1 -1
  50. package/skills/claude-code/verifying-mcp-delivery/SKILL.md +1 -1
  51. package/skills/claude-code/verifying-mcp-delivery/references/verify-and-recover.md +2 -1
  52. package/skills/claude-code/wrapping-existing-applications/SKILL.md +1 -1
  53. package/skills/claude-code/wrapping-existing-applications/references/authoring-workflow.md +7 -12
  54. package/skills/codex/SKILL.md +1 -1
  55. package/skills/codex/authoring-mcp-servers/SKILL.md +1 -1
  56. package/skills/codex/authoring-mcp-servers/references/authoring-workflow.md +7 -12
  57. package/skills/codex/authoring-mcp-servers/references/build-an-mcp-server.md +2 -2
  58. package/skills/codex/building-mcp-apps/SKILL.md +1 -1
  59. package/skills/codex/building-mcp-apps/references/build-an-mcp-app.md +2 -2
  60. package/skills/codex/building-mcp-apps/references/widgets-and-apps.md +1 -1
  61. package/skills/codex/connecting-apis-to-mcp/SKILL.md +1 -1
  62. package/skills/codex/connecting-apis-to-mcp/references/authoring-workflow.md +7 -12
  63. package/skills/codex/connecting-apis-to-mcp/references/connect-an-api.md +2 -2
  64. package/skills/codex/creating-product-agent-guides/SKILL.md +1 -1
  65. package/skills/codex/debugging-mcp-delivery/SKILL.md +1 -1
  66. package/skills/codex/debugging-mcp-delivery/references/verify-and-recover.md +2 -1
  67. package/skills/codex/deploying-mcp-services/SKILL.md +1 -1
  68. package/skills/codex/designing-mcp-products/SKILL.md +1 -1
  69. package/skills/codex/designing-mcp-products/references/authoring-workflow.md +7 -12
  70. package/skills/codex/embedding-mcp-assistants/SKILL.md +1 -1
  71. package/skills/codex/embedding-mcp-assistants/references/authoring-workflow.md +7 -12
  72. package/skills/codex/embedding-mcp-assistants/references/embedded-assistant.md +33 -27
  73. package/skills/codex/examples/acme-tasks/README.md +14 -4
  74. package/skills/codex/examples/customer-auth/README.md +3 -0
  75. package/skills/codex/examples/hello/README.md +6 -2
  76. package/skills/codex/examples/stateful-draft/README.md +115 -0
  77. package/skills/codex/examples/stateful-draft/noodle.json +5 -0
  78. package/skills/codex/examples/stateful-draft/package.json +22 -0
  79. package/skills/codex/examples/stateful-draft/site/client.js +44 -0
  80. package/skills/codex/examples/stateful-draft/site/demo.mjs +158 -0
  81. package/skills/codex/examples/stateful-draft/site/index.html +41 -0
  82. package/skills/codex/examples/stateful-draft/src/helpers.ts +6 -0
  83. package/skills/codex/examples/stateful-draft/src/server.ts +159 -0
  84. package/skills/codex/examples/stateful-draft/src/views/draft-card.tsx +152 -0
  85. package/skills/codex/examples/stateful-draft/src/views/widget-style.css +93 -0
  86. package/skills/codex/examples/stateful-draft/test/draft-card.test.tsx +88 -0
  87. package/skills/codex/examples/stateful-draft/test/server.test.ts +58 -0
  88. package/skills/codex/examples/stateful-draft/vitest.config.ts +6 -0
  89. package/skills/codex/examples/weather/README.md +4 -0
  90. package/skills/codex/executing-noodle-plans/SKILL.md +1 -1
  91. package/skills/codex/publishing-mcp-integrations/SKILL.md +1 -1
  92. package/skills/codex/references/authoring-workflow.md +7 -12
  93. package/skills/codex/references/build-an-mcp-app.md +2 -2
  94. package/skills/codex/references/build-an-mcp-server.md +2 -2
  95. package/skills/codex/references/connect-an-api.md +2 -2
  96. package/skills/codex/references/embedded-assistant.md +33 -27
  97. package/skills/codex/references/examples.md +1 -1
  98. package/skills/codex/references/verify-and-recover.md +2 -1
  99. package/skills/codex/references/widgets-and-apps.md +1 -1
  100. package/skills/codex/reporting-noodle-feedback/SKILL.md +1 -1
  101. package/skills/codex/verifying-mcp-delivery/SKILL.md +1 -1
  102. package/skills/codex/verifying-mcp-delivery/references/verify-and-recover.md +2 -1
  103. package/skills/codex/wrapping-existing-applications/SKILL.md +1 -1
  104. package/skills/codex/wrapping-existing-applications/references/authoring-workflow.md +7 -12
@@ -0,0 +1,152 @@
1
+ import { useEffect, useState } from 'react';
2
+ import { useCallTool, useSendFollowUpMessage, useToolInfo } from '../helpers.js';
3
+ import './widget-style.css';
4
+
5
+ type Brief = { title?: string; audience?: string; goal?: string };
6
+ type Snapshot = { value: Brief; revision: number; status: string; proposal?: Brief };
7
+
8
+ function displayBrief(data: Snapshot | undefined): Brief {
9
+ const proposal = data?.proposal;
10
+ return proposal && (proposal.title || proposal.audience || proposal.goal)
11
+ ? proposal
12
+ : (data?.value ?? {});
13
+ }
14
+
15
+ function snapshot(result: unknown): Snapshot | undefined {
16
+ if (!result || typeof result !== 'object') return;
17
+ const envelope = result as { isError?: boolean; structuredContent?: Partial<Snapshot> };
18
+ const data = envelope.structuredContent;
19
+ if (
20
+ !envelope.isError &&
21
+ data &&
22
+ data.value &&
23
+ typeof data.value === 'object' &&
24
+ Number.isInteger(data.revision) &&
25
+ typeof data.status === 'string'
26
+ ) {
27
+ return data as Snapshot;
28
+ }
29
+ }
30
+
31
+ export default function DraftCard() {
32
+ const info = useToolInfo('open_draft');
33
+ const read = useCallTool('open_draft');
34
+ const save = useCallTool('save_draft');
35
+ const followUp = useSendFollowUpMessage();
36
+ const [saved, setSaved] = useState(() => snapshot(info));
37
+ const [brief, setBrief] = useState<Brief>(() => displayBrief(snapshot(info)));
38
+ const [busy, setBusy] = useState(false);
39
+ const [reloadRequired, setReloadRequired] = useState(false);
40
+ const [message, setMessage] = useState('Review your brief. An account is optional.');
41
+ useEffect(() => {
42
+ const next = snapshot({ structuredContent: info.structuredContent });
43
+ if (next) {
44
+ setSaved(next);
45
+ setBrief(displayBrief(next));
46
+ }
47
+ }, [info.structuredContent]);
48
+ const complete = Boolean(brief.title?.trim() && brief.audience?.trim() && brief.goal?.trim());
49
+ const unchanged = ['title', 'audience', 'goal'].every(
50
+ (key) => brief[key as keyof Brief] === saved?.value[key as keyof Brief],
51
+ );
52
+
53
+ async function run(operation: 'load' | 'save') {
54
+ setBusy(true);
55
+ try {
56
+ const result =
57
+ operation === 'load'
58
+ ? await read.callTool({})
59
+ : await save.callTool({ ...brief, expectedRevision: saved?.revision });
60
+ const next = snapshot(result);
61
+ if (!next) throw new Error('No authoritative result');
62
+ setSaved(next);
63
+ setBrief(displayBrief(next));
64
+ setReloadRequired(false);
65
+ setMessage(operation === 'save' ? 'Your brief is saved.' : 'Saved brief loaded.');
66
+ } catch {
67
+ setReloadRequired(true);
68
+ setMessage(
69
+ 'No save was confirmed. Reload saved to check the latest brief before trying again.',
70
+ );
71
+ } finally {
72
+ setBusy(false);
73
+ }
74
+ }
75
+
76
+ return (
77
+ <main className="brief-shell">
78
+ <header>
79
+ <p className="brief-eyebrow">YOUR FIRST USEFUL STEP</p>
80
+ <h1>Your project brief</h1>
81
+ <p>Make something useful before creating an account.</p>
82
+ </header>
83
+ <section className="brief-fields">
84
+ <label>
85
+ Project title
86
+ <input
87
+ required
88
+ maxLength={120}
89
+ value={brief.title ?? ''}
90
+ placeholder="A better first week"
91
+ onChange={(event) => setBrief({ ...brief, title: event.currentTarget.value })}
92
+ />
93
+ </label>
94
+ <label>
95
+ Who is this for?
96
+ <textarea
97
+ required
98
+ maxLength={240}
99
+ rows={2}
100
+ value={brief.audience ?? ''}
101
+ placeholder="New teammates joining our product team"
102
+ onChange={(event) => setBrief({ ...brief, audience: event.currentTarget.value })}
103
+ />
104
+ </label>
105
+ <label>
106
+ What would success look like?
107
+ <textarea
108
+ required
109
+ maxLength={240}
110
+ rows={3}
111
+ value={brief.goal ?? ''}
112
+ placeholder="Complete their first useful project in a week"
113
+ onChange={(event) => setBrief({ ...brief, goal: event.currentTarget.value })}
114
+ />
115
+ </label>
116
+ <p role="status" aria-live="polite">
117
+ {busy ? 'Waiting for the result…' : message}
118
+ </p>
119
+ <div className="brief-actions">
120
+ <button type="button" disabled={busy} onClick={() => void run('load')}>
121
+ Reload saved
122
+ </button>
123
+ <button
124
+ className="brief-primary"
125
+ type="button"
126
+ onClick={() => void run('save')}
127
+ disabled={busy || !saved || !complete || reloadRequired}
128
+ >
129
+ Save brief
130
+ </button>
131
+ </div>
132
+ </section>
133
+ <footer>
134
+ <p>
135
+ A saved brief lasts up to 24 hours and can follow you when you sign in. No project has
136
+ been created.
137
+ </p>
138
+ <button
139
+ type="button"
140
+ disabled={
141
+ busy || !complete || !unchanged || !saved || saved.revision === 0 || reloadRequired
142
+ }
143
+ onClick={() =>
144
+ void followUp({ prompt: 'I would like to continue with my saved brief in an account.' })
145
+ }
146
+ >
147
+ Continue with an account
148
+ </button>
149
+ </footer>
150
+ </main>
151
+ );
152
+ }
@@ -0,0 +1,93 @@
1
+ @layer stateful-draft {
2
+ .brief-shell {
3
+ box-sizing: border-box;
4
+ max-width: 600px;
5
+ margin: 0 auto;
6
+ padding: 24px;
7
+ color: var(--ns-text-primary, #172033);
8
+ background: var(--ns-surface, #f8fafc);
9
+ font-family: var(--ns-font, system-ui, sans-serif);
10
+ line-height: 1.5;
11
+ }
12
+ .brief-shell * {
13
+ box-sizing: border-box;
14
+ }
15
+ .brief-shell h1 {
16
+ margin: 4px 0;
17
+ font-size: 26px;
18
+ letter-spacing: -0.03em;
19
+ }
20
+ .brief-shell p {
21
+ color: var(--ns-text-secondary, #526077);
22
+ }
23
+ .brief-eyebrow {
24
+ font-size: 11px;
25
+ font-weight: 700;
26
+ letter-spacing: 0.12em;
27
+ }
28
+ .brief-fields {
29
+ display: grid;
30
+ gap: 16px;
31
+ margin: 24px 0;
32
+ }
33
+ .brief-shell label {
34
+ display: grid;
35
+ gap: 6px;
36
+ font-size: 14px;
37
+ font-weight: 600;
38
+ }
39
+ .brief-shell input,
40
+ .brief-shell textarea {
41
+ width: 100%;
42
+ padding: 10px 12px;
43
+ border: 1px solid var(--ns-border, #cbd5e1);
44
+ border-radius: var(--ns-radius-md, 8px);
45
+ color: inherit;
46
+ background: var(--ns-surface-raised, #fff);
47
+ font: inherit;
48
+ font-weight: 400;
49
+ resize: vertical;
50
+ }
51
+ .brief-actions {
52
+ display: flex;
53
+ flex-wrap: wrap;
54
+ gap: 8px;
55
+ }
56
+ .brief-shell button {
57
+ padding: 10px 16px;
58
+ border: 1px solid var(--ns-border, #cbd5e1);
59
+ border-radius: var(--ns-radius-md, 8px);
60
+ background: var(--ns-surface-raised, #fff);
61
+ color: inherit;
62
+ font: inherit;
63
+ font-size: 14px;
64
+ font-weight: 600;
65
+ cursor: pointer;
66
+ }
67
+ .brief-shell .brief-primary {
68
+ background: var(--ns-accent, #2563eb);
69
+ color: var(--ns-accent-text, #fff);
70
+ border-color: transparent;
71
+ }
72
+ .brief-shell button:disabled {
73
+ opacity: 0.5;
74
+ cursor: default;
75
+ }
76
+ .brief-shell :focus-visible {
77
+ outline: 2px solid var(--ns-focus, #2563eb);
78
+ outline-offset: 3px;
79
+ }
80
+ .brief-shell footer {
81
+ border-top: 1px solid var(--ns-border, #cbd5e1);
82
+ padding-top: 12px;
83
+ font-size: 13px;
84
+ }
85
+ @media (max-width: 380px) {
86
+ .brief-shell {
87
+ padding: 16px;
88
+ }
89
+ .brief-actions button {
90
+ width: 100%;
91
+ }
92
+ }
93
+ }
@@ -0,0 +1,88 @@
1
+ // @vitest-environment happy-dom
2
+ import { act } from 'react';
3
+ import { createRoot } from 'react-dom/client';
4
+ import { afterEach, beforeEach, expect, it, vi } from 'vitest';
5
+
6
+ const callTool = vi.fn();
7
+ const followUp = vi.fn();
8
+ const initial = {
9
+ value: { title: 'Team launch', audience: 'New teammates', goal: 'Complete their first project' },
10
+ revision: 4,
11
+ status: 'active',
12
+ };
13
+ let entry: unknown = initial;
14
+ vi.mock('../src/helpers.js', () => ({
15
+ useToolInfo: () => ({ structuredContent: entry }),
16
+ useCallTool: () => ({ callTool }),
17
+ useSendFollowUpMessage: () => followUp,
18
+ }));
19
+
20
+ import DraftCard from '../src/views/draft-card.js';
21
+
22
+ let host: HTMLDivElement;
23
+ let root: ReturnType<typeof createRoot>;
24
+ beforeEach(async () => {
25
+ vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true);
26
+ callTool.mockReset();
27
+ followUp.mockReset();
28
+ entry = initial;
29
+ host = document.createElement('div');
30
+ document.body.append(host);
31
+ root = createRoot(host);
32
+ await act(async () => root.render(<DraftCard />));
33
+ });
34
+ afterEach(() => {
35
+ act(() => root.unmount());
36
+ host.remove();
37
+ });
38
+ function button(label: string) {
39
+ const found = [...host.querySelectorAll('button')].find((entry) => entry.textContent === label);
40
+ if (!found) throw new Error(`Missing button: ${label}`);
41
+ return found;
42
+ }
43
+
44
+ it('saves using the server revision and displays only the returned result', async () => {
45
+ callTool.mockResolvedValue({ structuredContent: { ...initial, revision: 8 } });
46
+ await act(async () => button('Save brief').click());
47
+ expect(callTool).toHaveBeenCalledWith({ ...initial.value, expectedRevision: 4 });
48
+ expect(host.textContent).toContain('Your brief is saved.');
49
+ expect(button('Continue with an account').disabled).toBe(false);
50
+ });
51
+
52
+ it('shows a proposed brief without pretending it is already saved', async () => {
53
+ entry = { value: {}, revision: 0, status: 'active', proposal: initial.value };
54
+ await act(async () => root.render(<DraftCard />));
55
+ expect(host.querySelector('input')?.value).toBe('Team launch');
56
+ expect(button('Continue with an account').disabled).toBe(true);
57
+ callTool.mockResolvedValue({ structuredContent: { ...initial, revision: 1 } });
58
+ await act(async () => button('Save brief').click());
59
+ expect(callTool).toHaveBeenCalledWith({ ...initial.value, expectedRevision: 0 });
60
+ });
61
+
62
+ it('does not invent a save when confirmation is pending or the response is missing', async () => {
63
+ callTool.mockResolvedValue({});
64
+ await act(async () => button('Save brief').click());
65
+ expect(host.textContent).not.toContain('Your brief is saved.');
66
+ expect(host.textContent).toContain('No save was confirmed.');
67
+ });
68
+
69
+ it('retains edits on a stale write and requires a reload before another save', async () => {
70
+ callTool.mockResolvedValue({ isError: true });
71
+ await act(async () => button('Save brief').click());
72
+ expect(host.querySelector('input')?.value).toBe('Team launch');
73
+ expect(button('Save brief').disabled).toBe(true);
74
+ expect(host.textContent).toContain('Reload saved');
75
+ callTool.mockResolvedValue({ structuredContent: { ...initial, revision: 7 } });
76
+ await act(async () => button('Reload saved').click());
77
+ await act(async () => button('Save brief').click());
78
+ expect(callTool).toHaveBeenLastCalledWith({ ...initial.value, expectedRevision: 7 });
79
+ });
80
+
81
+ it('keeps continuing separate from saving and makes no project-creation claim', async () => {
82
+ await act(async () => button('Continue with an account').click());
83
+ expect(callTool).not.toHaveBeenCalled();
84
+ expect(followUp).toHaveBeenCalledWith({
85
+ prompt: 'I would like to continue with my saved brief in an account.',
86
+ });
87
+ expect(host.textContent).not.toContain('Project created');
88
+ });
@@ -0,0 +1,58 @@
1
+ import { fileURLToPath } from 'node:url';
2
+ import { validate } from '@noodleseed/one';
3
+ import { describe, expect, it } from 'vitest';
4
+ import app from '../src/server.js';
5
+
6
+ describe('stateful draft onboarding reference', () => {
7
+ it('reads and saves authoritative state instead of a widget-only copy', async () => {
8
+ const manifest = await app.toManifest();
9
+ expect(manifest.tools.find((entry) => entry.name === 'open_draft')?.fulfilment.steps).toEqual([
10
+ expect.objectContaining({ use: 'state.read_state', args: { handle: 'draft' } }),
11
+ ]);
12
+ expect(manifest.tools.find((entry) => entry.name === 'save_draft')).toMatchObject({
13
+ annotations: { readOnlyHint: false, confirm: true },
14
+ fulfilment: {
15
+ steps: [
16
+ expect.objectContaining({
17
+ use: 'state.patch_state',
18
+ args: {
19
+ handle: 'draft',
20
+ expectedRevision: '${input.expectedRevision}',
21
+ value: {
22
+ title: '${input.title}',
23
+ audience: '${input.audience}',
24
+ goal: '${input.goal}',
25
+ },
26
+ },
27
+ }),
28
+ ],
29
+ },
30
+ });
31
+ });
32
+
33
+ it('limits anonymous access and transfers only an expiring draft after verified login', async () => {
34
+ const manifest = await app.toManifest();
35
+ expect(manifest.state?.handles.draft).toMatchObject({
36
+ scope: 'caller',
37
+ ttlSeconds: 86400,
38
+ claimOnAuthentication: true,
39
+ });
40
+ expect(manifest.server.assistant?.surfaces?.map((surface) => surface.mode)).toEqual([
41
+ 'mixed',
42
+ 'authenticated',
43
+ ]);
44
+ const continued = manifest.tools.find((entry) => entry.name === 'continue_draft');
45
+ expect(continued?.annotations?.readOnlyHint).toBe(true);
46
+ expect(continued?.fulfilment.output).toMatchObject({ accountId: '${user.id}' });
47
+ expect(continued?.fulfilment.steps).toEqual([
48
+ expect.objectContaining({ use: 'state.read_state', args: { handle: 'draft' } }),
49
+ ]);
50
+ });
51
+
52
+ it('compiles through the public validator, including anonymous action confirmation', async () => {
53
+ const result = await validate({
54
+ manifestPath: fileURLToPath(new URL('../src/server.ts', import.meta.url)),
55
+ });
56
+ expect(result.ok, JSON.stringify(result.ok ? [] : result.errors)).toBe(true);
57
+ });
58
+ });
@@ -0,0 +1,6 @@
1
+ import { defineConfig } from 'vitest/config';
2
+
3
+ export default defineConfig({
4
+ oxc: { jsx: { runtime: 'automatic' } },
5
+ test: { include: ['test/**/*.test.{ts,tsx}'], testTimeout: 30_000, maxWorkers: 2 },
6
+ });
@@ -7,6 +7,10 @@ Capability slots: HTTP connector authoring, ordered fulfilment flows, query/resp
7
7
  **list-returning connector output** (a connector that returns a live, variable-length array), and
8
8
  sandboxed compute, including an explicit least-privilege per-operation response-size bound.
9
9
 
10
+ For a different API with an OpenAPI document, start with `noodle import openapi <file>` in a separate
11
+ directory. Its shared schemas and offline test establish the contract, not live behavior; follow the
12
+ [connector guide](https://docs.noodleseed.dev/docs/guides/connectors) before replacing this curated flow.
13
+
10
14
  1. **`geo.search`** → geocode the city to coordinates (Open-Meteo Geocoding API)
11
15
  2. **`forecast.current`** → fetch current weather for those coordinates (Open-Meteo Forecast API)
12
16
  3. **`brief.summarize`** → derive a human-readable briefing in a **WASM/QuickJS compute sandbox**
@@ -3,7 +3,7 @@ name: executing-noodle-plans
3
3
  description: "Use when the user asks to execute an approved, decision-complete implementation plan for a Noodle Seed project task by task with test-first changes, review, recovery, and final verification."
4
4
  ---
5
5
 
6
- <!-- noodle-skill version:0.94.0 hash:6a9f132ddb79352e -->
6
+ <!-- noodle-skill version:0.96.0 hash:6a9f132ddb79352e -->
7
7
 
8
8
  # Execute a Noodle Seed implementation plan
9
9
 
@@ -3,7 +3,7 @@ name: publishing-mcp-integrations
3
3
  description: "Use when preparing, reviewing, or submitting a Noodle Seed MCP integration to a host or app directory."
4
4
  ---
5
5
 
6
- <!-- noodle-skill version:0.94.0 hash:0ccafb222038f553 -->
6
+ <!-- noodle-skill version:0.96.0 hash:0ccafb222038f553 -->
7
7
 
8
8
  # publishing-mcp-integrations
9
9
 
@@ -22,8 +22,9 @@
22
22
  ## Input paths
23
23
 
24
24
  1. **Website scrape** — if the user gives a URL, scrape it for surface hints (products, services, hours, contact, pricing). Stop there: the URL does not reveal CRM, booking systems, custom APIs, auth model, eligibility rules, quoting logic, or approval flows. Those live in the business systems and the owner’s head — ask.
25
- 2. **OpenAPI import** — `noodle import openapi <file>` emits a starter `server.ts` from a spec. Use it when the user provides an OpenAPI document.
25
+ 2. **OpenAPI import** — `noodle import openapi <file>` writes a pinned project at `src/server.ts`, shared operation/tool schemas, managed auth references, and an offline contract test. It does not install dependencies or call the backend. Follow its README, run `agent:check`, and add a reviewed sandbox-operation test; compile success is not live integration proof. Review unsupported-body/auth warnings before deployment. Modified files are preserved unless `--force` is explicit.
26
26
  3. **Upstream MCP import** — `noodle import mcp <url> --name <slug> --output <dir>` discovers `tools/list` once, validates and freezes tool schemas into TypeScript, and writes a secret-free drift snapshot. Upstream annotations are untrusted, so generated tools remain destructive confirmed actions until an author verifies and narrows them. Use `--header-env <header>=<ENV_NAME>` for import-only auth and `--check` for classified, non-mutating drift detection. Runtime never performs discovery.
27
+ Both imports use `src/server.ts` as the declared entrypoint, include an offline compile test and `.env.example`, and write files only. Install the pinned dependencies, run the generated checks, then `noodle agents setup --apply`. Never treat generated contract tests as customer authentication or business-workflow evidence.
27
28
  4. **User interview** — Noodle does not interview; you do. Cover custom APIs/integrations, eligibility rules, quoting/approval logic, and private schemas (SQL DDL or JSON samples for custom `connector` declarations). Ask for concrete examples and sample payloads; do not guess a schema from a URL or invent endpoints.
28
29
 
29
30
  ## Fit check
@@ -393,20 +394,14 @@ Compute `run` functions are serialized and sandboxed: no imports, no closure cap
393
394
 
394
395
  ## Tests
395
396
 
396
- Use Vitest for app-local tests. The generated `npm test` command scans only the project-owned `test/` directory; skill-local example tests are reference material, not part of the app suite. Keep fixtures project-local; do not import from `examples/`. A minimum test suite imports the default server, checks the intended definitions compile, then lets `noodle test --json` perform the loopback MCP smoke.
397
+ Start from the generated `test/server.test.ts`, not a blank test file. `npm test` runs `vitest run --dir test` over the project-owned `test/` directory; skill-local example tests are reference material. The generated suite compiles the actual source/view, lists tools, asserts a representative result and rejects invalid input. It copies source into temporary fixture storage and uses synthetic configuration, never customer secrets or saved hosted targets. The HTTP profile proves one local backend read and zero extra calls for invalid input.
397
398
 
398
- ```ts
399
- import { describe, expect, it } from 'vitest';
400
- import app from '../src/server.js';
401
-
402
- describe('server', () => {
403
- it('declares the expected tool surface', () => {
404
- expect(app.name).toBe('support_assistant');
405
- });
406
- });
399
+ ```sh
400
+ npm test
401
+ npm run agent:check
407
402
  ```
408
403
 
409
- After focused tests pass, run `noodle validate --json`, `noodle test --json`, and then `noodle dev` for interactive local verification.
404
+ Adapt the representative arguments and assertions when business contracts change; keep invalid-input and authorization cases. A name/export assertion or `noodle test --json` without `--tool` proves no useful call. After fixture tests pass, bind the real local configuration, run an authorized representative read, and use `noodle dev` for interactive checks. Never use a production mutation or fixture success as customer-readiness evidence.
410
405
 
411
406
  ## Secrets and variables
412
407
 
@@ -25,14 +25,14 @@ Before implementation, capture a short design spec: target user, conversational
25
25
  4. **Preserve fallback.** Every tool that launches a widget must still return useful text without the widget, so unsupported hosts and failed rendering remain usable.
26
26
  5. **Decide product-guide coverage.** Record the required product-guide decision and its reason, then use `references/product-agent-guides.md` as the canonical selection and authoring guidance.
27
27
  6. **Author and wire the App contract.** Follow `references/widgets-and-apps.md` for the canonical component guidance, view registration, hooks, state, CSP, tool visibility, and output shaping. Keep tool effects and confirmation semantics correct independently of the UI.
28
- 7. **Validate the local artifact.** Run `noodle validate --json`, `noodle test --json`, and `noodle check --json`. Repair failures at the layer that produced them.
28
+ 7. **Validate the local artifact.** Run the generated/adapted `npm test`, `noodle validate --json` and `noodle check --json`. Prove a representative result and negative case, not just registration. Synthetic preference previews do not save; replace their backend seam and verify the authorized effect before showing success.
29
29
  8. **Inspect the experience.** Run `noodle devtools` and verify loading, empty, error, success, responsive layout, focus/keyboard behavior, and the text fallback.
30
30
  9. **Escalate evidence only on request.** Run a host test only when the user requested host verification. Run host-specific compliance only when preparing that host submission; select the exact host-testing or compliance entry from the router lookup catalog only after that evidence level is explicitly requested.
31
31
 
32
32
  ## Verification evidence
33
33
 
34
34
  - **Product:** the design spec states the user benefit, UI fit decision, and product-guide decision with its reason.
35
- - **Server:** `noodle validate --json` and `noodle test --json` succeeded.
35
+ - **Server:** compilation, a representative call and negative input/authorization cases passed; report whether fixtures or a real authorized backend were used.
36
36
  - **App contract:** `noodle check --json` succeeded.
37
37
  - **Local UX:** `noodle devtools` exercised the relevant states and the useful text fallback without the widget.
38
38
  - **Host/compliance:** report each requested host or compliance check with its evidence; report every unperformed higher level as not run.
@@ -26,7 +26,7 @@ Establish only the inputs needed for the requested stopping point. Follow `refer
26
26
  5. **Author in TypeScript.** Follow `references/authoring-workflow.md` for connector and flow patterns, `references/tool-design.md` for the model-facing tool surface, and `references/sdk-surface.md` for exact builders. These are this route’s complete canonical support set; use the router lookup catalog only when observed evidence names a different concern.
27
27
  MCP protocol versions are platform-owned and negotiated automatically at the serving endpoint. Do not add protocol-version settings to server options, `noodle.json`, app manifests, or deployment configuration.
28
28
  6. **Validate and repair.** Run `noodle validate --json`. Parse `error.errors[]`, repair the cited `path`, and rerun validation. Consult the lookup catalog only for the specific reported error code; do not open another reference speculatively.
29
- 7. **Run the local smoke.** After validation succeeds, run `noodle test --json` and repair any failure at that evidence layer.
29
+ 7. **Run local behavior.** Start from the generated tests and run `npm test`: compile, tools/list, representative result and invalid-input rejection. `noodle test --json` without `--tool` is registration-only; repair failures before claiming a working capability.
30
30
  8. **Prove external behavior.** For connector-backed reads, set credentials through the effective local target and run a safe representative `noodle tools call`. Confirm populated mapped fields from real output, not merely successful registration.
31
31
  9. **Stop at the requested boundary.** Do not add an App, host test, hosted environment, publication work, or deployment unless the user requested that outcome. Deploy only when the selected route or the user explicitly requires it.
32
32
 
@@ -36,7 +36,7 @@ Report evidence as a ladder and claim only levels actually exercised:
36
36
 
37
37
  - **Authoring:** the requested TypeScript behavior exists with typed inputs and outputs, and the product-guide decision and reason are recorded.
38
38
  - **Compilation:** `noodle validate --json` returned success.
39
- - **Local smoke:** `noodle test --json` returned success.
39
+ - **Local behavior:** the generated/adapted suite passed a representative call and a negative case. Synthetic fixtures prove only their configured behavior.
40
40
  - **Connector reality:** a representative safe read via `noodle tools call` returned populated mapped fields. This is required for connector-backed work.
41
41
  - **Higher levels:** explicitly report host, deployment, and production checks as not run unless they were separately requested and evidenced.
42
42
 
@@ -198,7 +198,7 @@ model" section of `references/authoring-workflow.md`.
198
198
 
199
199
  ### Set the secret for local runs
200
200
 
201
- Local `dev`, smoke commands, secrets, and variables resolve one effective target: explicit flags, then the project link, then the saved CLI target, then local defaults. Set the secret through that same target:
201
+ Local authoring resolves explicit coordinates, then the complete project link, then deterministic local defaults; saved global coordinates do not redirect an unlinked project. Follow the diagnostic’s exact-target commands from the project directory for every missing secret and variable:
202
202
 
203
203
  ```sh
204
204
  # Canonical: writes to the effective local environment used by dev/test/devtools:
@@ -215,7 +215,7 @@ Scoped local values live in `./.env.noodle`; the exact project-root `.env` is a
215
215
 
216
216
  ### Prove real output
217
217
 
218
- `noodle validate` / `noodle test` prove a connector tool *compiles and registers* — not that its
218
+ `noodle validate` / `noodle test` without `--tool` prove a connector tool *compiles and registers* — not that its
219
219
  mapping returns data. With the secret set, run a live read: `noodle tools call <read_tool> --args
220
220
  '{…}'` executes the connector against the real API in-process. Confirm the mapped fields are populated,
221
221
  not `undefined`; if they are empty, distinguish a legitimate empty result from a missing or incorrect mapping, fix `${response…}` paths against the real payload when needed, and re-run.
@@ -4,6 +4,7 @@
4
4
 
5
5
  - Architecture
6
6
  - Select the architecture before code
7
+ - Install into the existing application
7
8
  - Author and validate
8
9
  - Product workflow guidance
9
10
  - Customize the presentation
@@ -63,6 +64,20 @@ Use the existing host framework and its package manager. Recommend the managed r
63
64
 
64
65
  The architecture brief must state the selected topology, why it fits the named user and job, the two code owners and two local processes, credential and identity flow, routing authority, model owner, files expected to change, and the first requested evidence boundary. If identity/session ownership, exact origin, tenant-routing authority, model ownership, or evidence target is unavailable, return a blocked architecture brief naming the missing owner or decision and do not edit either codebase.
65
66
 
67
+ ## Install into the existing application
68
+
69
+ Configure or reuse an existing capability before generating files. Public embedding uses the existing script or embed ID and needs no customer backend session/config route. Reuse application APIs and business functions; generate a thin handler only where the approved architecture identifies a missing stable boundary.
70
+
71
+ For a supported host, first run `noodle assistant embed --framework nextjs --surface public --dry-run --json` in the customer application, substituting `authenticated` or `mixed` only for the selected surface. Read the returned recipe applicability, applicationSeams, generated contents/hashes, skipped conflicts and nextSteps. The output is an installation plan, not permission to change the repository.
72
+
73
+ For the documented non-Node backend/static-frontend authenticated profile, discover the framework choice through `noodle commands --json` and follow the linked framework guide below. The installer supplies a session view, a fail-closed identity seam, real CSRF-enforced request tests, a browser wrapper and exact-destination CSRF transport tests. Keep the existing package managers, authentication middleware and same-origin proxy. Pass the existing CSRF token and user/tenant principalKey to the wrapper; the managed element supports injected fetch, so preserving CSRF does not require a custom renderer or Node production server. Unsupported combinations use the neutral contract, not a claimed qualified profile.
74
+
75
+ Once local changes are authorized, rerun without `--dry-run`; never add `--force` to resolve a conflict automatically. Read the installed `NOODLE-INTEGRATION.md` and newly installed project skill before editing. Implement only the named session/business/mount seams and preserve the host framework, package manager, login and authorization. Mixed mode also requires an application-owned login transaction; the generated callback does not implement or verify that transaction.
76
+
77
+ Use generated tests and independently maintained acceptance cases at the actual customer boundary. A static host check, a mock test or installed files cannot prove application authorization or production browser behavior. Missing identities, sandbox access, customer approval or a reachable service remain unverified. Record the exact application/Noodle revisions, package versions and environment for observed evidence; rerun affected checks after changes.
78
+
79
+ Unsupported hosts use the framework-neutral integration contract below; do not claim they received a generated, qualified framework profile. Keep direct MCP and headless projections on the same TypeScript source, and preserve their separate identity requirements.
80
+
66
81
  ## Author and validate
67
82
 
68
83
  `noodle init` and `noodle init --template widget` deliberately produce credential-free MCP Apps. Add an assistant declaration only when the product explicitly includes a customer-hosted assistant; do not make ordinary external-host widgets depend on model-provider settings.
@@ -143,6 +158,16 @@ The sign-in card renders on the same themed chrome as every proposal card and fo
143
158
 
144
159
  #### Complete mixed-mode handoff
145
160
 
161
+ For a small SaaS onboarding flow, first read the bundled [stateful-draft example](../examples/stateful-draft/README.md). Compose the existing capabilities; do not introduce an onboarding platform API, workflow engine, research pipeline, or second identity provider.
162
+
163
+ Start from one useful result the visitor can see before signup. Ask only for missing information, one short question at a time. Show an editable preview, and offer an account only when it unlocks a meaningful next step. Keep the ordinary signup route available. Research and document parsing are optional enrichment, never required because the flow is conversational.
164
+
165
+ Use a finite-TTL caller state handle only if the draft needs server persistence. Opt it into `claimOnAuthentication` when it should move to the verified account. An explicit typed read tool supplies the current revision; a typed patch tool sends that revision and the complete reviewed value. Widget state is a display cache, never proof of a save. A failed or missing result must not increment the revision, mark the draft saved, or silently retry a conflicting write.
166
+
167
+ Keep the final business action in the customer API. Map the reviewed draft to its existing create/update operation after authentication, with confirmation and the backend’s existing authorization and idempotency. Ticket adoption transfers temporary state, not a CRM record or completed onboarding.
168
+
169
+ Prove this journey before calling it ready: useful anonymous preview; actual saved state; cancelled/expired signup; verified signup with the same draft; a destination conflict without an automatic merge; and no business write merely because login succeeded. A synthetic login demonstrates the integration shape but is not evidence that the customer identity system is connected.
170
+
146
171
  1. **Capture the ticket.** The widget raises `assistant-sign-in-requested` with a single-use `signInTicket`. POST it to the host application same-origin backend and bind it to the short-lived login transaction; keep it out of query strings, logs, analytics, and durable browser storage.
147
172
  2. **Use the existing login.** Send the visitor through the host application full-page redirect for sign-in or sign-up. Noodle never becomes the customer identity provider.
148
173
  3. **Elevate on the backend.** After authentication, recover the bound ticket and call `createAssistantSession({ ..., signInTicket })` with the verified user, server-owned routing, backend client credentials, and the exact **origin the conversation will continue on**. The allowed destination may differ from the anonymous marketing origin; successful elevation rebinds the session to it.
@@ -246,39 +271,20 @@ The doctor reads `NOODLE_ASSISTANT_CLIENT_ID` / `NOODLE_ASSISTANT_CLIENT_SECRET`
246
271
 
247
272
  Read the customer repository lockfile or `packageManager` field and install `@noodleseed/assistant` with that existing package manager; never introduce a second lockfile.
248
273
 
249
- Create an authenticated same-origin backend route:
274
+ Use the installed same-origin route and `createAssistantSessionHandler` from `@noodleseed/assistant/server`; do not regenerate guards, JSON parsing or token-exchange infrastructure. The only identity seam is `authenticateAssistantRequest` in `lib/noodle-assistant-auth.ts`.
250
275
 
251
- The route must return a JSON `401` for a signed-out caller rather than an HTML login redirect, require JSON, and compare the request against the exact configured Origin before exchange. The managed renderer sends same-origin cookies but does not invent an application CSRF token. Treat JSON plus exact Origin as the minimum cookie/CSRF boundary; when the host keeps token-based CSRF middleware, use the DOM-free client with an injected `fetch` that supplies the token instead of exempting the route. Resolve claims and connector routes from the authenticated user and server-owned membership only.
276
+ The route must return a JSON `401` for a signed-out caller rather than an HTML login redirect, require JSON, and compare the request against the exact configured Origin before exchange. The managed renderer sends same-origin cookies but does not invent an application CSRF token. Preserve the host token-based CSRF middleware: inject a same-origin-only token fetch into the managed element before mounting, as the Django/Vue profile does; a custom renderer is not required. Invalid CSRF may correctly fail before authentication. Resolve claims and connector routes from the authenticated user and server-owned membership only.
252
277
 
253
278
  For the complete framework-neutral server-to-server HTTP exchange in a non-Node host, link to the [Django and Vue guide](https://docs.noodleseed.dev/docs/guides/embedded-assistant-django-vue); keep its framework-specific middleware and mounting mechanics there rather than copying them into agent instructions.
254
279
 
255
- ```ts
256
- import { createAssistantSession } from "@noodleseed/assistant/server";
257
-
258
- export async function POST(request: Request) {
259
- const user = await requireCurrentUser(request);
260
- const { context } = await request.json();
261
- const session = await createAssistantSession({
262
- serviceUrl: process.env.NOODLE_SERVICE_URL!,
263
- clientId: process.env.NOODLE_ASSISTANT_CLIENT_ID!,
264
- clientSecret: process.env.NOODLE_ASSISTANT_CLIENT_SECRET!,
265
- origin: process.env.PUBLIC_APP_ORIGIN!,
266
- user: {
267
- id: user.id,
268
- email: user.email,
269
- roles: user.roles,
270
- scopes: user.scopes,
271
- },
272
- context,
273
- // Saved, backend-verified user preferences outrank browser hints.
274
- preferences: { locale: user.locale, timeZone: user.timeZone },
275
- });
276
- return Response.json(session);
277
- }
278
- ```
280
+ Return `AssistantSessionIdentity | null` from the adapter. Null means signed out; service failure must throw rather than masquerade as signed out. Derive `user`, claims, preferences and routes from the existing verified session/membership. The maintained handler owns exact-Origin/JSON checks, bounded flat context, no-store JSON failures, a 15-second deadline, no redirects and no exchange retries. For mixed-mode continuation, return a `signInTicket` only from the backend-bound login transaction; never take it directly from browser JSON.
281
+
282
+ For saved presentation preferences, return `preferences: { locale: user.locale, timeZone: user.timeZone }` beside the verified `user`. Omit unknown values; browser hints are not verified account preferences.
279
283
 
280
284
  Authenticate before exchange. Pass backend-verified `user.roles` and OAuth-style `user.scopes` separately; they govern the same per-tool authorization rules as verified MCP bearer claims. Source `origin` from trusted server configuration or strictly match the request origin against the same exact allowlist; never accept an arbitrary request header. Treat page context as untrusted model context, never authorization. Forward the helper response unchanged.
281
285
 
286
+ Run the generated `test/noodle-assistant.test.ts` with the application's Vitest runner (add the dev dependency using its existing package manager if absent). It tests the real generated route with synthetic identities. Separately exercise the real session function with signed-out, tenant-A, tenant-B and authorized fixtures before claiming integration proof. A green mock suite does not prove customer authorization.
287
+
282
288
  `serviceUrl` is the Noodle Seed control-plane base URL: the value `noodle assistant clients create` prints, also stored as `serviceUrl` in `deployment.json`. It is NOT the deployment MCP endpoint (`url`, which ends in `/v1/mcp` and rejects session exchange). Never probe or guess endpoints with real credentials.
283
289
 
284
290
  ### Route customer endpoints from the backend
@@ -765,7 +771,7 @@ noodle assistant embed --check --json
765
771
  noodle assistant embed --check --json --require-env EXAMPLE_DELEG_CLIENT_SECRET
766
772
  ```
767
773
 
768
- The check reports only required and missing environment names, never their values. Pass `--surface public|mixed|authenticated` to match the deployment: `public` drops the backend client id/secret requirement (a public embed has neither), and `public`/`mixed` additionally require `script-src` the one directive whose failure runs no widget code at all, so nothing can report it from inside the page. CSP directives verify against the service origin exactly, via the env placeholder, or through a covering wildcard (`https://*.example.com`); a dynamic expression is marked unverified instead of guessed. Additional `--require-env` names are application-owned; `--env-alias NAME=HOST_NAME` follows a host repo that names an env var differently.
774
+ The check reports required and missing environment names, never their values. Use the exact command in an installed NOODLE-INTEGRATION.md, including its aliases and additional names, with the host environment exported; the CLI does not load application dotenv files. Pass `--surface public|mixed|authenticated` to match the deployment: `public` needs no backend client credentials. Public script-tag mounts need the service in `script-src`; the generated bundled React mount needs only `connect-src` and `frame-src` for that service. Unknown public mounts retain the conservative script check. CSP verifies an exact service origin, an env placeholder or a covering wildcard; dynamic expressions stay unverified. `--require-env` adds application-owned names and `--env-alias NAME=HOST_NAME` follows existing host naming.
769
775
 
770
776
  Read `evidence.levels` in order and stop at `evidence.firstUnproven`:
771
777