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.
- package/.claude/worktrees/competent-burnell-8d1330/README.md +138 -0
- package/.claude/worktrees/competent-burnell-8d1330/cli/package.json +35 -0
- package/.claude/worktrees/competent-burnell-8d1330/cli/scripts/copy-web-assets.mjs +12 -0
- package/.claude/worktrees/competent-burnell-8d1330/cli/src/commands/logout.ts +12 -0
- package/.claude/worktrees/competent-burnell-8d1330/cli/src/commands/open.ts +19 -0
- package/.claude/worktrees/competent-burnell-8d1330/cli/src/commands/start.ts +97 -0
- package/.claude/worktrees/competent-burnell-8d1330/cli/src/commands/status.ts +23 -0
- package/.claude/worktrees/competent-burnell-8d1330/cli/src/commands/userinfo.ts +47 -0
- package/.claude/worktrees/competent-burnell-8d1330/cli/src/index.ts +23 -0
- package/.claude/worktrees/competent-burnell-8d1330/cli/src/lib/auth.ts +84 -0
- package/.claude/worktrees/competent-burnell-8d1330/cli/src/lib/browser.ts +37 -0
- package/.claude/worktrees/competent-burnell-8d1330/cli/src/lib/localState.ts +80 -0
- package/.claude/worktrees/competent-burnell-8d1330/cli/src/lib/runtime.ts +203 -0
- package/.claude/worktrees/competent-burnell-8d1330/cli/src/runtime/index.ts +36 -0
- package/.claude/worktrees/competent-burnell-8d1330/cli/src/types/inquirer.d.ts +9 -0
- package/.claude/worktrees/competent-burnell-8d1330/cli/tsconfig.json +19 -0
- package/.claude/worktrees/competent-burnell-8d1330/client/index.html +15 -0
- package/.claude/worktrees/competent-burnell-8d1330/client/package.json +27 -0
- package/.claude/worktrees/competent-burnell-8d1330/client/postcss.config.cjs +7 -0
- package/.claude/worktrees/competent-burnell-8d1330/client/src/App.tsx +57 -0
- package/.claude/worktrees/competent-burnell-8d1330/client/src/components/CliAuthPage.tsx +385 -0
- package/.claude/worktrees/competent-burnell-8d1330/client/src/components/ManifestoPage.tsx +946 -0
- package/.claude/worktrees/competent-burnell-8d1330/client/src/components/TrackCard.tsx +19 -0
- package/.claude/worktrees/competent-burnell-8d1330/client/src/lib/api.ts +58 -0
- package/.claude/worktrees/competent-burnell-8d1330/client/src/main.tsx +11 -0
- package/.claude/worktrees/competent-burnell-8d1330/client/src/styles/index.css +33 -0
- package/.claude/worktrees/competent-burnell-8d1330/client/src/styles/manifesto.css +1398 -0
- package/.claude/worktrees/competent-burnell-8d1330/client/tailwind.config.ts +36 -0
- package/.claude/worktrees/competent-burnell-8d1330/client/tsconfig.json +25 -0
- package/.claude/worktrees/competent-burnell-8d1330/client/vite.config.ts +20 -0
- package/.claude/worktrees/competent-burnell-8d1330/package-lock.json +5278 -0
- package/.claude/worktrees/competent-burnell-8d1330/package.json +24 -0
- package/.claude/worktrees/competent-burnell-8d1330/server/package.json +25 -0
- package/.claude/worktrees/competent-burnell-8d1330/server/src/app.ts +71 -0
- package/.claude/worktrees/competent-burnell-8d1330/server/src/config/env.ts +14 -0
- package/.claude/worktrees/competent-burnell-8d1330/server/src/features/auth/sessionStore.ts +74 -0
- package/.claude/worktrees/competent-burnell-8d1330/server/src/index.ts +8 -0
- package/.claude/worktrees/competent-burnell-8d1330/server/src/routes/auth.ts +112 -0
- package/.claude/worktrees/competent-burnell-8d1330/server/src/routes/health.ts +14 -0
- package/.claude/worktrees/competent-burnell-8d1330/server/tsconfig.json +19 -0
- package/.claude/worktrees/competent-burnell-8d1330/shared/package.json +24 -0
- package/.claude/worktrees/competent-burnell-8d1330/shared/src/index.ts +91 -0
- package/.claude/worktrees/competent-burnell-8d1330/shared/tsconfig.json +16 -0
- package/.claude/worktrees/competent-burnell-8d1330/tsconfig.base.json +12 -0
- package/.claude/worktrees/competent-burnell-8d1330/visionos-manifesto/index.html +392 -0
- package/.claude/worktrees/competent-burnell-8d1330/visionos-manifesto/script.js +146 -0
- package/.claude/worktrees/competent-burnell-8d1330/visionos-manifesto/styles.css +1082 -0
- package/.claude/worktrees/vigilant-napier-0de76f/README.md +138 -0
- package/.claude/worktrees/vigilant-napier-0de76f/cli/package.json +35 -0
- package/.claude/worktrees/vigilant-napier-0de76f/cli/scripts/copy-web-assets.mjs +12 -0
- package/.claude/worktrees/vigilant-napier-0de76f/cli/src/commands/logout.ts +12 -0
- package/.claude/worktrees/vigilant-napier-0de76f/cli/src/commands/open.ts +19 -0
- package/.claude/worktrees/vigilant-napier-0de76f/cli/src/commands/start.ts +97 -0
- package/.claude/worktrees/vigilant-napier-0de76f/cli/src/commands/status.ts +23 -0
- package/.claude/worktrees/vigilant-napier-0de76f/cli/src/commands/userinfo.ts +47 -0
- package/.claude/worktrees/vigilant-napier-0de76f/cli/src/index.ts +23 -0
- package/.claude/worktrees/vigilant-napier-0de76f/cli/src/lib/auth.ts +84 -0
- package/.claude/worktrees/vigilant-napier-0de76f/cli/src/lib/browser.ts +37 -0
- package/.claude/worktrees/vigilant-napier-0de76f/cli/src/lib/localState.ts +80 -0
- package/.claude/worktrees/vigilant-napier-0de76f/cli/src/lib/runtime.ts +203 -0
- package/.claude/worktrees/vigilant-napier-0de76f/cli/src/runtime/index.ts +36 -0
- package/.claude/worktrees/vigilant-napier-0de76f/cli/src/types/inquirer.d.ts +9 -0
- package/.claude/worktrees/vigilant-napier-0de76f/cli/tsconfig.json +19 -0
- package/.claude/worktrees/vigilant-napier-0de76f/client/index.html +15 -0
- package/.claude/worktrees/vigilant-napier-0de76f/client/package.json +27 -0
- package/.claude/worktrees/vigilant-napier-0de76f/client/postcss.config.cjs +7 -0
- package/.claude/worktrees/vigilant-napier-0de76f/client/src/App.tsx +57 -0
- package/.claude/worktrees/vigilant-napier-0de76f/client/src/components/CliAuthPage.tsx +385 -0
- package/.claude/worktrees/vigilant-napier-0de76f/client/src/components/ManifestoPage.tsx +946 -0
- package/.claude/worktrees/vigilant-napier-0de76f/client/src/components/TrackCard.tsx +19 -0
- package/.claude/worktrees/vigilant-napier-0de76f/client/src/lib/api.ts +58 -0
- package/.claude/worktrees/vigilant-napier-0de76f/client/src/main.tsx +11 -0
- package/.claude/worktrees/vigilant-napier-0de76f/client/src/styles/index.css +33 -0
- package/.claude/worktrees/vigilant-napier-0de76f/client/src/styles/manifesto.css +1398 -0
- package/.claude/worktrees/vigilant-napier-0de76f/client/tailwind.config.ts +36 -0
- package/.claude/worktrees/vigilant-napier-0de76f/client/tsconfig.json +25 -0
- package/.claude/worktrees/vigilant-napier-0de76f/client/vite.config.ts +20 -0
- package/.claude/worktrees/vigilant-napier-0de76f/package-lock.json +5278 -0
- package/.claude/worktrees/vigilant-napier-0de76f/package.json +24 -0
- package/.claude/worktrees/vigilant-napier-0de76f/server/package.json +25 -0
- package/.claude/worktrees/vigilant-napier-0de76f/server/src/app.ts +71 -0
- package/.claude/worktrees/vigilant-napier-0de76f/server/src/config/env.ts +14 -0
- package/.claude/worktrees/vigilant-napier-0de76f/server/src/features/auth/sessionStore.ts +74 -0
- package/.claude/worktrees/vigilant-napier-0de76f/server/src/index.ts +8 -0
- package/.claude/worktrees/vigilant-napier-0de76f/server/src/routes/auth.ts +112 -0
- package/.claude/worktrees/vigilant-napier-0de76f/server/src/routes/health.ts +14 -0
- package/.claude/worktrees/vigilant-napier-0de76f/server/tsconfig.json +19 -0
- package/.claude/worktrees/vigilant-napier-0de76f/shared/package.json +24 -0
- package/.claude/worktrees/vigilant-napier-0de76f/shared/src/index.ts +91 -0
- package/.claude/worktrees/vigilant-napier-0de76f/shared/tsconfig.json +16 -0
- package/.claude/worktrees/vigilant-napier-0de76f/tsconfig.base.json +12 -0
- package/.claude/worktrees/vigilant-napier-0de76f/visionos-manifesto/index.html +392 -0
- package/.claude/worktrees/vigilant-napier-0de76f/visionos-manifesto/script.js +146 -0
- package/.claude/worktrees/vigilant-napier-0de76f/visionos-manifesto/styles.css +1082 -0
- package/.github/workflows/publish.yml +30 -0
- package/README.md +175 -0
- package/cli/README.md +165 -0
- package/cli/package.json +36 -0
- package/cli/scripts/copy-web-assets.mjs +12 -0
- package/cli/src/commands/lessons.ts +68 -0
- package/cli/src/commands/login.ts +46 -0
- package/cli/src/commands/logout.ts +12 -0
- package/cli/src/commands/open.ts +29 -0
- package/cli/src/commands/start.ts +146 -0
- package/cli/src/commands/status.ts +28 -0
- package/cli/src/commands/userinfo.ts +59 -0
- package/cli/src/index.ts +109 -0
- package/cli/src/lib/auth.ts +84 -0
- package/cli/src/lib/browser.ts +37 -0
- package/cli/src/lib/content.ts +57 -0
- package/cli/src/lib/lessonPrinter.ts +38 -0
- package/cli/src/lib/lessonRunner.ts +381 -0
- package/cli/src/lib/localState.ts +114 -0
- package/cli/src/lib/loginFlow.ts +74 -0
- package/cli/src/lib/progress.ts +94 -0
- package/cli/src/lib/runtime.ts +220 -0
- package/cli/src/lib/validator.ts +401 -0
- package/cli/src/runtime/index.ts +108 -0
- package/cli/src/types/inquirer.d.ts +9 -0
- package/cli/tsconfig.json +19 -0
- package/client/index.html +15 -0
- package/client/package.json +27 -0
- package/client/postcss.config.cjs +7 -0
- package/client/src/App.tsx +102 -0
- package/client/src/components/AccountPage.tsx +79 -0
- package/client/src/components/AuthPanel.tsx +312 -0
- package/client/src/components/CliAuthPage.tsx +367 -0
- package/client/src/components/CreatorPortal.tsx +885 -0
- package/client/src/components/ErrorBoundary.tsx +92 -0
- package/client/src/components/ManifestoPage.tsx +1126 -0
- package/client/src/components/TrackCard.tsx +19 -0
- package/client/src/lib/api.ts +215 -0
- package/client/src/main.tsx +14 -0
- package/client/src/styles/index.css +33 -0
- package/client/src/styles/manifesto.css +1828 -0
- package/client/tailwind.config.ts +36 -0
- package/client/tsconfig.json +25 -0
- package/client/vercel.json +8 -0
- package/client/vite.config.ts +33 -0
- package/package.json +27 -0
- package/server/package.json +26 -0
- package/server/src/app.ts +132 -0
- package/server/src/config/env.ts +135 -0
- package/server/src/features/accounts/accountStore.ts +359 -0
- package/server/src/features/accounts/contentStore.ts +264 -0
- package/server/src/features/accounts/password.ts +26 -0
- package/server/src/features/auth/sessionStore.ts +79 -0
- package/server/src/index.ts +8 -0
- package/server/src/routes/auth.ts +328 -0
- package/server/src/routes/content.ts +174 -0
- package/server/src/routes/health.ts +14 -0
- package/server/src/routes/progress.ts +105 -0
- package/server/tsconfig.json +19 -0
- package/shared/package.json +24 -0
- package/shared/src/index.ts +455 -0
- package/shared/tsconfig.json +16 -0
- package/tsconfig.base.json +12 -0
- package/visionos-manifesto/index.html +392 -0
- package/visionos-manifesto/script.js +146 -0
- package/visionos-manifesto/styles.css +1082 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { Router } from "express";
|
|
2
|
+
import {
|
|
3
|
+
LEARNING_TRACKS,
|
|
4
|
+
type LearnerIdentity,
|
|
5
|
+
type UpsertLearningProgressRequest
|
|
6
|
+
} from "../../../shared/src/index.js";
|
|
7
|
+
import {
|
|
8
|
+
AccountStoreError,
|
|
9
|
+
bearerTokenFromHeader,
|
|
10
|
+
type AccountStore
|
|
11
|
+
} from "../features/accounts/accountStore.js";
|
|
12
|
+
|
|
13
|
+
export function createProgressRouter(
|
|
14
|
+
accountStore: AccountStore | null,
|
|
15
|
+
getCurrentUser?: () => Promise<LearnerIdentity | null> | LearnerIdentity | null,
|
|
16
|
+
getProgress?: (userId: string) => Promise<any[]> | any[],
|
|
17
|
+
upsertProgress?: (userId: string, input: UpsertLearningProgressRequest) => Promise<any> | any
|
|
18
|
+
) {
|
|
19
|
+
const router = Router();
|
|
20
|
+
|
|
21
|
+
async function resolveUser(authorization: string | undefined) {
|
|
22
|
+
const token = bearerTokenFromHeader(authorization);
|
|
23
|
+
|
|
24
|
+
if (token && accountStore) {
|
|
25
|
+
const user = await accountStore.getUserFromToken(token);
|
|
26
|
+
if (user) {
|
|
27
|
+
return user;
|
|
28
|
+
}
|
|
29
|
+
throw new AccountStoreError("Your session is invalid or expired. Sign in again.", 401);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (getCurrentUser) {
|
|
33
|
+
const user = await getCurrentUser();
|
|
34
|
+
if (user) {
|
|
35
|
+
return user;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
throw new AccountStoreError("Sign in before accessing progress.", 401);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
router.get("/", async (req, res, next) => {
|
|
43
|
+
try {
|
|
44
|
+
const user = await resolveUser(req.header("authorization"));
|
|
45
|
+
|
|
46
|
+
let progress;
|
|
47
|
+
if (getProgress) {
|
|
48
|
+
progress = await getProgress(user.userId);
|
|
49
|
+
} else {
|
|
50
|
+
const store = requireAccountStore(accountStore);
|
|
51
|
+
progress = await store.getProgress(user.userId);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return res.status(200).json({ progress });
|
|
55
|
+
} catch (error) {
|
|
56
|
+
return next(error);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
router.put("/", async (req, res, next) => {
|
|
61
|
+
try {
|
|
62
|
+
const user = await resolveUser(req.header("authorization"));
|
|
63
|
+
const body = req.body as Partial<UpsertLearningProgressRequest>;
|
|
64
|
+
|
|
65
|
+
if (!body.trackId || !LEARNING_TRACKS.some((track) => track.id === body.trackId)) {
|
|
66
|
+
return res.status(400).json({
|
|
67
|
+
message: "A valid learning track is required to save progress."
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
let progress;
|
|
72
|
+
if (upsertProgress) {
|
|
73
|
+
progress = await upsertProgress(user.userId, {
|
|
74
|
+
trackId: body.trackId,
|
|
75
|
+
completedLessonIds: body.completedLessonIds,
|
|
76
|
+
completedStepIds: body.completedStepIds,
|
|
77
|
+
currentLessonId: body.currentLessonId
|
|
78
|
+
});
|
|
79
|
+
} else {
|
|
80
|
+
const store = requireAccountStore(accountStore);
|
|
81
|
+
progress = await store.upsertProgress(user.userId, {
|
|
82
|
+
trackId: body.trackId,
|
|
83
|
+
completedLessonIds: body.completedLessonIds,
|
|
84
|
+
completedStepIds: body.completedStepIds,
|
|
85
|
+
currentLessonId: body.currentLessonId
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return res.status(200).json({ progress });
|
|
90
|
+
} catch (error) {
|
|
91
|
+
return next(error);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
return router;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function requireAccountStore(accountStore: AccountStore | null) {
|
|
99
|
+
if (!accountStore) {
|
|
100
|
+
throw new AccountStoreError("MongoDB account storage is not configured. Set MONGODB_URI first.", 503);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return accountStore;
|
|
104
|
+
}
|
|
105
|
+
|
|
@@ -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,455 @@
|
|
|
1
|
+
export const APP_NAME = "VisionOS";
|
|
2
|
+
export const APP_VERSION = "0.1.0";
|
|
3
|
+
|
|
4
|
+
export type LearningTrackId = string;
|
|
5
|
+
|
|
6
|
+
export interface LearningTrack {
|
|
7
|
+
id: LearningTrackId;
|
|
8
|
+
label: string;
|
|
9
|
+
description: string;
|
|
10
|
+
creatorId?: string;
|
|
11
|
+
createdAt?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface LearnerIdentity {
|
|
15
|
+
userId: string;
|
|
16
|
+
name: string;
|
|
17
|
+
email: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface VisionOsUserProfile extends LearnerIdentity {
|
|
21
|
+
sessionId?: string;
|
|
22
|
+
trackId?: LearningTrackId;
|
|
23
|
+
authenticatedAt?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type AccountAuthMode = "login" | "register";
|
|
27
|
+
|
|
28
|
+
export interface AccountAuthRequest {
|
|
29
|
+
email: string;
|
|
30
|
+
password: string;
|
|
31
|
+
name?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface GoogleAuthRequest {
|
|
35
|
+
credential: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface AccountUser extends LearnerIdentity {
|
|
39
|
+
createdAt: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface AccountAuthResponse {
|
|
43
|
+
user: AccountUser;
|
|
44
|
+
token: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export type CliAuthSessionStatus = "pending" | "authenticated" | "expired";
|
|
48
|
+
|
|
49
|
+
export interface CliAuthSession {
|
|
50
|
+
sessionId: string;
|
|
51
|
+
trackId: LearningTrackId;
|
|
52
|
+
status: CliAuthSessionStatus;
|
|
53
|
+
createdAt: string;
|
|
54
|
+
expiresAt: string;
|
|
55
|
+
authenticatedAt?: string;
|
|
56
|
+
accountToken?: string;
|
|
57
|
+
learner?: LearnerIdentity;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface CliAuthSessionEnvelope {
|
|
61
|
+
session: CliAuthSession;
|
|
62
|
+
auth?: AccountAuthResponse;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface CreateCliAuthSessionRequest {
|
|
66
|
+
trackId: LearningTrackId;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface CreateCliAuthSessionResponse extends CliAuthSessionEnvelope {
|
|
70
|
+
browserUrl: string;
|
|
71
|
+
pollIntervalMs: number;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface CompleteCliAuthSessionRequest {
|
|
75
|
+
mode: AccountAuthMode;
|
|
76
|
+
learnerName?: string;
|
|
77
|
+
learnerEmail: string;
|
|
78
|
+
password: string;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface VisionOsUserInfoResponse {
|
|
82
|
+
user: VisionOsUserProfile | null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface LearningProgress {
|
|
86
|
+
userId: string;
|
|
87
|
+
trackId: LearningTrackId;
|
|
88
|
+
completedLessonIds: string[];
|
|
89
|
+
completedStepIds: string[];
|
|
90
|
+
currentLessonId?: string;
|
|
91
|
+
updatedAt: string;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export interface LearningProgressResponse {
|
|
95
|
+
progress: LearningProgress[];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export interface UpsertLearningProgressRequest {
|
|
99
|
+
trackId: LearningTrackId;
|
|
100
|
+
completedLessonIds?: string[];
|
|
101
|
+
completedStepIds?: string[];
|
|
102
|
+
currentLessonId?: string;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export interface VisionOsRuntimeInfo {
|
|
106
|
+
appUrl: string;
|
|
107
|
+
apiBaseUrl: string;
|
|
108
|
+
mainAppUrl?: string;
|
|
109
|
+
mode: "development" | "embedded";
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export interface VisionOsRuntimeInfoResponse {
|
|
113
|
+
runtime: VisionOsRuntimeInfo;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export type LessonDifficulty = "beginner" | "intermediate" | "advanced";
|
|
117
|
+
|
|
118
|
+
export interface LessonStep {
|
|
119
|
+
id: string;
|
|
120
|
+
title: string;
|
|
121
|
+
objective: string;
|
|
122
|
+
command: string;
|
|
123
|
+
explanation: string;
|
|
124
|
+
validationType?: "syntax" | "file-exists" | "file-contains";
|
|
125
|
+
validationPath?: string;
|
|
126
|
+
validationContent?: string;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export interface LearningLesson {
|
|
130
|
+
id: string;
|
|
131
|
+
trackId: LearningTrackId;
|
|
132
|
+
title: string;
|
|
133
|
+
difficulty: LessonDifficulty;
|
|
134
|
+
summary: string;
|
|
135
|
+
estimatedMinutes: number;
|
|
136
|
+
steps: LessonStep[];
|
|
137
|
+
creatorId?: string;
|
|
138
|
+
createdAt?: string;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export const CLI_AUTH_POLL_INTERVAL_MS = 1_500;
|
|
142
|
+
export const CLI_AUTH_SESSION_TTL_MS = 10 * 60 * 1_000;
|
|
143
|
+
|
|
144
|
+
export const LEARNING_TRACKS: LearningTrack[] = [
|
|
145
|
+
{
|
|
146
|
+
id: "git",
|
|
147
|
+
label: "Git",
|
|
148
|
+
description: "Learn branching, commits, rebasing, and collaboration commands."
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
id: "linux",
|
|
152
|
+
label: "Linux",
|
|
153
|
+
description: "Build confidence with filesystem navigation, permissions, and shell basics."
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
id: "react-cli",
|
|
157
|
+
label: "React CLI",
|
|
158
|
+
description: "Practice project scaffolding, package scripts, and frontend workflows."
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
id: "os-commands",
|
|
162
|
+
label: "OS Commands",
|
|
163
|
+
description: "Train with practical day-to-day terminal commands across common environments."
|
|
164
|
+
}
|
|
165
|
+
];
|
|
166
|
+
|
|
167
|
+
export const BEGINNER_LESSONS: LearningLesson[] = [
|
|
168
|
+
{
|
|
169
|
+
id: "git-first-repo",
|
|
170
|
+
trackId: "git",
|
|
171
|
+
title: "Your First Git Repository",
|
|
172
|
+
difficulty: "beginner",
|
|
173
|
+
summary: "Create a repository, inspect status, and make a first commit.",
|
|
174
|
+
estimatedMinutes: 12,
|
|
175
|
+
steps: [
|
|
176
|
+
{
|
|
177
|
+
id: "git-init",
|
|
178
|
+
title: "Initialize the repository",
|
|
179
|
+
objective: "Create Git tracking in the current project folder.",
|
|
180
|
+
command: "git init",
|
|
181
|
+
explanation: "This creates the hidden .git directory where Git stores repository history."
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
id: "git-status",
|
|
185
|
+
title: "Check the working tree",
|
|
186
|
+
objective: "See which files are untracked, staged, or modified.",
|
|
187
|
+
command: "git status",
|
|
188
|
+
explanation: "Status is the safest command to run before and after every Git action."
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
id: "git-first-commit",
|
|
192
|
+
title: "Create a first snapshot",
|
|
193
|
+
objective: "Stage files and save them in history.",
|
|
194
|
+
command: "git add . && git commit -m \"Initial commit\"",
|
|
195
|
+
explanation: "Staging chooses what goes into the next commit; commit records the snapshot."
|
|
196
|
+
}
|
|
197
|
+
]
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
id: "git-branch-basics",
|
|
201
|
+
trackId: "git",
|
|
202
|
+
title: "Branch Basics",
|
|
203
|
+
difficulty: "beginner",
|
|
204
|
+
summary: "Create a branch, switch to it, and inspect branch state.",
|
|
205
|
+
estimatedMinutes: 10,
|
|
206
|
+
steps: [
|
|
207
|
+
{
|
|
208
|
+
id: "git-branch-list",
|
|
209
|
+
title: "List branches",
|
|
210
|
+
objective: "See the branches in a repository.",
|
|
211
|
+
command: "git branch",
|
|
212
|
+
explanation: "The current branch is marked with an asterisk."
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
id: "git-switch-create",
|
|
216
|
+
title: "Create and switch",
|
|
217
|
+
objective: "Start isolated work on a new branch.",
|
|
218
|
+
command: "git switch -c feature/readme-update",
|
|
219
|
+
explanation: "A branch lets you work without changing the main line until you merge."
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
id: "git-log-short",
|
|
223
|
+
title: "Read recent history",
|
|
224
|
+
objective: "Inspect compact commit history.",
|
|
225
|
+
command: "git log --oneline",
|
|
226
|
+
explanation: "Short logs make it easier to understand recent snapshots."
|
|
227
|
+
}
|
|
228
|
+
]
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
id: "git-github-connect",
|
|
232
|
+
trackId: "git",
|
|
233
|
+
title: "Connecting to GitHub",
|
|
234
|
+
difficulty: "beginner",
|
|
235
|
+
summary: "Rename your main branch, link your terminal to GitHub, and push your first commit.",
|
|
236
|
+
estimatedMinutes: 15,
|
|
237
|
+
steps: [
|
|
238
|
+
{
|
|
239
|
+
id: "git-branch-main",
|
|
240
|
+
title: "Rename branch to main",
|
|
241
|
+
objective: "Set the local branch name to 'main' for compatibility with GitHub.",
|
|
242
|
+
command: "git branch -M main",
|
|
243
|
+
explanation: "GitHub expects the primary branch of a repository to be named 'main' rather than 'master' or 'temp'."
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
id: "git-github-remote",
|
|
247
|
+
title: "Create GitHub repository and remote",
|
|
248
|
+
objective: "Create a GitHub repository from your terminal and link it as 'origin'.",
|
|
249
|
+
command: "gh repo create my-vision-project --public --source=. --remote=origin",
|
|
250
|
+
explanation: "This uses the GitHub CLI to create a public cloud repository named 'my-vision-project' and hooks it to your local files as 'origin'. Alternatively, if you don't have GitHub CLI installed, you can create a repository at github.com and link it using: git remote add origin https://github.com/YOUR_USERNAME/my-vision-project.git"
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
id: "git-github-push",
|
|
254
|
+
title: "Push your first commit",
|
|
255
|
+
objective: "Upload your local code snapshots to GitHub.",
|
|
256
|
+
command: "git push -u origin main",
|
|
257
|
+
explanation: "The -u flag sets your upstream, linking local main to remote main. Future pushes only need 'git push'!"
|
|
258
|
+
}
|
|
259
|
+
]
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
id: "linux-navigation",
|
|
263
|
+
trackId: "linux",
|
|
264
|
+
title: "Terminal Navigation",
|
|
265
|
+
difficulty: "beginner",
|
|
266
|
+
summary: "Move through folders and inspect the filesystem confidently.",
|
|
267
|
+
estimatedMinutes: 10,
|
|
268
|
+
steps: [
|
|
269
|
+
{
|
|
270
|
+
id: "linux-pwd",
|
|
271
|
+
title: "Print your location",
|
|
272
|
+
objective: "Show the absolute path of the current directory.",
|
|
273
|
+
command: "pwd",
|
|
274
|
+
explanation: "pwd tells you exactly where your shell is operating."
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
id: "linux-ls",
|
|
278
|
+
title: "List files",
|
|
279
|
+
objective: "View visible and hidden files with details.",
|
|
280
|
+
command: "ls -la",
|
|
281
|
+
explanation: "The -l flag shows details; -a includes hidden dotfiles."
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
id: "linux-cd",
|
|
285
|
+
title: "Change directory",
|
|
286
|
+
objective: "Move into a folder and back out.",
|
|
287
|
+
command: "cd ..",
|
|
288
|
+
explanation: "Two dots mean the parent directory."
|
|
289
|
+
}
|
|
290
|
+
]
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
id: "linux-files",
|
|
294
|
+
trackId: "linux",
|
|
295
|
+
title: "Create And Read Files",
|
|
296
|
+
difficulty: "beginner",
|
|
297
|
+
summary: "Create folders, write text, and read file contents.",
|
|
298
|
+
estimatedMinutes: 12,
|
|
299
|
+
steps: [
|
|
300
|
+
{
|
|
301
|
+
id: "linux-mkdir",
|
|
302
|
+
title: "Create a folder",
|
|
303
|
+
objective: "Make a new practice directory.",
|
|
304
|
+
command: "mkdir practice",
|
|
305
|
+
explanation: "mkdir creates a directory when it does not already exist."
|
|
306
|
+
},
|
|
307
|
+
{
|
|
308
|
+
id: "linux-echo-file",
|
|
309
|
+
title: "Write a small file",
|
|
310
|
+
objective: "Redirect text into a file.",
|
|
311
|
+
command: "echo \"hello terminal\" > practice/hello.txt",
|
|
312
|
+
explanation: "The > operator writes command output into a file."
|
|
313
|
+
},
|
|
314
|
+
{
|
|
315
|
+
id: "linux-cat",
|
|
316
|
+
title: "Read file contents",
|
|
317
|
+
objective: "Print a file to the terminal.",
|
|
318
|
+
command: "cat practice/hello.txt",
|
|
319
|
+
explanation: "cat is useful for quick inspection of small text files."
|
|
320
|
+
}
|
|
321
|
+
]
|
|
322
|
+
},
|
|
323
|
+
{
|
|
324
|
+
id: "react-cli-scaffold",
|
|
325
|
+
trackId: "react-cli",
|
|
326
|
+
title: "React Project Commands",
|
|
327
|
+
difficulty: "beginner",
|
|
328
|
+
summary: "Understand the commands used to create and run a React app.",
|
|
329
|
+
estimatedMinutes: 12,
|
|
330
|
+
steps: [
|
|
331
|
+
{
|
|
332
|
+
id: "react-create",
|
|
333
|
+
title: "Create a Vite React app",
|
|
334
|
+
objective: "Scaffold a React project from the terminal.",
|
|
335
|
+
command: "npm create vite@latest my-app -- --template react",
|
|
336
|
+
explanation: "Vite creates a fast local React project with sensible defaults."
|
|
337
|
+
},
|
|
338
|
+
{
|
|
339
|
+
id: "react-install",
|
|
340
|
+
title: "Install dependencies",
|
|
341
|
+
objective: "Download the packages required by the app.",
|
|
342
|
+
command: "cd my-app && npm install",
|
|
343
|
+
explanation: "npm install reads package.json and creates node_modules."
|
|
344
|
+
},
|
|
345
|
+
{
|
|
346
|
+
id: "react-dev",
|
|
347
|
+
title: "Start development server",
|
|
348
|
+
objective: "Run the app locally for development.",
|
|
349
|
+
command: "npm run dev",
|
|
350
|
+
explanation: "The dev script starts Vite with hot reloading."
|
|
351
|
+
}
|
|
352
|
+
]
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
id: "react-cli-package-scripts",
|
|
356
|
+
trackId: "react-cli",
|
|
357
|
+
title: "Package Script Basics",
|
|
358
|
+
difficulty: "beginner",
|
|
359
|
+
summary: "Inspect and run common npm scripts in a frontend project.",
|
|
360
|
+
estimatedMinutes: 10,
|
|
361
|
+
steps: [
|
|
362
|
+
{
|
|
363
|
+
id: "react-scripts-list",
|
|
364
|
+
title: "List scripts",
|
|
365
|
+
objective: "See available project commands.",
|
|
366
|
+
command: "npm run",
|
|
367
|
+
explanation: "npm run without a script name lists scripts from package.json."
|
|
368
|
+
},
|
|
369
|
+
{
|
|
370
|
+
id: "react-lint",
|
|
371
|
+
title: "Run checks",
|
|
372
|
+
objective: "Execute the project's lint or type-check script.",
|
|
373
|
+
command: "npm run lint",
|
|
374
|
+
explanation: "Linting catches mistakes before they reach the browser."
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
id: "react-build",
|
|
378
|
+
title: "Create production build",
|
|
379
|
+
objective: "Compile optimized assets.",
|
|
380
|
+
command: "npm run build",
|
|
381
|
+
explanation: "Build output is what gets deployed to static hosting."
|
|
382
|
+
}
|
|
383
|
+
]
|
|
384
|
+
},
|
|
385
|
+
{
|
|
386
|
+
id: "os-process-basics",
|
|
387
|
+
trackId: "os-commands",
|
|
388
|
+
title: "Processes And Ports",
|
|
389
|
+
difficulty: "beginner",
|
|
390
|
+
summary: "Inspect running commands and understand local ports.",
|
|
391
|
+
estimatedMinutes: 10,
|
|
392
|
+
steps: [
|
|
393
|
+
{
|
|
394
|
+
id: "os-ps",
|
|
395
|
+
title: "List processes",
|
|
396
|
+
objective: "Show running processes for your user.",
|
|
397
|
+
command: "ps",
|
|
398
|
+
explanation: "Processes are programs currently running on your machine."
|
|
399
|
+
},
|
|
400
|
+
{
|
|
401
|
+
id: "os-lsof-port",
|
|
402
|
+
title: "Check a local port",
|
|
403
|
+
objective: "Find what is listening on a development port.",
|
|
404
|
+
command: "lsof -nP -iTCP:3000 -sTCP:LISTEN",
|
|
405
|
+
explanation: "This helps diagnose why a dev server port is unavailable."
|
|
406
|
+
},
|
|
407
|
+
{
|
|
408
|
+
id: "os-kill",
|
|
409
|
+
title: "Stop a process",
|
|
410
|
+
objective: "Terminate a process by PID.",
|
|
411
|
+
command: "kill <pid>",
|
|
412
|
+
explanation: "Use kill carefully after confirming the PID belongs to the process you intend to stop."
|
|
413
|
+
}
|
|
414
|
+
]
|
|
415
|
+
},
|
|
416
|
+
{
|
|
417
|
+
id: "os-network-basics",
|
|
418
|
+
trackId: "os-commands",
|
|
419
|
+
title: "Network Checks",
|
|
420
|
+
difficulty: "beginner",
|
|
421
|
+
summary: "Use common commands to inspect connectivity and HTTP responses.",
|
|
422
|
+
estimatedMinutes: 10,
|
|
423
|
+
steps: [
|
|
424
|
+
{
|
|
425
|
+
id: "os-ping",
|
|
426
|
+
title: "Check reachability",
|
|
427
|
+
objective: "Send a small network probe.",
|
|
428
|
+
command: "ping example.com",
|
|
429
|
+
explanation: "ping helps check whether a host can be reached."
|
|
430
|
+
},
|
|
431
|
+
{
|
|
432
|
+
id: "os-curl-head",
|
|
433
|
+
title: "Inspect response headers",
|
|
434
|
+
objective: "Fetch only HTTP headers from a URL.",
|
|
435
|
+
command: "curl -I https://example.com",
|
|
436
|
+
explanation: "Headers reveal status codes, cache rules, and server metadata."
|
|
437
|
+
},
|
|
438
|
+
{
|
|
439
|
+
id: "os-dns",
|
|
440
|
+
title: "Resolve a hostname",
|
|
441
|
+
objective: "Look up DNS records for a domain.",
|
|
442
|
+
command: "nslookup example.com",
|
|
443
|
+
explanation: "DNS resolution maps names to network addresses."
|
|
444
|
+
}
|
|
445
|
+
]
|
|
446
|
+
}
|
|
447
|
+
];
|
|
448
|
+
|
|
449
|
+
export function getLessonsForTrack(trackId: LearningTrackId) {
|
|
450
|
+
return BEGINNER_LESSONS.filter((lesson) => lesson.trackId === trackId);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
export function getLessonById(lessonId: string) {
|
|
454
|
+
return BEGINNER_LESSONS.find((lesson) => lesson.id === lessonId);
|
|
455
|
+
}
|