@learnpack/learnpack 5.0.15 → 5.0.16

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.15 win32-x64 node-v20.16.0
24
+ @learnpack/learnpack/5.0.16 win32-x64 node-v20.16.0
25
25
  $ learnpack --help [COMMAND]
26
26
  USAGE
27
27
  $ learnpack COMMAND
@@ -75,7 +75,7 @@ DESCRIPTION
75
75
  12. If there is a file within the exercises folder but not inside of any particular exercise's folder. (Warning)
76
76
  ```
77
77
 
78
- _See code: [src\commands\audit.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.15/src\commands\audit.ts)_
78
+ _See code: [src\commands\audit.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.16/src\commands\audit.ts)_
79
79
 
80
80
  ## `learnpack clean`
81
81
 
@@ -90,7 +90,7 @@ DESCRIPTION
90
90
  Extra documentation goes here
91
91
  ```
92
92
 
93
- _See code: [src\commands\clean.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.15/src\commands\clean.ts)_
93
+ _See code: [src\commands\clean.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.16/src\commands\clean.ts)_
94
94
 
95
95
  ## `learnpack download [PACKAGE]`
96
96
 
@@ -108,7 +108,7 @@ DESCRIPTION
108
108
  Extra documentation goes here
109
109
  ```
110
110
 
111
- _See code: [src\commands\download.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.15/src\commands\download.ts)_
111
+ _See code: [src\commands\download.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.16/src\commands\download.ts)_
112
112
 
113
113
  ## `learnpack help [COMMAND]`
114
114
 
@@ -137,9 +137,10 @@ USAGE
137
137
 
138
138
  OPTIONS
139
139
  -h, --grading show CLI help
140
+ -y, --yes Skip all prompts and initialize an empty project
140
141
  ```
141
142
 
142
- _See code: [src\commands\init.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.15/src\commands\init.ts)_
143
+ _See code: [src\commands\init.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.16/src\commands\init.ts)_
143
144
 
144
145
  ## `learnpack login [PACKAGE]`
145
146
 
@@ -157,7 +158,7 @@ DESCRIPTION
157
158
  Extra documentation goes here
158
159
  ```
159
160
 
160
- _See code: [src\commands\login.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.15/src\commands\login.ts)_
161
+ _See code: [src\commands\login.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.16/src\commands\login.ts)_
161
162
 
162
163
  ## `learnpack logout [PACKAGE]`
163
164
 
@@ -175,7 +176,7 @@ DESCRIPTION
175
176
  Extra documentation goes here
176
177
  ```
177
178
 
178
- _See code: [src\commands\logout.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.15/src\commands\logout.ts)_
179
+ _See code: [src\commands\logout.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.16/src\commands\logout.ts)_
179
180
 
180
181
  ## `learnpack plugins`
181
182
 
@@ -306,7 +307,7 @@ OPTIONS
306
307
  -h, --help show CLI help
307
308
  ```
308
309
 
309
- _See code: [src\commands\publish.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.15/src\commands\publish.ts)_
310
+ _See code: [src\commands\publish.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.16/src\commands\publish.ts)_
310
311
 
311
312
  ## `learnpack start`
312
313
 
@@ -325,9 +326,10 @@ OPTIONS
325
326
  -p, --port=port server port
326
327
  -v, --version=version E.g: 1.0.1
327
328
  -w, --watch Watch for file changes
329
+ -y, --yes Skip all prompts and initialize an empty project
328
330
  ```
329
331
 
330
- _See code: [src\commands\start.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.15/src\commands\start.ts)_
332
+ _See code: [src\commands\start.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.16/src\commands\start.ts)_
331
333
 
332
334
  ## `learnpack test [EXERCISESLUG]`
333
335
 
@@ -339,9 +341,12 @@ USAGE
339
341
 
340
342
  ARGUMENTS
341
343
  EXERCISESLUG The name of the exercise to test
344
+
345
+ OPTIONS
346
+ -y, --yes Skip all prompts and initialize an empty project
342
347
  ```
343
348
 
344
- _See code: [src\commands\test.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.15/src\commands\test.ts)_
349
+ _See code: [src\commands\test.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.16/src\commands\test.ts)_
345
350
 
346
351
  ## `learnpack translate`
347
352
 
@@ -350,9 +355,12 @@ List all the lessons, the user is able of select many of them to translate to th
350
355
  ```
351
356
  USAGE
352
357
  $ learnpack translate
