ivorleaf 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,87 @@
1
+ import { input } from "@inquirer/prompts";
2
+ import { IvorleafApi } from "../api.js";
3
+ import { renderHeader, renderResponse, success } from "../render.js";
4
+ import { clearActiveSession } from "../utils/activeSession.js";
5
+ const actions = [
6
+ ["Quiz", "quiz"], ["Flashcards", "flashcards"], ["Table", "table"],
7
+ ["Citation Builder", "citation_builder"], ["Educator Mode", "educator_mode"],
8
+ ["SOP / Procedure", "sop_mode"], ["Review Sheet", "review_sheet"],
9
+ ["Literature Expansion", "literature_expansion"], ["Source Comparison", "source_comparison"],
10
+ ["Recommend Next Step", "recommend"], ["Continue Learning Path", "continue"],
11
+ ];
12
+ export function shouldShowWorkflowMenu(options) {
13
+ return !options.json && !options.plain;
14
+ }
15
+ export function formatWorkflowMenu() {
16
+ return [
17
+ "Available Actions",
18
+ "",
19
+ ...actions.map(([label], index) => `[${index + 1}] ${label}`),
20
+ "[0] Exit",
21
+ ].join("\n");
22
+ }
23
+ export function formatWorkflowMenuHelp() {
24
+ return [
25
+ "Menu Commands",
26
+ "",
27
+ "clear Clear the active session and exit",
28
+ "exit Exit the menu",
29
+ "help Show this help",
30
+ "",
31
+ formatWorkflowMenu(),
32
+ ].join("\n");
33
+ }
34
+ export function parseWorkflowMenuInput(value) {
35
+ const normalized = value.trim().toLowerCase();
36
+ if (normalized === "0" || normalized === "exit")
37
+ return { type: "exit" };
38
+ if (normalized === "clear")
39
+ return { type: "clear" };
40
+ if (normalized === "help")
41
+ return { type: "help" };
42
+ const number = Number(normalized);
43
+ if (Number.isInteger(number) && number >= 1 && number <= actions.length) {
44
+ return { type: "action", action: actions[number - 1][1] };
45
+ }
46
+ return { type: "invalid", message: `Enter a number from 0 to ${actions.length}, or type clear, exit, or help.` };
47
+ }
48
+ export async function workflowMenu(sessionId, options) {
49
+ if (!shouldShowWorkflowMenu(options))
50
+ return;
51
+ console.log(`\n${formatWorkflowMenu()}\n`);
52
+ // Piped/non-interactive output can display the available actions but cannot prompt safely.
53
+ if (!process.stdin.isTTY)
54
+ return;
55
+ while (true) {
56
+ const choice = await input({
57
+ message: "Select:",
58
+ validate: value => {
59
+ const selection = parseWorkflowMenuInput(value);
60
+ return selection.type === "invalid" ? selection.message : true;
61
+ },
62
+ });
63
+ const selection = parseWorkflowMenuInput(choice);
64
+ if (selection.type === "exit")
65
+ return;
66
+ if (selection.type === "clear") {
67
+ await clearActiveSession();
68
+ console.log(success("Active session cleared"));
69
+ return;
70
+ }
71
+ if (selection.type === "help") {
72
+ console.log(`\n${formatWorkflowMenuHelp()}\n`);
73
+ continue;
74
+ }
75
+ if (selection.type !== "action")
76
+ continue;
77
+ const action = selection.action;
78
+ const api = await IvorleafApi.fromConfig();
79
+ const data = action === "recommend" || action === "continue"
80
+ ? await api.orchestrate(sessionId, action)
81
+ : await api.action(sessionId, action);
82
+ await renderHeader(action, { sessionId });
83
+ await renderResponse(data, options, { kind: "workflow", actionName: action, sessionId, command: action });
84
+ return;
85
+ }
86
+ }
87
+ //# sourceMappingURL=workflowMenu.js.map
package/dist/config.js ADDED
@@ -0,0 +1,33 @@
1
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
2
+ import { homedir } from "node:os";
3
+ import { dirname, join } from "node:path";
4
+ // This is the single configured default. Override it per environment with IVORLEAF_API_URL.
5
+ export const IVORLEAF_API_URL = "https://ivorleaf-api-745737209421.us-east1.run.app";
6
+ export const CONFIG_PATH = join(homedir(), ".ivorleaf", "config.json");
7
+ export function getApiUrl() {
8
+ return (process.env.IVORLEAF_API_URL || IVORLEAF_API_URL).replace(/\/$/, "");
9
+ }
10
+ export async function loadConfig() {
11
+ try {
12
+ return JSON.parse(await readFile(CONFIG_PATH, "utf8"));
13
+ }
14
+ catch (error) {
15
+ const code = error.code;
16
+ if (code === "ENOENT" || error instanceof SyntaxError)
17
+ return null;
18
+ throw error;
19
+ }
20
+ }
21
+ export async function saveConfig(config) {
22
+ await mkdir(dirname(CONFIG_PATH), { recursive: true, mode: 0o700 });
23
+ await writeFile(CONFIG_PATH, `${JSON.stringify(config, null, 2)}\n`, { mode: 0o600 });
24
+ }
25
+ export async function getApiKey() {
26
+ if (process.env.IVORLEAF_API_KEY?.trim())
27
+ return process.env.IVORLEAF_API_KEY.trim();
28
+ return (await loadConfig())?.apiKey?.trim() || null;
29
+ }
30
+ export function redactSecrets(value) {
31
+ return value.replace(/(Bearer\s+)[^\s]+/gi, "$1[REDACTED]");
32
+ }
33
+ //# sourceMappingURL=config.js.map
package/dist/index.js ADDED
@@ -0,0 +1,88 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from "commander";
3
+ import { actionCommand } from "./commands/action.js";
4
+ import { clearCommand } from "./commands/clear.js";
5
+ import { doctorCommand } from "./commands/doctor.js";
6
+ import { exploreCommand } from "./commands/explore.js";
7
+ import { historyCommand } from "./commands/history.js";
8
+ import { initCommand } from "./commands/init.js";
9
+ import { limitsCommand } from "./commands/limits.js";
10
+ import { openCommand } from "./commands/open.js";
11
+ import { orchestrateCommand } from "./commands/orchestrate.js";
12
+ import { sourcesCommand } from "./commands/sources.js";
13
+ import { statusCommand } from "./commands/status.js";
14
+ import { joinPromptParts } from "./utils/prompt.js";
15
+ const program = new Command();
16
+ const output = () => {
17
+ // The output flags belong to the root program. Reading them here works across
18
+ // Commander versions and avoids relying on subcommand-specific option APIs.
19
+ const options = program.opts();
20
+ if (options.json && options.plain)
21
+ throw new Error("Use either --json or --plain, not both.");
22
+ return {
23
+ ...options,
24
+ pager: process.argv.includes("--pager") || options.pager,
25
+ noPager: process.argv.includes("--no-pager"),
26
+ };
27
+ };
28
+ program
29
+ .name("ivorleaf")
30
+ .description("Terminal client for the Ivorleaf API")
31
+ .version("1.1.0")
32
+ .option("--json", "print the raw JSON API response")
33
+ .option("--plain", "print unstyled Markdown")
34
+ .option("--sources", "include full source details when available")
35
+ .option("--debug-response", "print non-secret response-shape diagnostics")
36
+ .option("--pager", "force paged output")
37
+ .option("--no-pager", "disable paged output");
38
+ program.command("init").description("configure and validate an API key").action(initCommand);
39
+ program.command("doctor").description("diagnose configuration and connectivity").action(doctorCommand);
40
+ program.command("limits").description("show account usage and limits").action(async () => limitsCommand(output()));
41
+ program.command("clear").description("clear the active session reference").action(clearCommand);
42
+ program.command("clear-session").description("clear the active session reference").action(clearCommand);
43
+ program.command("exit").description("clear the active session reference").action(clearCommand);
44
+ program.command("status").description("show the active session").action(statusCommand);
45
+ program
46
+ .command("explore <prompt...>")
47
+ .description("create a lesson from a prompt")
48
+ .option("--show-sources", "include full source details for this explore response")
49
+ .option("--debug-response", "print non-secret response-shape diagnostics")
50
+ .action(async (promptParts, commandOptions) => {
51
+ const options = output();
52
+ await exploreCommand(joinPromptParts(promptParts), {
53
+ ...options,
54
+ sources: options.sources || commandOptions.showSources,
55
+ debugResponse: options.debugResponse || commandOptions.debugResponse,
56
+ });
57
+ });
58
+ program.command("history").description("list recent sessions").action(async () => historyCommand(output()));
59
+ program.command("open <session_id>").description("open a saved session").action(async (id) => openCommand(id, output()));
60
+ program.command("sources").description("show sources from the most recent explore/open response").action(async () => sourcesCommand(output()));
61
+ program.command("action <action_name> [session_id]").description("run a workflow action").action(async (name, id) => actionCommand(name, id, output()));
62
+ program.command("quiz [session_id]").description("generate a quiz for the active session").action(async (id) => actionCommand("quiz", id, output()));
63
+ program.command("flashcards [session_id]").description("generate flashcards for the active session").action(async (id) => actionCommand("flashcards", id, output()));
64
+ program.command("citations [session_id]").description("build citations for the active session").action(async (id) => actionCommand("citation_builder", id, output()));
65
+ program.command("compare [session_id]").description("compare saved sources for the active session").action(async (id) => actionCommand("source_comparison", id, output()));
66
+ program.command("recommend [session_id]").description("recommend the next learning step").action(async (id) => orchestrateCommand("recommend", id, output()));
67
+ program.command("continue [session_id]").description("continue the learning path").action(async (id) => orchestrateCommand("continue", id, output()));
68
+ program.addHelpText("after", `
69
+ Examples:
70
+ ivorleaf init
71
+ ivorleaf explore Teach me photosynthesis
72
+ ivorleaf quiz
73
+ ivorleaf citations
74
+ ivorleaf compare
75
+ ivorleaf status
76
+ ivorleaf exit
77
+ ivorleaf sources
78
+ ivorleaf history --json
79
+ ivorleaf open SESSION_ID --plain`);
80
+ try {
81
+ await program.parseAsync(process.argv);
82
+ }
83
+ catch (error) {
84
+ const message = error instanceof Error ? error.message : String(error);
85
+ console.error(`Error: ${message}`);
86
+ process.exitCode = 1;
87
+ }
88
+ //# sourceMappingURL=index.js.map