@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.
@@ -279,9 +279,7 @@ throw new Error("File not found: " + filePath)
279
279
  TelemetryManager.registerStepEvent(exercise.position, "open_step", {})
280
280
  }
281
281
 
282
- if (configObject.config?.editor.agent === "os") {
283
- eventManager.enqueue(dispatcher.events.START_EXERCISE, exercise)
284
- } else {
282
+ if (configObject.config?.editor.agent !== "os") {
285
283
  dispatcher.enqueue(dispatcher.events.START_EXERCISE, req.params.slug)
286
284
  }
287
285
 
@@ -167,6 +167,17 @@ const Session: ISession = {
167
167
  this.token = null
168
168
  Console.success("You have logged out")
169
169
  },
170
+ breakToken: async function () {
171
+ const payload = await this.getPayload()
172
+ if (payload) {
173
+ this.token = "asdasdasdasd"
174
+ await storage.setItem("bc-payload", {
175
+ ...payload,
176
+ token: "asdasdsad",
177
+ })
178
+ Console.success("Token broken successfully")
179
+ }
180
+ },
170
181
  }
171
182
 
172
183
  export default Session
@@ -35,4 +35,5 @@ export interface ISession {
35
35
  sync: () => Promise<void>
36
36
  start: ({ token, payload }: IStartProps) => Promise<void>
37
37
  destroy: () => Promise<void>
38
+ breakToken: () => Promise<void>
38
39
  }