358
+
359
+ OPTIONS
360
+ -y, --yes Skip all prompts and initialize an empty project
353
361
  ```
354
362
 
355
- _See code: [src\commands\translate.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.15/src\commands\translate.ts)_
363
+ _See code: [src\commands\translate.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.16/src\commands\translate.ts)_
356
364
  <!-- commandsstop -->
357
365
 
358
366
  > > > > > > > 0cb3e56d84c197f9d008836bb573eade212b7e57
@@ -3,6 +3,7 @@ declare class InitComand extends BaseCommand {
3
3
  static description: string;
4
4
  static flags: {
5
5
  grading: import("@oclif/parser/lib/flags").IBooleanFlag<void>;
6
+ yes: import("@oclif/parser/lib/flags").IBooleanFlag<boolean>;
6
7
  };
7
8
  run(): Promise<void>;
8
9
  }
@@ -6,11 +6,13 @@ const BaseCommand_1 = require("../utils/BaseCommand");
6
6
  const fs = require("fs-extra");
7
7
  const prompts = require("prompts");
8
8
  const cli_ux_1 = require("cli-ux");
9
+ const eta = require("eta");
9
10
  const api_1 = require("../utils/api");
10
11
  const console_1 = require("../utils/console");
11
12
  const errors_1 = require("../utils/errors");
12
13
  const path = require("path");
13
14
  const rigoActions_1 = require("../utils/rigoActions");
15
+ const api_2 = require("../utils/api");
14
16
  const slugify = (text) => {
15
17
  return text
16
18
  .toString()
@@ -38,6 +40,24 @@ function extractImagesFromMarkdown(markdown) {
38
40
  function getFilenameFromUrl(url) {
39
41
  return path.basename(url);
40
42
  }
43
+ const makePackageInfo = (choices) => {
44
+ const packageInfo = {
45
+ grading: choices.grading,
46
+ difficulty: choices.difficulty,
47
+ duration: parseInt(choices.duration),
48
+ description: {
49
+ us: choices.description,
50
+ },
51
+ title: {
52
+ us: choices.title,
53
+ },
54
+ slug: choices.title
55
+ .toLowerCase()
56
+ .replace(/ /g, "-")
57
+ .replace(/[^\w-]+/g, ""),
58
+ };
59
+ return packageInfo;
60
+ };
41
61
  async function createPreviewReadme(tutorialDir, packageInfo, rigoToken, readmeContents) {
42
62
  const readmeFilename = `README.md`;
43
63
  const readmeContent = await (0, rigoActions_1.generateCourseIntroduction)(rigoToken, {
@@ -47,7 +67,7 @@ async function createPreviewReadme(tutorialDir, packageInfo, rigoToken, readmeCo
47
67
  fs.writeFileSync(path.join(tutorialDir, readmeFilename), readmeContent.answer);
48
68
  }
49
69
  const handleAILogic = async (tutorialDir, packageInfo) => {
50
- console_1.default.info("Almost there! First you need to login to use the AI creator");
70
+ console_1.default.info("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");
51
71
  fs.removeSync(path.join(tutorialDir, "exercises", "01-hello-world"));
52
72
  const loginPrompts = await prompts([
53
73
  {
@@ -77,10 +97,17 @@ const handleAILogic = async (tutorialDir, packageInfo) => {
77
97
  return;
78
98
  }
79
99
  const rigoToken = sessionPayload.rigobot.key;
100
+ const consumables = await (0, api_2.getConsumables)(sessionPayload.token);
101
+ if (consumables.ai_tutorial_generation === 0) {
102
+ console_1.default.error("It seems you cannot generate tutorials with AI. Make sure you creator subscription is up to date here: https://4geeks.com/profile/subscriptions?token=" +
103
+ sessionPayload.token +
104
+ ". If you believe there is an issue you can always contact support@4geeks.com");
105
+ process.exit(1);
106
+ }
80
107
  const isCreator = await (0, rigoActions_1.hasCreatorPermission)(rigoToken);
81
108
  if (!isCreator) {
82
109
  console_1.default.error("👀 Oops! You need to be a creator to use our specialized AI. Please contact support");
83
- return;
110
+ process.exit(1);
84
111
  }
85
112
  console_1.default.success("🎉 Let's begin this learning journey!");
86
113
  const aiChoices = await prompts([
@@ -161,91 +188,92 @@ const handleAILogic = async (tutorialDir, packageInfo) => {
161
188
  await createPreviewReadme(tutorialDir, packageInfo, rigoToken, readmeContents);
162
189
  return true;
163
190
  };
191
+ const getChoices = async (empty) => {
192
+ const defaultChoices = {
193
+ title: "My Interactive Tutorial",
194
+ description: "",
195
+ difficulty: "beginner",
196
+ duration: 1,
197
+ useAI: "no",
198
+ grading: "isolated",
199
+ };
200
+ if (empty) {
201
+ return defaultChoices;
202
+ }
203
+ const choices = await prompts([
204
+ {
205
+ type: "select",
206
+ name: "grading",
207
+ message: "Is the auto-grading going to be isolated or incremental?",
208
+ choices: [
209
+ {
210
+ title: "Incremental: Build on top of each other like a tutorial",
211
+ value: "incremental",
212
+ },
213
+ { title: "Isolated: Small separated exercises", value: "isolated" },
214
+ {
215
+ title: "No grading: No feedback or testing whatsoever",
216
+ value: null,
217
+ },
218
+ ],
219
+ },
220
+ {
221
+ type: "text",
222
+ name: "title",
223
+ initial: "My Interactive Tutorial",
224
+ message: "Title for your tutorial? Press enter to leave as it is",
225
+ },
226
+ {
227
+ type: "text",
228
+ name: "description",
229
+ initial: "",
230
+ message: "Description for your tutorial? Press enter to leave blank",
231
+ },
232
+ {
233
+ type: "select",
234
+ name: "difficulty",
235
+ message: "How difficulty will be to complete the tutorial?",
236
+ choices: [
237
+ { title: "Begginer (no previous experience)", value: "beginner" },
238
+ { title: "Easy (just a bit of experience required)", value: "easy" },
239
+ {
240
+ title: "Intermediate (you need experience)",
241
+ value: "intermediate",
242
+ },
243
+ { title: "Hard (master the topic)", value: "hard" },
244
+ ],
245
+ },
246
+ {
247
+ type: "text",
248
+ name: "duration",
249
+ initial: "1",
250
+ message: "How many hours avg it takes to complete (number)?",
251
+ validate: (value) => {
252
+ const n = Math.floor(Number(value));
253
+ return n !== Number.POSITIVE_INFINITY && String(n) === value && n >= 0;
254
+ },
255
+ },
256
+ {
257
+ type: "select",
258
+ name: "useAI",
259
+ message: "Want a little bit of AI magic to help you? Our AI can craft the tutorial for you",
260
+ choices: [
261
+ {
262
+ title: "Yes, please help me",
263
+ value: "yes",
264
+ },
265
+ { title: "No, thanks, I prefer to do it manually", value: "no" },
266
+ ],
267
+ },
268
+ ]);
269
+ return choices;
270
+ };
164
271
  class InitComand extends BaseCommand_1.default {
165
272
  async run() {
166
273
  const { flags } = this.parse(InitComand);
167
274
  await alreadyInitialized();
168
- const choices = await prompts([
169
- {
170
- type: "select",
171
- name: "grading",
172
- message: "Is the auto-grading going to be isolated or incremental?",
173
- choices: [
174
- {
175
- title: "Incremental: Build on top of each other like a tutorial",
176
- value: "incremental",
177
- },
178
- { title: "Isolated: Small separated exercises", value: "isolated" },
179
- {
180
- title: "No grading: No feedback or testing whatsoever",
181
- value: null,
182
- },
183
- ],
184
- },
185
- {
186
- type: "text",
187
- name: "title",
188
- initial: "My Interactive Tutorial",
189
- message: "Title for your tutorial? Press enter to leave as it is",
190
- },
191
- {
192
- type: "text",
193
- name: "description",
194
- initial: "",
195
- message: "Description for your tutorial? Press enter to leave blank",
196
- },
197
- {
198
- type: "select",
199
- name: "difficulty",
200
- message: "How difficulty will be to complete the tutorial?",
201
- choices: [
202
- { title: "Begginer (no previous experience)", value: "beginner" },
203
- { title: "Easy (just a bit of experience required)", value: "easy" },
204
- {
205
- title: "Intermediate (you need experience)",
206
- value: "intermediate",
207
- },
208
- { title: "Hard (master the topic)", value: "hard" },
209
- ],
210
- },
211
- {
212
- type: "text",
213
- name: "duration",
214
- initial: "1",
215
- message: "How many hours avg it takes to complete (number)?",
216
- validate: (value) => {
217
- const n = Math.floor(Number(value));
218
- return n !== Number.POSITIVE_INFINITY && String(n) === value && n >= 0;
219
- },
220
- },
221
- {
222
- type: "select",
223
- name: "useAI",
224
- message: "Want a little bit of AI magic to help you? Our AI can craft the tutorial for you",
225
- choices: [
226
- {
227
- title: "Yes, please help me",
228
- value: "yes",
229
- },
230
- { title: "No, thanks, I prefer to do it manually", value: "no" },
231
- ],
232
- },
233
- ]);
234
- const packageInfo = {
235
- grading: choices.grading,
236
- difficulty: choices.difficulty,
237
- duration: parseInt(choices.duration),
238
- description: {
239
- us: choices.description,
240
- },
241
- title: {
242
- us: choices.title,
243
- },
244
- slug: choices.title
245
- .toLowerCase()
246
- .replace(/ /g, "-")
247
- .replace(/[^\w-]+/g, ""),
248
- };
275
+ const choices = await getChoices(flags.yes);
276
+ const packageInfo = makePackageInfo(choices);
249
277
  const tutorialDir = `./${packageInfo.slug}`;
250
278
  fs.ensureDirSync(tutorialDir);
251
279
  const templatesDir = path.resolve(__dirname, "../../src/utils/templates/" + (choices.grading || "no-grading"));
@@ -259,25 +287,16 @@ class InitComand extends BaseCommand_1.default {
259
287
  // Creating README files
260
288
  for (const language of languages) {
261
289
  const readmeFilename = `README${language !== "en" ? `.${language}` : ""}`;
262
- // const readmeTemplatePath = path.resolve(
263
- // templatesDir,
264
- // `${readmeFilename}.ejs`
265
- // )
266
- // const readmeObject = {
267
- // title: packageInfo.title.us,
268
- // description: packageInfo.description.us,
269
- // grading: packageInfo.grading,
270
- // difficulty: packageInfo.difficulty,
271
- // duration: packageInfo.duration,
272
- // }
273
- // const readmeContent = eta.render(
274
- // fs.readFileSync(readmeTemplatePath, "utf-8"),
275
- // readmeObject
276
- // )
277
- // fs.writeFileSync(
278
- // path.join(tutorialDir, `${readmeFilename}.md`),
279
- // readmeContent
280
- // )
290
+ const readmeTemplatePath = path.resolve(templatesDir, `${readmeFilename}.ejs`);
291
+ const readmeObject = {
292
+ title: packageInfo.title.us,
293
+ description: packageInfo.description.us,
294
+ grading: packageInfo.grading,
295
+ difficulty: packageInfo.difficulty,
296
+ duration: packageInfo.duration,
297
+ };
298
+ const readmeContent = eta.render(fs.readFileSync(readmeTemplatePath, "utf-8"), readmeObject);
299
+ fs.writeFileSync(path.join(tutorialDir, `${readmeFilename}.md`), readmeContent);
281
300
  if (fs.existsSync(path.join(tutorialDir, `${readmeFilename}.ejs`)))
282
301
  fs.removeSync(path.join(tutorialDir, `${readmeFilename}.ejs`));
283
302
  }
@@ -289,6 +308,7 @@ class InitComand extends BaseCommand_1.default {
289
308
  console_1.default.success(`😋 Package initialized successfully in ${tutorialDir}`);
290
309
  console_1.default.help(`Get inside the tutorial with the command: $ cd ${tutorialDir}`);
291
310
  console_1.default.help(`Start the exercises by running the following command on your terminal: $ learnpack start`);
311
+ process.exit(0);
292
312
  }
293
313
  }
294
314
  InitComand.description = "Create a new learning package: Book, Tutorial or Exercise";
@@ -3,6 +3,9 @@ declare class BaseCommand extends Command {
3
3
  catch(err: any): Promise<void>;
4
4
  init(): Promise<void>;
5
5
  finally(): Promise<void>;
6
+ static flags: {
7
+ yes: import("@oclif/parser/lib/flags").IBooleanFlag<boolean>;
8
+ };
6
9
  run(): Promise<void>;
7
10
  }
8
11
  export default BaseCommand;
@@ -38,4 +38,11 @@ class BaseCommand extends command_1.Command {
38
38
  // console.log('running my command')
39
39
  }
40
40
  }
41
+ BaseCommand.flags = {
42
+ yes: command_1.flags.boolean({
43
+ char: "y",
44
+ description: "Skip all prompts and initialize an empty project",
45
+ default: false,
46
+ }),
47
+ };
41
48
  exports.default = BaseCommand;
@@ -1,3 +1,6 @@
1
+ type TConsumableSlug = "ai-conversation-message" | "ai-compilation" | "ai-tutorial-generation";
2
+ export declare const countConsumables: (consumables: any, consumableSlug?: TConsumableSlug) => any;
3
+ export declare const getConsumables: (token: string) => Promise<any>;
1
4
  declare const _default: {
2
5
  login: (identification: string, password: string) => Promise<any>;
3
6
  publish: (config: any) => Promise<any>;
package/lib/utils/api.js CHANGED
@@ -1,8 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getConsumables = exports.countConsumables = void 0;
3
4
  const console_1 = require("../utils/console");
4
5
  const storage = require("node-persist");
5
6
  const cli_ux_1 = require("cli-ux");
7
+ const axios_1 = require("axios");
6
8
  const HOST = "https://breathecode.herokuapp.com";
7
9
  const RIGOBOT_HOST = "https://rigobot.herokuapp.com";
8
10
  // const RIGOBOT_HOST = "https://8000-charlytoc-rigobot-bmwdeam7cev.ws-us116.gitpod.io"
@@ -249,6 +251,29 @@ const sendStreamTelemetry = async function (url, body) {
249
251
  console_1.default.debug("Error while sending stream Telemetry", error);
250
252
  });
251
253
  };
254
+ const countConsumables = (consumables, consumableSlug = "ai-tutorial-generation") => {
255
+ // Find the void that matches the consumableSlug
256
+ const consumable = consumables.voids.find((voidItem) => voidItem.slug === consumableSlug);
257
+ // Return the available units or 0 if not found
258
+ return consumable ? consumable.balance.unit : 0;
259
+ };
260
+ exports.countConsumables = countConsumables;
261
+ const getConsumables = async (token) => {
262
+ const url = `${HOST}/v1/payments/me/service/consumable?virtual=true`;
263
+ const headers = {
264
+ Authorization: `Token ${token}`,
265
+ };
266
+ try {
267
+ const response = await axios_1.default.get(url, { headers });
268
+ const ai_tutorial_generation = (0, exports.countConsumables)(response.data, "ai-tutorial-generation");
269
+ return { ai_tutorial_generation };
270
+ }
271
+ catch (error) {
272
+ console.error("Error fetching consumables:", error);
273
+ throw error;
274
+ }
275
+ };
276
+ exports.getConsumables = getConsumables;
252
277
  exports.default = {
253
278
  login,
254
279
  publish,
@@ -1 +1 @@
1
- {"version":"5.0.15","commands":{"audit":{"id":"audit","description":"learnpack audit is the command in charge of creating an auditory of the repository\n...\nlearnpack audit checks for the following information in a repository:\n 1. The configuration object has slug, repository and description. (Error)\n 2. The command learnpack clean has been run. (Error)\n 3. If a markdown or test file doesn't have any content. (Error)\n 4. The links are accessing to valid servers. (Error)\n 5. The relative images are working (If they have the shortest path to the image or if the images exists in the assets). (Error)\n 6. The external images are working (If they are pointing to a valid server). (Error)\n 7. The exercises directory names are valid. (Error)\n 8. If an exercise doesn't have a README file. (Error)\n 9. The exercises array (Of the config file) has content. (Error)\n 10. The exercses have the same translations. (Warning)\n 11. The .gitignore file exists. (Warning)\n 12. If there is a file within the exercises folder but not inside of any particular exercise's folder. (Warning)\n","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{},"args":[]},"clean":{"id":"clean","description":"Clean the configuration object\n ...\n Extra documentation goes here\n ","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{},"args":[]},"download":{"id":"download","description":"Describe the command here\n...\nExtra documentation goes here\n","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{},"args":[{"name":"package","description":"The unique string that identifies this package on learnpack","required":false,"hidden":false}]},"init":{"id":"init","description":"Create a new learning package: Book, Tutorial or Exercise","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"grading":{"name":"grading","type":"boolean","char":"h","description":"show CLI help","allowNo":false}},"args":[]},"login":{"id":"login","description":"Describe the command here\n ...\n Extra documentation goes here\n ","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{},"args":[{"name":"package","description":"The unique string that identifies this package on learnpack","required":false,"hidden":false}]},"logout":{"id":"logout","description":"Describe the command here\n ...\n Extra documentation goes here\n ","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{},"args":[{"name":"package","description":"The unique string that identifies this package on learnpack","required":false,"hidden":false}]},"publish":{"id":"publish","description":"Builds the project by copying necessary files and directories into a zip file","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"help":{"name":"help","type":"boolean","char":"h","description":"show CLI help","allowNo":false}},"args":[]},"start":{"id":"start","description":"Runs a small server with all the exercise instructions","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"port":{"name":"port","type":"option","char":"p","description":"server port"},"host":{"name":"host","type":"option","char":"h","description":"server host"},"disableGrading":{"name":"disableGrading","type":"boolean","char":"D","description":"disble grading functionality","allowNo":false},"watch":{"name":"watch","type":"boolean","char":"w","description":"Watch for file changes","allowNo":false},"editor":{"name":"editor","type":"option","char":"e","description":"[preview, extension]","options":["extension","preview"]},"version":{"name":"version","type":"option","char":"v","description":"E.g: 1.0.1"},"grading":{"name":"grading","type":"option","char":"g","description":"[isolated, incremental]","options":["isolated","incremental"]},"debug":{"name":"debug","type":"boolean","char":"d","description":"debugger mode for more verbage","allowNo":false}},"args":[]},"test":{"id":"test","description":"Test exercises","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{},"args":[{"name":"exerciseSlug","description":"The name of the exercise to test","required":false,"hidden":false}]},"translate":{"id":"translate","description":"List all the lessons, the user is able of select many of them to translate to the given languages","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{},"args":[]}}}
1
+ {"version":"5.0.16","commands":{"audit":{"id":"audit","description":"learnpack audit is the command in charge of creating an auditory of the repository\n...\nlearnpack audit checks for the following information in a repository:\n 1. The configuration object has slug, repository and description. (Error)\n 2. The command learnpack clean has been run. (Error)\n 3. If a markdown or test file doesn't have any content. (Error)\n 4. The links are accessing to valid servers. (Error)\n 5. The relative images are working (If they have the shortest path to the image or if the images exists in the assets). (Error)\n 6. The external images are working (If they are pointing to a valid server). (Error)\n 7. The exercises directory names are valid. (Error)\n 8. If an exercise doesn't have a README file. (Error)\n 9. The exercises array (Of the config file) has content. (Error)\n 10. The exercses have the same translations. (Warning)\n 11. The .gitignore file exists. (Warning)\n 12. If there is a file within the exercises folder but not inside of any particular exercise's folder. (Warning)\n","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{},"args":[]},"clean":{"id":"clean","description":"Clean the configuration object\n ...\n Extra documentation goes here\n ","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{},"args":[]},"download":{"id":"download","description":"Describe the command here\n...\nExtra documentation goes here\n","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{},"args":[{"name":"package","description":"The unique string that identifies this package on learnpack","required":false,"hidden":false}]},"init":{"id":"init","description":"Create a new learning package: Book, Tutorial or Exercise","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"yes":{"name":"yes","type":"boolean","char":"y","description":"Skip all prompts and initialize an empty project","allowNo":false},"grading":{"name":"grading","type":"boolean","char":"h","description":"show CLI help","allowNo":false}},"args":[]},"login":{"id":"login","description":"Describe the command here\n ...\n Extra documentation goes here\n ","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{},"args":[{"name":"package","description":"The unique string that identifies this package on learnpack","required":false,"hidden":false}]},"logout":{"id":"logout","description":"Describe the command here\n ...\n Extra documentation goes here\n ","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{},"args":[{"name":"package","description":"The unique string that identifies this package on learnpack","required":false,"hidden":false}]},"publish":{"id":"publish","description":"Builds the project by copying necessary files and directories into a zip file","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"help":{"name":"help","type":"boolean","char":"h","description":"show CLI help","allowNo":false}},"args":[]},"start":{"id":"start","description":"Runs a small server with all the exercise instructions","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"yes":{"name":"yes","type":"boolean","char":"y","description":"Skip all prompts and initialize an empty project","allowNo":false},"port":{"name":"port","type":"option","char":"p","description":"server port"},"host":{"name":"host","type":"option","char":"h","description":"server host"},"disableGrading":{"name":"disableGrading","type":"boolean","char":"D","description":"disble grading functionality","allowNo":false},"watch":{"name":"watch","type":"boolean","char":"w","description":"Watch for file changes","allowNo":false},"editor":{"name":"editor","type":"option","char":"e","description":"[preview, extension]","options":["extension","preview"]},"version":{"name":"version","type":"option","char":"v","description":"E.g: 1.0.1"},"grading":{"name":"grading","type":"option","char":"g","description":"[isolated, incremental]","options":["isolated","incremental"]},"debug":{"name":"debug","type":"boolean","char":"d","description":"debugger mode for more verbage","allowNo":false}},"args":[]},"test":{"id":"test","description":"Test exercises","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"yes":{"name":"yes","type":"boolean","char":"y","description":"Skip all prompts and initialize an empty project","allowNo":false}},"args":[{"name":"exerciseSlug","description":"The name of the exercise to test","required":false,"hidden":false}]},"translate":{"id":"translate","description":"List all the lessons, the user is able of select many of them to translate to the given languages","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"yes":{"name":"yes","type":"boolean","char":"y","description":"Skip all prompts and initialize an empty project","allowNo":false}},"args":[]}}}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@learnpack/learnpack",
3
3
  "description": "Seamlessly build, sell and/or take interactive & auto-graded tutorials, start learning now or build a new tutorial to your audience.",
4
- "version": "5.0.15",
4
+ "version": "5.0.16",
5
5
  "author": "Alejandro Sanchez @alesanchezr",
6
6
  "contributors": [
7
7
  {
@@ -19,7 +19,7 @@ import {
19
19
  downloadImage,
20
20
  generateCourseIntroduction,
21
21
  } from "../utils/rigoActions"
22
-
22
+ import { getConsumables } from "../utils/api"
23
23
  const slugify = (text: string) => {
24
24
  return text
25
25
  .toString()
@@ -53,6 +53,25 @@ function getFilenameFromUrl(url: string) {
53
53
  return path.basename(url)
54
54
  }
55
55
 
56
+ const makePackageInfo = (choices: any) => {
57
+ const packageInfo = {
58
+ grading: choices.grading,
59
+ difficulty: choices.difficulty,
60
+ duration: parseInt(choices.duration),
61
+ description: {
62
+ us: choices.description,
63
+ },
64
+ title: {
65
+ us: choices.title,
66
+ },
67
+ slug: choices.title
68
+ .toLowerCase()
69
+ .replace(/ /g, "-")
70
+ .replace(/[^\w-]+/g, ""),
71
+ }
72
+ return packageInfo
73
+ }
74
+
56
75
  async function createPreviewReadme(
57
76
  tutorialDir: string,
58
77
  packageInfo: PackageInfo,
@@ -81,7 +100,9 @@ type PackageInfo = {
81
100
  }
82
101
 
83
102
  const handleAILogic = async (tutorialDir: string, packageInfo: PackageInfo) => {
84
- Console.info("Almost there! First you need to login to use the AI creator")
103
+ Console.info(
104
+ "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"
105
+ )
85
106
 
86
107
  fs.removeSync(path.join(tutorialDir, "exercises", "01-hello-world"))
87
108
 
@@ -115,12 +136,23 @@ const handleAILogic = async (tutorialDir: string, packageInfo: PackageInfo) => {
115
136
 
116
137
  const rigoToken = sessionPayload.rigobot.key
117
138
 
139
+ const consumables = await getConsumables(sessionPayload.token)
140
+
141
+ if (consumables.ai_tutorial_generation === 0) {
142
+ Console.error(
143
+ "It seems you cannot generate tutorials with AI. Make sure you creator subscription is up to date here: https://4geeks.com/profile/subscriptions?token=" +
144
+ sessionPayload.token +
145
+ ". If you believe there is an issue you can always contact support@4geeks.com"
146
+ )
147
+ process.exit(1)
148
+ }
149
+
118
150
  const isCreator = await hasCreatorPermission(rigoToken)
119
151
  if (!isCreator) {
120
152
  Console.error(
121
153
  "👀 Oops! You need to be a creator to use our specialized AI. Please contact support"
122
154
  )
123
- return
155
+ process.exit(1)
124
156
  }
125
157
 
126
158
  Console.success("🎉 Let's begin this learning journey!")
@@ -236,6 +268,91 @@ const handleAILogic = async (tutorialDir: string, packageInfo: PackageInfo) => {
236
268
  return true
237
269
  }
238
270
 
271
+ const getChoices = async (empty: boolean) => {
272
+ const defaultChoices = {
273
+ title: "My Interactive Tutorial",
274
+ description: "",
275
+ difficulty: "beginner",
276
+ duration: 1,
277
+ useAI: "no",
278
+ grading: "isolated",
279
+ }
280
+
281
+ if (empty) {
282
+ return defaultChoices
283
+ }
284
+
285
+ const choices = await prompts([
286
+ {
287
+ type: "select",
288
+ name: "grading",
289
+ message: "Is the auto-grading going to be isolated or incremental?",
290
+ choices: [
291
+ {
292
+ title: "Incremental: Build on top of each other like a tutorial",
293
+ value: "incremental",
294
+ },
295
+ { title: "Isolated: Small separated exercises", value: "isolated" },
296
+ {
297
+ title: "No grading: No feedback or testing whatsoever",
298
+ value: null,
299
+ },
300
+ ],
301
+ },
302
+ {
303
+ type: "text",
304
+ name: "title",
305
+ initial: "My Interactive Tutorial",
306
+ message: "Title for your tutorial? Press enter to leave as it is",
307
+ },
308
+ {
309
+ type: "text",
310
+ name: "description",
311
+ initial: "",
312
+ message: "Description for your tutorial? Press enter to leave blank",
313
+ },
314
+ {
315
+ type: "select",
316
+ name: "difficulty",
317
+ message: "How difficulty will be to complete the tutorial?",
318
+ choices: [
319
+ { title: "Begginer (no previous experience)", value: "beginner" },
320
+ { title: "Easy (just a bit of experience required)", value: "easy" },
321
+ {
322
+ title: "Intermediate (you need experience)",
323
+ value: "intermediate",
324
+ },
325
+ { title: "Hard (master the topic)", value: "hard" },
326
+ ],
327
+ },
328
+ {
329
+ type: "text",
330
+ name: "duration",
331
+ initial: "1",
332
+ message: "How many hours avg it takes to complete (number)?",
333
+ validate: (value: string) => {
334
+ const n = Math.floor(Number(value))
335
+ return n !== Number.POSITIVE_INFINITY && String(n) === value && n >= 0
336
+ },
337
+ },
338
+ {
339
+ type: "select",
340
+ name: "useAI",
341
+ message:
342
+ "Want a little bit of AI magic to help you? Our AI can craft the tutorial for you",
343
+ choices: [
344
+ {
345
+ title: "Yes, please help me",
346
+ value: "yes",
347
+ },
348
+ { title: "No, thanks, I prefer to do it manually", value: "no" },
349
+ ],
350
+ },
351
+ ])
352
+
353
+ return choices
354
+ }
355
+
239
356
  class InitComand extends BaseCommand {
240
357
  static description =
241
358
  "Create a new learning package: Book, Tutorial or Exercise"
@@ -250,89 +367,9 @@ class InitComand extends BaseCommand {
250
367
 
251
368
  await alreadyInitialized()
252
369
 
253
- const choices = await prompts([
254
- {
255
- type: "select",
256
- name: "grading",
257
- message: "Is the auto-grading going to be isolated or incremental?",
258
- choices: [
259
- {
260
- title: "Incremental: Build on top of each other like a tutorial",
261
- value: "incremental",
262
- },
263
- { title: "Isolated: Small separated exercises", value: "isolated" },
264
- {
265
- title: "No grading: No feedback or testing whatsoever",
266
- value: null,
267
- },
268
- ],
269
- },
270
- {
271
- type: "text",
272
- name: "title",
273
- initial: "My Interactive Tutorial",
274
- message: "Title for your tutorial? Press enter to leave as it is",
275
- },
276
- {
277
- type: "text",
278
- name: "description",
279
- initial: "",
280
- message: "Description for your tutorial? Press enter to leave blank",
281
- },
282
- {
283
- type: "select",
284
- name: "difficulty",
285
- message: "How difficulty will be to complete the tutorial?",
286
- choices: [
287
- { title: "Begginer (no previous experience)", value: "beginner" },
288
- { title: "Easy (just a bit of experience required)", value: "easy" },
289
- {
290
- title: "Intermediate (you need experience)",
291
- value: "intermediate",
292
- },
293
- { title: "Hard (master the topic)", value: "hard" },
294
- ],
295
- },
296
- {
297
- type: "text",
298
- name: "duration",
299
- initial: "1",
300
- message: "How many hours avg it takes to complete (number)?",
301
- validate: (value: string) => {
302
- const n = Math.floor(Number(value))
303
- return n !== Number.POSITIVE_INFINITY && String(n) === value && n >= 0
304
- },
305
- },
306
- {
307
- type: "select",
308
- name: "useAI",
309
- message:
310
- "Want a little bit of AI magic to help you? Our AI can craft the tutorial for you",
311
- choices: [
312
- {
313
- title: "Yes, please help me",
314
- value: "yes",
315
- },
316
- { title: "No, thanks, I prefer to do it manually", value: "no" },
317
- ],
318
- },
319
- ])
320
-
321
- const packageInfo = {
322
- grading: choices.grading,
323
- difficulty: choices.difficulty,
324
- duration: parseInt(choices.duration),
325
- description: {
326
- us: choices.description,
327
- },
328
- title: {
329
- us: choices.title,
330
- },
331
- slug: choices.title
332
- .toLowerCase()
333
- .replace(/ /g, "-")
334
- .replace(/[^\w-]+/g, ""),
335
- }
370
+ const choices = await getChoices(flags.yes)
371
+
372
+ const packageInfo = makePackageInfo(choices)
336
373
 
337
374
  const tutorialDir = `./${packageInfo.slug}`
338
375
  fs.ensureDirSync(tutorialDir)
@@ -354,26 +391,26 @@ class InitComand extends BaseCommand {
354
391
  // Creating README files
355
392
  for (const language of languages) {
356
393
  const readmeFilename = `README${language !== "en" ? `.${language}` : ""}`
357
- // const readmeTemplatePath = path.resolve(
358
- // templatesDir,
359
- // `${readmeFilename}.ejs`
360
- // )
361
-
362
- // const readmeObject = {
363
- // title: packageInfo.title.us,
364
- // description: packageInfo.description.us,
365
- // grading: packageInfo.grading,
366
- // difficulty: packageInfo.difficulty,
367
- // duration: packageInfo.duration,
368
- // }
369
- // const readmeContent = eta.render(
370
- // fs.readFileSync(readmeTemplatePath, "utf-8"),
371
- // readmeObject
372
- // )
373
- // fs.writeFileSync(
374
- // path.join(tutorialDir, `${readmeFilename}.md`),
375
- // readmeContent
376
- // )
394
+ const readmeTemplatePath = path.resolve(
395
+ templatesDir,
396
+ `${readmeFilename}.ejs`
397
+ )
398
+
399
+ const readmeObject = {
400
+ title: packageInfo.title.us,
401
+ description: packageInfo.description.us,
402
+ grading: packageInfo.grading,
403
+ difficulty: packageInfo.difficulty,
404
+ duration: packageInfo.duration,
405
+ }
406
+ const readmeContent = eta.render(
407
+ fs.readFileSync(readmeTemplatePath, "utf-8"),
408
+ readmeObject
409
+ )
410
+ fs.writeFileSync(
411
+ path.join(tutorialDir, `${readmeFilename}.md`),
412
+ readmeContent
413
+ )
377
414
  if (fs.existsSync(path.join(tutorialDir, `${readmeFilename}.ejs`)))
378
415
  fs.removeSync(path.join(tutorialDir, `${readmeFilename}.ejs`))
379
416
  }
@@ -398,6 +435,7 @@ class InitComand extends BaseCommand {
398
435
  Console.help(
399
436
  `Start the exercises by running the following command on your terminal: $ learnpack start`
400
437
  )
438
+ process.exit(0)
401
439
  }
402
440
  }
403
441
 
@@ -1,48 +1,56 @@
1
- import { Command } from "@oclif/command"
2
- import Console from "./console"
3
- import { createInterface } from "readline"
4
- // import SessionManager from '../managers/session'
5
-
6
- class BaseCommand extends Command {
7
- async catch(err: any) {
8
- Console.debug("COMMAND CATCH", err)
9
-
10
- throw err
11
- }
12
-
13
- async init() {
14
- const { flags, args } = this.parse(BaseCommand)
15
- Console.debug("COMMAND INIT")
16
- Console.debug("These are your flags: ", flags)
17
- Console.debug("These are your args: ", args)
18
-
19
- // quick fix for listening to the process termination on windows
20
- if (process.platform === "win32") {
21
- const rl = createInterface({
22
- input: process.stdin,
23
- output: process.stdout,
24
- })
25
-
26
- rl.on("SIGINT", function () {
27
- // process.emit('SIGINT')
28
- // process.emit('SIGINT')
29
- })
30
- }
31
-
32
- process.on("SIGINT", function () {
33
- Console.debug("Terminated (SIGINT)")
34
- process.exit()
35
- })
36
- }
37
-
38
- async finally() {
39
- Console.debug("COMMAND FINALLY")
40
- // called after run and catch regardless of whether or not the command errored
41
- }
42
-
43
- async run() {
44
- // console.log('running my command')
45
- }
46
- }
47
-
48
- export default BaseCommand
1
+ import { Command, flags } from "@oclif/command"
2
+ import Console from "./console"
3
+ import { createInterface } from "readline"
4
+ // import SessionManager from '../managers/session'
5
+
6
+ class BaseCommand extends Command {
7
+ async catch(err: any) {
8
+ Console.debug("COMMAND CATCH", err)
9
+
10
+ throw err
11
+ }
12
+
13
+ async init() {
14
+ const { flags, args } = this.parse(BaseCommand)
15
+ Console.debug("COMMAND INIT")
16
+ Console.debug("These are your flags: ", flags)
17
+ Console.debug("These are your args: ", args)
18
+
19
+ // quick fix for listening to the process termination on windows
20
+ if (process.platform === "win32") {
21
+ const rl = createInterface({
22
+ input: process.stdin,
23
+ output: process.stdout,
24
+ })
25
+
26
+ rl.on("SIGINT", function () {
27
+ // process.emit('SIGINT')
28
+ // process.emit('SIGINT')
29
+ })
30
+ }
31
+
32
+ process.on("SIGINT", function () {
33
+ Console.debug("Terminated (SIGINT)")
34
+ process.exit()
35
+ })
36
+ }
37
+
38
+ async finally() {
39
+ Console.debug("COMMAND FINALLY")
40
+ // called after run and catch regardless of whether or not the command errored
41
+ }
42
+
43
+ static flags = {
44
+ yes: flags.boolean({
45
+ char: "y",
46
+ description: "Skip all prompts and initialize an empty project",
47
+ default: false,
48
+ }),
49
+ }
50
+
51
+ async run() {
52
+ // console.log('running my command')
53
+ }
54
+ }
55
+
56
+ export default BaseCommand
package/src/utils/api.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import Console from "../utils/console"
2
2
  import * as storage from "node-persist"
3
3
  import cli from "cli-ux"
4
+ import axios from "axios"
4
5
  const HOST = "https://breathecode.herokuapp.com"
5
6
  const RIGOBOT_HOST = "https://rigobot.herokuapp.com"
6
7
  // const RIGOBOT_HOST = "https://8000-charlytoc-rigobot-bmwdeam7cev.ws-us116.gitpod.io"
@@ -299,6 +300,47 @@ const sendStreamTelemetry = async function (url: string, body: object) {
299
300
  })
300
301
  }
301
302
 
303
+ type TConsumableSlug =
304
+ | "ai-conversation-message"
305
+ | "ai-compilation"
306
+ | "ai-tutorial-generation"
307
+
308
+ export const countConsumables = (
309
+ consumables: any,
310
+ consumableSlug: TConsumableSlug = "ai-tutorial-generation"
311
+ ) => {
312
+ // Find the void that matches the consumableSlug
313
+
314
+ const consumable = consumables.voids.find(
315
+ (voidItem: any) => voidItem.slug === consumableSlug
316
+ )
317
+
318
+ // Return the available units or 0 if not found
319
+ return consumable ? consumable.balance.unit : 0
320
+ }
321
+
322
+ export const getConsumables = async (token: string): Promise<any> => {
323
+ const url = `${HOST}/v1/payments/me/service/consumable?virtual=true`
324
+
325
+ const headers = {
326
+ Authorization: `Token ${token}`,
327
+ }
328
+
329
+ try {
330
+ const response = await axios.get(url, { headers })
331
+
332
+ const ai_tutorial_generation = countConsumables(
333
+ response.data,
334
+ "ai-tutorial-generation"
335
+ )
336
+
337
+ return { ai_tutorial_generation }
338
+ } catch (error) {
339
+ console.error("Error fetching consumables:", error)
340
+ throw error
341
+ }
342
+ }
343
+
302
344
  export default {
303
345
  login,
304
346
  publish,