@longtable/setup 0.1.9
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/CHANGELOG.md +30 -0
- package/README.md +46 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +275 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/numbered-checkpoint.d.ts +3 -0
- package/dist/numbered-checkpoint.js +42 -0
- package/dist/onboarding.d.ts +11 -0
- package/dist/onboarding.js +212 -0
- package/dist/persistence.d.ts +21 -0
- package/dist/persistence.js +178 -0
- package/dist/types.d.ts +73 -0
- package/dist/types.js +1 -0
- package/examples/claude-setup-output.json +27 -0
- package/examples/codex-setup-output.json +27 -0
- package/package.json +54 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.4
|
|
4
|
+
|
|
5
|
+
- treat global setup as researcher-profile onboarding, not project intake
|
|
6
|
+
- remove project-specific defaults from setup examples and persisted summaries
|
|
7
|
+
- align setup output with the new `longtable start` project interview flow
|
|
8
|
+
|
|
9
|
+
## 0.1.3
|
|
10
|
+
|
|
11
|
+
- make setup prompts read more like a short researcher interview and less like a raw config form
|
|
12
|
+
|
|
13
|
+
## 0.1.2
|
|
14
|
+
|
|
15
|
+
- add `quickstart` and `interview` setup flows
|
|
16
|
+
- persist topic, blocker, preferred entry mode, weakest domain, and panel preference
|
|
17
|
+
- enrich runtime artifacts so setup can connect directly to the first research question
|
|
18
|
+
|
|
19
|
+
## 0.1.1
|
|
20
|
+
|
|
21
|
+
- switch CLI packaging to `directories.bin` so npm preserves the published executable
|
|
22
|
+
- keep the executable name as `longtable-setup`
|
|
23
|
+
|
|
24
|
+
## 0.1.0
|
|
25
|
+
|
|
26
|
+
- initial publish-ready setup package scaffold
|
|
27
|
+
- quick setup flow and provider selection
|
|
28
|
+
- persisted setup output generation
|
|
29
|
+
- CLI entrypoint for setup initialization
|
|
30
|
+
- example setup outputs for Codex and Claude
|
package/README.md
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# @longtable/setup
|
|
2
|
+
|
|
3
|
+
Researcher onboarding and setup flows for LongTable.
|
|
4
|
+
|
|
5
|
+
## Recommended Usage
|
|
6
|
+
|
|
7
|
+
Researchers should use the unified CLI:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g @longtable/cli
|
|
11
|
+
longtable init --flow interview
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
This package exists so the setup flow can also be consumed programmatically or tested in isolation during development.
|
|
15
|
+
|
|
16
|
+
By default this writes:
|
|
17
|
+
|
|
18
|
+
- setup output to `~/.longtable/setup.json`
|
|
19
|
+
- Codex runtime config to `~/.longtable/runtime/codex/longtable.toml`
|
|
20
|
+
- Claude runtime config to `~/.longtable/runtime/claude/longtable.json`
|
|
21
|
+
|
|
22
|
+
The generated runtime config does not overwrite platform-native config files directly. It creates LongTable-managed runtime artifacts that can later be wired into provider-specific runtimes during migration.
|
|
23
|
+
|
|
24
|
+
## Package Role
|
|
25
|
+
|
|
26
|
+
The setup contract combines technical setup with researcher-profile calibration rather than treating installation as a purely technical step.
|
|
27
|
+
|
|
28
|
+
Global setup should answer:
|
|
29
|
+
|
|
30
|
+
- who the researcher is
|
|
31
|
+
- how much challenge or slowdown they want
|
|
32
|
+
- what makes writing still feel like theirs
|
|
33
|
+
- how visible disagreement should be by default
|
|
34
|
+
|
|
35
|
+
Project and session intake belongs to `longtable start`, not library-level setup helpers.
|
|
36
|
+
|
|
37
|
+
## Included Outputs
|
|
38
|
+
|
|
39
|
+
- quick setup question flow
|
|
40
|
+
- provider selection resolution
|
|
41
|
+
- persisted setup output generator
|
|
42
|
+
- saved setup output helpers
|
|
43
|
+
- runtime config installer helpers
|
|
44
|
+
- numbered checkpoint helpers
|
|
45
|
+
|
|
46
|
+
See `examples/` for sample Codex and Claude setup outputs.
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import { createInterface } from "node:readline/promises";
|
|
2
|
+
import { stdin as input, stdout as output } from "node:process";
|
|
3
|
+
import { buildProviderChoices, buildQuickSetupFlow, createPersistedSetupOutput } from "./onboarding.js";
|
|
4
|
+
import { installRuntimeConfigFromStoredSetup, loadSetupOutput, renderInstallSummary, renderSetupSummary, resolveDefaultSetupPath, saveSetupAndRuntimeConfig, saveSetupOutput, serializeSetupOutput } from "./persistence.js";
|
|
5
|
+
function printUsage() {
|
|
6
|
+
console.log(`Usage:
|
|
7
|
+
longtable-setup init [--flow quickstart|interview] --provider <codex|claude> --field <field> --career-stage <stage> --experience <novice|intermediate|advanced> --checkpoint <low|balanced|high> [--authorship-signal <text>] [--entry-mode <explore|review|critique|draft|commit>] [--weakest-domain <theory|methodology|measurement|analysis|writing>] [--panel-preference <synthesis_only|show_on_conflict|always_visible>] [--json]
|
|
8
|
+
longtable-setup install [--path <file>] [--runtime-path <file>] [--json]
|
|
9
|
+
longtable-setup show [--path <file>] [--json]
|
|
10
|
+
|
|
11
|
+
Example:
|
|
12
|
+
longtable-setup init --flow interview --provider codex --field education --career-stage doctoral --experience intermediate --checkpoint balanced --entry-mode explore --panel-preference show_on_conflict --json
|
|
13
|
+
|
|
14
|
+
If required flags are omitted, interactive setup starts automatically.
|
|
15
|
+
Use --write to save setup.json and --install to also generate provider runtime config.`);
|
|
16
|
+
}
|
|
17
|
+
function parseArgs(argv) {
|
|
18
|
+
const parsed = {};
|
|
19
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
20
|
+
const token = argv[index];
|
|
21
|
+
if (!token.startsWith("--")) {
|
|
22
|
+
parsed._command = token;
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
const key = token.slice(2);
|
|
26
|
+
const next = argv[index + 1];
|
|
27
|
+
if (!next || next.startsWith("--")) {
|
|
28
|
+
parsed[key] = true;
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
parsed[key] = next;
|
|
32
|
+
index += 1;
|
|
33
|
+
}
|
|
34
|
+
return parsed;
|
|
35
|
+
}
|
|
36
|
+
function requireString(args, key) {
|
|
37
|
+
const value = args[key];
|
|
38
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
39
|
+
throw new Error(`Missing required argument: --${key}`);
|
|
40
|
+
}
|
|
41
|
+
return value;
|
|
42
|
+
}
|
|
43
|
+
function toSetupAnswers(args) {
|
|
44
|
+
return {
|
|
45
|
+
field: requireString(args, "field"),
|
|
46
|
+
careerStage: requireString(args, "career-stage"),
|
|
47
|
+
experienceLevel: requireString(args, "experience"),
|
|
48
|
+
preferredCheckpointIntensity: requireString(args, "checkpoint"),
|
|
49
|
+
humanAuthorshipSignal: typeof args["authorship-signal"] === "string" && args["authorship-signal"].trim().length > 0
|
|
50
|
+
? args["authorship-signal"].trim()
|
|
51
|
+
: undefined,
|
|
52
|
+
preferredEntryMode: typeof args["entry-mode"] === "string" && args["entry-mode"].trim().length > 0
|
|
53
|
+
? args["entry-mode"].trim()
|
|
54
|
+
: undefined,
|
|
55
|
+
weakestDomain: typeof args["weakest-domain"] === "string" && args["weakest-domain"].trim().length > 0
|
|
56
|
+
? args["weakest-domain"].trim()
|
|
57
|
+
: undefined,
|
|
58
|
+
panelPreference: typeof args["panel-preference"] === "string" && args["panel-preference"].trim().length > 0
|
|
59
|
+
? args["panel-preference"].trim()
|
|
60
|
+
: undefined
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
function hasCompleteFlagInput(args) {
|
|
64
|
+
const requiredKeys = [
|
|
65
|
+
"provider",
|
|
66
|
+
"field",
|
|
67
|
+
"career-stage",
|
|
68
|
+
"experience",
|
|
69
|
+
"checkpoint"
|
|
70
|
+
];
|
|
71
|
+
return requiredKeys.every((key) => typeof args[key] === "string" && String(args[key]).trim().length > 0);
|
|
72
|
+
}
|
|
73
|
+
function resolveSetupFlow(args) {
|
|
74
|
+
return args.flow === "interview" ? "interview" : "quickstart";
|
|
75
|
+
}
|
|
76
|
+
function renderChoices(choices) {
|
|
77
|
+
return choices
|
|
78
|
+
.map((choice, index) => `${index + 1}. ${choice.label} — ${choice.description}`)
|
|
79
|
+
.join("\n");
|
|
80
|
+
}
|
|
81
|
+
async function readPipedLines() {
|
|
82
|
+
const chunks = [];
|
|
83
|
+
for await (const chunk of input) {
|
|
84
|
+
chunks.push(String(chunk));
|
|
85
|
+
}
|
|
86
|
+
return chunks
|
|
87
|
+
.join("")
|
|
88
|
+
.split(/\r?\n/);
|
|
89
|
+
}
|
|
90
|
+
async function promptChoice(rl, prompt, choices) {
|
|
91
|
+
while (true) {
|
|
92
|
+
const answer = await rl.question(`${prompt}\n${renderChoices(choices)}\nSelect one number: `);
|
|
93
|
+
const numeric = Number(answer.trim());
|
|
94
|
+
if (!Number.isInteger(numeric) || numeric < 1 || numeric > choices.length) {
|
|
95
|
+
console.log("Invalid selection. Enter one of the listed numbers.");
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
const choice = choices[numeric - 1];
|
|
99
|
+
if (choice.fallbackToText) {
|
|
100
|
+
const freeText = await rl.question("Type your custom value: ");
|
|
101
|
+
if (freeText.trim().length === 0) {
|
|
102
|
+
console.log("Custom value cannot be empty.");
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
return freeText.trim();
|
|
106
|
+
}
|
|
107
|
+
return choice.id;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
function promptChoiceFromLines(prompt, choices, lines, state) {
|
|
111
|
+
while (true) {
|
|
112
|
+
console.log(`${prompt}\n${renderChoices(choices)}\nSelect one number: `);
|
|
113
|
+
const answer = lines[state.index] ?? "";
|
|
114
|
+
state.index += 1;
|
|
115
|
+
const numeric = Number(answer.trim());
|
|
116
|
+
if (!Number.isInteger(numeric) || numeric < 1 || numeric > choices.length) {
|
|
117
|
+
console.log("Invalid selection. Enter one of the listed numbers.");
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
const choice = choices[numeric - 1];
|
|
121
|
+
if (choice.fallbackToText) {
|
|
122
|
+
console.log("Type your custom value: ");
|
|
123
|
+
const freeText = (lines[state.index] ?? "").trim();
|
|
124
|
+
state.index += 1;
|
|
125
|
+
if (freeText.length === 0) {
|
|
126
|
+
console.log("Custom value cannot be empty.");
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
return freeText;
|
|
130
|
+
}
|
|
131
|
+
return choice.id;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
function printInitResult(outputValue, options, installResult) {
|
|
135
|
+
if (options.writeOutput && installResult) {
|
|
136
|
+
console.error(`Saved setup output to ${installResult.setupTarget.path}`);
|
|
137
|
+
console.error(`Installed runtime config to ${installResult.runtimeTarget.path}`);
|
|
138
|
+
}
|
|
139
|
+
else if (options.writeOutput) {
|
|
140
|
+
const target = resolveDefaultSetupPath(options.customPath);
|
|
141
|
+
console.error(`Saved setup output to ${target.path}`);
|
|
142
|
+
}
|
|
143
|
+
if (options.jsonOutput) {
|
|
144
|
+
console.log(serializeSetupOutput(outputValue));
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
const summary = [renderSetupSummary(outputValue)];
|
|
148
|
+
if (installResult) {
|
|
149
|
+
summary.push("");
|
|
150
|
+
summary.push(renderInstallSummary(installResult));
|
|
151
|
+
}
|
|
152
|
+
console.log(summary.join("\n"));
|
|
153
|
+
}
|
|
154
|
+
async function persistInitResult(outputValue, options) {
|
|
155
|
+
let installResult;
|
|
156
|
+
if (options.installRuntime) {
|
|
157
|
+
installResult = await saveSetupAndRuntimeConfig(outputValue, {
|
|
158
|
+
setupPath: options.customPath,
|
|
159
|
+
runtimePath: options.runtimePath
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
else if (options.writeOutput) {
|
|
163
|
+
await saveSetupOutput(outputValue, options.customPath);
|
|
164
|
+
}
|
|
165
|
+
printInitResult(outputValue, options, installResult);
|
|
166
|
+
}
|
|
167
|
+
async function runNonInteractiveInputSetup(options, flow) {
|
|
168
|
+
const lines = await readPipedLines();
|
|
169
|
+
const state = { index: 0 };
|
|
170
|
+
const provider = promptChoiceFromLines("Which provider do you want to configure?", buildProviderChoices(), lines, state);
|
|
171
|
+
const answers = {};
|
|
172
|
+
for (const question of buildQuickSetupFlow(flow)) {
|
|
173
|
+
if (!question.choices) {
|
|
174
|
+
throw new Error(`Question ${question.id} requires choices in non-interactive mode.`);
|
|
175
|
+
}
|
|
176
|
+
const value = promptChoiceFromLines(question.prompt, question.choices, lines, state);
|
|
177
|
+
answers[question.id] = value;
|
|
178
|
+
}
|
|
179
|
+
const outputValue = createPersistedSetupOutput(answers, provider, flow);
|
|
180
|
+
await persistInitResult(outputValue, options);
|
|
181
|
+
}
|
|
182
|
+
async function runInteractiveSetup(options, flow) {
|
|
183
|
+
if (!input.isTTY) {
|
|
184
|
+
await runNonInteractiveInputSetup(options, flow);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
const rl = createInterface({ input, output });
|
|
188
|
+
try {
|
|
189
|
+
const provider = await promptChoice(rl, "Which provider do you want to configure?", buildProviderChoices());
|
|
190
|
+
const answers = {};
|
|
191
|
+
for (const question of buildQuickSetupFlow(flow)) {
|
|
192
|
+
if (question.kind !== "single_choice" || !question.choices) {
|
|
193
|
+
const response = await rl.question(`${question.prompt}\n> `);
|
|
194
|
+
if (response.trim().length === 0) {
|
|
195
|
+
console.log("This field cannot be empty.");
|
|
196
|
+
return await runInteractiveSetup(options, flow);
|
|
197
|
+
}
|
|
198
|
+
answers[question.id] = response.trim();
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
const value = await promptChoice(rl, question.prompt, question.choices);
|
|
202
|
+
answers[question.id] = value;
|
|
203
|
+
}
|
|
204
|
+
const outputValue = createPersistedSetupOutput(answers, provider, flow);
|
|
205
|
+
await persistInitResult(outputValue, options);
|
|
206
|
+
}
|
|
207
|
+
finally {
|
|
208
|
+
rl.close();
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
async function printStoredSetup(customPath, jsonOutput) {
|
|
212
|
+
const setup = await loadSetupOutput(customPath);
|
|
213
|
+
console.log(jsonOutput ? serializeSetupOutput(setup) : renderSetupSummary(setup));
|
|
214
|
+
}
|
|
215
|
+
async function installStoredSetup(customPath, runtimePath, jsonOutput) {
|
|
216
|
+
const result = await installRuntimeConfigFromStoredSetup({
|
|
217
|
+
setupPath: customPath,
|
|
218
|
+
runtimePath
|
|
219
|
+
});
|
|
220
|
+
if (jsonOutput) {
|
|
221
|
+
console.log(JSON.stringify(result, null, 2));
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
console.log(renderInstallSummary(result));
|
|
225
|
+
}
|
|
226
|
+
async function main() {
|
|
227
|
+
const args = parseArgs(process.argv.slice(2));
|
|
228
|
+
if (!args._command || args._command === "help" || args._command === "--help") {
|
|
229
|
+
printUsage();
|
|
230
|
+
process.exit(0);
|
|
231
|
+
}
|
|
232
|
+
if (args._command === "show") {
|
|
233
|
+
await printStoredSetup(typeof args.path === "string" ? args.path : undefined, args.json === true);
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
if (args._command === "install") {
|
|
237
|
+
await installStoredSetup(typeof args.path === "string" ? args.path : undefined, typeof args["runtime-path"] === "string" ? args["runtime-path"] : undefined, args.json === true);
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
if (args._command !== "init") {
|
|
241
|
+
printUsage();
|
|
242
|
+
throw new Error(`Unknown command: ${String(args._command)}`);
|
|
243
|
+
}
|
|
244
|
+
if (hasCompleteFlagInput(args)) {
|
|
245
|
+
const provider = requireString(args, "provider");
|
|
246
|
+
const outputValue = createPersistedSetupOutput(toSetupAnswers(args), provider, resolveSetupFlow(args));
|
|
247
|
+
const shouldWrite = args.write === true;
|
|
248
|
+
const shouldInstall = args.install === true;
|
|
249
|
+
const customPath = typeof args.path === "string" ? args.path : undefined;
|
|
250
|
+
const runtimePath = typeof args["runtime-path"] === "string"
|
|
251
|
+
? args["runtime-path"]
|
|
252
|
+
: undefined;
|
|
253
|
+
await persistInitResult(outputValue, {
|
|
254
|
+
jsonOutput: args.json === true,
|
|
255
|
+
writeOutput: shouldWrite || shouldInstall,
|
|
256
|
+
customPath,
|
|
257
|
+
installRuntime: shouldInstall,
|
|
258
|
+
runtimePath
|
|
259
|
+
});
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
await runInteractiveSetup({
|
|
263
|
+
jsonOutput: args.json === true,
|
|
264
|
+
writeOutput: args.write === true || args.install === true,
|
|
265
|
+
customPath: typeof args.path === "string" ? args.path : undefined,
|
|
266
|
+
installRuntime: args.install === true,
|
|
267
|
+
runtimePath: typeof args["runtime-path"] === "string"
|
|
268
|
+
? args["runtime-path"]
|
|
269
|
+
: undefined
|
|
270
|
+
}, resolveSetupFlow(args));
|
|
271
|
+
}
|
|
272
|
+
main().catch((error) => {
|
|
273
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
274
|
+
process.exit(1);
|
|
275
|
+
});
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { NumberedCheckpointSpec, ParsedCheckpointSelection } from "./types.js";
|
|
2
|
+
export declare function buildNumberedCheckpointPrompt(spec: NumberedCheckpointSpec): string;
|
|
3
|
+
export declare function parseNumberedCheckpointResponse(spec: NumberedCheckpointSpec, input: string): ParsedCheckpointSelection | null;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export function buildNumberedCheckpointPrompt(spec) {
|
|
2
|
+
const lines = [`CHECKPOINT: ${spec.title}`, ""];
|
|
3
|
+
if (spec.instructions) {
|
|
4
|
+
lines.push(spec.instructions, "");
|
|
5
|
+
}
|
|
6
|
+
spec.options.forEach((option, index) => {
|
|
7
|
+
lines.push(`${index + 1}. ${option.label}`);
|
|
8
|
+
});
|
|
9
|
+
lines.push("");
|
|
10
|
+
if (spec.allowRationale) {
|
|
11
|
+
lines.push("Reply with one number only, or one number followed by a short rationale on the next line.");
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
lines.push(`Reply with one number only: ${spec.options.map((_, index) => index + 1).join(", ")}.`);
|
|
15
|
+
}
|
|
16
|
+
return lines.join("\n");
|
|
17
|
+
}
|
|
18
|
+
export function parseNumberedCheckpointResponse(spec, input) {
|
|
19
|
+
const trimmed = input.trim();
|
|
20
|
+
if (trimmed.length === 0) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
const [firstLine, ...restLines] = trimmed.split(/\r?\n/);
|
|
24
|
+
if (!/^\d+$/.test(firstLine.trim())) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
const index = Number(firstLine.trim()) - 1;
|
|
28
|
+
const option = spec.options[index];
|
|
29
|
+
if (!option) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
const rationale = restLines.join("\n").trim();
|
|
33
|
+
if (!spec.allowRationale && rationale.length > 0) {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
return {
|
|
37
|
+
index,
|
|
38
|
+
value: option.value,
|
|
39
|
+
label: option.label,
|
|
40
|
+
rationale: rationale.length > 0 ? rationale : undefined
|
|
41
|
+
};
|
|
42
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ProviderKind, SetupChoice, SetupFlow, ProviderSelection, ResearcherProfileSeed, SetupAnswers, SetupPersistedOutput, SetupQuestion } from "./types.js";
|
|
2
|
+
export declare function buildQuickSetupFlow(flow?: SetupFlow): SetupQuestion[];
|
|
3
|
+
export declare function buildProviderChoices(): SetupChoice[];
|
|
4
|
+
export declare function buildFieldChoices(): SetupChoice[];
|
|
5
|
+
export declare function buildCareerStageChoices(): SetupChoice[];
|
|
6
|
+
export declare function buildProjectTypeChoices(): SetupChoice[];
|
|
7
|
+
export declare function isFallbackChoice(choice: SetupChoice | undefined): boolean;
|
|
8
|
+
export declare function normalizeProviderChoice(choice: string): ProviderKind;
|
|
9
|
+
export declare function createResearcherProfileSeed(answers: SetupAnswers): ResearcherProfileSeed;
|
|
10
|
+
export declare function resolveProviderSelection(provider: ProviderSelection["provider"]): ProviderSelection;
|
|
11
|
+
export declare function createPersistedSetupOutput(answers: SetupAnswers, provider: ProviderSelection["provider"], flow?: SetupFlow): SetupPersistedOutput;
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { createEmptyResearchState } from "@longtable/memory";
|
|
2
|
+
export function buildQuickSetupFlow(flow = "quickstart") {
|
|
3
|
+
const baseQuestions = [
|
|
4
|
+
{
|
|
5
|
+
id: "field",
|
|
6
|
+
prompt: "Before we begin, which research field best matches your work right now?",
|
|
7
|
+
required: true,
|
|
8
|
+
kind: "single_choice",
|
|
9
|
+
choices: buildFieldChoices()
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
id: "careerStage",
|
|
13
|
+
prompt: "What kind of researcher role best fits you today?",
|
|
14
|
+
required: true,
|
|
15
|
+
kind: "single_choice",
|
|
16
|
+
choices: buildCareerStageChoices()
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
id: "experienceLevel",
|
|
20
|
+
prompt: "How comfortable are you making independent research design decisions right now?",
|
|
21
|
+
required: true,
|
|
22
|
+
kind: "single_choice",
|
|
23
|
+
choices: [
|
|
24
|
+
{ id: "novice", label: "Novice", description: "Needs stronger checkpoint guidance." },
|
|
25
|
+
{ id: "intermediate", label: "Intermediate", description: "Wants balanced guidance." },
|
|
26
|
+
{ id: "advanced", label: "Advanced", description: "Prefers lighter intervention." }
|
|
27
|
+
]
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
id: "preferredCheckpointIntensity",
|
|
31
|
+
prompt: "How strongly should LongTable challenge or slow you down by default?",
|
|
32
|
+
required: true,
|
|
33
|
+
kind: "single_choice",
|
|
34
|
+
choices: [
|
|
35
|
+
{ id: "low", label: "Low", description: "Mostly lightweight logging and advisory prompts." },
|
|
36
|
+
{ id: "balanced", label: "Balanced", description: "Recommended by default." },
|
|
37
|
+
{ id: "high", label: "High", description: "Frequent structured commitment prompts." }
|
|
38
|
+
]
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
id: "humanAuthorshipSignal",
|
|
42
|
+
prompt: "When a piece of writing still feels like yours, what usually makes that true?",
|
|
43
|
+
required: false,
|
|
44
|
+
kind: "single_choice",
|
|
45
|
+
choices: [
|
|
46
|
+
{ id: "personal experience in the narrative", label: "Personal experience", description: "Humanity appears through lived experience and situated voice." },
|
|
47
|
+
{ id: "visible judgment and reasoning path", label: "Judgment trail", description: "Humanity appears through explicit choices and reasoning history." },
|
|
48
|
+
{ id: "contextual nuance that avoids generic fluency", label: "Contextual nuance", description: "Humanity appears through specificity and non-generic framing." },
|
|
49
|
+
{ id: "a distinctive voice that still sounds like the researcher", label: "Distinctive voice", description: "Humanity appears through authorial tone and cadence." },
|
|
50
|
+
{ id: "other", label: "None of the above", description: "Enter a custom authorship signal.", fallbackToText: true }
|
|
51
|
+
]
|
|
52
|
+
}
|
|
53
|
+
];
|
|
54
|
+
if (flow === "quickstart") {
|
|
55
|
+
return baseQuestions;
|
|
56
|
+
}
|
|
57
|
+
return [
|
|
58
|
+
...baseQuestions,
|
|
59
|
+
{
|
|
60
|
+
id: "preferredEntryMode",
|
|
61
|
+
prompt: "When you first open LongTable, where do you want it to begin?",
|
|
62
|
+
required: false,
|
|
63
|
+
kind: "single_choice",
|
|
64
|
+
choices: [
|
|
65
|
+
{ id: "explore", label: "Explore", description: "Open the problem, surface tensions, and ask better questions." },
|
|
66
|
+
{ id: "review", label: "Review", description: "Critically inspect a claim, plan, or draft." },
|
|
67
|
+
{ id: "critique", label: "Critique", description: "Stress-test weak assumptions and generate counterarguments." },
|
|
68
|
+
{ id: "draft", label: "Draft", description: "Write while preserving narrative trace and authorship." },
|
|
69
|
+
{ id: "commit", label: "Commit", description: "Slow down and make an explicit research decision." }
|
|
70
|
+
]
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
id: "weakestDomain",
|
|
74
|
+
prompt: "Where do you most want LongTable to push back on you first?",
|
|
75
|
+
required: false,
|
|
76
|
+
kind: "single_choice",
|
|
77
|
+
choices: [
|
|
78
|
+
{ id: "theory", label: "Theory", description: "Sharpen framing, constructs, and theoretical defensibility." },
|
|
79
|
+
{ id: "methodology", label: "Methodology", description: "Question design fit, sampling, and study structure." },
|
|
80
|
+
{ id: "measurement", label: "Measurement", description: "Challenge scales, validity, and operationalization." },
|
|
81
|
+
{ id: "analysis", label: "Analysis", description: "Probe analytic logic, interpretation, and evidence claims." },
|
|
82
|
+
{ id: "writing", label: "Writing", description: "Preserve voice while improving clarity and argument flow." }
|
|
83
|
+
]
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
id: "panelPreference",
|
|
87
|
+
prompt: "How visible should disagreement between roles be by default?",
|
|
88
|
+
required: false,
|
|
89
|
+
kind: "single_choice",
|
|
90
|
+
choices: [
|
|
91
|
+
{ id: "synthesis_only", label: "Synthesis only", description: "Show one LongTable answer unless I ask for more." },
|
|
92
|
+
{ id: "show_on_conflict", label: "Show on conflict", description: "Surface panel disagreement when roles materially diverge." },
|
|
93
|
+
{ id: "always_visible", label: "Always visible", description: "Keep panel opinions visible by default." }
|
|
94
|
+
]
|
|
95
|
+
}
|
|
96
|
+
];
|
|
97
|
+
}
|
|
98
|
+
export function buildProviderChoices() {
|
|
99
|
+
return [
|
|
100
|
+
{
|
|
101
|
+
id: "codex",
|
|
102
|
+
label: "Codex",
|
|
103
|
+
description: "Use numbered checkpoints and Codex runtime surfaces."
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
id: "claude",
|
|
107
|
+
label: "Claude",
|
|
108
|
+
description: "Use structured-question capable Claude runtime surfaces."
|
|
109
|
+
}
|
|
110
|
+
];
|
|
111
|
+
}
|
|
112
|
+
export function buildFieldChoices() {
|
|
113
|
+
return [
|
|
114
|
+
{ id: "education", label: "Education", description: "Learning, instruction, or educational technology." },
|
|
115
|
+
{ id: "psychology", label: "Psychology", description: "Behavior, cognition, or social psychology." },
|
|
116
|
+
{ id: "hrd", label: "HRD", description: "Human resource development and workplace learning." },
|
|
117
|
+
{ id: "management", label: "Management", description: "Organizations, strategy, or business research." },
|
|
118
|
+
{ id: "other", label: "None of the above", description: "Enter a custom field.", fallbackToText: true }
|
|
119
|
+
];
|
|
120
|
+
}
|
|
121
|
+
export function buildCareerStageChoices() {
|
|
122
|
+
return [
|
|
123
|
+
{ id: "doctoral", label: "Doctoral student", description: "Currently in doctoral training." },
|
|
124
|
+
{ id: "postdoctoral", label: "Postdoctoral researcher", description: "Research-focused early career stage." },
|
|
125
|
+
{ id: "faculty", label: "Faculty", description: "Professor or lecturer role." },
|
|
126
|
+
{ id: "industry", label: "Industry researcher", description: "Research outside a university role." },
|
|
127
|
+
{ id: "other", label: "None of the above", description: "Enter a custom career stage.", fallbackToText: true }
|
|
128
|
+
];
|
|
129
|
+
}
|
|
130
|
+
export function buildProjectTypeChoices() {
|
|
131
|
+
return [
|
|
132
|
+
{ id: "journal article", label: "Journal article", description: "A paper targeted at an academic journal." },
|
|
133
|
+
{ id: "conference paper", label: "Conference paper", description: "A paper targeted at a conference venue." },
|
|
134
|
+
{ id: "proposal", label: "Proposal", description: "A grant, dissertation, or research proposal." },
|
|
135
|
+
{ id: "mixed-methods study", label: "Mixed-methods study", description: "A study design spanning multiple methods." },
|
|
136
|
+
{ id: "other", label: "None of the above", description: "Enter a custom project type.", fallbackToText: true }
|
|
137
|
+
];
|
|
138
|
+
}
|
|
139
|
+
export function isFallbackChoice(choice) {
|
|
140
|
+
return choice?.fallbackToText === true;
|
|
141
|
+
}
|
|
142
|
+
export function normalizeProviderChoice(choice) {
|
|
143
|
+
return choice === "claude" ? "claude" : "codex";
|
|
144
|
+
}
|
|
145
|
+
export function createResearcherProfileSeed(answers) {
|
|
146
|
+
return {
|
|
147
|
+
currentProjectType: answers.currentProjectType ?? "unspecified research task",
|
|
148
|
+
...answers,
|
|
149
|
+
aiAutonomyPreference: answers.experienceLevel === "advanced"
|
|
150
|
+
? "balanced"
|
|
151
|
+
: answers.experienceLevel === "intermediate"
|
|
152
|
+
? "balanced"
|
|
153
|
+
: "low"
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
export function resolveProviderSelection(provider) {
|
|
157
|
+
if (provider === "claude") {
|
|
158
|
+
return {
|
|
159
|
+
provider,
|
|
160
|
+
checkpointProtocol: "native_structured",
|
|
161
|
+
supportsStructuredQuestions: true
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
return {
|
|
165
|
+
provider,
|
|
166
|
+
checkpointProtocol: "numbered",
|
|
167
|
+
supportsStructuredQuestions: false
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
export function createPersistedSetupOutput(answers, provider, flow = "quickstart") {
|
|
171
|
+
const profileSeed = createResearcherProfileSeed(answers);
|
|
172
|
+
const initialState = createEmptyResearchState();
|
|
173
|
+
initialState.explicitState = {
|
|
174
|
+
field: profileSeed.field,
|
|
175
|
+
careerStage: profileSeed.careerStage,
|
|
176
|
+
experienceLevel: profileSeed.experienceLevel,
|
|
177
|
+
...(profileSeed.currentProjectType
|
|
178
|
+
? { currentProjectType: profileSeed.currentProjectType }
|
|
179
|
+
: {}),
|
|
180
|
+
preferredCheckpointIntensity: profileSeed.preferredCheckpointIntensity,
|
|
181
|
+
...(profileSeed.humanAuthorshipSignal
|
|
182
|
+
? { humanAuthorshipSignal: profileSeed.humanAuthorshipSignal }
|
|
183
|
+
: {}),
|
|
184
|
+
...(profileSeed.preferredEntryMode
|
|
185
|
+
? { preferredEntryMode: profileSeed.preferredEntryMode }
|
|
186
|
+
: {}),
|
|
187
|
+
...(profileSeed.weakestDomain
|
|
188
|
+
? { weakestDomain: profileSeed.weakestDomain }
|
|
189
|
+
: {}),
|
|
190
|
+
...(profileSeed.panelPreference
|
|
191
|
+
? { panelPreference: profileSeed.panelPreference }
|
|
192
|
+
: {})
|
|
193
|
+
};
|
|
194
|
+
if (profileSeed.humanAuthorshipSignal) {
|
|
195
|
+
initialState.narrativeTraces.push({
|
|
196
|
+
id: "setup-human-authorship-signal",
|
|
197
|
+
timestamp: new Date().toISOString(),
|
|
198
|
+
source: "setup",
|
|
199
|
+
traceType: "voice",
|
|
200
|
+
summary: `Researcher identifies human authorship through ${profileSeed.humanAuthorshipSignal}.`,
|
|
201
|
+
visibility: "explicit",
|
|
202
|
+
importance: "high"
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
return {
|
|
206
|
+
setupFlow: flow,
|
|
207
|
+
profileSeed,
|
|
208
|
+
providerSelection: resolveProviderSelection(provider),
|
|
209
|
+
defaultInteractionMode: profileSeed.preferredEntryMode ?? "explore",
|
|
210
|
+
initialState
|
|
211
|
+
};
|
|
212
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ProviderKind } from "@longtable/core";
|
|
2
|
+
import type { RuntimeConfigTarget, SetupAnswers, SetupInstallResult, SetupPersistedOutput, SetupStorageTarget } from "./types.js";
|
|
3
|
+
export declare function serializeSetupOutput(output: SetupPersistedOutput): string;
|
|
4
|
+
export declare function parseSetupOutput(input: string): SetupPersistedOutput;
|
|
5
|
+
export declare function createSetupOutputExample(provider: ProviderKind, overrides?: Partial<SetupAnswers>): SetupPersistedOutput;
|
|
6
|
+
export declare function renderSetupSummary(output: SetupPersistedOutput): string;
|
|
7
|
+
export declare function resolveDefaultSetupPath(customPath?: string): SetupStorageTarget;
|
|
8
|
+
export declare function resolveDefaultRuntimeConfigPath(provider: ProviderKind, customPath?: string): RuntimeConfigTarget;
|
|
9
|
+
export declare function saveSetupOutput(output: SetupPersistedOutput, customPath?: string): Promise<SetupStorageTarget>;
|
|
10
|
+
export declare function loadSetupOutput(customPath?: string): Promise<SetupPersistedOutput>;
|
|
11
|
+
export declare function renderRuntimeConfig(output: SetupPersistedOutput, setupPath: string): string;
|
|
12
|
+
export declare function writeRuntimeConfig(output: SetupPersistedOutput, setupPath: string, customPath?: string): Promise<RuntimeConfigTarget>;
|
|
13
|
+
export declare function saveSetupAndRuntimeConfig(output: SetupPersistedOutput, options?: {
|
|
14
|
+
setupPath?: string;
|
|
15
|
+
runtimePath?: string;
|
|
16
|
+
}): Promise<SetupInstallResult>;
|
|
17
|
+
export declare function installRuntimeConfigFromStoredSetup(options?: {
|
|
18
|
+
setupPath?: string;
|
|
19
|
+
runtimePath?: string;
|
|
20
|
+
}): Promise<SetupInstallResult>;
|
|
21
|
+
export declare function renderInstallSummary(result: SetupInstallResult): string;
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { dirname, join, resolve } from "node:path";
|
|
4
|
+
import { createPersistedSetupOutput } from "./onboarding.js";
|
|
5
|
+
export function serializeSetupOutput(output) {
|
|
6
|
+
return JSON.stringify(output, null, 2);
|
|
7
|
+
}
|
|
8
|
+
export function parseSetupOutput(input) {
|
|
9
|
+
return JSON.parse(input);
|
|
10
|
+
}
|
|
11
|
+
export function createSetupOutputExample(provider, overrides = {}) {
|
|
12
|
+
return createPersistedSetupOutput({
|
|
13
|
+
field: overrides.field ?? "education",
|
|
14
|
+
careerStage: overrides.careerStage ?? "doctoral",
|
|
15
|
+
experienceLevel: overrides.experienceLevel ?? "intermediate",
|
|
16
|
+
preferredCheckpointIntensity: overrides.preferredCheckpointIntensity ?? "balanced"
|
|
17
|
+
}, provider);
|
|
18
|
+
}
|
|
19
|
+
export function renderSetupSummary(output) {
|
|
20
|
+
const lines = [
|
|
21
|
+
"LongTable setup summary",
|
|
22
|
+
`setup flow: ${output.setupFlow}`,
|
|
23
|
+
`provider: ${output.providerSelection.provider}`,
|
|
24
|
+
`checkpoint protocol: ${output.providerSelection.checkpointProtocol}`,
|
|
25
|
+
`default mode: ${output.defaultInteractionMode}`,
|
|
26
|
+
`field: ${output.profileSeed.field}`,
|
|
27
|
+
`career stage: ${output.profileSeed.careerStage}`,
|
|
28
|
+
`experience: ${output.profileSeed.experienceLevel}`,
|
|
29
|
+
`checkpoint intensity: ${output.profileSeed.preferredCheckpointIntensity}`
|
|
30
|
+
];
|
|
31
|
+
if (output.profileSeed.weakestDomain) {
|
|
32
|
+
lines.push(`challenge first: ${output.profileSeed.weakestDomain}`);
|
|
33
|
+
}
|
|
34
|
+
if (output.profileSeed.panelPreference) {
|
|
35
|
+
lines.push(`panel preference: ${output.profileSeed.panelPreference}`);
|
|
36
|
+
}
|
|
37
|
+
return lines.join("\n");
|
|
38
|
+
}
|
|
39
|
+
export function resolveDefaultSetupPath(customPath) {
|
|
40
|
+
const targetPath = customPath
|
|
41
|
+
? resolve(customPath)
|
|
42
|
+
: join(homedir(), ".longtable", "setup.json");
|
|
43
|
+
return {
|
|
44
|
+
path: targetPath,
|
|
45
|
+
directory: dirname(targetPath)
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
export function resolveDefaultRuntimeConfigPath(provider, customPath) {
|
|
49
|
+
const format = provider === "codex" ? "toml" : "json";
|
|
50
|
+
const fileName = provider === "codex" ? "longtable.toml" : "longtable.json";
|
|
51
|
+
const targetPath = customPath
|
|
52
|
+
? resolve(customPath)
|
|
53
|
+
: join(homedir(), ".longtable", "runtime", provider, fileName);
|
|
54
|
+
return {
|
|
55
|
+
provider,
|
|
56
|
+
path: targetPath,
|
|
57
|
+
directory: dirname(targetPath),
|
|
58
|
+
format
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
export async function saveSetupOutput(output, customPath) {
|
|
62
|
+
const target = resolveDefaultSetupPath(customPath);
|
|
63
|
+
await mkdir(target.directory, { recursive: true });
|
|
64
|
+
await writeFile(target.path, serializeSetupOutput(output), "utf8");
|
|
65
|
+
return target;
|
|
66
|
+
}
|
|
67
|
+
export async function loadSetupOutput(customPath) {
|
|
68
|
+
const target = resolveDefaultSetupPath(customPath);
|
|
69
|
+
const content = await readFile(target.path, "utf8");
|
|
70
|
+
return parseSetupOutput(content);
|
|
71
|
+
}
|
|
72
|
+
function resolveRuntimeGuidance(output) {
|
|
73
|
+
if (output.providerSelection.provider === "codex") {
|
|
74
|
+
return {
|
|
75
|
+
askAtLeastTwoQuestionsInExplore: true,
|
|
76
|
+
preserveNarrativeTraceInDraft: true,
|
|
77
|
+
requireWhyMayBeWrongInReview: true,
|
|
78
|
+
questionBiasCompensation: "strong"
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
return {
|
|
82
|
+
askAtLeastTwoQuestionsInExplore: true,
|
|
83
|
+
preserveNarrativeTraceInDraft: true,
|
|
84
|
+
requireWhyMayBeWrongInReview: true,
|
|
85
|
+
structuredQuestionBias: "strong"
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
function renderCodexRuntimeConfig(output, setupPath) {
|
|
89
|
+
const runtimeGuidance = resolveRuntimeGuidance(output);
|
|
90
|
+
return [
|
|
91
|
+
"[longtable]",
|
|
92
|
+
`provider = "${output.providerSelection.provider}"`,
|
|
93
|
+
`setup_path = "${setupPath}"`,
|
|
94
|
+
`checkpoint_protocol = "${output.providerSelection.checkpointProtocol}"`,
|
|
95
|
+
`default_interaction_mode = "${output.defaultInteractionMode}"`,
|
|
96
|
+
`setup_flow = "${output.setupFlow}"`,
|
|
97
|
+
"",
|
|
98
|
+
"[longtable.profile]",
|
|
99
|
+
`field = "${output.profileSeed.field}"`,
|
|
100
|
+
`career_stage = "${output.profileSeed.careerStage}"`,
|
|
101
|
+
`experience_level = "${output.profileSeed.experienceLevel}"`,
|
|
102
|
+
...(output.profileSeed.currentProjectType && output.profileSeed.currentProjectType !== "unspecified research task"
|
|
103
|
+
? [`current_project_type = "${output.profileSeed.currentProjectType}"`]
|
|
104
|
+
: []),
|
|
105
|
+
...(output.profileSeed.weakestDomain
|
|
106
|
+
? [`weakest_domain = "${output.profileSeed.weakestDomain}"`]
|
|
107
|
+
: []),
|
|
108
|
+
...(output.profileSeed.panelPreference
|
|
109
|
+
? [`panel_preference = "${output.profileSeed.panelPreference}"`]
|
|
110
|
+
: []),
|
|
111
|
+
"",
|
|
112
|
+
"[longtable.runtime_guidance]",
|
|
113
|
+
`ask_at_least_two_questions_in_explore = ${runtimeGuidance.askAtLeastTwoQuestionsInExplore}`,
|
|
114
|
+
`preserve_narrative_trace_in_draft = ${runtimeGuidance.preserveNarrativeTraceInDraft}`,
|
|
115
|
+
`require_why_may_be_wrong_in_review = ${runtimeGuidance.requireWhyMayBeWrongInReview}`,
|
|
116
|
+
`question_bias_compensation = "${runtimeGuidance.questionBiasCompensation}"`
|
|
117
|
+
].join("\n");
|
|
118
|
+
}
|
|
119
|
+
function renderClaudeRuntimeConfig(output, setupPath) {
|
|
120
|
+
const runtimeGuidance = resolveRuntimeGuidance(output);
|
|
121
|
+
return JSON.stringify({
|
|
122
|
+
setupPath,
|
|
123
|
+
provider: output.providerSelection.provider,
|
|
124
|
+
checkpointProtocol: output.providerSelection.checkpointProtocol,
|
|
125
|
+
defaultInteractionMode: output.defaultInteractionMode,
|
|
126
|
+
setupFlow: output.setupFlow,
|
|
127
|
+
profileSummary: {
|
|
128
|
+
field: output.profileSeed.field,
|
|
129
|
+
careerStage: output.profileSeed.careerStage,
|
|
130
|
+
experienceLevel: output.profileSeed.experienceLevel,
|
|
131
|
+
currentProjectType: output.profileSeed.currentProjectType && output.profileSeed.currentProjectType !== "unspecified research task"
|
|
132
|
+
? output.profileSeed.currentProjectType
|
|
133
|
+
: undefined,
|
|
134
|
+
weakestDomain: output.profileSeed.weakestDomain,
|
|
135
|
+
panelPreference: output.profileSeed.panelPreference
|
|
136
|
+
},
|
|
137
|
+
runtimeGuidance
|
|
138
|
+
}, null, 2);
|
|
139
|
+
}
|
|
140
|
+
export function renderRuntimeConfig(output, setupPath) {
|
|
141
|
+
return output.providerSelection.provider === "codex"
|
|
142
|
+
? renderCodexRuntimeConfig(output, setupPath)
|
|
143
|
+
: renderClaudeRuntimeConfig(output, setupPath);
|
|
144
|
+
}
|
|
145
|
+
export async function writeRuntimeConfig(output, setupPath, customPath) {
|
|
146
|
+
const target = resolveDefaultRuntimeConfigPath(output.providerSelection.provider, customPath);
|
|
147
|
+
await mkdir(target.directory, { recursive: true });
|
|
148
|
+
await writeFile(target.path, renderRuntimeConfig(output, setupPath), "utf8");
|
|
149
|
+
return target;
|
|
150
|
+
}
|
|
151
|
+
export async function saveSetupAndRuntimeConfig(output, options = {}) {
|
|
152
|
+
const setupTarget = await saveSetupOutput(output, options.setupPath);
|
|
153
|
+
const runtimeTarget = await writeRuntimeConfig(output, setupTarget.path, options.runtimePath);
|
|
154
|
+
return {
|
|
155
|
+
provider: output.providerSelection.provider,
|
|
156
|
+
setupTarget,
|
|
157
|
+
runtimeTarget
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
export async function installRuntimeConfigFromStoredSetup(options = {}) {
|
|
161
|
+
const setupTarget = resolveDefaultSetupPath(options.setupPath);
|
|
162
|
+
const output = await loadSetupOutput(options.setupPath);
|
|
163
|
+
const runtimeTarget = await writeRuntimeConfig(output, setupTarget.path, options.runtimePath);
|
|
164
|
+
return {
|
|
165
|
+
provider: output.providerSelection.provider,
|
|
166
|
+
setupTarget,
|
|
167
|
+
runtimeTarget
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
export function renderInstallSummary(result) {
|
|
171
|
+
return [
|
|
172
|
+
"LongTable runtime install summary",
|
|
173
|
+
`provider: ${result.provider}`,
|
|
174
|
+
`setup path: ${result.setupTarget.path}`,
|
|
175
|
+
`runtime config path: ${result.runtimeTarget.path}`,
|
|
176
|
+
`runtime config format: ${result.runtimeTarget.format}`
|
|
177
|
+
].join("\n");
|
|
178
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { CheckpointIntensity, ExperienceLevel, InteractionMode, ProviderKind, ResearcherConfidenceByDomain, ResearchState } from "@longtable/core";
|
|
2
|
+
export interface SetupChoice {
|
|
3
|
+
id: string;
|
|
4
|
+
label: string;
|
|
5
|
+
description: string;
|
|
6
|
+
fallbackToText?: boolean;
|
|
7
|
+
}
|
|
8
|
+
export interface SetupQuestion {
|
|
9
|
+
id: string;
|
|
10
|
+
prompt: string;
|
|
11
|
+
required: boolean;
|
|
12
|
+
kind: "single_choice" | "text";
|
|
13
|
+
choices?: SetupChoice[];
|
|
14
|
+
}
|
|
15
|
+
export type SetupFlow = "quickstart" | "interview";
|
|
16
|
+
export interface SetupAnswers {
|
|
17
|
+
field: string;
|
|
18
|
+
careerStage: string;
|
|
19
|
+
experienceLevel: ExperienceLevel;
|
|
20
|
+
currentProjectType?: string;
|
|
21
|
+
preferredCheckpointIntensity: CheckpointIntensity;
|
|
22
|
+
humanAuthorshipSignal?: string;
|
|
23
|
+
preferredEntryMode?: Exclude<InteractionMode, "submit">;
|
|
24
|
+
weakestDomain?: Extract<keyof ResearcherConfidenceByDomain, string>;
|
|
25
|
+
panelPreference?: "synthesis_only" | "show_on_conflict" | "always_visible";
|
|
26
|
+
}
|
|
27
|
+
export interface ResearcherProfileSeed extends SetupAnswers {
|
|
28
|
+
aiAutonomyPreference: "low" | "balanced" | "high";
|
|
29
|
+
}
|
|
30
|
+
export interface ProviderSelection {
|
|
31
|
+
provider: ProviderKind;
|
|
32
|
+
checkpointProtocol: "native_structured" | "numbered";
|
|
33
|
+
supportsStructuredQuestions: boolean;
|
|
34
|
+
}
|
|
35
|
+
export interface SetupPersistedOutput {
|
|
36
|
+
setupFlow: SetupFlow;
|
|
37
|
+
profileSeed: ResearcherProfileSeed;
|
|
38
|
+
providerSelection: ProviderSelection;
|
|
39
|
+
defaultInteractionMode: Exclude<InteractionMode, "submit">;
|
|
40
|
+
initialState: ResearchState;
|
|
41
|
+
}
|
|
42
|
+
export interface SetupStorageTarget {
|
|
43
|
+
path: string;
|
|
44
|
+
directory: string;
|
|
45
|
+
}
|
|
46
|
+
export interface RuntimeConfigTarget {
|
|
47
|
+
provider: ProviderKind;
|
|
48
|
+
path: string;
|
|
49
|
+
directory: string;
|
|
50
|
+
format: "toml" | "json";
|
|
51
|
+
}
|
|
52
|
+
export interface SetupInstallResult {
|
|
53
|
+
provider: ProviderKind;
|
|
54
|
+
setupTarget: SetupStorageTarget;
|
|
55
|
+
runtimeTarget: RuntimeConfigTarget;
|
|
56
|
+
}
|
|
57
|
+
export interface NumberedCheckpointOption {
|
|
58
|
+
value: string;
|
|
59
|
+
label: string;
|
|
60
|
+
}
|
|
61
|
+
export interface NumberedCheckpointSpec {
|
|
62
|
+
title: string;
|
|
63
|
+
instructions?: string;
|
|
64
|
+
options: NumberedCheckpointOption[];
|
|
65
|
+
allowRationale?: boolean;
|
|
66
|
+
}
|
|
67
|
+
export interface ParsedCheckpointSelection {
|
|
68
|
+
index: number;
|
|
69
|
+
value: string;
|
|
70
|
+
label: string;
|
|
71
|
+
rationale?: string;
|
|
72
|
+
}
|
|
73
|
+
export type { CheckpointIntensity, ExperienceLevel, ProviderKind, ResearchState } from "@longtable/core";
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"profileSeed": {
|
|
3
|
+
"field": "psychology",
|
|
4
|
+
"careerStage": "faculty",
|
|
5
|
+
"experienceLevel": "advanced",
|
|
6
|
+
"preferredCheckpointIntensity": "low",
|
|
7
|
+
"aiAutonomyPreference": "balanced"
|
|
8
|
+
},
|
|
9
|
+
"providerSelection": {
|
|
10
|
+
"provider": "claude",
|
|
11
|
+
"checkpointProtocol": "native_structured",
|
|
12
|
+
"supportsStructuredQuestions": true
|
|
13
|
+
},
|
|
14
|
+
"defaultInteractionMode": "explore",
|
|
15
|
+
"initialState": {
|
|
16
|
+
"explicitState": {
|
|
17
|
+
"field": "psychology",
|
|
18
|
+
"careerStage": "faculty",
|
|
19
|
+
"experienceLevel": "advanced",
|
|
20
|
+
"preferredCheckpointIntensity": "low"
|
|
21
|
+
},
|
|
22
|
+
"inferredHypotheses": [],
|
|
23
|
+
"openTensions": [],
|
|
24
|
+
"decisionLog": [],
|
|
25
|
+
"artifactRecords": []
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"profileSeed": {
|
|
3
|
+
"field": "education",
|
|
4
|
+
"careerStage": "doctoral",
|
|
5
|
+
"experienceLevel": "intermediate",
|
|
6
|
+
"preferredCheckpointIntensity": "balanced",
|
|
7
|
+
"aiAutonomyPreference": "balanced"
|
|
8
|
+
},
|
|
9
|
+
"providerSelection": {
|
|
10
|
+
"provider": "codex",
|
|
11
|
+
"checkpointProtocol": "numbered",
|
|
12
|
+
"supportsStructuredQuestions": false
|
|
13
|
+
},
|
|
14
|
+
"defaultInteractionMode": "explore",
|
|
15
|
+
"initialState": {
|
|
16
|
+
"explicitState": {
|
|
17
|
+
"field": "education",
|
|
18
|
+
"careerStage": "doctoral",
|
|
19
|
+
"experienceLevel": "intermediate",
|
|
20
|
+
"preferredCheckpointIntensity": "balanced"
|
|
21
|
+
},
|
|
22
|
+
"inferredHypotheses": [],
|
|
23
|
+
"openTensions": [],
|
|
24
|
+
"decisionLog": [],
|
|
25
|
+
"artifactRecords": []
|
|
26
|
+
}
|
|
27
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@longtable/setup",
|
|
3
|
+
"version": "0.1.9",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "Researcher onboarding and setup flows for LongTable",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"README.md",
|
|
18
|
+
"CHANGELOG.md",
|
|
19
|
+
"examples"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsc -p tsconfig.json",
|
|
23
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@longtable/core": "0.1.9",
|
|
27
|
+
"@longtable/memory": "0.1.9"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@types/node": "^22.0.0",
|
|
31
|
+
"typescript": "^5.6.0"
|
|
32
|
+
},
|
|
33
|
+
"keywords": [
|
|
34
|
+
"longtable",
|
|
35
|
+
"research",
|
|
36
|
+
"setup",
|
|
37
|
+
"onboarding",
|
|
38
|
+
"codex",
|
|
39
|
+
"claude"
|
|
40
|
+
],
|
|
41
|
+
"author": "Hosung You",
|
|
42
|
+
"license": "MIT",
|
|
43
|
+
"repository": {
|
|
44
|
+
"type": "git",
|
|
45
|
+
"url": "git+https://github.com/HosungYou/LongTable.git"
|
|
46
|
+
},
|
|
47
|
+
"homepage": "https://github.com/HosungYou/LongTable#readme",
|
|
48
|
+
"publishConfig": {
|
|
49
|
+
"access": "public"
|
|
50
|
+
},
|
|
51
|
+
"engines": {
|
|
52
|
+
"node": ">=18.0.0"
|
|
53
|
+
}
|
|
54
|
+
}
|