@mutmutco/codex-plugin 4.5.101 → 4.5.103

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,23 @@
1
+ {
2
+ "name": "@mutmutco/{{REPO_SLUG}}-module",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "description": "MM-Work module screens",
7
+ "scripts": {
8
+ "lint": "mmwork-check-ui src",
9
+ "build": "vite build",
10
+ "dev": "vite build --watch",
11
+ "serve": "vite preview --port 5301",
12
+ "check": "npm run lint && npm run build"
13
+ },
14
+ "dependencies": {
15
+ "@mutmutco/mmwork-ds": "0.71.45",
16
+ "react": "^19.0.0",
17
+ "react-dom": "^19.0.0"
18
+ },
19
+ "devDependencies": {
20
+ "typescript": "^5.9.3",
21
+ "vite": "^8.0.0"
22
+ }
23
+ }
@@ -0,0 +1,105 @@
1
+ // The example module (#126): the smallest module MM-Work's shell loads, and the reference for module repos.
2
+ // It is built with mmworkModule() (vite.config.mjs), uses only the shared design system, and reaches Drive
3
+ // through MM-Work's surface actions, never its own Google access.
4
+ import { useEffect, useState } from 'react';
5
+ import { Button, Card, Cluster, DataList, Page, Stack, Text } from '@mutmutco/mmwork-ds';
6
+
7
+ export const manifest = { id: '{{REPO_SLUG}}', name: '{{REPO_NAME}}' };
8
+
9
+ // In the registry, declare `settings: { groups: [{ title: 'Display', items: [
10
+ // { key: 'compact', label: 'Compact view', type: 'toggle', default: false, lockable: true },
11
+ // { key: 'welcome', label: 'Welcome message', type: 'text', default: 'Hello', admin: true }
12
+ // ] }] }`. MM-Work draws those choices; ctx.settings.get('compact') reads the effective value.
13
+ // Declare `search: { title: 'Example module' }` and `sections: [{ label: 'Home', path: '/' },
14
+ // { label: 'Details', path: '/details' }]` in the registry. Results are data, not rendered rows.
15
+ export const search = {
16
+ find: async (query, ctx, signal) => {
17
+ if (signal.aborted) return { results: [] };
18
+ const files = await ctx.act('files.search', { query }); // this person's Google grant and access rules
19
+ if (signal.aborted) return { results: [] };
20
+ return { results: files.items.slice(0, 20).map((file) => ({ title: file.name, context: 'Recent files', path: '/details' })), count: files.items.length };
21
+ },
22
+ };
23
+
24
+ // Registry job: `jobs: [{ key: 'files.review', label: 'Review files', default: 'admins' }]`.
25
+ // The screen checks the job below; the module backend checks the sign-in `jobs` claim too.
26
+ // Home widgets (docs/decisions/2026-09-26-home-widgets.md): data only, MM-Work draws them. The registry entry declares
27
+ // { id: 'files', type: 'figure' }, { id: 'todo', type: 'list' } and { id: 'note', type: 'quick-add' }.
28
+ const due = (days) => new Date(Date.now() + days * 86_400_000).toISOString().slice(0, 10);
29
+ export const widgets = {
30
+ // A surface action's `reconnect` refusal becomes the not-connected state by itself.
31
+ files: { load: async (ctx) => {
32
+ const r = await ctx.act('files.list', { source: 'recent' });
33
+ return { figures: [{ label: 'Recent files', value: r.items.length, foot: 'In your Drive', href: ctx.base }] };
34
+ } },
35
+ todo: { refresh: 300, load: async (ctx) => ({
36
+ count: 1,
37
+ total: 7,
38
+ rows: [
39
+ { id: '1', title: 'Send the campaign brief', context: 'Autumn campaign', due: due(-1), priority: 'urgent', href: `${ctx.base}/details` },
40
+ { id: '2', title: 'Review the logo options', context: 'Brand refresh', due: due(0), priority: 'high', unread: true, href: `${ctx.base}/details` },
41
+ { id: '3', title: 'Book the studio', context: 'Autumn campaign', due: due(3), href: `${ctx.base}/details` },
42
+ { id: '4', title: 'Share the moodboard', context: 'Brand refresh', due: due(9), priority: 'low', href: `${ctx.base}/details` },
43
+ ],
44
+ }) },
45
+ note: { placeholder: 'Jot a note', submit: async (ctx) => ({ href: `${ctx.base}/details` }) },
46
+ };
47
+
48
+ export default function ExampleModule({ ctx }) {
49
+ const [files, setFiles] = useState(null);
50
+ const [failed, setFailed] = useState(false);
51
+ // Help (GUIDE: Help (required)): name this screen's registry help; its tour rings the data-tour pieces below.
52
+ useEffect(() => { ctx.help.screen('home'); }, [ctx.help]);
53
+ useEffect(() => {
54
+ ctx.act('files.list', { source: 'recent' })
55
+ .then((r) => setFiles(r.items))
56
+ .catch(() => setFailed(true));
57
+ }, [ctx]);
58
+
59
+ return (
60
+ <Page title={`Hello, ${ctx.person.name ?? ctx.person.email}`} width="narrow">
61
+ <Stack gap="groups">
62
+ <Text as="p" variant="lead" data-tour="example-intro">
63
+ This screen comes from a module's own bundle. It uses MM-Work's React and design system, and reads Drive through MM-Work.
64
+ </Text>
65
+ <Card data-tour="example-overview" title="What the module sees" icon="info">
66
+ <DataList rows={[
67
+ { label: 'Workspace', value: ctx.workspace.name },
68
+ { label: 'Address in MM-Work', value: `${ctx.base}${ctx.path === '/' ? '' : ctx.path}` },
69
+ { label: 'Recent files (files.list)', value: failed ? 'Drive is not connected' : files === null ? 'Loading…' : String(files.length) },
70
+ { label: 'Review files job', value: ctx.can('files.review') ? 'Held' : 'Not held' },
71
+ ]} />
72
+ </Card>
73
+ <Cluster data-tour="example-actions">
74
+ <Button variant="secondary" onClick={() => ctx.navigate(`${ctx.base}/details`)}>Open a page inside the module</Button>
75
+ <Button variant="ghost" onClick={() => ctx.navigate('/files')}>Go to Files</Button>
76
+ </Cluster>
77
+ </Stack>
78
+ </Page>
79
+ );
80
+ }
81
+
82
+ // Module file adapter (#2177, GUIDE "Module file adapter"): the module's own media at
83
+ // /files/example/<id> opens in the shell's file viewer through this export. The shell holds only
84
+ // the opaque id; this check decides, so a sealed item never reaches the shell as plaintext.
85
+ // `hello` is the demo file, `private` answers forbidden, anything else is not found. Link to it with
86
+ // ctx.navigate(ctx.files.href({ source: 'module', module: 'example', id: 'hello' })).
87
+ function ExampleFileBody({ ctx, id }) {
88
+ return (
89
+ <Card title={`File ${id}`} icon="doc">
90
+ <DataList rows={[
91
+ { label: 'Workspace', value: ctx.workspace.name },
92
+ { label: 'Opened by', value: ctx.person.name ?? ctx.person.email },
93
+ ]} />
94
+ <Text as="p">This body comes from the module's own bundle, inside the shell's file viewer.</Text>
95
+ </Card>
96
+ );
97
+ }
98
+ export const files = {
99
+ open: async (id, ctx, signal) => {
100
+ if (signal.aborted) return null;
101
+ if (id === 'private') throw Object.assign(new Error('Not a member'), { code: 'forbidden' });
102
+ if (id !== 'hello') return null;
103
+ return { name: 'Hello from the module', kind: 'doc', typeLabel: 'Example file', Body: ExampleFileBody };
104
+ },
105
+ };
@@ -0,0 +1,2 @@
1
+ @mutmutco:registry=https://npm.pkg.github.com
2
+ //npm.pkg.github.com/:_authToken=${NODE_AUTH_TOKEN}
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "{{REPO_SLUG}}",
3
+ "version": "0.1.0",
4
+ "lockfileVersion": 3,
5
+ "requires": true,
6
+ "packages": {
7
+ "": {
8
+ "name": "{{REPO_SLUG}}",
9
+ "version": "0.1.0",
10
+ "devDependencies": {
11
+ "@types/node": "^24.0.0",
12
+ "typescript": "^5.9.3"
13
+ },
14
+ "engines": {
15
+ "node": ">=24"
16
+ }
17
+ },
18
+ "node_modules/@types/node": {
19
+ "version": "24.19.1",
20
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.19.1.tgz",
21
+ "integrity": "sha512-aS3/DG0oM05K0RIXXP+hKjinGG5IgSSVGzswZxW3O0sS3pH4/fycXundUC9XsszgKCk4gHXylTEK6hyFxVxnoQ==",
22
+ "dev": true,
23
+ "license": "MIT",
24
+ "dependencies": {
25
+ "undici-types": ">=7.24.0 <7.24.7"
26
+ }
27
+ },
28
+ "node_modules/typescript": {
29
+ "version": "5.9.3",
30
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
31
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
32
+ "dev": true,
33
+ "license": "Apache-2.0",
34
+ "bin": {
35
+ "tsc": "bin/tsc",
36
+ "tsserver": "bin/tsserver"
37
+ },
38
+ "engines": {
39
+ "node": ">=14.17"
40
+ }
41
+ },
42
+ "node_modules/undici-types": {
43
+ "version": "7.24.6",
44
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz",
45
+ "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==",
46
+ "dev": true,
47
+ "license": "MIT"
48
+ }
49
+ }
50
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "{{REPO_SLUG}}",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "engines": {
7
+ "node": ">=24"
8
+ },
9
+ "scripts": {
10
+ "build": "tsc",
11
+ "typecheck": "tsc --noEmit",
12
+ "check": "npm run typecheck && npm run build && npm --prefix module run check",
13
+ "start": "node dist/index.js"
14
+ },
15
+ "devDependencies": {
16
+ "@types/node": "^24.0.0",
17
+ "typescript": "^5.9.3"
18
+ }
19
+ }
@@ -0,0 +1,170 @@
1
+ {
2
+ "module": {
3
+ "id": "{{REPO_SLUG}}",
4
+ "name": "{{REPO_NAME}}",
5
+ "glyph": "grid",
6
+ "tone": "blue",
7
+ "description": "Offline module screen check",
8
+ "sections": [
9
+ {
10
+ "label": "Home",
11
+ "path": "/"
12
+ },
13
+ {
14
+ "label": "Details",
15
+ "path": "/details"
16
+ }
17
+ ],
18
+ "help": {
19
+ "home": {
20
+ "name": "Example home",
21
+ "howTos": [
22
+ {
23
+ "id": "recent",
24
+ "title": "See recent files",
25
+ "intro": "The module counts the files you opened lately in Google Drive.",
26
+ "steps": [
27
+ {
28
+ "title": "Connect Drive",
29
+ "text": "Connect your Google account in Files to see your recent files.",
30
+ "scene": "connect"
31
+ },
32
+ {
33
+ "title": "Come back here",
34
+ "text": "The overview shows how many recent files MM-Work found."
35
+ }
36
+ ]
37
+ },
38
+ {
39
+ "id": "details",
40
+ "title": "Open the details page",
41
+ "steps": [
42
+ {
43
+ "title": "Press Open a page inside the module",
44
+ "text": "It is under the overview. Details is also in the section row.",
45
+ "scene": "screen"
46
+ },
47
+ {
48
+ "title": "Go back",
49
+ "text": "Press Home in the section row."
50
+ }
51
+ ]
52
+ }
53
+ ],
54
+ "faq": [
55
+ {
56
+ "id": "drive",
57
+ "question": "Where are my files?",
58
+ "answer": "Recent files come from your connected Google Drive."
59
+ },
60
+ {
61
+ "id": "job",
62
+ "question": "What is the Review files job?",
63
+ "answer": "A job your admin can give you. The overview says whether you hold it."
64
+ },
65
+ {
66
+ "id": "access",
67
+ "question": "Does this module get my Google password?",
68
+ "answer": "No. It asks MM-Work for your files, and MM-Work uses the access you granted it."
69
+ }
70
+ ],
71
+ "tour": [
72
+ {
73
+ "anchor": "example-intro",
74
+ "title": "What this is",
75
+ "text": "A module's own screen, drawn with MM-Work's design system."
76
+ },
77
+ {
78
+ "anchor": "example-overview",
79
+ "title": "Your overview",
80
+ "text": "See your workspace and recent Drive files here."
81
+ },
82
+ {
83
+ "anchor": "example-actions",
84
+ "title": "Where to go next",
85
+ "text": "Open the details page, or go to Files."
86
+ }
87
+ ]
88
+ }
89
+ },
90
+ "privacy": [
91
+ {
92
+ "class": "company-visible",
93
+ "what": {
94
+ "en": "The example reads recent Google Drive file metadata through MM-Work after the person connects Files.",
95
+ "tr": "Örnek modül, kişi Files bağlantısını kurduktan sonra MM-Work üzerinden son Google Drive dosyalarının meta verilerini okur."
96
+ },
97
+ "who": {
98
+ "en": "The signed-in person sees the count on this example screen; no file content is saved by the example.",
99
+ "tr": "Oturum açmış kişi örnek ekranda sayıyı görür; örnek modül dosya içeriğini kaydetmez."
100
+ },
101
+ "evidence": [
102
+ "repo:MM-Work:examples/example-module/src/module.jsx"
103
+ ]
104
+ }
105
+ ]
106
+ },
107
+ "screens": [
108
+ {
109
+ "name": "home",
110
+ "path": "/m/example",
111
+ "jobs": [
112
+ "files.review"
113
+ ],
114
+ "routes": {
115
+ "POST /api/actions/files.list": "files.json"
116
+ }
117
+ },
118
+ {
119
+ "name": "open details journey",
120
+ "path": "/m/example",
121
+ "steps": [
122
+ {
123
+ "assert": {
124
+ "selector": "[data-tour=example-actions] button",
125
+ "count": 2
126
+ }
127
+ },
128
+ {
129
+ "click": "[data-tour=example-actions] button:first-child"
130
+ },
131
+ {
132
+ "assert": {
133
+ "url": "/m/example/details"
134
+ }
135
+ }
136
+ ],
137
+ "routes": {
138
+ "POST /api/actions/files.list": "files.json"
139
+ }
140
+ },
141
+ {
142
+ "name": "details",
143
+ "path": "/m/example/details",
144
+ "routes": {
145
+ "POST /api/actions/files.list": "files.json"
146
+ }
147
+ },
148
+ {
149
+ "name": "loading",
150
+ "path": "/m/example",
151
+ "query": "state=loading",
152
+ "routes": {
153
+ "POST /api/actions/files.list": "pending"
154
+ }
155
+ },
156
+ {
157
+ "name": "error",
158
+ "path": "/m/example",
159
+ "query": "state=error",
160
+ "routes": {
161
+ "POST /api/actions/files.list": {
162
+ "status": 500,
163
+ "body": {
164
+ "error": "offline failure"
165
+ }
166
+ }
167
+ }
168
+ }
169
+ ]
170
+ }
@@ -0,0 +1,61 @@
1
+ name: screens
2
+ # A required check (#250): it runs wherever gate runs, so no PR into development, rc or main and no train tag
3
+ # waits on a context that never reports.
4
+ on:
5
+ pull_request:
6
+ push:
7
+ branches: {{GATE_PUSH_BRANCHES_YAML}}
8
+ tags: ['v*']
9
+ # Cancel superseded runs per PR, or per branch for pushes (MMI-Hub#7515, #3001; as MM-Strategy#415).
10
+ concurrency:
11
+ group: screens-${{ github.event.pull_request.number || github.ref }}
12
+ cancel-in-progress: true
13
+ permissions:
14
+ contents: read
15
+ packages: read
16
+ pull-requests: read
17
+ jobs:
18
+ # Owner ruling (#474, 6 Oct 2026): browser checks block nothing, releases included. Every PR, push and tag
19
+ # reports this required context without a browser audit (run: false); screens-sweep.yml audits nightly and
20
+ # on demand (issue-on-red). A development push still reports
21
+ # the required "screens / screens" on the SHA, which the dev deploy needs (MMI-Hub dev-auto-deploy).
22
+ changes:
23
+ runs-on: [self-hosted, linux, x64, mmi-live]
24
+ outputs:
25
+ run: ${{ steps.pick.outputs.run }}
26
+ full: ${{ steps.pick.outputs.full }}
27
+ steps:
28
+ - id: pick
29
+ env:
30
+ GH_TOKEN: ${{ github.token }}
31
+ EVENT: ${{ github.event_name }}
32
+ BASE: ${{ github.base_ref }}
33
+ REF: ${{ github.ref_name }}
34
+ REF_TYPE: ${{ github.ref_type }}
35
+ PR: ${{ github.event.pull_request.number }}
36
+ REPO: ${{ github.repository }}
37
+ run: |
38
+ set -euo pipefail
39
+ # #474: no browser audit on any PR, push or tag; screens-sweep.yml runs it nightly.
40
+ run=false; full=false
41
+ echo "run=$run full=$full"
42
+ { echo "run=$run"; echo "full=$full"; } >> "$GITHUB_OUTPUT"
43
+ screens:
44
+ needs: changes
45
+ # Fail closed (MMI-Hub, 6 Oct): a SKIPPED required check counts as passing, so this job runs even when
46
+ # `changes` failed, and a missing answer means the full audit, never a skip.
47
+ if: ${{ !cancelled() }}
48
+ uses: mutmutco/MM-Work/.github/workflows/module-screens.yml@development
49
+ with:
50
+ run: ${{ needs.changes.outputs.run != 'false' }}
51
+ audit-sizes: ${{ needs.changes.outputs.full != 'false' && '["phone","768","1024","1280"]' || '["phone"]' }}
52
+ # Pinned to the shell built from the kit this module ships (ds-v0.71.42): the runner MM-Work
53
+ # published with that kit; bump with the kit.
54
+ # 0.1.55+ checks MM-Work #1575: selectedStyle, pageBackground, noJump (#418).
55
+ screens-version: '0.1.113'
56
+ working-directory: 'module'
57
+ build-command: 'npm run build'
58
+ bundle-path: 'dist/module.js'
59
+ fixtures-path: 'screens/screens.json'
60
+ # Every screen in English and Turkish (#369): Turkish words run longer, so they get their own audit.
61
+ languages: 'en,tr'
@@ -0,0 +1,22 @@
1
+ // MM-Work owns sign-in. Add backend routes only after verifying an MM-Work pass (see the module GUIDE).
2
+ import { createServer } from 'node:http';
3
+ import { readFile } from 'node:fs/promises';
4
+
5
+ createServer(async (req, res) => {
6
+ const path = new URL(req.url ?? '/', 'http://localhost').pathname;
7
+ if (path === '/health') {
8
+ res.writeHead(200, { 'content-type': 'application/json' });
9
+ res.end(JSON.stringify({ ok: true }));
10
+ return;
11
+ }
12
+ if (path === '/module.js') {
13
+ try {
14
+ const bundle = await readFile(new URL('../module/dist/module.js', import.meta.url));
15
+ res.writeHead(200, { 'content-type': 'text/javascript', 'access-control-allow-origin': '*' });
16
+ res.end(bundle);
17
+ return;
18
+ } catch { /* no bundle yet: return 404, not an unhandled rejection */ }
19
+ }
20
+ res.writeHead(404);
21
+ res.end('Not found');
22
+ }).listen(Number(process.env.PORT || 3000), '0.0.0.0');
@@ -0,0 +1,14 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "strict": true,
7
+ "outDir": "dist",
8
+ "rootDir": "src",
9
+ "skipLibCheck": true
10
+ },
11
+ "include": [
12
+ "src/**/*.ts"
13
+ ]
14
+ }
@@ -0,0 +1,12 @@
1
+ // A module repo's whole build setting (#126): one plugin from the design system package.
2
+ // Build: npx vite build --config module/vite.config.mjs → module/dist/module.js
3
+ import { fileURLToPath } from 'node:url';
4
+
5
+ import { mmworkModule } from '@mutmutco/mmwork-ds/vite';
6
+
7
+ const here = (p) => fileURLToPath(new URL(p, import.meta.url));
8
+
9
+ export default {
10
+ root: here('.'),
11
+ plugins: [mmworkModule({ entry: here('src/module.jsx'), outDir: here('dist') })],
12
+ };
@@ -11,6 +11,7 @@ with `--var SUITE=mm-work`.
11
11
  Cloudflare owns public DNS and edge TLS. Preserve unrelated records during an authorized change.
12
12
  The Hub creates each registered host's proxied record during bootstrap and never edits an existing
13
13
  one; check or re-run it with `mmi-cli devops runtime edge dns status|ensure <owner/repo> <stage>`.
14
+ A dev or rc host also needs its advanced edge certificate: `mmi-cli devops runtime edge cert status|ensure <owner/repo> <stage>`.
14
15
  A dev stage is private only after its independent edge-block check succeeds. A configured record
15
16
  or successful deployment alone does not prove access control. Use [stage](../../stage/SKILL.md)
16
17
  for the supported private live workflow.
@@ -21,7 +22,8 @@ rules itself; it asks MM-Work for an address.
21
22
 
22
23
  ## Google OAuth
23
24
 
24
- An eligible project uses one Google web client across local, dev, rc, and main, not one per stage.
25
+ An MM-Work suite module gets no Google client: it signs in through the MM-Work pass and reaches
26
+ Google only via `ctx.act`. Any other eligible project uses one Google web client across local, dev, rc, and main, not one per stage.
25
27
  The registry declares OAuth metadata and extra callback paths for additional login/connect flows.
26
28
  Generate the current origins and redirects with
27
29
  `mmi-cli vault org oauth plan --repo <owner/repo> --json`; do not assemble a copied URI list.