@learnpack/learnpack 5.0.28 → 5.0.30

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 CHANGED
@@ -21,7 +21,7 @@ $ npm install -g @learnpack/learnpack
21
21
  $ learnpack COMMAND
22
22
  running command...
23
23
  $ learnpack (-v|--version|version)
24
- @learnpack/learnpack/5.0.28 win32-x64 node-v20.16.0
24
+ @learnpack/learnpack/5.0.30 win32-x64 node-v20.16.0
25
25
  $ learnpack --help [COMMAND]
26
26
  USAGE
27
27
  $ learnpack COMMAND
@@ -76,7 +76,7 @@ DESCRIPTION
76
76
  12. If there is a file within the exercises folder but not inside of any particular exercise's folder. (Warning)
77
77
  ```
78
78
 
79
- _See code: [src\commands\audit.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.28/src\commands\audit.ts)_
79
+ _See code: [src\commands\audit.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.30/src\commands\audit.ts)_
80
80
 
81
81
  ## `learnpack breakToken`
82
82
 
@@ -91,7 +91,7 @@ OPTIONS
91
91
  -y, --yes Skip all prompts and initialize an empty project
92
92
  ```
93
93
 
94
- _See code: [src\commands\breakToken.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.28/src\commands\breakToken.ts)_
94
+ _See code: [src\commands\breakToken.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.30/src\commands\breakToken.ts)_
95
95
 
96
96
  ## `learnpack clean`
97
97
 
@@ -106,7 +106,7 @@ DESCRIPTION
106
106
  Extra documentation goes here
107
107
  ```
108
108
 
109
- _See code: [src\commands\clean.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.28/src\commands\clean.ts)_
109
+ _See code: [src\commands\clean.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.30/src\commands\clean.ts)_
110
110
 
111
111
  ## `learnpack download [PACKAGE]`
112
112
 
@@ -124,7 +124,7 @@ DESCRIPTION
124
124
  Extra documentation goes here
125
125
  ```
126
126
 
127
- _See code: [src\commands\download.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.28/src\commands\download.ts)_
127
+ _See code: [src\commands\download.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.30/src\commands\download.ts)_
128
128
 
129
129
  ## `learnpack help [COMMAND]`
130
130
 
@@ -156,7 +156,7 @@ OPTIONS
156
156
  -y, --yes Skip all prompts and initialize an empty project
157
157
  ```
158
158
 
159
- _See code: [src\commands\init.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.28/src\commands\init.ts)_
159
+ _See code: [src\commands\init.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.30/src\commands\init.ts)_
160
160
 
161
161
  ## `learnpack login [PACKAGE]`
162
162
 
@@ -174,7 +174,7 @@ DESCRIPTION
174
174
  Extra documentation goes here
175
175
  ```
176
176
 
177
- _See code: [src\commands\login.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.28/src\commands\login.ts)_
177
+ _See code: [src\commands\login.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.30/src\commands\login.ts)_
178
178
 
179
179
  ## `learnpack logout [PACKAGE]`
180
180
 
@@ -192,7 +192,7 @@ DESCRIPTION
192
192
  Extra documentation goes here
193
193
  ```
194
194
 
195
- _See code: [src\commands\logout.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.28/src\commands\logout.ts)_
195
+ _See code: [src\commands\logout.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.30/src\commands\logout.ts)_
196
196
 
197
197
  ## `learnpack plugins`
198
198
 
@@ -323,7 +323,7 @@ OPTIONS
323
323
  -h, --help show CLI help
324
324
  ```
325
325
 
326
- _See code: [src\commands\publish.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.28/src\commands\publish.ts)_
326
+ _See code: [src\commands\publish.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.30/src\commands\publish.ts)_
327
327
 
328
328
  ## `learnpack start`
329
329
 
@@ -345,7 +345,7 @@ OPTIONS
345
345
  -y, --yes Skip all prompts and initialize an empty project
346
346
  ```
347
347
 
348
- _See code: [src\commands\start.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.28/src\commands\start.ts)_
348
+ _See code: [src\commands\start.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.30/src\commands\start.ts)_
349
349
 
350
350
  ## `learnpack test [EXERCISESLUG]`
351
351
 
@@ -362,7 +362,7 @@ OPTIONS
362
362
  -y, --yes Skip all prompts and initialize an empty project
363
363
  ```
364
364
 
365
- _See code: [src\commands\test.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.28/src\commands\test.ts)_
365
+ _See code: [src\commands\test.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.30/src\commands\test.ts)_
366
366
 
