minutework 0.1.19 → 0.1.21

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 (71) hide show
  1. package/EXTERNAL_ALPHA.md +15 -4
  2. package/README.md +48 -6
  3. package/assets/claude-local/CLAUDE.md.template +56 -3
  4. package/assets/claude-local/bundle.json +4 -1
  5. package/assets/claude-local/skills/README.md +5 -1
  6. package/assets/claude-local/skills/app-pack-authoring/SKILL.md +57 -0
  7. package/assets/claude-local/skills/contract-first-public-intake/SKILL.md +22 -0
  8. package/assets/claude-local/skills/generated-workspace-architecture/SKILL.md +16 -2
  9. package/assets/claude-local/skills/published-web-and-mw-core-site/SKILL.md +12 -0
  10. package/assets/claude-local/skills/runtime-capability-inventory/SKILL.md +17 -0
  11. package/assets/claude-local/skills/schema-engine/SKILL.md +18 -0
  12. package/assets/claude-local/skills/shell-architecture/SKILL.md +3 -0
  13. package/assets/claude-local/skills/standalone-mobile-client/SKILL.md +105 -0
  14. package/assets/claude-local/skills/vuilder-discovery-output-contract/SKILL.md +136 -0
  15. package/assets/claude-local/skills/vuilder-public-site-authoring/SKILL.md +76 -0
  16. package/assets/templates/fastapi-sidecar/README.md +7 -1
  17. package/assets/templates/fastapi-sidecar/template.schema.json +4 -2
  18. package/assets/templates/mobile-app/.env.example +16 -0
  19. package/assets/templates/mobile-app/AGENTS.md +44 -0
  20. package/assets/templates/mobile-app/README.md +123 -0
  21. package/assets/templates/mobile-app/app/(app)/_layout.tsx +10 -0
  22. package/assets/templates/mobile-app/app/(app)/index.tsx +72 -0
  23. package/assets/templates/mobile-app/app/(auth)/login.tsx +91 -0
  24. package/assets/templates/mobile-app/app/_layout.tsx +15 -0
  25. package/assets/templates/mobile-app/app.json +31 -0
  26. package/assets/templates/mobile-app/babel.config.js +7 -0
  27. package/assets/templates/mobile-app/eas.json +24 -0
  28. package/assets/templates/mobile-app/expo-env.d.ts +5 -0
  29. package/assets/templates/mobile-app/metro.config.js +7 -0
  30. package/assets/templates/mobile-app/package.json +32 -0
  31. package/assets/templates/mobile-app/src/mw/client.ts +251 -0
  32. package/assets/templates/mobile-app/src/mw/contracts.ts +79 -0
  33. package/assets/templates/mobile-app/src/mw/endpoints.ts +42 -0
  34. package/assets/templates/mobile-app/src/mw/env.ts +59 -0
  35. package/assets/templates/mobile-app/src/mw/session.ts +50 -0
  36. package/assets/templates/mobile-app/template.json +18 -0
  37. package/assets/templates/mobile-app/tools/template/validate-template.mjs +69 -0
  38. package/assets/templates/mobile-app/tsconfig.json +16 -0
  39. package/assets/templates/next-tenant-app/README.md +15 -4
  40. package/assets/templates/next-tenant-app/template.schema.json +4 -2
  41. package/dist/cli-json.d.ts +28 -0
  42. package/dist/cli-json.js +20 -0
  43. package/dist/cli-json.js.map +1 -0
  44. package/dist/compile.js +71 -7
  45. package/dist/compile.js.map +1 -1
  46. package/dist/deploy.js +147 -14
  47. package/dist/deploy.js.map +1 -1
  48. package/dist/developer-client.d.ts +22 -0
  49. package/dist/developer-client.js +9 -0
  50. package/dist/developer-client.js.map +1 -1
  51. package/dist/index.js +43 -14
  52. package/dist/index.js.map +1 -1
  53. package/dist/init-prompt.js +10 -2
  54. package/dist/init-prompt.js.map +1 -1
  55. package/dist/init.d.ts +2 -1
  56. package/dist/init.js +59 -2
  57. package/dist/init.js.map +1 -1
  58. package/dist/publish.d.ts +29 -0
  59. package/dist/publish.js +254 -0
  60. package/dist/publish.js.map +1 -0
  61. package/dist/runtime-package.d.ts +3 -1
  62. package/dist/runtime-package.js +13 -0
  63. package/dist/runtime-package.js.map +1 -1
  64. package/dist/workspace-assets.js +37 -5
  65. package/dist/workspace-assets.js.map +1 -1
  66. package/dist/workspace-bootstrap.d.ts +1 -0
  67. package/dist/workspace-bootstrap.js.map +1 -1
  68. package/dist/workspace.js +2 -0
  69. package/dist/workspace.js.map +1 -1
  70. package/package.json +2 -2
  71. package/vendor/workspace-mcp/types.d.ts +10 -0
