thinkwell 0.5.5 ā 0.5.7
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/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +232 -278
- package/dist/agent.js.map +1 -1
- package/dist/build.js +44 -98
- package/dist/cli/build.js +92 -227
- package/dist/cli/bundle.js +570 -1136
- package/dist/cli/check.js +125 -214
- package/dist/cli/commands.js +63 -177
- package/dist/cli/compiler-host.js +81 -190
- package/dist/cli/dependency-check.js +125 -269
- package/dist/cli/dependency-errors.js +12 -84
- package/dist/cli/fmt.js +1 -13
- package/dist/cli/init-command.js +21 -68
- package/dist/cli/init.js +90 -220
- package/dist/cli/loader.js +95 -361
- package/dist/cli/new-command.js +25 -73
- package/dist/cli/package-manager.js +50 -117
- package/dist/cli/schema.d.ts.map +1 -1
- package/dist/cli/schema.js +91 -245
- package/dist/cli/schema.js.map +1 -1
- package/dist/cli/workspace.js +92 -226
- package/dist/connectors/index.js +1 -7
- package/dist/generated/features.d.ts +6 -0
- package/dist/generated/features.d.ts.map +1 -0
- package/dist/generated/features.js +5 -0
- package/dist/generated/features.js.map +1 -0
- package/dist/index.js +0 -5
- package/dist/schema.js +3 -36
- package/dist/session.js +50 -82
- package/dist/think-builder.d.ts.map +1 -1
- package/dist/think-builder.js +287 -368
- package/dist/think-builder.js.map +1 -1
- package/dist/thought-event.d.ts +1 -0
- package/dist/thought-event.d.ts.map +1 -1
- package/dist/thought-event.js +0 -1
- package/dist/thought-stream.js +60 -96
- package/dist-pkg/acp.cjs +13386 -1876
- package/dist-pkg/cli-build.cjs +264 -446
- package/dist-pkg/cli-bundle.cjs +433 -818
- package/dist-pkg/cli-check.cjs +302 -499
- package/dist-pkg/cli-dependency-check.cjs +39 -82
- package/dist-pkg/cli-dependency-errors.cjs +9 -41
- package/dist-pkg/cli-loader.cjs +91 -173
- package/dist-pkg/protocol.cjs +2 -8
- package/dist-pkg/thinkwell.cjs +927 -1846
- package/package.json +9 -7
package/dist/cli/check.js
CHANGED
|
@@ -1,12 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Check command for type-checking with @JSONSchema transformation.
|
|
3
|
-
*
|
|
4
|
-
* This module provides the `thinkwell check` command that runs TypeScript's
|
|
5
|
-
* type checker without producing output files. It uses the same custom
|
|
6
|
-
* CompilerHost as `thinkwell build`, but with `--noEmit` to skip emit.
|
|
7
|
-
*
|
|
8
|
-
* Supports single-package projects and workspace mode (pnpm/npm workspaces).
|
|
9
|
-
*/
|
|
10
1
|
import ts from "typescript";
|
|
11
2
|
import { existsSync } from "node:fs";
|
|
12
3
|
import { resolve, join } from "node:path";
|
|
@@ -15,224 +6,144 @@ import { cyan, cyanBold, greenBold, whiteBold, dim } from "./fmt.js";
|
|
|
15
6
|
import { detectWorkspace, resolvePackageName } from "./workspace.js";
|
|
16
7
|
import { checkDependencies, hasPackageJson } from "./dependency-check.js";
|
|
17
8
|
import { formatMissingDependencyError, hasMissingDependencies } from "./dependency-errors.js";
|
|
18
|
-
// ============================================================================
|
|
19
|
-
// Diagnostics Formatting
|
|
20
|
-
// ============================================================================
|
|
21
9
|
function createDiagnosticsHost(pretty) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
10
|
+
return {
|
|
11
|
+
getCanonicalFileName: (fileName) => fileName,
|
|
12
|
+
getCurrentDirectory: ts.sys.getCurrentDirectory,
|
|
13
|
+
getNewLine: () => ts.sys.newLine
|
|
14
|
+
};
|
|
27
15
|
}
|
|
28
16
|
function formatDiagnostics(diagnostics, pretty) {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
? ts.formatDiagnosticsWithColorAndContext(diagnostics, host)
|
|
34
|
-
: ts.formatDiagnostics(diagnostics, host);
|
|
17
|
+
if (diagnostics.length === 0)
|
|
18
|
+
return "";
|
|
19
|
+
const host = createDiagnosticsHost(pretty);
|
|
20
|
+
return pretty ? ts.formatDiagnosticsWithColorAndContext(diagnostics, host) : ts.formatDiagnostics(diagnostics, host);
|
|
35
21
|
}
|
|
36
|
-
// ============================================================================
|
|
37
|
-
// Single-Package Check
|
|
38
|
-
// ============================================================================
|
|
39
|
-
/**
|
|
40
|
-
* Type-check a single package directory. Returns the number of errors found.
|
|
41
|
-
*/
|
|
42
22
|
function checkPackage(dir, pretty, projectDir) {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
process.stderr.write(formatDiagnostics(configErrors, pretty));
|
|
50
|
-
const hasFatal = configErrors.some((d) => d.category === ts.DiagnosticCategory.Error);
|
|
51
|
-
if (hasFatal) {
|
|
52
|
-
return configErrors.filter((d) => d.category === ts.DiagnosticCategory.Error).length;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
const diagnostics = ts.getPreEmitDiagnostics(program);
|
|
56
|
-
const errors = diagnostics.filter((d) => d.category === ts.DiagnosticCategory.Error);
|
|
57
|
-
if (diagnostics.length > 0) {
|
|
58
|
-
process.stderr.write("\n");
|
|
59
|
-
process.stderr.write(formatDiagnostics(diagnostics, pretty));
|
|
60
|
-
}
|
|
61
|
-
return errors.length;
|
|
23
|
+
const configPath = resolve(dir, "tsconfig.json"), { program, configErrors } = projectDir ? createThinkwellProgram({ configPath, projectDir }) : createThinkwellProgram(configPath);
|
|
24
|
+
if (configErrors.length > 0 && (process.stderr.write(formatDiagnostics(configErrors, pretty)), configErrors.some((d) => d.category === ts.DiagnosticCategory.Error)))
|
|
25
|
+
return configErrors.filter((d) => d.category === ts.DiagnosticCategory.Error).length;
|
|
26
|
+
const diagnostics = ts.getPreEmitDiagnostics(program), errors = diagnostics.filter((d) => d.category === ts.DiagnosticCategory.Error);
|
|
27
|
+
return diagnostics.length > 0 && (process.stderr.write(`
|
|
28
|
+
`), process.stderr.write(formatDiagnostics(diagnostics, pretty))), errors.length;
|
|
62
29
|
}
|
|
63
|
-
// ============================================================================
|
|
64
|
-
// Check Command
|
|
65
|
-
// ============================================================================
|
|
66
30
|
export async function runCheck(options) {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
if (
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}
|
|
76
|
-
// Single-package mode (no workspace detected)
|
|
77
|
-
if (!workspace) {
|
|
78
|
-
// Check for required dependencies when a package.json exists
|
|
79
|
-
if (hasPackageJson(cwd)) {
|
|
80
|
-
const depCheck = await checkDependencies(cwd);
|
|
81
|
-
if (hasMissingDependencies(depCheck)) {
|
|
82
|
-
process.stderr.write(formatMissingDependencyError(depCheck) + "\n");
|
|
83
|
-
process.exit(2);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
const configPath = resolve(cwd, "tsconfig.json");
|
|
87
|
-
if (!existsSync(configPath)) {
|
|
88
|
-
process.stderr.write("Error: Cannot find tsconfig.json\n");
|
|
89
|
-
process.stderr.write("\n");
|
|
90
|
-
process.stderr.write(" Run this command from a directory with a tsconfig.json.\n");
|
|
91
|
-
process.exit(2);
|
|
92
|
-
}
|
|
93
|
-
// Read package name for display, fall back to directory name
|
|
94
|
-
let pkgName = cwd.split("/").pop() ?? "project";
|
|
95
|
-
try {
|
|
96
|
-
const pkgPath = join(cwd, "package.json");
|
|
97
|
-
if (existsSync(pkgPath)) {
|
|
98
|
-
const pkg = JSON.parse((await import("node:fs")).readFileSync(pkgPath, "utf-8"));
|
|
99
|
-
if (pkg.name)
|
|
100
|
-
pkgName = pkg.name;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
catch {
|
|
104
|
-
// Use directory name
|
|
105
|
-
}
|
|
106
|
-
process.stderr.write(` Checking ${pkgName}...\n`);
|
|
107
|
-
const projectDir = hasPackageJson(cwd) ? cwd : undefined;
|
|
108
|
-
const errorCount = checkPackage(cwd, pretty, projectDir);
|
|
109
|
-
if (errorCount > 0) {
|
|
110
|
-
process.exit(1);
|
|
111
|
-
}
|
|
112
|
-
process.stderr.write(" No type errors found.\n");
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
// Workspace mode: check dependencies at workspace root
|
|
116
|
-
if (hasPackageJson(workspace.rootDir)) {
|
|
117
|
-
const depCheck = await checkDependencies(workspace.rootDir);
|
|
118
|
-
if (hasMissingDependencies(depCheck)) {
|
|
119
|
-
process.stderr.write(formatMissingDependencyError(depCheck) + "\n");
|
|
120
|
-
process.exit(2);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
// Workspace mode: determine which packages to check
|
|
124
|
-
let packagesToCheck;
|
|
125
|
-
if (options.packages && options.packages.length > 0) {
|
|
126
|
-
// Resolve named packages
|
|
127
|
-
packagesToCheck = [];
|
|
128
|
-
for (const name of options.packages) {
|
|
129
|
-
const result = resolvePackageName(name, workspace.members);
|
|
130
|
-
switch (result.kind) {
|
|
131
|
-
case "found":
|
|
132
|
-
if (!result.member.hasTsConfig) {
|
|
133
|
-
process.stderr.write(`Error: Package "${result.member.name}" has no tsconfig.json.\n`);
|
|
134
|
-
process.exit(2);
|
|
135
|
-
}
|
|
136
|
-
packagesToCheck.push(result.member);
|
|
137
|
-
break;
|
|
138
|
-
case "ambiguous":
|
|
139
|
-
process.stderr.write(`Error: Ambiguous package name "${result.name}". Matches:\n`);
|
|
140
|
-
for (const m of result.matches) {
|
|
141
|
-
process.stderr.write(` - ${m.name}\n`);
|
|
142
|
-
}
|
|
143
|
-
process.stderr.write("\nUse the full package name to disambiguate.\n");
|
|
144
|
-
process.exit(2);
|
|
145
|
-
break; // unreachable but satisfies control flow
|
|
146
|
-
case "not-found":
|
|
147
|
-
process.stderr.write(`Error: Package "${result.name}" not found in workspace.\n`);
|
|
148
|
-
process.stderr.write("\nAvailable packages:\n");
|
|
149
|
-
for (const available of result.available) {
|
|
150
|
-
process.stderr.write(` - ${available}\n`);
|
|
151
|
-
}
|
|
152
|
-
process.exit(2);
|
|
153
|
-
break; // unreachable but satisfies control flow
|
|
154
|
-
}
|
|
155
|
-
}
|
|
31
|
+
const cwd = process.cwd(), pretty = options.pretty ?? process.stdout.isTTY ?? !1, workspace = detectWorkspace(cwd);
|
|
32
|
+
if (options.packages && options.packages.length > 0 && !workspace && (process.stderr.write(`Error: --package can only be used in a workspace.
|
|
33
|
+
No pnpm-workspace.yaml or package.json "workspaces" found in current directory.
|
|
34
|
+
`), process.exit(2)), !workspace) {
|
|
35
|
+
if (hasPackageJson(cwd)) {
|
|
36
|
+
const depCheck = await checkDependencies(cwd);
|
|
37
|
+
hasMissingDependencies(depCheck) && (process.stderr.write(formatMissingDependencyError(depCheck) + `
|
|
38
|
+
`), process.exit(2));
|
|
156
39
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
40
|
+
const configPath = resolve(cwd, "tsconfig.json");
|
|
41
|
+
existsSync(configPath) || (process.stderr.write(`Error: Cannot find tsconfig.json
|
|
42
|
+
`), process.stderr.write(`
|
|
43
|
+
`), process.stderr.write(` Run this command from a directory with a tsconfig.json.
|
|
44
|
+
`), process.exit(2));
|
|
45
|
+
let pkgName = cwd.split("/").pop() ?? "project";
|
|
46
|
+
try {
|
|
47
|
+
const pkgPath = join(cwd, "package.json");
|
|
48
|
+
if (existsSync(pkgPath)) {
|
|
49
|
+
const pkg = JSON.parse((await import("node:fs")).readFileSync(pkgPath, "utf-8"));
|
|
50
|
+
pkg.name && (pkgName = pkg.name);
|
|
51
|
+
}
|
|
52
|
+
} catch {
|
|
160
53
|
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
54
|
+
process.stderr.write(` Checking ${pkgName}...
|
|
55
|
+
`);
|
|
56
|
+
const projectDir = hasPackageJson(cwd) ? cwd : void 0;
|
|
57
|
+
checkPackage(cwd, pretty, projectDir) > 0 && process.exit(1), process.stderr.write(` No type errors found.
|
|
58
|
+
`);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (hasPackageJson(workspace.rootDir)) {
|
|
62
|
+
const depCheck = await checkDependencies(workspace.rootDir);
|
|
63
|
+
hasMissingDependencies(depCheck) && (process.stderr.write(formatMissingDependencyError(depCheck) + `
|
|
64
|
+
`), process.exit(2));
|
|
65
|
+
}
|
|
66
|
+
let packagesToCheck;
|
|
67
|
+
if (options.packages && options.packages.length > 0) {
|
|
68
|
+
packagesToCheck = [];
|
|
69
|
+
for (const name of options.packages) {
|
|
70
|
+
const result = resolvePackageName(name, workspace.members);
|
|
71
|
+
switch (result.kind) {
|
|
72
|
+
case "found":
|
|
73
|
+
result.member.hasTsConfig || (process.stderr.write(`Error: Package "${result.member.name}" has no tsconfig.json.
|
|
74
|
+
`), process.exit(2)), packagesToCheck.push(result.member);
|
|
75
|
+
break;
|
|
76
|
+
case "ambiguous":
|
|
77
|
+
process.stderr.write(`Error: Ambiguous package name "${result.name}". Matches:
|
|
78
|
+
`);
|
|
79
|
+
for (const m of result.matches)
|
|
80
|
+
process.stderr.write(` - ${m.name}
|
|
81
|
+
`);
|
|
82
|
+
process.stderr.write(`
|
|
83
|
+
Use the full package name to disambiguate.
|
|
84
|
+
`), process.exit(2);
|
|
85
|
+
break;
|
|
86
|
+
// unreachable but satisfies control flow
|
|
87
|
+
case "not-found":
|
|
88
|
+
process.stderr.write(`Error: Package "${result.name}" not found in workspace.
|
|
89
|
+
`), process.stderr.write(`
|
|
90
|
+
Available packages:
|
|
91
|
+
`);
|
|
92
|
+
for (const available of result.available)
|
|
93
|
+
process.stderr.write(` - ${available}
|
|
94
|
+
`);
|
|
95
|
+
process.exit(2);
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
164
98
|
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
finally {
|
|
184
|
-
process.chdir(originalCwd);
|
|
185
|
-
}
|
|
99
|
+
} else
|
|
100
|
+
packagesToCheck = workspace.members.filter((m) => m.hasTsConfig);
|
|
101
|
+
packagesToCheck.length === 0 && (process.stderr.write(`No TypeScript packages found in workspace.
|
|
102
|
+
`), process.exit(2));
|
|
103
|
+
let totalErrors = 0, packagesWithErrors = 0;
|
|
104
|
+
const total = packagesToCheck.length;
|
|
105
|
+
for (const member of packagesToCheck) {
|
|
106
|
+
process.stderr.write(` Checking ${member.name}...`);
|
|
107
|
+
const originalCwd = process.cwd();
|
|
108
|
+
process.chdir(member.dir);
|
|
109
|
+
try {
|
|
110
|
+
const errorCount = checkPackage(member.dir, pretty, member.dir);
|
|
111
|
+
errorCount > 0 ? (totalErrors += errorCount, packagesWithErrors++) : process.stderr.write(` ok
|
|
112
|
+
`);
|
|
113
|
+
} finally {
|
|
114
|
+
process.chdir(originalCwd);
|
|
186
115
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
}
|
|
193
|
-
process.stderr.write(` All ${total} package${total === 1 ? "" : "s"} passed.\n`);
|
|
116
|
+
}
|
|
117
|
+
process.stderr.write(`
|
|
118
|
+
`), packagesWithErrors > 0 && (process.stderr.write(` ${packagesWithErrors} of ${total} package${total === 1 ? "" : "s"} had errors.
|
|
119
|
+
`), process.exit(1)), process.stderr.write(` All ${total} package${total === 1 ? "" : "s"} passed.
|
|
120
|
+
`);
|
|
194
121
|
}
|
|
195
|
-
// ============================================================================
|
|
196
|
-
// Argument Parsing
|
|
197
|
-
// ============================================================================
|
|
198
122
|
export function parseCheckArgs(args) {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
}
|
|
221
|
-
else {
|
|
222
|
-
throw new Error(`Unexpected argument: ${arg}\n\n` +
|
|
223
|
-
` "thinkwell check" type-checks the project without producing output.\n` +
|
|
224
|
-
` It does not take positional arguments.\n\n` +
|
|
225
|
-
` To check a specific workspace package, use: thinkwell check -p ${arg}`);
|
|
226
|
-
}
|
|
227
|
-
i++;
|
|
228
|
-
}
|
|
229
|
-
return options;
|
|
123
|
+
const options = {};
|
|
124
|
+
let i = 0;
|
|
125
|
+
for (; i < args.length; ) {
|
|
126
|
+
const arg = args[i];
|
|
127
|
+
if (arg === "-p" || arg === "--package") {
|
|
128
|
+
if (i++, i >= args.length)
|
|
129
|
+
throw new Error("Missing value for --package");
|
|
130
|
+
options.packages || (options.packages = []), options.packages.push(args[i]);
|
|
131
|
+
} else if (arg === "--pretty")
|
|
132
|
+
options.pretty = !0;
|
|
133
|
+
else if (arg === "--no-pretty")
|
|
134
|
+
options.pretty = !1;
|
|
135
|
+
else throw arg.startsWith("-") ? new Error(`Unknown option: ${arg}`) : new Error(`Unexpected argument: ${arg}
|
|
136
|
+
|
|
137
|
+
"thinkwell check" type-checks the project without producing output.
|
|
138
|
+
It does not take positional arguments.
|
|
139
|
+
|
|
140
|
+
To check a specific workspace package, use: thinkwell check -p ${arg}`);
|
|
141
|
+
i++;
|
|
142
|
+
}
|
|
143
|
+
return options;
|
|
230
144
|
}
|
|
231
|
-
// ============================================================================
|
|
232
|
-
// Help
|
|
233
|
-
// ============================================================================
|
|
234
145
|
export function showCheckHelp() {
|
|
235
|
-
|
|
146
|
+
console.log(`
|
|
236
147
|
${cyanBold("thinkwell check")} - ${whiteBold("Type-check TypeScript with @JSONSchema support")}
|
|
237
148
|
|
|
238
149
|
${greenBold("Usage:")}
|
|
@@ -264,6 +175,6 @@ ${greenBold("Examples:")}
|
|
|
264
175
|
${cyanBold("thinkwell check")} ${cyan("-p acp")} Check a specific workspace package
|
|
265
176
|
${cyanBold("thinkwell check")} ${cyan("-p acp -p protocol")} Check multiple packages
|
|
266
177
|
${cyanBold("thinkwell check")} ${cyan("--no-pretty")} Disable colorized output ${dim("(for CI)")}
|
|
267
|
-
`.trim() +
|
|
178
|
+
`.trim() + `
|
|
179
|
+
`);
|
|
268
180
|
}
|
|
269
|
-
//# sourceMappingURL=check.js.map
|
package/dist/cli/commands.js
CHANGED
|
@@ -1,67 +1,31 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared CLI commands and help text.
|
|
3
|
-
*
|
|
4
|
-
* This module is the single source of truth for help screens and
|
|
5
|
-
* utility functions shared between the two CLI entry points:
|
|
6
|
-
* - src/cli/main.cjs (compiled binary, CommonJS)
|
|
7
|
-
* - bin/thinkwell (npm distribution, ESM)
|
|
8
|
-
*
|
|
9
|
-
* IMPORTANT: This module must remain self-contained (no local imports)
|
|
10
|
-
* because main.cjs loads it via require() inside the pkg snapshot,
|
|
11
|
-
* where ESM import resolution for sibling modules fails.
|
|
12
|
-
*/
|
|
13
1
|
import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
14
2
|
import { join, dirname } from "node:path";
|
|
15
3
|
import { homedir } from "node:os";
|
|
16
4
|
import { styleText } from "node:util";
|
|
17
|
-
const cyan = (t) => styleText("cyan", t);
|
|
18
|
-
const cyanBold = (t) => styleText(["cyan", "bold"], t);
|
|
19
|
-
const greenBold = (t) => styleText(["green", "bold"], t);
|
|
20
|
-
const whiteBold = (t) => styleText(["white", "bold"], t);
|
|
21
|
-
const redBold = (t) => styleText(["red", "bold"], t);
|
|
22
|
-
const dim = (t) => styleText("dim", t);
|
|
5
|
+
const cyan = (t) => styleText("cyan", t), cyanBold = (t) => styleText(["cyan", "bold"], t), greenBold = (t) => styleText(["green", "bold"], t), whiteBold = (t) => styleText(["white", "bold"], t), redBold = (t) => styleText(["red", "bold"], t), dim = (t) => styleText("dim", t);
|
|
23
6
|
function getWelcomeMarkerPath() {
|
|
24
|
-
|
|
25
|
-
|
|
7
|
+
const cacheDir = process.env.THINKWELL_CACHE_DIR || join(homedir(), ".cache", "thinkwell");
|
|
8
|
+
return join(cacheDir, "welcome-version");
|
|
26
9
|
}
|
|
27
10
|
function shouldAnimate(version, forceWelcome) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
return true;
|
|
36
|
-
}
|
|
11
|
+
if (forceWelcome)
|
|
12
|
+
return !0;
|
|
13
|
+
try {
|
|
14
|
+
return readFileSync(getWelcomeMarkerPath(), "utf-8").trim() !== version;
|
|
15
|
+
} catch {
|
|
16
|
+
return !0;
|
|
17
|
+
}
|
|
37
18
|
}
|
|
38
19
|
function recordWelcomeVersion(version) {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
// Best-effort ā if we can't write, the animation plays again next time.
|
|
46
|
-
}
|
|
20
|
+
try {
|
|
21
|
+
const markerPath = getWelcomeMarkerPath();
|
|
22
|
+
mkdirSync(dirname(markerPath), { recursive: !0 }), writeFileSync(markerPath, version + `
|
|
23
|
+
`, "utf-8");
|
|
24
|
+
} catch {
|
|
25
|
+
}
|
|
47
26
|
}
|
|
48
|
-
/**
|
|
49
|
-
* Print the main help screen to stdout, with an optional typewriter
|
|
50
|
-
* animation on the tagline when stdout is an interactive TTY.
|
|
51
|
-
*
|
|
52
|
-
* The animation is gated to run once per installed version. Pass
|
|
53
|
-
* `forceWelcome: true` (via the undocumented `--welcome` flag) to
|
|
54
|
-
* bypass version gating.
|
|
55
|
-
*/
|
|
56
27
|
export async function showMainHelp(options) {
|
|
57
|
-
|
|
58
|
-
const revealedText = " - agent scripting made easy";
|
|
59
|
-
const pen = " āļø ";
|
|
60
|
-
const penWithSparkle = " āØāļø ";
|
|
61
|
-
const { version, forceWelcome = false } = options;
|
|
62
|
-
const ttyOk = process.stdout.isTTY && !process.env.CI;
|
|
63
|
-
const animate = ttyOk && shouldAnimate(version, forceWelcome);
|
|
64
|
-
const middleHelp = `
|
|
28
|
+
const anchor = cyanBold("thinkwell"), revealedText = " - agent scripting made easy", pen = " \u270D\uFE0F ", penWithSparkle = " \u2728\u270D\uFE0F ", { version, forceWelcome = !1 } = options, animate = process.stdout.isTTY && !process.env.CI && shouldAnimate(version, forceWelcome), middleHelp = `
|
|
65
29
|
|
|
66
30
|
${greenBold("Usage:")}
|
|
67
31
|
${cyanBold("thinkwell")} ${dim("<script.ts> [args...]")} Run a TypeScript script
|
|
@@ -76,136 +40,58 @@ ${greenBold("Usage:")}
|
|
|
76
40
|
|
|
77
41
|
${greenBold("Example:")}
|
|
78
42
|
${cyanBold("thinkwell")} ${cyan("my-agent.ts")}
|
|
79
|
-
`;
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
process.stdout.write(
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
const styled = revealed.length <= infoLineText.length
|
|
122
|
-
? revealed
|
|
123
|
-
: infoLineText + cyanBold(revealed.slice(infoLineText.length));
|
|
124
|
-
process.stdout.write(`\r\x1b[Kš ${styled}${pen}`);
|
|
125
|
-
if (i >= infoChars.length) {
|
|
126
|
-
clearInterval(timer);
|
|
127
|
-
resolve();
|
|
128
|
-
}
|
|
129
|
-
}, interval);
|
|
130
|
-
});
|
|
131
|
-
// Remove pen, keep š
|
|
132
|
-
process.stdout.write(`\r\x1b[Kš ${infoLine}`);
|
|
133
|
-
// Stage 3: Pen travels up vertically in the same column to the tagline
|
|
134
|
-
//
|
|
135
|
-
// The pen sits just after "š For more information, visit: https://thinkwell.sh"
|
|
136
|
-
// "š " = 3 visible cols, + infoLineText (29) + infoLineUrl (20) = 52 cols.
|
|
137
|
-
// The pen emoji occupies col 53+. On each intermediate line, we save the
|
|
138
|
-
// plain character at col 53 (all description text at that column is unstyled
|
|
139
|
-
// ASCII), write the pen over it, then restore it before moving up.
|
|
140
|
-
const penCol = 53;
|
|
141
|
-
const goCol = `\x1b[${penCol}G`;
|
|
142
|
-
// Strip ANSI escape codes to get visible text
|
|
143
|
-
const stripAnsi = (s) => s.replace(/\x1b\[[0-9;]*m/g, "");
|
|
144
|
-
// Build array of plain chars at penCol for each line (bottom to top)
|
|
145
|
-
const helpLines = (middleHelp + "\n").split("\n");
|
|
146
|
-
// helpLines[0] corresponds to the line just below the tagline (bottom of array = closest to info line)
|
|
147
|
-
const charAtCol = helpLines.map((line) => {
|
|
148
|
-
const plain = stripAnsi(line);
|
|
149
|
-
// penCol is 1-based, string index is 0-based
|
|
150
|
-
return plain[penCol - 1] ?? " ";
|
|
151
|
-
});
|
|
152
|
-
// Number of \n characters between tagline and info line
|
|
153
|
-
const linesFromInfoToTagline = helpLines.length - 1;
|
|
154
|
-
const stepDelay = 40; // ms per line
|
|
155
|
-
// Animate pen traveling upward ā save/restore single char at fixed col
|
|
156
|
-
for (let linesUp = 1; linesUp < linesFromInfoToTagline; linesUp++) {
|
|
157
|
-
// Draw pen at fixed column on this line
|
|
158
|
-
process.stdout.write(`\x1b[${linesUp}A${goCol}āļø\x1b[${linesUp}B\r`);
|
|
159
|
-
await new Promise((r) => setTimeout(r, stepDelay));
|
|
160
|
-
// Restore original character at that column
|
|
161
|
-
const originalChar = charAtCol[helpLines.length - 1 - linesUp];
|
|
162
|
-
process.stdout.write(`\x1b[${linesUp}A${goCol}${originalChar}\x1b[${linesUp}B\r`);
|
|
163
|
-
}
|
|
164
|
-
// Pen arrives at tagline ā slide left from col 53 to final position
|
|
165
|
-
// The tagline "thinkwell - agent scripting made easy" is 38 visible chars.
|
|
166
|
-
// The sparkle goes at col 39, pen (āļø) at col 41 (after "āØ").
|
|
167
|
-
// But first the pen slides from col 53 to col 40 (one past the sparkle slot).
|
|
168
|
-
const taglineRow = linesFromInfoToTagline;
|
|
169
|
-
const sparkleCol = 39;
|
|
170
|
-
for (let col = penCol; col > sparkleCol; col--) {
|
|
171
|
-
process.stdout.write(`\x1b[${taglineRow}A\x1b[${col}Gāļø\x1b[${taglineRow}B\r`);
|
|
172
|
-
await new Promise((r) => setTimeout(r, stepDelay));
|
|
173
|
-
// Erase pen (it's over spaces on the tagline)
|
|
174
|
-
process.stdout.write(`\x1b[${taglineRow}A\x1b[${col}G \x1b[${taglineRow}B\r`);
|
|
175
|
-
}
|
|
176
|
-
// Write sparkle + pen at final position
|
|
177
|
-
process.stdout.write(`\x1b[${taglineRow}A\r\x1b[K${anchor}${whiteBold(revealedText + penWithSparkle)}\x1b[${taglineRow}B\r`);
|
|
178
|
-
// Record that this version's welcome animation has been shown
|
|
179
|
-
recordWelcomeVersion(version);
|
|
180
|
-
// Show cursor
|
|
181
|
-
process.stdout.write("\x1b[?25h\n");
|
|
43
|
+
`, infoLineText = "For more information, visit: ", infoLineUrl = "https://thinkwell.sh", infoLine = `${infoLineText}${cyanBold(infoLineUrl)}`;
|
|
44
|
+
if (!animate) {
|
|
45
|
+
console.log(anchor + whiteBold(revealedText + penWithSparkle) + middleHelp + `
|
|
46
|
+
` + infoLine);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const typewrite = (chars, duration, styleFn) => new Promise((resolve) => {
|
|
50
|
+
const interval = duration / chars.length;
|
|
51
|
+
let i = 0;
|
|
52
|
+
const timer = setInterval(() => {
|
|
53
|
+
i++;
|
|
54
|
+
const revealed = chars.slice(0, i).join("");
|
|
55
|
+
process.stdout.write(`\r\x1B[K${styleFn(revealed)}${pen}`), i >= chars.length && (clearInterval(timer), resolve());
|
|
56
|
+
}, interval);
|
|
57
|
+
});
|
|
58
|
+
process.stdout.write("\x1B[?25l"), process.stdout.write(pen);
|
|
59
|
+
const fullChars = [..."thinkwell" + revealedText];
|
|
60
|
+
await typewrite(fullChars, 1e3, (revealed) => revealed.length <= 9 ? cyanBold(revealed) : cyanBold("thinkwell") + whiteBold(revealed.slice(9))), process.stdout.write(`\r\x1B[K${anchor}${whiteBold(revealedText)}`), process.stdout.write(middleHelp + `
|
|
61
|
+
`);
|
|
62
|
+
const infoChars = [...infoLineText + infoLineUrl];
|
|
63
|
+
await new Promise((resolve) => {
|
|
64
|
+
const interval = 1e3 / infoChars.length;
|
|
65
|
+
let i = 0;
|
|
66
|
+
const timer = setInterval(() => {
|
|
67
|
+
i++;
|
|
68
|
+
const revealed = infoChars.slice(0, i).join(""), styled = revealed.length <= infoLineText.length ? revealed : infoLineText + cyanBold(revealed.slice(infoLineText.length));
|
|
69
|
+
process.stdout.write(`\r\x1B[K\u{1F4D6} ${styled}${pen}`), i >= infoChars.length && (clearInterval(timer), resolve());
|
|
70
|
+
}, interval);
|
|
71
|
+
}), process.stdout.write(`\r\x1B[K\u{1F4D6} ${infoLine}`);
|
|
72
|
+
const penCol = 53, goCol = `\x1B[${penCol}G`, stripAnsi = (s) => s.replace(/\x1b\[[0-9;]*m/g, ""), helpLines = (middleHelp + `
|
|
73
|
+
`).split(`
|
|
74
|
+
`), charAtCol = helpLines.map((line) => stripAnsi(line)[penCol - 1] ?? " "), linesFromInfoToTagline = helpLines.length - 1, stepDelay = 40;
|
|
75
|
+
for (let linesUp = 1; linesUp < linesFromInfoToTagline; linesUp++) {
|
|
76
|
+
process.stdout.write(`\x1B[${linesUp}A${goCol}\u270D\uFE0F\x1B[${linesUp}B\r`), await new Promise((r) => setTimeout(r, stepDelay));
|
|
77
|
+
const originalChar = charAtCol[helpLines.length - 1 - linesUp];
|
|
78
|
+
process.stdout.write(`\x1B[${linesUp}A${goCol}${originalChar}\x1B[${linesUp}B\r`);
|
|
79
|
+
}
|
|
80
|
+
const taglineRow = linesFromInfoToTagline, sparkleCol = 39;
|
|
81
|
+
for (let col = penCol; col > sparkleCol; col--)
|
|
82
|
+
process.stdout.write(`\x1B[${taglineRow}A\x1B[${col}G\u270D\uFE0F\x1B[${taglineRow}B\r`), await new Promise((r) => setTimeout(r, stepDelay)), process.stdout.write(`\x1B[${taglineRow}A\x1B[${col}G \x1B[${taglineRow}B\r`);
|
|
83
|
+
process.stdout.write(`\x1B[${taglineRow}A\r\x1B[K${anchor}${whiteBold(revealedText + penWithSparkle)}\x1B[${taglineRow}B\r`), recordWelcomeVersion(version), process.stdout.write(`\x1B[?25h
|
|
84
|
+
`);
|
|
182
85
|
}
|
|
183
|
-
/**
|
|
184
|
-
* Print the "no script provided" error and exit.
|
|
185
|
-
*/
|
|
186
86
|
export function showNoScriptError() {
|
|
187
|
-
|
|
188
|
-
console.error("");
|
|
189
|
-
console.error("Usage: thinkwell <script.ts> [args...]");
|
|
190
|
-
process.exit(1);
|
|
87
|
+
console.error(`${redBold("Error:")} No script provided.`), console.error(""), console.error("Usage: thinkwell <script.ts> [args...]"), process.exit(1);
|
|
191
88
|
}
|
|
192
|
-
/**
|
|
193
|
-
* Check if args contain a help flag (--help or -h).
|
|
194
|
-
*/
|
|
195
89
|
export function hasHelpFlag(args) {
|
|
196
|
-
|
|
90
|
+
return args.includes("--help") || args.includes("-h");
|
|
197
91
|
}
|
|
198
|
-
/**
|
|
199
|
-
* Check if args contain the undocumented --welcome flag.
|
|
200
|
-
*/
|
|
201
92
|
export function hasWelcomeFlag(args) {
|
|
202
|
-
|
|
93
|
+
return args.includes("--welcome");
|
|
203
94
|
}
|
|
204
|
-
/**
|
|
205
|
-
* Format an error message with a red bold "Error:" prefix.
|
|
206
|
-
* Use with console.error(): `console.error(fmtError("something went wrong"))`
|
|
207
|
-
*/
|
|
208
95
|
export function fmtError(message) {
|
|
209
|
-
|
|
96
|
+
return `${redBold("Error:")} ${message}`;
|
|
210
97
|
}
|
|
211
|
-
//# sourceMappingURL=commands.js.map
|