@longtable/cli 0.1.30 → 0.1.32

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,130 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { createInterface } from "node:readline/promises";
3
+ import { stdin as input, stdout as output } from "node:process";
4
+ import { cancel, isCancel, multiselect, note, select, text } from "@clack/prompts";
5
+ function isInteractiveTerminal() {
6
+ return Boolean(input.isTTY && output.isTTY);
7
+ }
8
+ function guardCancel(value) {
9
+ if (isCancel(value)) {
10
+ cancel("LongTable cancelled.");
11
+ throw new Error("LongTable cancelled.");
12
+ }
13
+ return value;
14
+ }
15
+ function renderChoices(choices) {
16
+ return choices
17
+ .map((choice, index) => `${index + 1}. ${choice.label} - ${choice.description}`)
18
+ .join("\n");
19
+ }
20
+ let fallbackReadline;
21
+ let fallbackInputLines;
22
+ function getFallbackReadline() {
23
+ fallbackReadline ??= createInterface({ input, output });
24
+ return fallbackReadline;
25
+ }
26
+ async function askLine(prompt) {
27
+ if (!input.isTTY) {
28
+ output.write(prompt);
29
+ fallbackInputLines ??= readFileSync(0, "utf8").split(/\r?\n/);
30
+ return fallbackInputLines.shift() ?? "";
31
+ }
32
+ return getFallbackReadline().question(prompt);
33
+ }
34
+ async function promptChoiceByNumber(prompt, choices) {
35
+ while (true) {
36
+ const answer = await askLine(`${prompt}\n${renderChoices(choices)}\nSelect one number: `);
37
+ const numeric = Number(answer.trim());
38
+ if (!Number.isInteger(numeric) || numeric < 1 || numeric > choices.length) {
39
+ console.log("Invalid selection. Enter one of the listed numbers.");
40
+ continue;
41
+ }
42
+ const choice = choices[numeric - 1];
43
+ if (choice.fallbackToText) {
44
+ const freeText = await askLine("Type your custom value: ");
45
+ if (!freeText.trim()) {
46
+ console.log("Custom value cannot be empty.");
47
+ continue;
48
+ }
49
+ return freeText.trim();
50
+ }
51
+ return choice.id;
52
+ }
53
+ }
54
+ async function promptTextLine(prompt, required) {
55
+ while (true) {
56
+ const answer = (await askLine(`${prompt}\n> `)).trim();
57
+ if (!required) {
58
+ return answer || undefined;
59
+ }
60
+ if (answer) {
61
+ return answer;
62
+ }
63
+ console.log("This answer cannot be empty.");
64
+ }
65
+ }
66
+ export function createPromptRenderer() {
67
+ return {
68
+ async text(prompt, options) {
69
+ const required = options?.required ?? false;
70
+ if (!isInteractiveTerminal()) {
71
+ return promptTextLine(prompt, required);
72
+ }
73
+ return guardCancel(await text({
74
+ message: prompt,
75
+ placeholder: options?.placeholder,
76
+ validate: required
77
+ ? (value) => value?.trim() ? undefined : "This answer cannot be empty."
78
+ : undefined
79
+ }))?.trim() || undefined;
80
+ },
81
+ async select(prompt, choices) {
82
+ if (!isInteractiveTerminal()) {
83
+ return promptChoiceByNumber(prompt, choices);
84
+ }
85
+ const selected = guardCancel(await select({
86
+ message: prompt,
87
+ options: choices.map((choice) => ({
88
+ value: choice.id,
89
+ label: choice.label,
90
+ hint: choice.description
91
+ })),
92
+ maxItems: 7
93
+ }));
94
+ const choice = choices.find((candidate) => candidate.id === selected);
95
+ if (choice?.fallbackToText) {
96
+ return guardCancel(await text({
97
+ message: "Type your custom value",
98
+ validate: (value) => value?.trim() ? undefined : "This answer cannot be empty."
99
+ })).trim();
100
+ }
101
+ return selected;
102
+ },
103
+ async multiselect(prompt, choices) {
104
+ if (!isInteractiveTerminal()) {
105
+ const answer = await askLine(`${prompt}\nType comma-separated ids or leave blank for auto.\n> `);
106
+ return answer
107
+ .split(",")
108
+ .map((part) => part.trim())
109
+ .filter(Boolean);
110
+ }
111
+ return guardCancel(await multiselect({
112
+ message: prompt,
113
+ options: choices.map((choice) => ({
114
+ value: choice.id,
115
+ label: choice.label,
116
+ hint: choice.description
117
+ })),
118
+ required: false,
119
+ maxItems: 7
120
+ }));
121
+ },
122
+ note(message, title) {
123
+ if (isInteractiveTerminal()) {
124
+ note(message, title);
125
+ return;
126
+ }
127
+ console.log(title ? `${title}\n${message}` : message);
128
+ }
129
+ };
130
+ }
@@ -151,7 +151,7 @@ export async function discoverCrossrefTdm(doi, env = process.env, httpFetch = de
151
151
  const response = await httpFetch(url, {
152
152
  headers: {
153
153
  accept: "application/json",
154
- "user-agent": "LongTable/0.1.30 (https://github.com/HosungYou/LongTable)"
154
+ "user-agent": "LongTable/0.1.31 (https://github.com/HosungYou/LongTable)"
155
155
  }
156
156
  });
157
157
  if (!response.ok) {
@@ -155,7 +155,7 @@ async function fetchJson(context, url) {
155
155
  const response = await context.fetch(url, {
156
156
  headers: {
157
157
  "accept": "application/json",
158
- "user-agent": "LongTable/0.1.30 (https://github.com/HosungYou/LongTable)"
158
+ "user-agent": "LongTable/0.1.31 (https://github.com/HosungYou/LongTable)"
159
159
  }
160
160
  });
161
161
  if (!response.ok) {
@@ -167,7 +167,7 @@ async function fetchText(context, url) {
167
167
  const response = await context.fetch(url, {
168
168
  headers: {
169
169
  "accept": "application/xml, text/xml, application/atom+xml, text/plain",
170
- "user-agent": "LongTable/0.1.30 (https://github.com/HosungYou/LongTable)"
170
+ "user-agent": "LongTable/0.1.31 (https://github.com/HosungYou/LongTable)"
171
171
  }
172
172
  });
173
173
  if (!response.ok) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@longtable/cli",
3
- "version": "0.1.30",
3
+ "version": "0.1.32",
4
4
  "private": false,
5
5
  "description": "Researcher-facing LongTable CLI",
6
6
  "type": "module",
@@ -28,12 +28,13 @@
28
28
  "typecheck": "tsc -p tsconfig.json --noEmit"
29
29
  },
30
30
  "dependencies": {
31
- "@longtable/checkpoints": "0.1.30",
32
- "@longtable/core": "0.1.30",
33
- "@longtable/memory": "0.1.30",
34
- "@longtable/provider-claude": "0.1.30",
35
- "@longtable/provider-codex": "0.1.30",
36
- "@longtable/setup": "0.1.30"
31
+ "@clack/prompts": "^1.2.0",
32
+ "@longtable/checkpoints": "0.1.32",
33
+ "@longtable/core": "0.1.32",
34
+ "@longtable/memory": "0.1.32",
35
+ "@longtable/provider-claude": "0.1.32",
36
+ "@longtable/provider-codex": "0.1.32",
37
+ "@longtable/setup": "0.1.32"
37
38
  },
38
39
  "devDependencies": {
39
40
  "@types/node": "^22.10.1",