@@ -0,0 +1,136 @@
1
+ ---
2
+ name: vuilder-discovery-output-contract
3
+ description: "Constrain Vuilder discovery to AI-native service operations and MinuteWork-buildable delivery systems instead of generic startup ideas."
4
+ ---
5
+
6
+ # Vuilder Discovery Output Contract
7
+
8
+ Use this skill when Vuilder discovery is deciding what to propose from a user's
9
+ profile, resume, pasted context, or refinement request.
10
+
11
+ Discovery is pre-workspace. It must produce a buildable service direction that
12
+ can flow into the paid Planning Blueprint and later Builder handoff. It must not
13
+ pretend to create projects, runtimes, releases, packages, secrets, or live
14
+ published surfaces.
15
+
16
+ ## Core Output
17
+
18
+ Discovery cards are build cards, not founder idea cards.
19
+
20
+ Each card must describe:
21
+
22
+ - an AI-native service outcome the operator can credibly deliver
23
+ - the buyer or internal team receiving that service outcome
24
+ - evidence from the user's profile that makes the operator credible
25
+ - the MinuteWork delivery system Builder can prepare
26
+ - the first workflow the system should support
27
+ - the runtime records, AI workers, Builder surfaces, and artifacts involved
28
+ - capability gap candidates for unsupported substrate
29
+
30
+ Discovery should ask:
31
+
32
+ > What service outcome can this operator credibly deliver, and what
33
+ > MinuteWork-supported system can Builder create to help deliver it?
34
+
35
+ Do not ask:
36
+
37
+ > What SaaS startup should this person found?
38
+
39
+ ## Builder Constraints
40
+
41
+ - Compose shared MinuteWork substrate before bespoke code.
42
+ - Treat the app pack as the shipped product unit.
43
+ - Check shell fit first for member-facing collaboration and operator work.
44
+ - Use `tenant-app` only for public surfaces or explicit standalone UI
45
+ exceptions after shell fit has been checked.
46
+ - Use `sidecar` only for concrete backend/runtime responsibilities such as
47
+ webhooks, workers, schedulers, external sync, or Python-heavy compute.
48
+ - Reuse runtime AI for drafting/generation before proposing bespoke provider
49
+ calls in `tenant-app` or `sidecar`.
50
+ - Per-customer deliverables should come from runtime data/content/workflows, not
51
+ fresh Builder/codegen work for each customer.
52
+ - Unsupported substrate becomes a capability gap candidate, not a polished
53
+ promise.
54
+
55
+ ## Banned Framing
56
+
57
+ Do not make these the primary discovery output:
58
+
59
+ - generic SaaS ideas
60
+ - founder coaching
61
+ - TAM-first or ARR-first startup cards
62
+ - broad knowledge-management products without a specific service workflow
63
+ - unsupported "Builder can build anything" claims
64
+
65
+ Service packaging, pricing, market notes, or revenue hypotheses may appear as
66
+ secondary context only after the service outcome and buildable system are clear.
67
+
68
+ ## Discovery Tool Use
69
+
70
+ The discovery agent may use read-only evaluation and research tools to sharpen
71
+ cards before it saves proposal fields:
72
+
73
+ - `extract_workflows_from_context` for repeatable work, bottlenecks, buyers, and
74
+ AI workforce jobs already implied by the user's context.
75
+ - `search_industry_examples` for curated MinuteWork service-business patterns.
76
+ This is not open web search.
77
+ - `estimate_market_pricing` for bounded pilot and retainer ranges with explicit
78
+ assumptions and uncertainty.
79
+ - `score_service_card_fit` for use-it-first fit, sell-it-back potential, buyer
80
+ urgency, founder advantage, implementation difficulty, and jargon risk.
81
+ - `rewrite_card_for_plain_language` for advisory rewrites of user-facing fields.
82
+ - `validate_discovery_proposal` before saving or marking a proposal ready.
83
+ - `research_unknown_entity` only as a confidence-gated fallback for an obscure
84
+ company, product, acronym, vendor, or niche workflow already present in the
85
+ resume, pasted context, or user message.
86
+
87
+ Do not use web research for well-known entities or general concepts the model
88
+ already understands, such as ADP, Workday, Fidelity, COBRA, or 401k, unless the
89
+ user explicitly asks for current/recent information. Web results are cited
90
+ context evidence, not authority for market-size or revenue claims.
91
+
92
+ ## Required Build Card Fields
93
+
94
+ Use the `DiscoveryBuildCardV1` shape:
95
+
96
+ - `id`
97
+ - `plain_title`
98
+ - `plain_summary`
99
+ - `work_you_already_do`
100
+ - `ai_workforce_summary`
101
+ - `use_it_first`
102
+ - `sell_it_back`
103
+ - `first_pilot`
104
+ - `market_size_estimate`
105
+ - `yearly_revenue_potential`
106
+ - `pilot_pricing`
107
+ - `why_you_can_win`
108
+ - `who_it_helps`
109
+ - `what_minutework_helps_with`
110
+ - `service_outcome`
111
+ - `buyer`
112
+ - `operator_fit_summary`
113
+ - `evidence_from_profile`
114
+ - `minute_work_build`
115
+ - `builder_surfaces`
116
+ - `shell_fit`
117
+ - `shell_surface`
118
+ - `standalone_ui_exception_reason`
119
+ - `first_workflow`
120
+ - `runtime_records`
121
+ - `ai_workforce`
122
+ - `expected_artifact_graph`
123
+ - `required_capabilities`
124
+ - `capability_gap_candidates`
125
+ - `paid_blueprint_will_create`
126
+ - optional `service_packaging`
127
+
128
+ Plain-language fields are user-facing. They must express MinuteWork's loop:
129
+ build the AI workforce from work the operator already does, use it first under
130
+ their own judgment, then sell it back to their industry. Do not expose Builder
131
+ jargon such as app pack, schemas, runtime records, right rail, runtime agent seed,
132
+ or capability gaps in those fields.
133
+
134
+ Capability gap candidates use the same layer vocabulary as
135
+ `MinuteWorkCapabilityGapReportV1`, but they are not the workspace gap report.
136
+ The actual gap report is created later inside the generated Builder workspace.
@@ -0,0 +1,76 @@
1
+ ---
2
+ name: vuilder-public-site-authoring
3
+ description: "Vuilder-owned public websites, public-dj CMS manifests, vuilder-public-site template work, or customer onboarding from a Vuilder vertical site."
4
+ ---
5
+
6
+ # Vuilder Public Site Authoring
7
+
8
+ Use this skill when the request touches a Vuilder-owned public website,
9
+ `vuilder-public-site`, public-dj CMS content, or customer signup through a
10
+ Vuilder vertical.
11
+
12
+ ## System Boundary
13
+
14
+ - Vuilder public sites are authored in a Vuilder builder/operator workspace.
15
+ - Generated React, Next.js, route, component, and styling code belongs in the
16
+ workspace/template source tree, not in public-dj database rows.
17
+ - public-dj is the CMS and revision store for Vuilder-owned public content:
18
+ pages, blog posts, onboarding flow content, navigation, brand profile, media,
19
+ theme tokens, and approved package/install metadata.
20
+ - Platform owns `PublishedWebProperty`, `SiteRelease`, domains, hosted
21
+ deployment receipts, onboarding intents, billing disposition, and runtime
22
+ provisioning.
23
+ - Tenant/customer runtime content after customer provisioning remains
24
+ runtime-canonical through `mw.core.site` and app-pack data.
25
+
26
+ ## Template Contract
27
+
28
+ - Use `runtime/builder/templates/vuilder-public-site` for Vuilder-owned landing,
29
+ blog, public onboarding, and other public marketing routes.
30
+ - The template reads public-dj through a server-only CMS adapter.
31
+ - Expected release env includes `MW_PUBLIC_DJ_BASE_URL`,
32
+ `MW_PUBLIC_CONTENT_REF`, `MW_PUBLIC_CONTENT_DIGEST`, and
33
+ `MW_PUBLIC_DJ_READ_TOKEN`.
34
+ - Resolve CMS data by immutable `content_ref + digest`; do not render live
35
+ public pages from mutable drafts.
36
+ - The template may be released as `static_export` or `ssr_container` behind the
37
+ hosted deployment provider abstraction.
38
+
39
+ ## Onboarding Intent Rules
40
+
41
+ - Vuilder authoring signup uses `vuilder_authoring`: create workspace/profile,
42
+ no runtime VM.
43
+ - Vuilder upgrade uses `vuilder_operator_runtime`: provision the Vuilder's own
44
+ builder/operator VM and materialize the public-site template.
45
+ - Customer signup through the Vuilder site uses `customer_vertical_signup`:
46
+ provision the customer's tenant runtime and install the approved vertical
47
+ package.
48
+ - The public-site BFF creates customer onboarding intents from server-pinned
49
+ release context: property/release refs, manifest ref/digest, package
50
+ ref/digest, onboarding flow ref/digest, and Vuilder workspace context.
51
+ - Browser intake answers are untrusted input. Browsers must not author package
52
+ refs, manifest refs, provisioning kind, or workspace identity.
53
+
54
+ ## Release Rules
55
+
56
+ - Platform `SiteRelease.content_source` should be `shared_public_content` for a
57
+ Vuilder public site.
58
+ - `shared_public_content_ref` and `shared_public_content_digest` point to the
59
+ public-dj `PublicSiteManifestRevision`.
60
+ - Live activation remains a platform/manual approval action using approved
61
+ public content and package refs.
62
+ - Public-dj may store markdown/content/media/theme revisions; it must not store
63
+ or execute generated React component code as database content.
64
+
65
+ ## Hard Rules
66
+
67
+ - Do not use tenant `/w/*`, `/app`, or the universal tenant shell in
68
+ `vuilder-public-site`.
69
+ - Do not use runtime `mw.core.site` as the CMS for Vuilder-owned public
70
+ websites.
71
+ - Do not infer provisioning behavior from browser labels, hostnames, or query
72
+ params; use platform-owned onboarding intents.
73
+ - Do not expose platform credentials, public-dj tokens, package refs, or
74
+ OpenRouter/provider keys to browser bundles.
75
+ - Do not let public-dj execute tenant runtime code or import runtime Django
76
+ apps for public-site rendering.
@@ -1,6 +1,12 @@
1
1
  # FastAPI Sidecar Template