367
367
  ## `learnpack translate`
368
368
 
@@ -376,7 +376,7 @@ OPTIONS
376
376
  -y, --yes Skip all prompts and initialize an empty project
377
377
  ```
378
378
 
379
- _See code: [src\commands\translate.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.28/src\commands\translate.ts)_
379
+ _See code: [src\commands\translate.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.30/src\commands\translate.ts)_
380
380
  <!-- commandsstop -->
381
381
 
382
382
  > > > > > > > 0cb3e56d84c197f9d008836bb573eade212b7e57
@@ -2,12 +2,20 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const command_1 = require("@oclif/command");
4
4
  const BaseCommand_1 = require("../utils/BaseCommand");
5
- const session_1 = require("../managers/session");
5
+ const console_1 = require("../utils/console");
6
+ const creatorUtilities_1 = require("../utils/creatorUtilities");
6
7
  class BreakTokenCommand extends BaseCommand_1.default {
7
8
  async run() {
8
9
  const { flags } = this.parse(BreakTokenCommand);
9
- await session_1.default.breakToken();
10
- process.exit(0);
10
+ // await SessionManager.breakToken()
11
+ await (0, creatorUtilities_1.createFileOnDesktop)();
12
+ console_1.default.info("File created on desktop, please make the necessary changes and continue when ready");
13
+ // Wait for user to press enter
14
+ process.stdin.once("data", () => {
15
+ console_1.default.info("File content:");
16
+ console_1.default.info((0, creatorUtilities_1.getContentIndex)());
17
+ process.exit(0);
18
+ });
11
19
  }
12
20
  }
13
21
  BreakTokenCommand.description = "Break the token";
@@ -15,12 +15,18 @@ const rigoActions_1 = require("../utils/rigoActions");
15
15
  const api_2 = require("../utils/api");
16
16
  const creatorUtilities_1 = require("../utils/creatorUtilities");
17
17
  const session_1 = require("../managers/session");
18
+ const durationByKind = {
19
+ code: 3,
20
+ quiz: 2,
21
+ read: 1,
22
+ };
18
23
  const initializeInteractiveCreation = async (rigoToken, courseInfo) => {
19
24
  let prevInteractions = "";
20
25
  let isReady = false;
21
26
  let currentSteps = [];
22
27
  let currentTitle = "";
23
28
  let currentDescription = "";
29
+ let currentDifficulty = "";
24
30
  while (!isReady) {
25
31
  let wholeInfo = courseInfo;
26
32
  wholeInfo += `
@@ -41,6 +47,9 @@ const initializeInteractiveCreation = async (rigoToken, courseInfo) => {
41
47
  currentDescription !== res.parsed.description) {
42
48
  currentDescription = res.parsed.description;
43
49
  }
50
+ if (res.parsed.difficulty && currentDifficulty !== res.parsed.difficulty) {
51
+ currentDifficulty = res.parsed.difficulty;
52
+ }
44
53
  if (!isReady) {
45
54
  console.log(currentSteps);
46
55
  console_1.default.info(`AI: ${res.parsed.aiMessage}`);
@@ -56,13 +65,43 @@ const initializeInteractiveCreation = async (rigoToken, courseInfo) => {
56
65
  prevInteractions += `\nUser: ${userMessage.userMessage}`;
57
66
  }
58
67
  }
68
+ const duration = (0, creatorUtilities_1.estimateDuration)(currentSteps);
59
69
  return {
60
70
  steps: currentSteps,
61
71
  title: currentTitle,
62
72
  description: currentDescription,
63
73
  interactions: prevInteractions,
74
+ difficulty: currentDifficulty,
75
+ duration,
64
76
  };
65
77
  };