package/src/utils/api.ts CHANGED
@@ -79,13 +79,12 @@ const login = async (identification: string, password: string) => {
79
79
  await cli.wait(1000)
80
80
  const url = `${HOST}/v1/auth/login/`
81
81
 
82
- const data = await fetch(url, {
83
- body: JSON.stringify({
84
- email: identification,
85
- password: password,
86
- }),
87
- method: "post",
82
+ const res = await axios.post(url, {
83
+ email: identification,
84
+ password: password,
88
85
  })
86
+ const data = res.data
87
+
89
88
  cli.action.stop("ready")
90
89
  let rigoPayload = null
91
90
  try {
@@ -320,7 +319,7 @@ export const countConsumables = (
320
319
  return consumable ? consumable.balance.unit : 0
321
320
  }
322
321
 
323
- export const getConsumables = async (
322
+ export const getConsumable = async (
324
323
  token: string,
325
324
  consumableSlug: TConsumableSlug = "ai-generation"
326
325
  ): Promise<any> => {
@@ -333,18 +332,65 @@ export const getConsumables = async (
333
332
  try {
334
333
  const response = await axios.get(url, { headers })
335
334
 
336
- const ai_tutorial_generation = countConsumables(
337
- response.data,
338
- consumableSlug
339
- )
335
+ const count = countConsumables(response.data, consumableSlug)
340
336
 
341
- return { ai_tutorial_generation }
337
+ return { count }
342
338
  } catch (error) {
343
339
  console.error("Error fetching consumables:", error)
344
340
  throw error
345
341
  }
346
342
  }
347
343
 
344
+ export interface TAcademy {
345
+ id: number
346
+ name: string
347
+ slug: string
348
+ timezone: string
349
+ }
350
+
351
+ export const listUserAcademies = async (
352
+ breathecodeToken: string
353
+ ): Promise<TAcademy[]> => {
354
+ const url = "https://breathecode.herokuapp.com/v1/auth/user/me"
355
+
356
+ try {
357
+ const response = await axios.get(url, {
358
+ headers: {
359
+ Authorization: `Token ${breathecodeToken}`,
360
+ },
361
+ })
362
+
363
+ const data = response.data
364
+
365
+ const academiesMap = new Map<number, TAcademy>()
366
+ for (const role of data.roles) {
367
+ const academy = role.academy
368
+ if (!academiesMap.has(academy.id)) {
369
+ academiesMap.set(academy.id, academy)
370
+ }
371
+ }
372
+
373
+ return [...academiesMap.values()]
374
+ } catch (error) {
375
+ console.error("Failed to fetch user academies:", error)
376
+ return []
377
+ }
378
+ }
379
+
380
+ export const validateToken = async (token: string) => {
381
+ const url = "https://breathecode.herokuapp.com/v1/auth/user/me"
382
+ const headers = {
383
+ Authorization: `Token ${token}`,
384
+ }
385
+
386
+ try {
387
+ const response = await axios.get(url, { headers })
388
+ return response.data
389
+ } catch {
390
+ return false
391
+ }
392
+ }
393
+
348
394
  export default {
349
395
  login,
350
396
  publish,
@@ -354,4 +400,6 @@ export default {
354
400
  getAllPackages,
355
401
  sendBatchTelemetry,
356
402
  sendStreamTelemetry,
403
+ listUserAcademies,
404
+ validateToken,
357
405
  }
@@ -1,6 +1,11 @@
1
1
  // eslint-disable-next-line
2
2
  const frontMatter = require("front-matter")
3
3
  import * as path from "path"
4
+ import * as fs from "fs"
5
+ import { homedir } from "os"
6
+ import { join } from "path"
7
+ import { exec } from "child_process"
8
+ import { promisify } from "util"
4
9
 
5
10
  import * as yaml from "js-yaml"
6
11
 
@@ -49,10 +54,18 @@ export type PackageInfo = {
49
54
 
50
55
  export function checkReadingTime(
51
56
  markdown: string,
52
- wordsPerMinute = 150
53
- ): { newMarkdown: string; exceedsThreshold: boolean } {
57
+ wordsPerMinute = 200,
58
+ maxMinutes = 1
59
+ ): {
60
+ newMarkdown: string
61
+ exceedsThreshold: boolean
62
+ minutes: number
63
+ body: string
64
+ } {
54
65
  const parsed = frontMatter(markdown)
66
+
55
67
  const readingTime = estimateReadingTime(parsed.body, wordsPerMinute)
68
+
56
69
  let attributes = parsed.attributes ? parsed.attributes : {}
57
70
 
58
71
  if (typeof parsed.attributes !== "object") {
@@ -64,15 +77,26 @@ export function checkReadingTime(
64
77
  readingTime,
65
78
  }
66
79
 
67
- // Convert the front matter back to a proper YAML string
68
- const yamlFrontMatter = yaml.dump(updatedAttributes).trim()
80
+ let yamlFrontMatter = ""
81
+ try {
82
+ yamlFrontMatter = yaml.dump(updatedAttributes).trim()
83
+ } catch {
84
+ return {
85
+ newMarkdown: "",
86
+ exceedsThreshold: false,
87
+ minutes: 0,
88
+ body: "",
89
+ }
90
+ }
69
91
 
70
92
  // Reconstruct the markdown with the front matter
71
93
  const newMarkdown = `---\n${yamlFrontMatter}\n---\n\n${parsed.body}`
72
94
 
73
95
  return {
74
96
  newMarkdown,
75
- exceedsThreshold: readingTime.minutes > wordsPerMinute,
97
+ exceedsThreshold: readingTime.minutes > maxMinutes,
98
+ minutes: readingTime.minutes,
99
+ body: parsed.body,
76
100
  }
77
101
  }
78
102
 
@@ -130,8 +154,8 @@ export function getFilenameFromUrl(url: string) {
130
154
  export const makePackageInfo = (choices: any) => {
131
155
  const packageInfo = {
132
156
  grading: choices.grading,
133
- difficulty: choices.difficulty,
134
- duration: parseInt(choices.duration),
157
+ difficulty: "beginner",
158
+ duration: 5,
135
159
  description: {
136
160
  us: choices.description,
137
161
  },
@@ -145,3 +169,73 @@ export const makePackageInfo = (choices: any) => {
145
169
  }
146
170
  return packageInfo
147
171
  }
172
+
173
+ export function estimateDuration(listOfSteps: string[]): number {
174
+ let duration = 0
175
+
176
+ for (const step of listOfSteps) {
177
+ if (step.includes("[READ:")) {
178
+ duration += 1
179
+ } else if (step.includes("[QUIZ:")) {
180
+ duration += 2
181
+ } else if (step.includes("[CODE:")) {
182
+ duration += 3
183
+ }
184
+ }
185
+
186
+ return duration
187
+ }
188
+
189
+ const writeFilePromise = promisify(fs.writeFile)
190
+ const execPromise = promisify(exec)
191
+
192
+ const example_content = `
193
+ 00.1 - Introduction to AI [READ: Small introduction to important concepts such as AI, machine learning, and their applications]
194
+ 01.1 - Introduction to Machine Learning [READ: Small introduction to important concepts such as AI, machine learning, and their applications]
195
+ 01.2 - Introduction to Deep Learning [QUIZ: Small introduction to important concepts such as AI, machine learning, and their applications]
196
+ 02.1 - Test your knowledge [CODE: Code problem to solve]
197
+ `
198
+
199
+ export async function createFileOnDesktop() {
200
+ try {
201
+ const desktopPath = join(homedir(), "Desktop")
202
+ const filePath = join(desktopPath, "content_index.txt")
203
+
204
+ const content = example_content.trim()
205
+
206
+ await writeFilePromise(filePath, content)
207
+ console.log(`File created successfully at: ${filePath}`)
208
+
209
+ await openFile(filePath)
210
+ } catch (error) {
211
+ console.error("Error:", error)
212
+ }
213
+ }
214
+
215
+ async function openFile(filePath: string) {
216
+ const platform = process.platform
217
+ let command
218
+
219
+ if (platform === "win32") {
220
+ command = `start "" "${filePath}"`
221
+ } else if (platform === "darwin") {
222
+ command = `open "${filePath}"`
223
+ } else {
224
+ command = `xdg-open "${filePath}"`
225
+ }
226
+
227
+ try {
228
+ await execPromise(command)
229
+ console.log("File opened successfully.")
230
+ } catch (error) {
231
+ console.error("Error opening the file:", error)
232
+ }
233
+ }
234
+
235
+ export function getContentIndex() {
236
+ const desktopPath = join(homedir(), "Desktop")
237
+ const filePath = join(desktopPath, "content_index.txt")
238
+
239
+ const content = fs.readFileSync(filePath, "utf8")
240
+ return content
241
+ }
@@ -293,3 +293,33 @@ export async function createPreviewReadme(
293
293
  })
294
294
  fs.writeFileSync(path.join(tutorialDir, readmeFilename), readmeContent.answer)
295
295
  }
296
+
297
+ // {"lesson": "The text of the lesson", "number_of_words": "Words lenght of the lesson", "expected_number_words": "The expected number of words"}
298
+
299
+ type TReduceReadmeInputs = {
300
+ lesson: string
301
+ number_of_words: string
302
+ expected_number_words: string
303
+ }
304
+ export async function reduceReadme(
305
+ rigoToken: string,
306
+ inputs: TReduceReadmeInputs
307
+ ) {
308
+ try {
309
+ const response = await axios.post(
310
+ `${RIGOBOT_HOST}/v1/prompting/completion/588/`,
311
+ { inputs, include_purpose_objective: false, execute_async: false },
312
+ {
313
+ headers: {
314
+ "Content-Type": "application/json",
315
+ Authorization: "Token " + rigoToken,
316
+ },
317
+ }
318
+ )
319
+
320
+ return response.data
321
+ } catch (error) {
322
+ Console.debug(error)
323
+ return null
324
+ }
325
+ }