2
2
 
3
- This directory is the canonical internal FastAPI sidecar scaffold for Builder. Builder will later materialize this bundle into `BuilderWorkspace.sandbox_root/sidecar/` and edit only that workspace copy.
3
+ This directory is the canonical internal FastAPI sidecar scaffold for Builder.
4
+ Builder will later materialize this bundle into
5
+ `BuilderWorkspace.sandbox_root/sidecar/` and edit only that workspace copy.
6
+
7
+ On managed tenant VMs, `BuilderWorkspace.sandbox_root` normally lives under
8
+ `/srv/minutework/workspaces/<workspace_id>/`, so the Builder-edited sidecar copy
9
+ is typically `/srv/minutework/workspaces/<workspace_id>/sidecar/`.
4
10
 
5
11
  `seed_source` intentionally points back to this directory because the canonical bundle itself is the v1 source of truth. There is no separate `apps/*` seed app for this template.
6
12
 
@@ -25,14 +25,16 @@
25
25
  "enum": [
26
26
  "sidecar_nextjs_private",
27
27
  "sidecar_fastapi_internal",
28
- "combined_web"
28
+ "combined_web",
29
+ "public_site"
29
30
  ]
30
31
  },
31
32
  "template_profile": {
32
33
  "type": "string",
33
34
  "enum": [
34
35
  "platform_session_bff",
35
- "bridge_internal"
36
+ "bridge_internal",
37
+ "public_dj_cms"
36
38
  ]
37
39
  },
