visionos-monorepo 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. package/.claude/worktrees/competent-burnell-8d1330/README.md +138 -0
  2. package/.claude/worktrees/competent-burnell-8d1330/cli/package.json +35 -0
  3. package/.claude/worktrees/competent-burnell-8d1330/cli/scripts/copy-web-assets.mjs +12 -0
  4. package/.claude/worktrees/competent-burnell-8d1330/cli/src/commands/logout.ts +12 -0
  5. package/.claude/worktrees/competent-burnell-8d1330/cli/src/commands/open.ts +19 -0
  6. package/.claude/worktrees/competent-burnell-8d1330/cli/src/commands/start.ts +97 -0
  7. package/.claude/worktrees/competent-burnell-8d1330/cli/src/commands/status.ts +23 -0
  8. package/.claude/worktrees/competent-burnell-8d1330/cli/src/commands/userinfo.ts +47 -0
  9. package/.claude/worktrees/competent-burnell-8d1330/cli/src/index.ts +23 -0
  10. package/.claude/worktrees/competent-burnell-8d1330/cli/src/lib/auth.ts +84 -0
  11. package/.claude/worktrees/competent-burnell-8d1330/cli/src/lib/browser.ts +37 -0
  12. package/.claude/worktrees/competent-burnell-8d1330/cli/src/lib/localState.ts +80 -0
  13. package/.claude/worktrees/competent-burnell-8d1330/cli/src/lib/runtime.ts +203 -0
  14. package/.claude/worktrees/competent-burnell-8d1330/cli/src/runtime/index.ts +36 -0
  15. package/.claude/worktrees/competent-burnell-8d1330/cli/src/types/inquirer.d.ts +9 -0
  16. package/.claude/worktrees/competent-burnell-8d1330/cli/tsconfig.json +19 -0
  17. package/.claude/worktrees/competent-burnell-8d1330/client/index.html +15 -0
  18. package/.claude/worktrees/competent-burnell-8d1330/client/package.json +27 -0
  19. package/.claude/worktrees/competent-burnell-8d1330/client/postcss.config.cjs +7 -0
  20. package/.claude/worktrees/competent-burnell-8d1330/client/src/App.tsx +57 -0
  21. package/.claude/worktrees/competent-burnell-8d1330/client/src/components/CliAuthPage.tsx +385 -0
  22. package/.claude/worktrees/competent-burnell-8d1330/client/src/components/ManifestoPage.tsx +946 -0
  23. package/.claude/worktrees/competent-burnell-8d1330/client/src/components/TrackCard.tsx +19 -0
  24. package/.claude/worktrees/competent-burnell-8d1330/client/src/lib/api.ts +58 -0
  25. package/.claude/worktrees/competent-burnell-8d1330/client/src/main.tsx +11 -0
  26. package/.claude/worktrees/competent-burnell-8d1330/client/src/styles/index.css +33 -0
  27. package/.claude/worktrees/competent-burnell-8d1330/client/src/styles/manifesto.css +1398 -0
  28. package/.claude/worktrees/competent-burnell-8d1330/client/tailwind.config.ts +36 -0
  29. package/.claude/worktrees/competent-burnell-8d1330/client/tsconfig.json +25 -0
  30. package/.claude/worktrees/competent-burnell-8d1330/client/vite.config.ts +20 -0
  31. package/.claude/worktrees/competent-burnell-8d1330/package-lock.json +5278 -0
  32. package/.claude/worktrees/competent-burnell-8d1330/package.json +24 -0
  33. package/.claude/worktrees/competent-burnell-8d1330/server/package.json +25 -0
  34. package/.claude/worktrees/competent-burnell-8d1330/server/src/app.ts +71 -0
  35. package/.claude/worktrees/competent-burnell-8d1330/server/src/config/env.ts +14 -0
  36. package/.claude/worktrees/competent-burnell-8d1330/server/src/features/auth/sessionStore.ts +74 -0
  37. package/.claude/worktrees/competent-burnell-8d1330/server/src/index.ts +8 -0
  38. package/.claude/worktrees/competent-burnell-8d1330/server/src/routes/auth.ts +112 -0
  39. package/.claude/worktrees/competent-burnell-8d1330/server/src/routes/health.ts +14 -0
  40. package/.claude/worktrees/competent-burnell-8d1330/server/tsconfig.json +19 -0
  41. package/.claude/worktrees/competent-burnell-8d1330/shared/package.json +24 -0
  42. package/.claude/worktrees/competent-burnell-8d1330/shared/src/index.ts +91 -0
  43. package/.claude/worktrees/competent-burnell-8d1330/shared/tsconfig.json +16 -0
  44. package/.claude/worktrees/competent-burnell-8d1330/tsconfig.base.json +12 -0
  45. package/.claude/worktrees/competent-burnell-8d1330/visionos-manifesto/index.html +392 -0
  46. package/.claude/worktrees/competent-burnell-8d1330/visionos-manifesto/script.js +146 -0
  47. package/.claude/worktrees/competent-burnell-8d1330/visionos-manifesto/styles.css +1082 -0
  48. package/.claude/worktrees/vigilant-napier-0de76f/README.md +138 -0
  49. package/.claude/worktrees/vigilant-napier-0de76f/cli/package.json +35 -0
  50. package/.claude/worktrees/vigilant-napier-0de76f/cli/scripts/copy-web-assets.mjs +12 -0
  51. package/.claude/worktrees/vigilant-napier-0de76f/cli/src/commands/logout.ts +12 -0
  52. package/.claude/worktrees/vigilant-napier-0de76f/cli/src/commands/open.ts +19 -0
  53. package/.claude/worktrees/vigilant-napier-0de76f/cli/src/commands/start.ts +97 -0
  54. package/.claude/worktrees/vigilant-napier-0de76f/cli/src/commands/status.ts +23 -0
  55. package/.claude/worktrees/vigilant-napier-0de76f/cli/src/commands/userinfo.ts +47 -0
  56. package/.claude/worktrees/vigilant-napier-0de76f/cli/src/index.ts +23 -0
  57. package/.claude/worktrees/vigilant-napier-0de76f/cli/src/lib/auth.ts +84 -0
  58. package/.claude/worktrees/vigilant-napier-0de76f/cli/src/lib/browser.ts +37 -0
  59. package/.claude/worktrees/vigilant-napier-0de76f/cli/src/lib/localState.ts +80 -0
  60. package/.claude/worktrees/vigilant-napier-0de76f/cli/src/lib/runtime.ts +203 -0
  61. package/.claude/worktrees/vigilant-napier-0de76f/cli/src/runtime/index.ts +36 -0
  62. package/.claude/worktrees/vigilant-napier-0de76f/cli/src/types/inquirer.d.ts +9 -0
  63. package/.claude/worktrees/vigilant-napier-0de76f/cli/tsconfig.json +19 -0
  64. package/.claude/worktrees/vigilant-napier-0de76f/client/index.html +15 -0
  65. package/.claude/worktrees/vigilant-napier-0de76f/client/package.json +27 -0
  66. package/.claude/worktrees/vigilant-napier-0de76f/client/postcss.config.cjs +7 -0
  67. package/.claude/worktrees/vigilant-napier-0de76f/client/src/App.tsx +57 -0
  68. package/.claude/worktrees/vigilant-napier-0de76f/client/src/components/CliAuthPage.tsx +385 -0
  69. package/.claude/worktrees/vigilant-napier-0de76f/client/src/components/ManifestoPage.tsx +946 -0
  70. package/.claude/worktrees/vigilant-napier-0de76f/client/src/components/TrackCard.tsx +19 -0
  71. package/.claude/worktrees/vigilant-napier-0de76f/client/src/lib/api.ts +58 -0
  72. package/.claude/worktrees/vigilant-napier-0de76f/client/src/main.tsx +11 -0
  73. package/.claude/worktrees/vigilant-napier-0de76f/client/src/styles/index.css +33 -0
  74. package/.claude/worktrees/vigilant-napier-0de76f/client/src/styles/manifesto.css +1398 -0
  75. package/.claude/worktrees/vigilant-napier-0de76f/client/tailwind.config.ts +36 -0
  76. package/.claude/worktrees/vigilant-napier-0de76f/client/tsconfig.json +25 -0
  77. package/.claude/worktrees/vigilant-napier-0de76f/client/vite.config.ts +20 -0
  78. package/.claude/worktrees/vigilant-napier-0de76f/package-lock.json +5278 -0
  79. package/.claude/worktrees/vigilant-napier-0de76f/package.json +24 -0
  80. package/.claude/worktrees/vigilant-napier-0de76f/server/package.json +25 -0
  81. package/.claude/worktrees/vigilant-napier-0de76f/server/src/app.ts +71 -0
  82. package/.claude/worktrees/vigilant-napier-0de76f/server/src/config/env.ts +14 -0
  83. package/.claude/worktrees/vigilant-napier-0de76f/server/src/features/auth/sessionStore.ts +74 -0
  84. package/.claude/worktrees/vigilant-napier-0de76f/server/src/index.ts +8 -0
  85. package/.claude/worktrees/vigilant-napier-0de76f/server/src/routes/auth.ts +112 -0
  86. package/.claude/worktrees/vigilant-napier-0de76f/server/src/routes/health.ts +14 -0
  87. package/.claude/worktrees/vigilant-napier-0de76f/server/tsconfig.json +19 -0
  88. package/.claude/worktrees/vigilant-napier-0de76f/shared/package.json +24 -0
  89. package/.claude/worktrees/vigilant-napier-0de76f/shared/src/index.ts +91 -0
  90. package/.claude/worktrees/vigilant-napier-0de76f/shared/tsconfig.json +16 -0
  91. package/.claude/worktrees/vigilant-napier-0de76f/tsconfig.base.json +12 -0
  92. package/.claude/worktrees/vigilant-napier-0de76f/visionos-manifesto/index.html +392 -0
  93. package/.claude/worktrees/vigilant-napier-0de76f/visionos-manifesto/script.js +146 -0
  94. package/.claude/worktrees/vigilant-napier-0de76f/visionos-manifesto/styles.css +1082 -0
  95. package/.github/workflows/publish.yml +30 -0
  96. package/README.md +175 -0
  97. package/cli/README.md +165 -0
  98. package/cli/package.json +36 -0
  99. package/cli/scripts/copy-web-assets.mjs +12 -0
  100. package/cli/src/commands/lessons.ts +68 -0
  101. package/cli/src/commands/login.ts +46 -0
  102. package/cli/src/commands/logout.ts +12 -0
  103. package/cli/src/commands/open.ts +29 -0
  104. package/cli/src/commands/start.ts +146 -0
  105. package/cli/src/commands/status.ts +28 -0
  106. package/cli/src/commands/userinfo.ts +59 -0
  107. package/cli/src/index.ts +109 -0
  108. package/cli/src/lib/auth.ts +84 -0
  109. package/cli/src/lib/browser.ts +37 -0
  110. package/cli/src/lib/content.ts +57 -0
  111. package/cli/src/lib/lessonPrinter.ts +38 -0
  112. package/cli/src/lib/lessonRunner.ts +381 -0
  113. package/cli/src/lib/localState.ts +114 -0
  114. package/cli/src/lib/loginFlow.ts +74 -0
  115. package/cli/src/lib/progress.ts +94 -0
  116. package/cli/src/lib/runtime.ts +220 -0
  117. package/cli/src/lib/validator.ts +401 -0
  118. package/cli/src/runtime/index.ts +108 -0
  119. package/cli/src/types/inquirer.d.ts +9 -0
  120. package/cli/tsconfig.json +19 -0
  121. package/client/index.html +15 -0
  122. package/client/package.json +27 -0
  123. package/client/postcss.config.cjs +7 -0
  124. package/client/src/App.tsx +102 -0
  125. package/client/src/components/AccountPage.tsx +79 -0
  126. package/client/src/components/AuthPanel.tsx +312 -0
  127. package/client/src/components/CliAuthPage.tsx +367 -0
  128. package/client/src/components/CreatorPortal.tsx +885 -0
  129. package/client/src/components/ErrorBoundary.tsx +92 -0
  130. package/client/src/components/ManifestoPage.tsx +1126 -0
  131. package/client/src/components/TrackCard.tsx +19 -0
  132. package/client/src/lib/api.ts +215 -0
  133. package/client/src/main.tsx +14 -0
  134. package/client/src/styles/index.css +33 -0
  135. package/client/src/styles/manifesto.css +1828 -0
  136. package/client/tailwind.config.ts +36 -0
  137. package/client/tsconfig.json +25 -0
  138. package/client/vercel.json +8 -0
  139. package/client/vite.config.ts +33 -0
  140. package/package.json +27 -0
  141. package/server/package.json +26 -0
  142. package/server/src/app.ts +132 -0
  143. package/server/src/config/env.ts +135 -0
  144. package/server/src/features/accounts/accountStore.ts +359 -0
  145. package/server/src/features/accounts/contentStore.ts +264 -0
  146. package/server/src/features/accounts/password.ts +26 -0
  147. package/server/src/features/auth/sessionStore.ts +79 -0
  148. package/server/src/index.ts +8 -0
  149. package/server/src/routes/auth.ts +328 -0
  150. package/server/src/routes/content.ts +174 -0
  151. package/server/src/routes/health.ts +14 -0
  152. package/server/src/routes/progress.ts +105 -0
  153. package/server/tsconfig.json +19 -0
  154. package/shared/package.json +24 -0
  155. package/shared/src/index.ts +455 -0
  156. package/shared/tsconfig.json +16 -0
  157. package/tsconfig.base.json +12 -0
  158. package/visionos-manifesto/index.html +392 -0
  159. package/visionos-manifesto/script.js +146 -0
  160. package/visionos-manifesto/styles.css +1082 -0
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "visionos-monorepo",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "packageManager": "npm@10",
6
+ "workspaces": [
7
+ "cli",
8
+ "server",
9
+ "client",
10
+ "shared"
11
+ ],
12
+ "scripts": {
13
+ "build": "npm run build -w shared && npm run build -w server && npm run build -w client && npm run build -w cli",
14
+ "pack:cli": "npm run build && npm pack -w cli",
15
+ "dev": "concurrently -k -n server,client -c blue,magenta \"npm run dev:server\" \"npm run dev:client\"",
16
+ "dev:cli": "npm run dev -w cli",
17
+ "dev:server": "npm run dev -w server",
18
+ "dev:client": "npm run dev -w client",
19
+ "lint": "npm run lint --workspaces --if-present"
20
+ },
21
+ "devDependencies": {
22
+ "concurrently": "^9.2.1"
23
+ }
24
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "@visionos/server",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "build": "tsup src/index.ts --format esm --clean --out-dir dist",
8
+ "dev": "tsx watch src/index.ts",
9
+ "start": "node dist/index.js",
10
+ "lint": "tsc --noEmit -p tsconfig.json"
11
+ },
12
+ "dependencies": {
13
+ "cors": "^2.8.5",
14
+ "dotenv": "^16.4.5",
15
+ "express": "^4.21.1"
16
+ },
17
+ "devDependencies": {
18
+ "@types/cors": "^2.8.17",
19
+ "@types/express": "^4.17.21",
20
+ "@types/node": "^22.10.1",
21
+ "tsup": "^8.3.0",
22
+ "tsx": "^4.19.1",
23
+ "typescript": "^5.6.3"
24
+ }
25
+ }
@@ -0,0 +1,71 @@
1
+ import path from "node:path";
2
+ import cors from "cors";
3
+ import express from "express";
4
+ import { env } from "./config/env.js";
5
+ import { createAuthRouter, type CreateAuthRouterOptions } from "./routes/auth.js";
6
+ import { healthRouter } from "./routes/health.js";
7
+
8
+ export interface CreateAppOptions extends CreateAuthRouterOptions {
9
+ apiBaseUrl?: string;
10
+ clientOrigin?: string;
11
+ corsOrigin?: string;
12
+ mode?: "development" | "embedded";
13
+ staticSiteDir?: string;
14
+ }
15
+
16
+ export function createApp(options: CreateAppOptions = {}) {
17
+ const app = express();
18
+ const clientOrigin = options.clientOrigin ?? env.clientOrigin;
19
+ const corsOrigin = options.corsOrigin ?? env.corsOrigin;
20
+ const apiBaseUrl = options.apiBaseUrl ?? `http://${env.host}:${env.port}`;
21
+ const authRouter = createAuthRouter({
22
+ clientOrigin,
23
+ getCurrentUser: options.getCurrentUser,
24
+ onAuthenticated: options.onAuthenticated
25
+ });
26
+
27
+ app.use(
28
+ cors({
29
+ origin: corsOrigin
30
+ })
31
+ );
32
+ app.use(express.json());
33
+ app.use(express.urlencoded({ extended: true }));
34
+
35
+ app.use("/auth", authRouter);
36
+ app.use("/health", healthRouter);
37
+
38
+ app.get("/runtime/info", (_req, res) => {
39
+ res.status(200).json({
40
+ runtime: {
41
+ appUrl: clientOrigin,
42
+ apiBaseUrl,
43
+ mode: options.mode ?? "development"
44
+ }
45
+ });
46
+ });
47
+
48
+ if (options.staticSiteDir) {
49
+ app.use(express.static(options.staticSiteDir));
50
+
51
+ app.get("*", (_req, res) => {
52
+ res.sendFile(path.join(options.staticSiteDir as string, "index.html"));
53
+ });
54
+ } else {
55
+ app.get("/", (_req, res) => {
56
+ res.status(200).json({
57
+ message: "VisionOS API bootstrap is running."
58
+ });
59
+ });
60
+ }
61
+
62
+ app.use((error: unknown, _req: express.Request, res: express.Response, _next: express.NextFunction) => {
63
+ const message = error instanceof Error ? error.message : "Unexpected VisionOS server error.";
64
+
65
+ res.status(500).json({
66
+ message
67
+ });
68
+ });
69
+
70
+ return app;
71
+ }
@@ -0,0 +1,14 @@
1
+ import dotenv from "dotenv";
2
+
3
+ dotenv.config();
4
+
5
+ const rawPort = Number(process.env.PORT ?? 4000);
6
+ const host = process.env.HOST ?? "127.0.0.1";
7
+ const clientOrigin = process.env.CLIENT_ORIGIN ?? "http://localhost:5173";
8
+
9
+ export const env = {
10
+ host,
11
+ port: Number.isNaN(rawPort) ? 4000 : rawPort,
12
+ clientOrigin,
13
+ corsOrigin: process.env.CORS_ORIGIN ?? clientOrigin
14
+ };
@@ -0,0 +1,74 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import {
3
+ CLI_AUTH_POLL_INTERVAL_MS,
4
+ CLI_AUTH_SESSION_TTL_MS,
5
+ type CliAuthSession,
6
+ type CreateCliAuthSessionResponse,
7
+ type LearnerIdentity,
8
+ type LearningTrackId
9
+ } from "../../../../shared/src/index.js";
10
+
11
+ export class InMemoryCliAuthSessionStore {
12
+ private readonly sessions = new Map<string, CliAuthSession>();
13
+
14
+ constructor(private readonly clientOrigin: string) {}
15
+
16
+ createSession(trackId: LearningTrackId): CreateCliAuthSessionResponse {
17
+ const createdAt = new Date();
18
+ const session: CliAuthSession = {
19
+ sessionId: randomUUID(),
20
+ trackId,
21
+ status: "pending",
22
+ createdAt: createdAt.toISOString(),
23
+ expiresAt: new Date(createdAt.getTime() + CLI_AUTH_SESSION_TTL_MS).toISOString()
24
+ };
25
+
26
+ this.sessions.set(session.sessionId, session);
27
+
28
+ return {
29
+ session,
30
+ browserUrl: new URL(`/?cliAuthSession=${session.sessionId}`, this.clientOrigin).toString(),
31
+ pollIntervalMs: CLI_AUTH_POLL_INTERVAL_MS
32
+ };
33
+ }
34
+
35
+ getSession(sessionId: string): CliAuthSession | undefined {
36
+ const session = this.sessions.get(sessionId);
37
+
38
+ if (!session) {
39
+ return undefined;
40
+ }
41
+
42
+ if (session.status === "pending" && Date.now() >= Date.parse(session.expiresAt)) {
43
+ const expiredSession: CliAuthSession = {
44
+ ...session,
45
+ status: "expired"
46
+ };
47
+
48
+ this.sessions.set(sessionId, expiredSession);
49
+
50
+ return expiredSession;
51
+ }
52
+
53
+ return session;
54
+ }
55
+
56
+ completeSession(sessionId: string, learner: LearnerIdentity): CliAuthSession | undefined {
57
+ const session = this.getSession(sessionId);
58
+
59
+ if (!session || session.status !== "pending") {
60
+ return session;
61
+ }
62
+
63
+ const authenticatedSession: CliAuthSession = {
64
+ ...session,
65
+ status: "authenticated",
66
+ authenticatedAt: new Date().toISOString(),
67
+ learner
68
+ };
69
+
70
+ this.sessions.set(sessionId, authenticatedSession);
71
+
72
+ return authenticatedSession;
73
+ }
74
+ }
@@ -0,0 +1,8 @@
1
+ import { createApp } from "./app.js";
2
+ import { env } from "./config/env.js";
3
+
4
+ const app = createApp();
5
+
6
+ app.listen(env.port, env.host, () => {
7
+ console.log(`VisionOS server listening on http://${env.host}:${env.port}`);
8
+ });
@@ -0,0 +1,112 @@
1
+ import { Router } from "express";
2
+ import {
3
+ type CliAuthSession,
4
+ type CompleteCliAuthSessionRequest,
5
+ type CreateCliAuthSessionRequest,
6
+ LEARNING_TRACKS,
7
+ type LearnerIdentity,
8
+ type LearningTrackId
9
+ } from "../../../shared/src/index.js";
10
+ import { env } from "../config/env.js";
11
+ import { InMemoryCliAuthSessionStore } from "../features/auth/sessionStore.js";
12
+
13
+ const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
14
+
15
+ function isLearningTrackId(value: unknown): value is LearningTrackId {
16
+ return typeof value === "string" && LEARNING_TRACKS.some((track) => track.id === value);
17
+ }
18
+
19
+ export interface CreateAuthRouterOptions {
20
+ clientOrigin?: string;
21
+ getCurrentUser?: () => Promise<LearnerIdentity | null> | LearnerIdentity | null;
22
+ onAuthenticated?: (session: CliAuthSession) => Promise<void> | void;
23
+ }
24
+
25
+ export function createAuthRouter(options: CreateAuthRouterOptions = {}) {
26
+ const router = Router();
27
+ const cliAuthSessionStore = new InMemoryCliAuthSessionStore(options.clientOrigin ?? env.clientOrigin);
28
+
29
+ router.get("/me", async (_req, res, next) => {
30
+ try {
31
+ const user = (await options.getCurrentUser?.()) ?? null;
32
+
33
+ return res.status(200).json({ user });
34
+ } catch (error) {
35
+ return next(error);
36
+ }
37
+ });
38
+
39
+ router.post("/cli/sessions", (req, res) => {
40
+ const body = req.body as Partial<CreateCliAuthSessionRequest>;
41
+
42
+ if (!isLearningTrackId(body.trackId)) {
43
+ return res.status(400).json({
44
+ message: "A valid learning track is required to start browser authentication."
45
+ });
46
+ }
47
+
48
+ return res.status(201).json(cliAuthSessionStore.createSession(body.trackId));
49
+ });
50
+
51
+ router.get("/cli/sessions/:sessionId", (req, res) => {
52
+ const session = cliAuthSessionStore.getSession(req.params.sessionId);
53
+
54
+ if (!session) {
55
+ return res.status(404).json({
56
+ message: "Auth session not found."
57
+ });
58
+ }
59
+
60
+ return res.status(200).json({ session });
61
+ });
62
+
63
+ router.post("/cli/sessions/:sessionId/complete", async (req, res, next) => {
64
+ try {
65
+ const session = cliAuthSessionStore.getSession(req.params.sessionId);
66
+
67
+ if (!session) {
68
+ return res.status(404).json({
69
+ message: "Auth session not found."
70
+ });
71
+ }
72
+
73
+ if (session.status === "expired") {
74
+ return res.status(410).json({
75
+ message: "Auth session expired.",
76
+ session
77
+ });
78
+ }
79
+
80
+ if (session.status === "authenticated") {
81
+ return res.status(200).json({ session });
82
+ }
83
+
84
+ const body = req.body as Partial<CompleteCliAuthSessionRequest>;
85
+ const learnerName = body.learnerName?.trim();
86
+ const learnerEmail = body.learnerEmail?.trim().toLowerCase();
87
+
88
+ if (!learnerName || !learnerEmail || !emailPattern.test(learnerEmail)) {
89
+ return res.status(400).json({
90
+ message: "A learner name and valid email address are required to complete sign-in."
91
+ });
92
+ }
93
+
94
+ const authenticatedSession = cliAuthSessionStore.completeSession(req.params.sessionId, {
95
+ name: learnerName,
96
+ email: learnerEmail
97
+ });
98
+
99
+ if (authenticatedSession) {
100
+ await options.onAuthenticated?.(authenticatedSession);
101
+ }
102
+
103
+ return res.status(200).json({ session: authenticatedSession });
104
+ } catch (error) {
105
+ return next(error);
106
+ }
107
+ });
108
+
109
+ return router;
110
+ }
111
+
112
+ export const authRouter = createAuthRouter();
@@ -0,0 +1,14 @@
1
+ import { Router } from "express";
2
+ import { APP_NAME, APP_VERSION } from "../../../shared/src/index.js";
3
+
4
+ export const healthRouter = Router();
5
+
6
+ healthRouter.get("/", (_req, res) => {
7
+ res.status(200).json({
8
+ status: "ok",
9
+ service: APP_NAME,
10
+ version: APP_VERSION,
11
+ timestamp: new Date().toISOString()
12
+ });
13
+ });
14
+
@@ -0,0 +1,19 @@
1
+ {
2
+ "extends": "../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "module": "ESNext",
5
+ "moduleResolution": "Bundler",
6
+ "lib": [
7
+ "ES2022"
8
+ ],
9
+ "types": [
10
+ "node"
11
+ ],
12
+ "noEmit": true
13
+ },
14
+ "include": [
15
+ "src/**/*.ts",
16
+ "../shared/src/**/*.ts"
17
+ ]
18
+ }
19
+
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "@visionos/shared",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "default": "./dist/index.js"
12
+ }
13
+ },
14
+ "scripts": {
15
+ "build": "tsup src/index.ts --format esm --dts --clean",
16
+ "dev": "tsup src/index.ts --format esm --dts --watch",
17
+ "lint": "tsc --noEmit -p tsconfig.json"
18
+ },
19
+ "devDependencies": {
20
+ "tsup": "^8.3.0",
21
+ "typescript": "^5.6.3"
22
+ }
23
+ }
24
+
@@ -0,0 +1,91 @@
1
+ export const APP_NAME = "VisionOS";
2
+ export const APP_VERSION = "0.1.0";
3
+
4
+ export type LearningTrackId = "git" | "linux" | "react-cli" | "os-commands";
5
+
6
+ export interface LearningTrack {
7
+ id: LearningTrackId;
8
+ label: string;
9
+ description: string;
10
+ }
11
+
12
+ export interface LearnerIdentity {
13
+ name: string;
14
+ email: string;
15
+ }
16
+
17
+ export interface VisionOsUserProfile extends LearnerIdentity {
18
+ sessionId: string;
19
+ trackId: LearningTrackId;
20
+ authenticatedAt: string;
21
+ }
22
+
23
+ export type CliAuthSessionStatus = "pending" | "authenticated" | "expired";
24
+
25
+ export interface CliAuthSession {
26
+ sessionId: string;
27
+ trackId: LearningTrackId;
28
+ status: CliAuthSessionStatus;
29
+ createdAt: string;
30
+ expiresAt: string;
31
+ authenticatedAt?: string;
32
+ learner?: LearnerIdentity;
33
+ }
34
+
35
+ export interface CliAuthSessionEnvelope {
36
+ session: CliAuthSession;
37
+ }
38
+
39
+ export interface CreateCliAuthSessionRequest {
40
+ trackId: LearningTrackId;
41
+ }
42
+
43
+ export interface CreateCliAuthSessionResponse extends CliAuthSessionEnvelope {
44
+ browserUrl: string;
45
+ pollIntervalMs: number;
46
+ }
47
+
48
+ export interface CompleteCliAuthSessionRequest {
49
+ learnerName: string;
50
+ learnerEmail: string;
51
+ }
52
+
53
+ export interface VisionOsUserInfoResponse {
54
+ user: VisionOsUserProfile | null;
55
+ }
56
+
57
+ export interface VisionOsRuntimeInfo {
58
+ appUrl: string;
59
+ apiBaseUrl: string;
60
+ mode: "development" | "embedded";
61
+ }
62
+
63
+ export interface VisionOsRuntimeInfoResponse {
64
+ runtime: VisionOsRuntimeInfo;
65
+ }
66
+
67
+ export const CLI_AUTH_POLL_INTERVAL_MS = 1_500;
68
+ export const CLI_AUTH_SESSION_TTL_MS = 10 * 60 * 1_000;
69
+
70
+ export const LEARNING_TRACKS: LearningTrack[] = [
71
+ {
72
+ id: "git",
73
+ label: "Git",
74
+ description: "Learn branching, commits, rebasing, and collaboration commands."
75
+ },
76
+ {
77
+ id: "linux",
78
+ label: "Linux",
79
+ description: "Build confidence with filesystem navigation, permissions, and shell basics."
80
+ },
81
+ {
82
+ id: "react-cli",
83
+ label: "React CLI",
84
+ description: "Practice project scaffolding, package scripts, and frontend workflows."
85
+ },
86
+ {
87
+ id: "os-commands",
88
+ label: "OS Commands",
89
+ description: "Train with practical day-to-day terminal commands across common environments."
90
+ }
91
+ ];
@@ -0,0 +1,16 @@
1
+ {
2
+ "extends": "../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "module": "ESNext",
5
+ "moduleResolution": "Bundler",
6
+ "lib": [
7
+ "ES2022"
8
+ ],
9
+ "declaration": true,
10
+ "noEmit": true
11
+ },
12
+ "include": [
13
+ "src/**/*.ts"
14
+ ]
15
+ }
16
+
@@ -0,0 +1,12 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "strict": true,
5
+ "esModuleInterop": true,
6
+ "forceConsistentCasingInFileNames": true,
7
+ "skipLibCheck": true,
8
+ "resolveJsonModule": true,
9
+ "allowSyntheticDefaultImports": true
10
+ }
11
+ }
12
+