@learnpack/learnpack 5.0.27 → 5.0.29
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/README.md +27 -11
- package/lib/commands/breakToken.d.ts +10 -0
- package/lib/commands/breakToken.js +23 -0
- package/lib/commands/init.js +78 -44
- package/lib/commands/publish.js +29 -2
- package/lib/commands/start.js +9 -12
- package/lib/managers/config/index.js +77 -77
- package/lib/managers/server/routes.js +1 -5
- package/lib/managers/session.js +8 -0
- package/lib/models/session.d.ts +1 -0
- package/lib/utils/api.d.ts +11 -1
- package/lib/utils/api.js +49 -11
- package/lib/utils/creatorUtilities.d.ts +7 -2
- package/lib/utils/creatorUtilities.js +88 -6
- package/lib/utils/rigoActions.d.ts +6 -0
- package/lib/utils/rigoActions.js +16 -0
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
- package/src/commands/breakToken.ts +36 -0
- package/src/commands/init.ts +113 -55
- package/src/commands/publish.ts +34 -2
- package/src/commands/start.ts +10 -14
- package/src/managers/config/index.ts +715 -715
- package/src/managers/server/routes.ts +1 -3
- package/src/managers/session.ts +11 -0
- package/src/models/session.ts +1 -0
- package/src/utils/api.ts +60 -12
- package/src/utils/creatorUtilities.ts +101 -7
- package/src/utils/rigoActions.ts +30 -0
package/src/commands/init.ts
CHANGED
@@ -19,8 +19,9 @@ import {
|
|
19
19
|
createCodeFile,
|
20
20
|
readmeCreator,
|
21
21
|
createPreviewReadme,
|
22
|
+
reduceReadme,
|
22
23
|
} from "../utils/rigoActions"
|
23
|
-
import {
|
24
|
+
import { getConsumable } from "../utils/api"
|
24
25
|
import {
|
25
26
|
checkReadingTime,
|
26
27
|
PackageInfo,
|
@@ -28,9 +29,18 @@ import {
|
|
28
29
|
extractImagesFromMarkdown,
|
29
30
|
getFilenameFromUrl,
|
30
31
|
makePackageInfo,
|
32
|
+
estimateDuration,
|
33
|
+
getContentIndex,
|
34
|
+
createFileOnDesktop,
|
31
35
|
} from "../utils/creatorUtilities"
|
32
36
|
import SessionManager from "../managers/session"
|
33
37
|
|
38
|
+
const durationByKind: Record<string, number> = {
|
39
|
+
code: 3,
|
40
|
+
quiz: 2,
|
41
|
+
read: 1,
|
42
|
+
}
|
43
|
+
|
34
44
|
const initializeInteractiveCreation = async (
|
35
45
|
rigoToken: string,
|
36
46
|
courseInfo: string
|
@@ -39,12 +49,16 @@ const initializeInteractiveCreation = async (
|
|
39
49
|
title: string
|
40
50
|
description: string
|
41
51
|
interactions: string
|
52
|
+
difficulty: string
|
53
|
+
duration: number
|
42
54
|
}> => {
|
43
55
|
let prevInteractions = ""
|
44
56
|
let isReady = false
|
45
57
|
let currentSteps = []
|
46
58
|
let currentTitle = ""
|
47
59
|
let currentDescription = ""
|
60
|
+
let currentDifficulty = ""
|
61
|
+
|
48
62
|
while (!isReady) {
|
49
63
|
let wholeInfo = courseInfo
|
50
64
|
wholeInfo += `
|
@@ -69,6 +83,10 @@ const initializeInteractiveCreation = async (
|
|
69
83
|
currentDescription = res.parsed.description
|
70
84
|
}
|
71
85
|
|
86
|
+
if (res.parsed.difficulty && currentDifficulty !== res.parsed.difficulty) {
|
87
|
+
currentDifficulty = res.parsed.difficulty
|
88
|
+
}
|
89
|
+
|
72
90
|
if (!isReady) {
|
73
91
|
console.log(currentSteps)
|
74
92
|
Console.info(`AI: ${res.parsed.aiMessage}`)
|
@@ -85,12 +103,47 @@ const initializeInteractiveCreation = async (
|
|
85
103
|
}
|
86
104
|
}
|
87
105
|
|
106
|
+
const duration = estimateDuration(currentSteps)
|
88
107
|
return {
|
89
108
|
steps: currentSteps,
|
90
109
|
title: currentTitle,
|
91
110
|
description: currentDescription,
|
92
111
|
interactions: prevInteractions,
|
112
|
+
difficulty: currentDifficulty,
|
113
|
+
duration,
|
114
|
+
}
|
115
|
+
}
|
116
|
+
|
117
|
+
const appendContentIndex = async () => {
|
118
|
+
const choices = await prompts([
|
119
|
+
{
|
120
|
+
type: "confirm",
|
121
|
+
name: "contentIndex",
|
122
|
+
message: "Do you have a content index for this tutorial?",
|
123
|
+
},
|
124
|
+
])
|
125
|
+
if (choices.contentIndex) {
|
126
|
+
await createFileOnDesktop()
|
127
|
+
Console.info(
|
128
|
+
"Please make the necessary in the recently created file in your desktop, it should automatically open. Edit the file to match your expectations and save it. Keep the same name and structure as the example file. Continue when ready."
|
129
|
+
)
|
130
|
+
const isReady = await prompts([
|
131
|
+
{
|
132
|
+
type: "confirm",
|
133
|
+
name: "isReady",
|
134
|
+
message: "Are you ready to continue?",
|
135
|
+
},
|
136
|
+
])
|
137
|
+
if (!isReady.isReady) {
|
138
|
+
Console.error("Please make the necessary changes and try again.")
|
139
|
+
process.exit(1)
|
140
|
+
}
|
141
|
+
|
142
|
+
const contentIndex = getContentIndex()
|
143
|
+
return contentIndex
|
93
144
|
}
|
145
|
+
|
146
|
+
return null
|
94
147
|
}
|
95
148
|
|
96
149
|
const handleAILogic = async (tutorialDir: string, packageInfo: PackageInfo) => {
|
@@ -98,7 +151,11 @@ const handleAILogic = async (tutorialDir: string, packageInfo: PackageInfo) => {
|
|
98
151
|
|
99
152
|
let sessionPayload = await SessionManager.getPayload()
|
100
153
|
|
101
|
-
if (
|
154
|
+
if (
|
155
|
+
!sessionPayload ||
|
156
|
+
!sessionPayload.rigobot ||
|
157
|
+
(sessionPayload.token && !(await api.validateToken(sessionPayload.token)))
|
158
|
+
) {
|
102
159
|
Console.info(
|
103
160
|
"Almost there! First you need to login with 4Geeks.com to use AI Generation tool for creators. You can create a new account here: https://4geeks.com/creators"
|
104
161
|
)
|
@@ -112,16 +169,13 @@ const handleAILogic = async (tutorialDir: string, packageInfo: PackageInfo) => {
|
|
112
169
|
|
113
170
|
const rigoToken = sessionPayload.rigobot.key
|
114
171
|
|
115
|
-
const
|
116
|
-
sessionPayload.token,
|
117
|
-
"ai-generation"
|
118
|
-
)
|
172
|
+
const consumable = await getConsumable(sessionPayload.token, "ai-generation")
|
119
173
|
|
120
|
-
if (
|
174
|
+
if (consumable.count === 0) {
|
121
175
|
Console.error(
|
122
|
-
"It seems you cannot generate tutorials with AI. Make sure you creator subscription is up to date here: https://4geeks.com/profile/subscriptions
|
176
|
+
"It seems you cannot generate tutorials with AI. Make sure you creator subscription is up to date here: https://4geeks.com/profile/subscriptions. If you believe there is an issue you can always contact support@4geeks.com"
|
123
177
|
)
|
124
|
-
process.exit(1)
|
178
|
+
// process.exit(1)
|
125
179
|
}
|
126
180
|
|
127
181
|
const isCreator = await hasCreatorPermission(rigoToken)
|
@@ -134,26 +188,32 @@ const handleAILogic = async (tutorialDir: string, packageInfo: PackageInfo) => {
|
|
134
188
|
|
135
189
|
Console.success("🎉 Let's begin this learning journey!")
|
136
190
|
|
191
|
+
const contentIndex = await appendContentIndex()
|
192
|
+
|
137
193
|
let packageContext = `
|
138
194
|
\n
|
139
195
|
Title: ${packageInfo.title.us}
|
140
196
|
Description: ${packageInfo.description.us}
|
141
|
-
|
142
|
-
|
197
|
+
|
198
|
+
${
|
199
|
+
contentIndex ?
|
200
|
+
`Content Index submitted by the user, use this to guide your creation: ${contentIndex}` :
|
201
|
+
""
|
202
|
+
}
|
203
|
+
|
143
204
|
`
|
144
205
|
|
145
|
-
const { steps, title, description } =
|
146
|
-
rigoToken,
|
147
|
-
packageContext
|
148
|
-
)
|
206
|
+
const { steps, title, description, duration, difficulty } =
|
207
|
+
await initializeInteractiveCreation(rigoToken, packageContext)
|
149
208
|
packageInfo.title.us = title
|
150
209
|
packageInfo.description.us = description
|
210
|
+
packageInfo.duration = duration
|
211
|
+
packageInfo.difficulty = difficulty
|
151
212
|
|
152
213
|
packageContext = `
|
153
214
|
Title: ${title}
|
154
215
|
Description: ${description}
|
155
|
-
|
156
|
-
Estimated duration: ${packageInfo.duration}
|
216
|
+
|
157
217
|
List of exercises: ${steps.join(", ")}
|
158
218
|
`
|
159
219
|
const exercisesDir = path.join(tutorialDir, "exercises")
|
@@ -179,21 +239,38 @@ const handleAILogic = async (tutorialDir: string, packageInfo: PackageInfo) => {
|
|
179
239
|
kind: kind.toLowerCase(),
|
180
240
|
})
|
181
241
|
|
182
|
-
const
|
242
|
+
const duration = durationByKind[kind.toLowerCase()]
|
243
|
+
let readingTime = checkReadingTime(
|
183
244
|
readme.parsed.content,
|
184
|
-
200
|
245
|
+
200,
|
246
|
+
duration || 1
|
185
247
|
)
|
186
248
|
|
187
|
-
if (exceedsThreshold) {
|
188
|
-
Console.
|
189
|
-
|
190
|
-
|
191
|
-
|
249
|
+
if (readingTime.exceedsThreshold) {
|
250
|
+
// Console.info(
|
251
|
+
// `The reading time for the lesson ${exTitle} exceeds the threshold, reducing it...`
|
252
|
+
// )
|
253
|
+
const reducedReadme = await reduceReadme(rigoToken, {
|
254
|
+
lesson: readingTime.body,
|
255
|
+
number_of_words: readingTime.minutes.toString(),
|
256
|
+
expected_number_words: "200",
|
257
|
+
})
|
258
|
+
|
259
|
+
if (reducedReadme) {
|
260
|
+
readingTime = checkReadingTime(
|
261
|
+
reducedReadme.parsed.content,
|
262
|
+
200,
|
263
|
+
duration || 1
|
264
|
+
)
|
265
|
+
}
|
192
266
|
}
|
193
267
|
|
194
268
|
const readmeFilename = "README.md"
|
195
269
|
|
196
|
-
fs.writeFileSync(
|
270
|
+
fs.writeFileSync(
|
271
|
+
path.join(exerciseDir, readmeFilename),
|
272
|
+
readingTime.newMarkdown
|
273
|
+
)
|
197
274
|
|
198
275
|
if (kind.toLowerCase() === "code") {
|
199
276
|
const codeFile = await createCodeFile(rigoToken, {
|
@@ -210,15 +287,14 @@ const handleAILogic = async (tutorialDir: string, packageInfo: PackageInfo) => {
|
|
210
287
|
)
|
211
288
|
}
|
212
289
|
|
213
|
-
return
|
290
|
+
return readingTime.newMarkdown
|
214
291
|
})
|
215
292
|
|
216
|
-
let imagesArray: any[] = []
|
217
|
-
|
218
293
|
const readmeContents = await Promise.all(exercisePromises)
|
219
|
-
|
220
294
|
Console.success("Lessons created! 🎉")
|
295
|
+
|
221
296
|
Console.info("Generating images for the lessons...")
|
297
|
+
let imagesArray: any[] = []
|
222
298
|
|
223
299
|
for (const content of readmeContents) {
|
224
300
|
imagesArray = [...imagesArray, ...extractImagesFromMarkdown(content)]
|
@@ -255,7 +331,7 @@ const getChoices = async (empty: boolean) => {
|
|
255
331
|
title: "My Interactive Tutorial",
|
256
332
|
description: "",
|
257
333
|
difficulty: "beginner",
|
258
|
-
duration:
|
334
|
+
duration: 5,
|
259
335
|
useAI: "no",
|
260
336
|
grading: "isolated",
|
261
337
|
}
|
@@ -293,30 +369,7 @@ const getChoices = async (empty: boolean) => {
|
|
293
369
|
initial: "",
|
294
370
|
message: "Description for your tutorial? Press enter to leave blank",
|
295
371
|
},
|
296
|
-
|
297
|
-
type: "select",
|
298
|
-
name: "difficulty",
|
299
|
-
message: "How difficulty will be to complete the tutorial?",
|
300
|
-
choices: [
|
301
|
-
{ title: "Begginer (no previous experience)", value: "beginner" },
|
302
|
-
{ title: "Easy (just a bit of experience required)", value: "easy" },
|
303
|
-
{
|
304
|
-
title: "Intermediate (you need experience)",
|
305
|
-
value: "intermediate",
|
306
|
-
},
|
307
|
-
{ title: "Hard (master the topic)", value: "hard" },
|
308
|
-
],
|
309
|
-
},
|
310
|
-
{
|
311
|
-
type: "text",
|
312
|
-
name: "duration",
|
313
|
-
initial: "1",
|
314
|
-
message: "How many hours avg it takes to complete (number)?",
|
315
|
-
validate: (value: string) => {
|
316
|
-
const n = Math.floor(Number(value))
|
317
|
-
return n !== Number.POSITIVE_INFINITY && String(n) === value && n >= 0
|
318
|
-
},
|
319
|
-
},
|
372
|
+
|
320
373
|
{
|
321
374
|
type: "select",
|
322
375
|
name: "useAI",
|
@@ -332,7 +385,12 @@ const getChoices = async (empty: boolean) => {
|
|
332
385
|
},
|
333
386
|
])
|
334
387
|
|
335
|
-
|
388
|
+
const completeChoices = {
|
389
|
+
...choices,
|
390
|
+
difficulty: "beginner",
|
391
|
+
duration: 30,
|
392
|
+
}
|
393
|
+
return completeChoices
|
336
394
|
}
|
337
395
|
|
338
396
|
class InitComand extends BaseCommand {
|
package/src/commands/publish.ts
CHANGED
@@ -15,6 +15,8 @@ import {
|
|
15
15
|
downloadEditor,
|
16
16
|
checkIfDirectoryExists,
|
17
17
|
} from "../managers/file"
|
18
|
+
import api, { TAcademy } from "../utils/api"
|
19
|
+
import * as prompts from "prompts"
|
18
20
|
|
19
21
|
const RIGOBOT_HOST = "https://rigobot.herokuapp.com"
|
20
22
|
// const RIGOBOT_HOST =
|
@@ -45,6 +47,29 @@ const runAudit = () => {
|
|
45
47
|
)
|
46
48
|
}
|
47
49
|
|
50
|
+
const selectAcademy = async (academies: TAcademy[]) => {
|
51
|
+
if (academies.length === 0) {
|
52
|
+
return null
|
53
|
+
}
|
54
|
+
|
55
|
+
if (academies.length === 1) {
|
56
|
+
return academies[0]
|
57
|
+
}
|
58
|
+
|
59
|
+
// prompts the user to select an academy to upload the assets
|
60
|
+
Console.info("In which academy do you want to publish the asset?")
|
61
|
+
const response = await prompts({
|
62
|
+
type: "select",
|
63
|
+
name: "academy",
|
64
|
+
message: "Select an academy",
|
65
|
+
choices: academies.map((academy) => ({
|
66
|
+
title: academy.name,
|
67
|
+
value: academy,
|
68
|
+
})),
|
69
|
+
})
|
70
|
+
return response.academy
|
71
|
+
}
|
72
|
+
|
48
73
|
export default class BuildCommand extends SessionCommand {
|
49
74
|
static description =
|
50
75
|
"Builds the project by copying necessary files and directories into a zip file"
|
@@ -65,7 +90,11 @@ export default class BuildCommand extends SessionCommand {
|
|
65
90
|
const configObject = this.configManager?.get()
|
66
91
|
|
67
92
|
let sessionPayload = await SessionManager.getPayload()
|
68
|
-
if (
|
93
|
+
if (
|
94
|
+
!sessionPayload ||
|
95
|
+
!sessionPayload.rigobot ||
|
96
|
+
(sessionPayload.token && !(await api.validateToken(sessionPayload.token)))
|
97
|
+
) {
|
69
98
|
Console.error("You must be logged in to upload a LearnPack package")
|
70
99
|
try {
|
71
100
|
sessionPayload = await SessionManager.login()
|
@@ -87,7 +116,10 @@ export default class BuildCommand extends SessionCommand {
|
|
87
116
|
this.configManager?.buildIndex()
|
88
117
|
}
|
89
118
|
|
90
|
-
// const
|
119
|
+
// const academies = await api.listUserAcademies(sessionPayload.token)
|
120
|
+
// // console.log(academies, "academies")
|
121
|
+
// const academy = await selectAcademy(academies)
|
122
|
+
// console.log(academy, "academy")
|
91
123
|
|
92
124
|
// Read learn.json to get the slug
|
93
125
|
const learnJsonPath = path.join(process.cwd(), "learn.json")
|
package/src/commands/start.ts
CHANGED
@@ -176,10 +176,12 @@ export default class StartCommand extends SessionCommand {
|
|
176
176
|
|
177
177
|
const files = prioritizeHTMLFile(data.files)
|
178
178
|
|
179
|
-
if (config.editor.agent
|
179
|
+
if (config.editor.agent !== "os") {
|
180
|
+
// Console.info("Opening files for vscode agent")
|
180
181
|
eventManager.enqueue(dispatcher.events.OPEN_FILES, files)
|
181
182
|
} else {
|
182
|
-
dispatcher.enqueue(dispatcher.events.OPEN_FILES, files)
|
183
|
+
// dispatcher.enqueue(dispatcher.events.OPEN_FILES, files)
|
184
|
+
Console.debug("Ignoring files for os agent")
|
183
185
|
}
|
184
186
|
|
185
187
|
socket.ready("Ready to compile...")
|
@@ -187,10 +189,14 @@ export default class StartCommand extends SessionCommand {
|
|
187
189
|
|
188
190
|
socket.on("open_window", (data: TOpenWindowData) => {
|
189
191
|
Console.debug("Opening window: ", data)
|
192
|
+
console.log("config.os", config.os)
|
193
|
+
|
190
194
|
// cli.open(data.url); This uses XDG under the ground
|
191
195
|
if (config.os !== "linux" || (config.os === "linux" && hasXDG)) {
|
196
|
+
console.log("Opening window with XDG")
|
192
197
|
eventManager.enqueue(dispatcher.events.OPEN_WINDOW, data)
|
193
198
|
} else {
|
199
|
+
console.log("Opening window without XDG")
|
194
200
|
dispatcher.enqueue(dispatcher.events.OPEN_WINDOW, data)
|
195
201
|
}
|
196
202
|
|
@@ -249,16 +255,6 @@ export default class StartCommand extends SessionCommand {
|
|
249
255
|
})
|
250
256
|
})
|
251
257
|
|
252
|
-
// socket.on("quiz_submission", (data: any) => {
|
253
|
-
// const { stepPosition, event, eventData } = data
|
254
|
-
// TelemetryManager.registerStepEvent(stepPosition, event, eventData)
|
255
|
-
// })
|
256
|
-
|
257
|
-
// socket.on("ai_interaction", (data: any) => {
|
258
|
-
// const { stepPosition, event, eventData } = data
|
259
|
-
// TelemetryManager.registerStepEvent(stepPosition, event, eventData)
|
260
|
-
// })
|
261
|
-
|
262
258
|
socket.on("telemetry_event", (data: any) => {
|
263
259
|
const { stepPosition, event, eventData } = data
|
264
260
|
|
@@ -358,9 +354,9 @@ export default class StartCommand extends SessionCommand {
|
|
358
354
|
|
359
355
|
// start watching for file changes
|
360
356
|
if (StartCommand.flags.watch)
|
361
|
-
this.configManager.watchIndex(_filename => {
|
357
|
+
this.configManager.watchIndex((_filename, _fileContent) => {
|
362
358
|
// Instead of reloading with socket.reload(), I just notify the frontend for the file change
|
363
|
-
socket.emit("file_change", "ready", _filename)
|
359
|
+
socket.emit("file_change", "ready", [_filename, _fileContent])
|
364
360
|
})
|
365
361
|
}
|
366
362
|
}
|