38
40
  "template_version": {
@@ -0,0 +1,16 @@
1
+ # DEVELOPER-OWNED — replace freely. Only src/mw/ is MinuteWork substrate.
2
+ #
3
+ # Copy this file to `.env` and set the values for your environment.
4
+ #
5
+ # Only EXPO_PUBLIC_* variables are exposed to the app bundle. They are NOT
6
+ # secret — anyone with the app binary can read them. Put only non-secret config
7
+ # here. Never add API keys, client secrets, or tokens as EXPO_PUBLIC_* values;
8
+ # the platform issues short-lived bearer tokens at runtime via the native
9
+ # device flow.
10
+
11
+ # Base URL of the MinuteWork platform this app talks to directly
12
+ # (e.g. https://platform.minutework.dev). The app calls /api/v1/native/... here.
13
+ EXPO_PUBLIC_MW_PLATFORM_BASE_URL=https://<your-platform-base-url>
14
+
15
+ # Optional display name shown in the UI. Defaults to "MinuteWork".
16
+ # EXPO_PUBLIC_MW_APP_NAME=MinuteWork
@@ -0,0 +1,44 @@
1
+ # AGENTS.md — mobile-app starter
2
+
3
+ You are working in a **bring-your-own-UI Expo (React Native + Expo Router)**
4
+ app. **You own all UI/UX.** The only MinuteWork-managed code is the thin
5
+ substrate under `src/mw/`.
6
+
7
+ ## The one rule
8
+
9
+ **Only `src/mw/` is MinuteWork substrate.** Build your product in `app/` and your
10
+ own components/modules. Do not move product logic into `src/mw/`, and do not
11
+ build a parallel/local auth stack — the platform owns identity. The mobile app
12
+ is a **direct platform native API client** (bearer token to `/api/v1/native/...`),
13
+ **not** a `tenant-app` BFF cookie client and **not** a `sidecar`.
14
+
15
+ ## Use your IDE's Expo skills
16
+
17
+ For the native craft (UI, data, build/release), lean on your local IDE skills:
18
+
19
+ - `building-native-ui` — Expo Router UI, styling, navigation, animations
20
+ - `native-data-fetching` — fetch/React Query/SWR, caching, offline, loaders
21
+ - `expo-deployment` — App Store / Play Store / EAS distribution
22
+ - `expo-dev-client` — custom dev client (needed for the browser-assisted auth flow)
23
+ - `expo-tailwind-setup` — NativeWind/Tailwind if you want utility styling
24
+ - `push-notification-setup` — APNs/FCM push
25
+ - `upgrading-expo` — SDK upgrades and dependency fixes
26
+ - `expo-cicd-workflows` — EAS build/deploy pipelines (`.eas/workflows/`)
27
+
28
+ ## MinuteWork integration shape
29
+
30
+ For how this app fits the platform (the standalone-client exception, the native
31
+ session token, and the device flow), read the Builder skill
32
+ **`standalone-mobile-client`** (in the Builder bundle) and the auth contract
33
+ `reference/mwv3-dj6-docs/auth_and_credential_contract.md` (Rule 7: non-browser
34
+ clients mint explicit scoped tokens after browser-assisted login / a device
35
+ flow).
36
+
37
+ ## Status
38
+
39
+ `src/mw/client.ts` and `src/mw/session.ts` are **implemented**: a real
40
+ browser-assisted PKCE device flow against the platform native session endpoints
41
+ (`/api/v1/native/session/*`), with tokens persisted in the device keychain via
42
+ `expo-secure-store`. Wire your UI against the documented client methods
43
+ (`authorize` -> `exchange`, `loadSession`, `logout`). Set `app.json` `expo.scheme`
44
+ to your own unique scheme — that is the OAuth-style redirect target.
@@ -0,0 +1,123 @@
1
+ # Mobile App Starter (Expo, bring-your-own-UI)
2
+
3
+ A minimal **Expo (React Native + Expo Router)** starter for building a native
4
+ MinuteWork client. **You own all of the UI/UX.** The only MinuteWork-managed
5
+ code is the thin substrate under `src/mw/`. Everything else in this template is
6
+ a deliberately plain placeholder meant to be replaced.
7
+
8
+ There is **no MinuteWork design system here** — bring your own. Style with
9
+ whatever you like (plain `StyleSheet`, NativeWind/Tailwind, Tamagui, etc.). The
10
+ plain screens use `StyleSheet` only so there is nothing to rip out.
11
+
12
+ ## What's substrate vs. yours
13
+
14
+ | Path | Owner | Notes |
15
+ | --- | --- | --- |
16
+ | `src/mw/env.ts` | **MinuteWork substrate** | Validates `EXPO_PUBLIC_MW_PLATFORM_BASE_URL` (+ optional app name) |
17
+ | `src/mw/endpoints.ts` | **MinuteWork substrate** | Builds platform `/api/v1/native/...` URLs |
18
+ | `src/mw/contracts.ts` | **MinuteWork substrate** | Zod schemas for native session + token payloads |
19
+ | `src/mw/client.ts` | **MinuteWork substrate** | Native token client — real browser-assisted PKCE device flow |
20
+ | `src/mw/session.ts` | **MinuteWork substrate** | Secure-store token wrapper (`expo-secure-store`) |
21
+ | `tools/template/` | **MinuteWork substrate** | Template-governance tooling, not shipped app code |
22
+ | `app/**` | **You** | Expo Router screens — replace freely |
23
+ | `package.json`, `app.json`, `eas.json` | **You** | App config — replace freely |
24
+ | `tsconfig.json`, `babel.config.js`, `metro.config.js` | **You** | Tooling config — replace freely |
25
+
26
+ Rule of thumb: **only `src/mw/` is MinuteWork substrate.** If you find yourself
27
+ editing `src/mw/` to add product behavior, that behavior probably belongs in
28
+ your own `app/` / components instead.
29
+
30
+ ## Auth model (read this)
31
+
32
+ This app authenticates as a **direct platform native client**:
33
+
34
+ - The platform issues a **bearer token** to the device through a
35
+ **browser-assisted PKCE device flow**: `GET /api/v1/native/session/authorize/`
36
+ (with a PKCE `code_challenge`) opens in a system browser; after login/consent
37
+ the platform redirects back with a one-time `code`, which you exchange at
38
+ `POST /api/v1/native/session/token-exchange/` (with the PKCE `code_verifier`)
39
+ for an `{access, refresh, expires_at}` pair, then store in the device keychain
40
+ via `expo-secure-store`.
41
+ - The app then calls the platform **directly** (e.g.
42
+ `GET /api/v1/native/session/me/` or `.../context/`) with
43
+ `Authorization: Bearer <access>`, and on `401` mints a fresh pair at
44
+ `POST /api/v1/native/session/refresh/` (rotating — persist the new pair) before
45
+ retrying. `POST /api/v1/native/session/logout/` revokes the token family.
46
+
47
+ This is **NOT** the Next.js `tenant-app` model. The tenant-app uses a
48
+ server-owned **BFF session cookie** (`platform_session_bff`) because a browser
49
+ can't safely hold tokens. A native app has secure device storage and no
50
+ per-app server in front of it, so it talks to the platform API directly with a
51
+ token instead of a cookie. **Do not** try to reuse the BFF cookie path here, and
52
+ **do not** build a parallel/local auth stack (no JWT minting, no local user
53
+ table) — the platform owns identity.
54
+
55
+ The token is bound to a single tenant + membership, and that binding **is**
56
+ enforced. The optional `audience` and `device_id` fields are **informational
57
+ only**: the platform carries them through the flow but does **not** validate or
58
+ compare them, so do not rely on them as a security boundary.
59
+
60
+ ### The auth client is real — configure your redirect scheme
61
+
62
+ `src/mw/client.ts` and `src/mw/session.ts` are **implemented**. The client:
63
+
64
+ - generates a PKCE `code_verifier` + S256 `code_challenge` (`expo-crypto`),
65
+ - opens the platform `authorize` URL in a system browser
66
+ (`expo-web-browser`'s `openAuthSessionAsync`) with an anti-forgery `state`,
67
+ - captures the returned one-time `code` from the deep-link redirect, exchanges
68
+ it (plus the `code_verifier`) for a token pair, and stores it in the device
69
+ keychain (`expo-secure-store`),
70
+ - sends `Authorization: Bearer <access>` on every platform call and
71
+ auto-refreshes once on a `401`, and
72
+ - revokes + clears local storage on `logout()`.
73
+
74
+ The redirect target is your app's deep-link scheme. The starter ships
75
+ `"scheme": "mobileapp"` in `app.json`, so the redirect is
76
+ `mobileapp://auth/native-callback` (built at runtime via `expo-linking`'s
77
+ `createURL`). **Set `app.json` `expo.scheme` to your own unique scheme** before
78
+ shipping; the client follows whatever you configure, and a unique scheme avoids
79
+ collisions with other apps that could intercept the callback. The browser-based
80
+ flow needs a custom dev client (not stock Expo Go) — see the `expo-dev-client`
81
+ IDE skill.
82
+
83
+ The full flow is documented in the doc-comment at the top of `src/mw/client.ts`.
84
+
85
+ ## Environment
86
+
87
+ Copy `.env.example` to `.env` and set:
88
+
89
+ ```
90
+ EXPO_PUBLIC_MW_PLATFORM_BASE_URL=https://<your-platform-base-url>
91
+ ```
92
+
93
+ Only `EXPO_PUBLIC_*` variables are bundled into the app, and they are **not
94
+ secret** — never put keys/tokens in them. `src/mw/env.ts` validates this value
95
+ at startup with zod and fails fast if it's missing or not a URL.
96
+
97
+ ## Running locally
98
+
99
+ ```
100
+ npm install # or pnpm/yarn/bun
101
+ npm run start # Expo dev server (then press i / a, or scan with Expo Go)
102
+ npm run ios
103
+ npm run android
104
+ npm run typecheck # tsc --noEmit
105
+ ```
106
+
107
+ The native auth flow opens a system browser, which needs a custom dev client
108
+ rather than stock Expo Go; see the `expo-dev-client` IDE skill.
109
+
110
+ ## Distribution
111
+
112
+ **Distribution is your EAS pipeline, not `minutework deploy`.** This starter is
113
+ not a hosted web/sidecar deployable; it produces native binaries. Build and ship
114
+ with EAS (`eas build`, `eas submit`, EAS Update). `eas.json` ships minimal
115
+ `preview` and `production` profiles to start from. See the `expo-deployment` and
116
+ `expo-cicd-workflows` IDE skills.
117
+
118
+ ## Template manifest
119
+
120
+ `template.json` declares `template_kind: "mobile-app"` and `deployable: false`.
121
+ Because mobile is not a web/sidecar deployable, it is validated by
122
+ `tools/template/validate-template.mjs` (run `node tools/template/validate-template.mjs`),
123
+ **not** the strict shared `runtime/builder/templates/template.schema.json`.
@@ -0,0 +1,10 @@
1
+ // DEVELOPER-OWNED — replace freely. Only src/mw/ is MinuteWork substrate.
2
+ //
3
+ // Authed area layout. This is where you'd guard on a loaded MinuteWork session
4
+ // (via `mwClient.loadSession()`) and redirect to /(auth)/login when there is no
5
+ // valid token. Left as a plain Stack so you can build your own gating/UX.
6
+ import { Stack } from "expo-router";
7
+
8
+ export default function AppLayout() {
9
+ return <Stack screenOptions={{ headerShown: false }} />;
10
+ }
@@ -0,0 +1,72 @@
1
+ // DEVELOPER-OWNED — replace freely. Only src/mw/ is MinuteWork substrate.
2
+ //
3
+ // Trivial authed screen. Replace with your real product home. Build your data
4
+ // fetching against the platform native API using the bearer token from
5
+ // `src/mw/` (see the `native-data-fetching` IDE skill).
6
+ import { Pressable, StyleSheet, Text, View } from "react-native";
7
+ import { router } from "expo-router";
8
+
9
+ import { mwClient } from "@/mw/client";
10
+
11
+ export default function HomeScreen() {
12
+ async function onSignOut() {
13
+ try {
14
+ await mwClient.logout();
15
+ } finally {
16
+ router.replace("/(auth)/login");
17
+ }
18
+ }
19
+
20
+ return (
21
+ <View style={styles.container}>
22
+ <Text style={styles.title}>You are in.</Text>
23
+ <Text style={styles.body}>
24
+ Replace this screen with your product. Only `src/mw/` is MinuteWork
25
+ substrate — everything else is yours.
26
+ </Text>
27
+
28
+ <Pressable
29
+ accessibilityRole="button"
30
+ onPress={onSignOut}
31
+ style={({ pressed }) => [styles.button, pressed && styles.buttonPressed]}
32
+ >
33
+ <Text style={styles.buttonText}>Sign out</Text>
34
+ </Pressable>
35
+ </View>
36
+ );
37
+ }
38
+
39
+ const styles = StyleSheet.create({
40
+ container: {
41
+ flex: 1,
42
+ alignItems: "center",
43
+ justifyContent: "center",
44
+ padding: 24,
45
+ gap: 12,
46
+ },
47
+ title: {
48
+ fontSize: 24,
49
+ fontWeight: "700",
50
+ },
51
+ body: {
52
+ fontSize: 15,
53
+ opacity: 0.7,
54
+ textAlign: "center",
55
+ },
56
+ button: {
57
+ marginTop: 16,
58
+ paddingVertical: 12,
59
+ paddingHorizontal: 20,
60
+ borderRadius: 10,
61
+ borderWidth: 1,
62
+ borderColor: "#111827",
63
+ },
64
+ buttonPressed: {
65
+ opacity: 0.6,
66
+ },
67
+ buttonText: {
68
+ fontSize: 15,
69
+ fontWeight: "600",
70
+ color: "#111827",
71
+ },
72
+ });
@@ -0,0 +1,91 @@
1
+ // DEVELOPER-OWNED — replace freely. Only src/mw/ is MinuteWork substrate.
2
+ //
3
+ // This screen is intentionally plain and is meant to be REWRITTEN. It exists to
4
+ // show the one integration seam you care about: kicking off MinuteWork's
5
+ // browser-assisted native sign-in through `src/mw/client.ts`.
6
+ //
7
+ // Pressing "Sign in" runs the real device flow: authorize in a system browser ->
8
+ // exchange the returned code for a platform bearer token pair -> route into the
9
+ // authed stack. Wire your own UI/UX around this call.
10
+ import { useState } from "react";
11
+ import { Alert, Pressable, StyleSheet, Text, View } from "react-native";
12
+ import { router } from "expo-router";
13
+
14
+ import { mwClient } from "@/mw/client";
15
+ import { mwEnv } from "@/mw/env";
16
+
17
+ export default function LoginScreen() {
18
+ const [busy, setBusy] = useState(false);
19
+
20
+ async function onSignIn() {
21
+ setBusy(true);
22
+ try {
23
+ // Device flow: authorize (browser) -> exchange code+verifier for tokens
24
+ // (stored in the keychain by the client), then route into the authed stack.
25
+ const { code, codeVerifier, redirectUri } = await mwClient.authorize();
26
+ await mwClient.exchange(code, codeVerifier, redirectUri);
27
+ router.replace("/(app)");
28
+ } catch (error) {
29
+ Alert.alert(
30
+ "Sign in unavailable",
31
+ error instanceof Error ? error.message : "Unknown error",
32
+ );
33
+ } finally {
34
+ setBusy(false);
35
+ }
36
+ }
37
+
38
+ return (
39
+ <View style={styles.container}>
40
+ <Text style={styles.title}>{mwEnv.appName}</Text>
41
+ <Text style={styles.subtitle}>Sign in to continue</Text>
42
+
43
+ <Pressable
44
+ accessibilityRole="button"
45
+ disabled={busy}
46
+ onPress={onSignIn}
47
+ style={({ pressed }) => [
48
+ styles.button,
49
+ (pressed || busy) && styles.buttonPressed,
50
+ ]}
51
+ >
52
+ <Text style={styles.buttonText}>
53
+ {busy ? "Opening sign in…" : "Sign in with MinuteWork"}
54
+ </Text>
55
+ </Pressable>
56
+ </View>
57
+ );
58
+ }
59
+
60
+ const styles = StyleSheet.create({
61
+ container: {
62
+ flex: 1,
63
+ alignItems: "center",
64
+ justifyContent: "center",
65
+ padding: 24,
66
+ gap: 12,
67
+ },
68
+ title: {
69
+ fontSize: 28,
70
+ fontWeight: "700",
71
+ },
72
+ subtitle: {
73
+ fontSize: 16,
74
+ opacity: 0.7,
75
+ marginBottom: 12,
76
+ },
77
+ button: {
78
+ backgroundColor: "#111827",
79
+ paddingVertical: 14,
80
+ paddingHorizontal: 24,
81
+ borderRadius: 10,
82
+ },
83
+ buttonPressed: {
84
+ opacity: 0.6,
85
+ },
86
+ buttonText: {
87
+ color: "#ffffff",
88
+ fontSize: 16,
89
+ fontWeight: "600",
90
+ },
91
+ });
@@ -0,0 +1,15 @@
1
+ // DEVELOPER-OWNED — replace freely. Only src/mw/ is MinuteWork substrate.
2
+ import { Stack } from "expo-router";
3
+ import { StatusBar } from "expo-status-bar";
4
+
5
+ export default function RootLayout() {
6
+ return (
7
+ <>
8
+ <Stack screenOptions={{ headerShown: false }}>
9
+ <Stack.Screen name="(auth)/login" />
10
+ <Stack.Screen name="(app)" />
11
+ </Stack>
12
+ <StatusBar style="auto" />
13
+ </>
14
+ );
15
+ }
@@ -0,0 +1,31 @@
1
+ {
2
+ "//": "DEVELOPER-OWNED — replace freely. Only src/mw/ is MinuteWork substrate.",
3
+ "//scheme": "expo.scheme is the OAuth-style redirect target for the native auth device flow (src/mw/client.ts builds <scheme>://auth/native-callback). Set a unique scheme before shipping.",
4
+ "expo": {
5
+ "name": "mobile-app",
6
+ "slug": "mobile-app",
7
+ "scheme": "mobileapp",
8
+ "version": "0.1.0",
9
+ "orientation": "portrait",
10
+ "userInterfaceStyle": "automatic",
11
+ "newArchEnabled": true,
12
+ "ios": {
13
+ "supportsTablet": true,
14
+ "bundleIdentifier": "com.example.mobileapp"
15
+ },
16
+ "android": {
17
+ "package": "com.example.mobileapp"
18
+ },
19
+ "web": {
20
+ "bundler": "metro",
21
+ "output": "static"
22
+ },
23
+ "plugins": [
24
+ "expo-router",
25
+ "expo-secure-store"
26
+ ],
27
+ "experiments": {
28
+ "typedRoutes": true
29
+ }
30
+ }
31
+ }
@@ -0,0 +1,7 @@
1
+ // DEVELOPER-OWNED — replace freely. Only src/mw/ is MinuteWork substrate.
2
+ module.exports = function (api) {
3
+ api.cache(true);
4
+ return {
5
+ presets: ["babel-preset-expo"],
6
+ };
7
+ };