78
+ const appendContentIndex = async () => {
79
+ const choices = await prompts([
80
+ {
81
+ type: "confirm",
82
+ name: "contentIndex",
83
+ message: "Do you have a content index for this tutorial?",
84
+ },
85
+ ]);
86
+ if (choices.contentIndex) {
87
+ await (0, creatorUtilities_1.createFileOnDesktop)();
88
+ console_1.default.info("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.");
89
+ const isReady = await prompts([
90
+ {
91
+ type: "confirm",
92
+ name: "isReady",
93
+ message: "Are you ready to continue?",
94
+ },
95
+ ]);
96
+ if (!isReady.isReady) {
97
+ console_1.default.error("Please make the necessary changes and try again.");
98
+ process.exit(1);
99
+ }
100
+ const contentIndex = (0, creatorUtilities_1.getContentIndex)();
101
+ return contentIndex;
102
+ }
103
+ return null;
104
+ };
66
105
  const handleAILogic = async (tutorialDir, packageInfo) => {
67
106
  fs.removeSync(path.join(tutorialDir, "exercises", "01-hello-world"));
68
107
  let sessionPayload = await session_1.default.getPayload();
@@ -90,21 +129,26 @@ const handleAILogic = async (tutorialDir, packageInfo) => {
90
129
  process.exit(1);
91
130
  }
92
131
  console_1.default.success("🎉 Let's begin this learning journey!");
132
+ const contentIndex = await appendContentIndex();
93
133
  let packageContext = `
94
134
  \n
95
135
  Title: ${packageInfo.title.us}
96
136
  Description: ${packageInfo.description.us}
97
- Difficulty: ${packageInfo.difficulty}
98
- Duration: ${packageInfo.duration}
137
+
138
+ ${contentIndex ?
139
+ `Content Index submitted by the user, use this to guide your creation: ${contentIndex}` :
140
+ ""}
141
+
99
142
  `;
100
- const { steps, title, description } = await initializeInteractiveCreation(rigoToken, packageContext);
143
+ const { steps, title, description, duration, difficulty } = await initializeInteractiveCreation(rigoToken, packageContext);
101
144
  packageInfo.title.us = title;
102
145
  packageInfo.description.us = description;
146
+ packageInfo.duration = duration;
147
+ packageInfo.difficulty = difficulty;
103
148
  packageContext = `
104
149
  Title: ${title}
105
150
  Description: ${description}
106
- Difficulty: ${packageInfo.difficulty}
107
- Estimated duration: ${packageInfo.duration}
151
+
108
152
  List of exercises: ${steps.join(", ")}
109
153
  `;
110
154
  const exercisesDir = path.join(tutorialDir, "exercises");
@@ -126,13 +170,23 @@ const handleAILogic = async (tutorialDir, packageInfo) => {
126
170
  lesson_description: description,
127
171
  kind: kind.toLowerCase(),
128
172
  });
129
- const { exceedsThreshold, newMarkdown } = (0, creatorUtilities_1.checkReadingTime)(readme.parsed.content, 200);
130
- if (exceedsThreshold) {
131
- console_1.default.error("The reading time exceeds the threshold");
132
- console_1.default.info(`Please reduce the reading time of the lesson, current reading time is ${exceedsThreshold} minutes`);
173
+ const duration = durationByKind[kind.toLowerCase()];
174
+ let readingTime = (0, creatorUtilities_1.checkReadingTime)(readme.parsed.content, 200, duration || 1);
175
+ if (readingTime.exceedsThreshold) {
176
+ // Console.info(
177
+ // `The reading time for the lesson ${exTitle} exceeds the threshold, reducing it...`
178
+ // )
179
+ const reducedReadme = await (0, rigoActions_1.reduceReadme)(rigoToken, {
180
+ lesson: readingTime.body,
181
+ number_of_words: readingTime.minutes.toString(),
182
+ expected_number_words: "200",
183
+ });
184
+ if (reducedReadme) {
185
+ readingTime = (0, creatorUtilities_1.checkReadingTime)(reducedReadme.parsed.content, 200, duration || 1);
186
+ }
133
187
  }
134
188
  const readmeFilename = "README.md";
135
- fs.writeFileSync(path.join(exerciseDir, readmeFilename), newMarkdown);
189
+ fs.writeFileSync(path.join(exerciseDir, readmeFilename), readingTime.newMarkdown);
136
190
  if (kind.toLowerCase() === "code") {
137
191
  const codeFile = await (0, rigoActions_1.createCodeFile)(rigoToken, {
138
192
  readme: readme.parsed.content,
@@ -140,12 +194,12 @@ const handleAILogic = async (tutorialDir, packageInfo) => {
140
194
  });
141
195
  fs.writeFileSync(path.join(exerciseDir, `app.${codeFile.parsed.extension.replace(".", "")}`), codeFile.parsed.content);
142
196
  }
143
- return readme.parsed.content;
197
+ return readingTime.newMarkdown;
144
198
  });
145
- let imagesArray = [];
146
199
  const readmeContents = await Promise.all(exercisePromises);
147
200
  console_1.default.success("Lessons created! 🎉");
148
201
  console_1.default.info("Generating images for the lessons...");
202
+ let imagesArray = [];
149
203
  for (const content of readmeContents) {
150
204
  imagesArray = [...imagesArray, ...(0, creatorUtilities_1.extractImagesFromMarkdown)(content)];
151
205
  }
@@ -173,7 +227,7 @@ const getChoices = async (empty) => {
173
227
  title: "My Interactive Tutorial",
174
228
  description: "",
175
229
  difficulty: "beginner",
176
- duration: 1,
230
+ duration: 5,
177
231
  useAI: "no",
178
232
  grading: "isolated",
179
233
  };
@@ -209,30 +263,6 @@ const getChoices = async (empty) => {
209
263
  initial: "",
210
264
  message: "Description for your tutorial? Press enter to leave blank",
211
265
  },
212
- {
213
- type: "select",
214
- name: "difficulty",
215
- message: "How difficulty will be to complete the tutorial?",
216
- choices: [
217
- { title: "Begginer (no previous experience)", value: "beginner" },
218
- { title: "Easy (just a bit of experience required)", value: "easy" },
219
- {
220
- title: "Intermediate (you need experience)",
221
- value: "intermediate",
222
- },
223
- { title: "Hard (master the topic)", value: "hard" },
224
- ],
225
- },
226
- {
227
- type: "text",
228
- name: "duration",
229
- initial: "1",
230
- message: "How many hours avg it takes to complete (number)?",
231
- validate: (value) => {
232
- const n = Math.floor(Number(value));
233
- return n !== Number.POSITIVE_INFINITY && String(n) === value && n >= 0;
234
- },
235
- },
236
266
  {
237
267
  type: "select",
238
268
  name: "useAI",
@@ -246,7 +276,8 @@ const getChoices = async (empty) => {
246
276
  ],
247
277
  },
248
278
  ]);
249
- return choices;
279
+ const completeChoices = Object.assign(Object.assign({}, choices), { difficulty: "beginner", duration: 30 });
280
+ return completeChoices;
250
281
  };
251
282
  class InitComand extends BaseCommand_1.default {
252
283
  async run() {
@@ -1,27 +1,22 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- // import {Command, flags} from '@oclif/command'
4
- // import { prompt } from "enquirer"
5
- // import fetch from 'node-fetch'
6
3
  const SessionCommand_1 = require("../utils/SessionCommand");
7
4
  const session_1 = require("../managers/session");
8
- // import Console from '../utils/console'
9
- // import { replace } from 'node-emoji'
10
- // import { validURL } from "../utils/validators"
11
- // const BaseCommand from '../utils/BaseCommand');
5
+ const console_1 = require("../utils/console");
12
6
  class LogoutCommand extends SessionCommand_1.default {
13
7
  async init() {
14
8
  const { flags } = this.parse(LogoutCommand);
15
- await this.initSession(flags);
9
+ // await this.initSession(flags)
10
+ console_1.default.debug("Logout command");
16
11
  }
17
12
  async run() {
18
13
  // const {flags, args} = this.parse(LogoutCommand)
19
14
  session_1.default.destroy();
20
15
  }
21
16
  }
22
- LogoutCommand.description = `Describe the command here
23
- ...
24
- Extra documentation goes here
17
+ LogoutCommand.description = `Describe the command here
18
+ ...
19
+ Extra documentation goes here
25
20
  `;
26
21
  LogoutCommand.flags = {
27
22
  // name: flags.string({char: 'n', description: 'name to print'}),
@@ -92,7 +92,7 @@ class BuildCommand extends SessionCommand_1.default {
92
92
  (_c = this.configManager) === null || _c === void 0 ? void 0 : _c.buildIndex();
93
93
  }
94
94
  // const academies = await api.listUserAcademies(sessionPayload.token)
95
- // console.log(academies, "academies")
95
+ // // console.log(academies, "academies")
96
96
  // const academy = await selectAcademy(academies)
97
97
  // console.log(academy, "academy")
98
98
  // Read learn.json to get the slug
@@ -82,11 +82,13 @@ class StartCommand extends SessionCommand_1.default {
82
82
  socket_1.default.on("open", (data) => {
83
83
  console_1.default.debug("Opening these files: ", data);
84
84
  const files = (0, misc_1.prioritizeHTMLFile)(data.files);
85
- if (config.editor.agent === "os") {
85
+ if (config.editor.agent !== "os") {
86
+ // Console.info("Opening files for vscode agent")
86
87
  osOperations_1.eventManager.enqueue(dispatcher.events.OPEN_FILES, files);
87
88
  }
88
89
  else {
89
- dispatcher.enqueue(dispatcher.events.OPEN_FILES, files);
90
+ // dispatcher.enqueue(dispatcher.events.OPEN_FILES, files)
91
+ console_1.default.debug("Ignoring files for os agent");
90
92
  }
91
93
  socket_1.default.ready("Ready to compile...");
92
94
  });
@@ -142,14 +144,6 @@ class StartCommand extends SessionCommand_1.default {
142
144
  telemetry: telemetry_1.default,
143
145
  });
144
146
  });
145
- // socket.on("quiz_submission", (data: any) => {
146
- // const { stepPosition, event, eventData } = data
147
- // TelemetryManager.registerStepEvent(stepPosition, event, eventData)
148
- // })
149
- // socket.on("ai_interaction", (data: any) => {
150
- // const { stepPosition, event, eventData } = data
151
- // TelemetryManager.registerStepEvent(stepPosition, event, eventData)
152
- // })
153
147
  socket_1.default.on("telemetry_event", (data) => {
154
148
  const { stepPosition, event, eventData } = data;
155
149
  telemetry_1.default.registerStepEvent(stepPosition, event, eventData);
@@ -222,9 +216,9 @@ class StartCommand extends SessionCommand_1.default {
222
216
  setTimeout(() => dispatcher.enqueue(dispatcher.events.RUNNING), 1000);
223
217
  // start watching for file changes
224
218
  if (StartCommand.flags.watch)
225
- this.configManager.watchIndex(_filename => {
219
+ this.configManager.watchIndex((_filename, _fileContent) => {
226
220
  // Instead of reloading with socket.reload(), I just notify the frontend for the file change
227
- socket_1.default.emit("file_change", "ready", _filename);
221
+ socket_1.default.emit("file_change", "ready", [_filename, _fileContent]);
228
222
  });
229
223
  }
230
224
  }
@@ -372,6 +372,31 @@ exports.default = async ({ grading, mode, disableGrading, version, }) => {
372
372
  [(0, exercise_1.exercise)(((_b = configObj === null || configObj === void 0 ? void 0 : configObj.config) === null || _b === void 0 ? void 0 : _b.exercisesPath) || "", 0, configObj)];
373
373
  this.save();
374
374
  },
375
+ createExercise: (slug, content, language) => {
376
+ var _a;
377
+ try {
378
+ const dirPath = `${(_a = configObj.config) === null || _a === void 0 ? void 0 : _a.exercisesPath}/${slug}`;
379
+ if (!fs.existsSync(dirPath))
380
+ fs.mkdirSync(dirPath, { recursive: true });
381
+ const isEnglish = language === "us" || language === "en";
382
+ const fileName = isEnglish ? "README.md" : `README.${language}.md`;
383
+ fs.writeFileSync(`${dirPath}/${fileName}`, content);
384
+ return true;
385
+ }
386
+ catch (error) {
387
+ console_1.default.error("Error creating exercise: ", error);
388
+ return false;
389
+ }
390
+ },
391
+ deleteExercise: (slug) => {
392
+ var _a;
393
+ const dirPath = `${(_a = configObj.config) === null || _a === void 0 ? void 0 : _a.exercisesPath}/${slug}`;
394
+ if (fs.existsSync(dirPath)) {
395
+ fs.rmSync(dirPath, { recursive: true, force: true });
396
+ return true;
397
+ }
398
+ return false;
399
+ },
375
400
  watchIndex: function (onChange) {
376
401
  var _a;
377
402
  if (configObj.config && !configObj.config.exercisesPath)
@@ -92,7 +92,7 @@ const download = (url, dest) => {
92
92
  });
93
93
  file.on("error", err => {
94
94
  file.close();
95
- if (err.code === "EEXIST") {
95
+ if (err.name === "EEXIST") {
96
96
  console_1.default.debug("File already exists");
97
97
  resolve("File already exists");
98
98
  }
@@ -11,7 +11,6 @@ const fileQueue_1 = require("../../utils/fileQueue");
11
11
  const exercise_1 = require("../config/exercise");
12
12
  const session_1 = require("../../managers/session");
13
13
  const telemetry_1 = require("../telemetry");
14
- const osOperations_1 = require("../../utils/osOperations");
15
14
  const withHandler = (func) => (req, res) => {
16
15
  try {
17
16
  func(req, res);
@@ -200,10 +199,7 @@ async function default_1(app, configObject, configManager) {
200
199
  if (exercise.position) {
201
200
  telemetry_1.default.registerStepEvent(exercise.position, "open_step", {});
202
201
  }
203
- if (((_a = configObject.config) === null || _a === void 0 ? void 0 : _a.editor.agent) === "os") {
204
- osOperations_1.eventManager.enqueue(dispatcher.events.START_EXERCISE, exercise);
205
- }
206
- else {
202
+ if (((_a = configObject.config) === null || _a === void 0 ? void 0 : _a.editor.agent) !== "os") {
207
203
  dispatcher.enqueue(dispatcher.events.START_EXERCISE, req.params.slug);
208
204
  }
209
205
  const entries = new Set(Object.keys(config === null || config === void 0 ? void 0 : config.entries).map(lang => config === null || config === void 0 ? void 0 : config.entries[lang]));
@@ -269,6 +265,36 @@ async function default_1(app, configObject, configManager) {
269
265
  res.end();
270
266
  }
271
267
  }));
268
+ app.delete("/exercise/:slug/delete", withHandler(async (req, res) => {
269
+ const exerciseDeleted = configManager.deleteExercise(req.params.slug);
270
+ if (exerciseDeleted) {
271
+ configManager.buildIndex();
272
+ res.json({ status: "ok" });
273
+ }
274
+ else {
275
+ res.status(500).json({ error: "Failed to delete exercise" });
276
+ }
277
+ }));
278
+ app.post("/exercise/:slug/create", jsonBodyParser, withHandler(async (req, res) => {
279
+ const { title, readme, language } = req.body;
280
+ const { slug } = req.params;
281
+ if (!title || !readme || !language) {
282
+ return res.status(400).json({ error: "Missing required fields" });
283
+ }
284
+ try {
285
+ const exerciseCreated = await configManager.createExercise(slug, readme, language);
286
+ if (exerciseCreated) {
287
+ configManager.buildIndex();
288
+ res.json({ status: "ok" });
289
+ }
290
+ else {
291
+ res.status(500).json({ error: "Failed to create exercise" });
292
+ }
293
+ }
294
+ catch (_a) {
295
+ res.status(500).json({ error: "Failed to create exercise" });
296
+ }
297
+ }));
272
298
  const textBodyParser = bodyParser.text();
273
299
  app.put("/exercise/:slug/file/:fileName", textBodyParser, withHandler((req, res) => {
274
300
  const exercise = configManager.getExercise(req.params.slug);
@@ -134,6 +134,9 @@ const Session = {
134
134
  }
135
135
  },
136
136
  destroy: async function () {
137
+ if (!this.sessionStarted) {
138
+ await this.initialize();
139
+ }
137
140
  await storage.clear();
138
141
  this.token = null;
139
142
  console_1.default.success("You have logged out");
@@ -13,6 +13,8 @@ export interface IConfigManager {
13
13
  getExercise: (slug: string | undefined) => IExercise;
14
14
  startExercise: (slug: string) => IExercise;
15
15
  reset: (slug: string) => void;
16
+ createExercise: (slug: string, content: string, language: string) => boolean;
17
+ deleteExercise: (slug: string) => boolean;
16
18
  buildIndex: () => boolean | void;
17
19
  watchIndex: (onChange: (...args: Array<any>) => void) => void;
18
20
  save: () => void;
@@ -14,9 +14,11 @@ export type PackageInfo = {
14
14
  us: string;
15
15
  };
16
16
  };
17
- export declare function checkReadingTime(markdown: string, wordsPerMinute?: number): {
17
+ export declare function checkReadingTime(markdown: string, wordsPerMinute?: number, maxMinutes?: number): {
18
18
  newMarkdown: string;
19
19
  exceedsThreshold: boolean;
20
+ minutes: number;
21
+ body: string;
20
22
  };
21
23
  export declare const getExInfo: (title: string) => {
22
24
  exNumber: string;
@@ -31,7 +33,7 @@ export declare function extractImagesFromMarkdown(markdown: string): {
31
33
  export declare function getFilenameFromUrl(url: string): string;
32
34
  export declare const makePackageInfo: (choices: any) => {
33
35
  grading: any;
34
- difficulty: any;
36
+ difficulty: string;
35
37
  duration: number;
36
38
  description: {
37
39
  us: any;
@@ -41,4 +43,7 @@ export declare const makePackageInfo: (choices: any) => {
41
43
  };
42
44
  slug: any;
43
45
  };
46
+ export declare function estimateDuration(listOfSteps: string[]): number;
47
+ export declare function createFileOnDesktop(): Promise<void>;
48
+ export declare function getContentIndex(): string;
44
49
  export {};