flintmaker 0.0.0-stage → 0.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.
- package/README.md +16 -2
- package/dist/flint.js +1000 -0
- package/package.json +31 -4
package/README.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
-
#
|
|
1
|
+
# flint — FlintMaker for your terminal
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
`flint` is an AI coding agent from [FlintMaker](https://flintmaker.com). It reads your project, edits files and runs commands — always showing you the diff or command first.
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
npm install -g flintmaker
|
|
7
|
+
flint login # connect your FlintMaker account (opens the browser)
|
|
8
|
+
cd my-project
|
|
9
|
+
flint # interactive session
|
|
10
|
+
flint "add a dark mode toggle" # one task, then exit
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
- Every file change is shown as a diff and needs your approval (or `a` = always for this session).
|
|
14
|
+
- Commands run only after you approve them; secret files such as `.env` and private keys are never read.
|
|
15
|
+
- Uses credits from your FlintMaker plan. See [pricing](https://flintmaker.com/pricing).
|
|
16
|
+
|
|
17
|
+
Made by PT Jiji Labs Digital · hello@jijilabsdigital.com
|
package/dist/flint.js
ADDED
|
@@ -0,0 +1,1000 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf, __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __commonJS = (cb, mod) => function() {
|
|
8
|
+
try {
|
|
9
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
10
|
+
} catch (e) {
|
|
11
|
+
throw mod = 0, e;
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
var __copyProps = (to, from, except, desc) => {
|
|
15
|
+
if (from && typeof from == "object" || typeof from == "function")
|
|
16
|
+
for (let key of __getOwnPropNames(from))
|
|
17
|
+
!__hasOwnProp.call(to, key) && key !== except && __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: !0 }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
|
|
29
|
+
// node_modules/picocolors/picocolors.js
|
|
30
|
+
var require_picocolors = __commonJS({
|
|
31
|
+
"node_modules/picocolors/picocolors.js"(exports, module) {
|
|
32
|
+
var p = process || {}, argv = p.argv || [], env = p.env || {}, isColorSupported = !(env.NO_COLOR || argv.includes("--no-color")) && (!!env.FORCE_COLOR || argv.includes("--color") || p.platform === "win32" || (p.stdout || {}).isTTY && env.TERM !== "dumb" || !!env.CI), formatter = (open, close, replace = open) => (input) => {
|
|
33
|
+
let string = "" + input, index = string.indexOf(close, open.length);
|
|
34
|
+
return ~index ? open + replaceClose(string, close, replace, index) + close : open + string + close;
|
|
35
|
+
}, replaceClose = (string, close, replace, index) => {
|
|
36
|
+
let result = "", cursor = 0;
|
|
37
|
+
do
|
|
38
|
+
result += string.substring(cursor, index) + replace, cursor = index + close.length, index = string.indexOf(close, cursor);
|
|
39
|
+
while (~index);
|
|
40
|
+
return result + string.substring(cursor);
|
|
41
|
+
}, createColors = (enabled = isColorSupported) => {
|
|
42
|
+
let f = enabled ? formatter : () => String;
|
|
43
|
+
return {
|
|
44
|
+
isColorSupported: enabled,
|
|
45
|
+
reset: f("\x1B[0m", "\x1B[0m"),
|
|
46
|
+
bold: f("\x1B[1m", "\x1B[22m", "\x1B[22m\x1B[1m"),
|
|
47
|
+
dim: f("\x1B[2m", "\x1B[22m", "\x1B[22m\x1B[2m"),
|
|
48
|
+
italic: f("\x1B[3m", "\x1B[23m"),
|
|
49
|
+
underline: f("\x1B[4m", "\x1B[24m"),
|
|
50
|
+
inverse: f("\x1B[7m", "\x1B[27m"),
|
|
51
|
+
hidden: f("\x1B[8m", "\x1B[28m"),
|
|
52
|
+
strikethrough: f("\x1B[9m", "\x1B[29m"),
|
|
53
|
+
black: f("\x1B[30m", "\x1B[39m"),
|
|
54
|
+
red: f("\x1B[31m", "\x1B[39m"),
|
|
55
|
+
green: f("\x1B[32m", "\x1B[39m"),
|
|
56
|
+
yellow: f("\x1B[33m", "\x1B[39m"),
|
|
57
|
+
blue: f("\x1B[34m", "\x1B[39m"),
|
|
58
|
+
magenta: f("\x1B[35m", "\x1B[39m"),
|
|
59
|
+
cyan: f("\x1B[36m", "\x1B[39m"),
|
|
60
|
+
white: f("\x1B[37m", "\x1B[39m"),
|
|
61
|
+
gray: f("\x1B[90m", "\x1B[39m"),
|
|
62
|
+
bgBlack: f("\x1B[40m", "\x1B[49m"),
|
|
63
|
+
bgRed: f("\x1B[41m", "\x1B[49m"),
|
|
64
|
+
bgGreen: f("\x1B[42m", "\x1B[49m"),
|
|
65
|
+
bgYellow: f("\x1B[43m", "\x1B[49m"),
|
|
66
|
+
bgBlue: f("\x1B[44m", "\x1B[49m"),
|
|
67
|
+
bgMagenta: f("\x1B[45m", "\x1B[49m"),
|
|
68
|
+
bgCyan: f("\x1B[46m", "\x1B[49m"),
|
|
69
|
+
bgWhite: f("\x1B[47m", "\x1B[49m"),
|
|
70
|
+
blackBright: f("\x1B[90m", "\x1B[39m"),
|
|
71
|
+
redBright: f("\x1B[91m", "\x1B[39m"),
|
|
72
|
+
greenBright: f("\x1B[92m", "\x1B[39m"),
|
|
73
|
+
yellowBright: f("\x1B[93m", "\x1B[39m"),
|
|
74
|
+
blueBright: f("\x1B[94m", "\x1B[39m"),
|
|
75
|
+
magentaBright: f("\x1B[95m", "\x1B[39m"),
|
|
76
|
+
cyanBright: f("\x1B[96m", "\x1B[39m"),
|
|
77
|
+
whiteBright: f("\x1B[97m", "\x1B[39m"),
|
|
78
|
+
bgBlackBright: f("\x1B[100m", "\x1B[49m"),
|
|
79
|
+
bgRedBright: f("\x1B[101m", "\x1B[49m"),
|
|
80
|
+
bgGreenBright: f("\x1B[102m", "\x1B[49m"),
|
|
81
|
+
bgYellowBright: f("\x1B[103m", "\x1B[49m"),
|
|
82
|
+
bgBlueBright: f("\x1B[104m", "\x1B[49m"),
|
|
83
|
+
bgMagentaBright: f("\x1B[105m", "\x1B[49m"),
|
|
84
|
+
bgCyanBright: f("\x1B[106m", "\x1B[49m"),
|
|
85
|
+
bgWhiteBright: f("\x1B[107m", "\x1B[49m")
|
|
86
|
+
};
|
|
87
|
+
};
|
|
88
|
+
module.exports = createColors();
|
|
89
|
+
module.exports.createColors = createColors;
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// src/index.ts
|
|
94
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
95
|
+
import { createInterface } from "node:readline/promises";
|
|
96
|
+
import { homedir as homedir2 } from "node:os";
|
|
97
|
+
|
|
98
|
+
// src/agent.ts
|
|
99
|
+
var ContextTooLarge = class extends Error {
|
|
100
|
+
}, DETAIL = {
|
|
101
|
+
list_files: (a) => String(a.path ?? "."),
|
|
102
|
+
read_file: (a) => `${a.path}${a.start_line ? `:${a.start_line}-${a.end_line ?? ""}` : ""}`,
|
|
103
|
+
search: (a) => `/${a.pattern}/ ${a.path ?? ""}`.trim(),
|
|
104
|
+
write_file: (a) => String(a.path),
|
|
105
|
+
edit_file: (a) => String(a.path),
|
|
106
|
+
run_command: (a) => String(a.command)
|
|
107
|
+
};
|
|
108
|
+
async function execute(ws, name, args, ui) {
|
|
109
|
+
switch (name) {
|
|
110
|
+
case "list_files":
|
|
111
|
+
return ws.listFiles(typeof args.path == "string" ? args.path : ".");
|
|
112
|
+
case "read_file":
|
|
113
|
+
return ws.readFile(String(args.path), num(args.start_line), num(args.end_line));
|
|
114
|
+
case "search":
|
|
115
|
+
return ws.search(String(args.pattern ?? ""), typeof args.path == "string" ? args.path : ".");
|
|
116
|
+
case "write_file":
|
|
117
|
+
case "edit_file": {
|
|
118
|
+
let change = await ws.plan(name, args);
|
|
119
|
+
return await ui.approveChange(change) ? (await change.apply(), `${change.created ? "Created" : "Updated"} ${change.path}.`) : "The user declined this change. Ask what they want instead, or try a different approach.";
|
|
120
|
+
}
|
|
121
|
+
case "run_command": {
|
|
122
|
+
let command = String(args.command ?? "").trim();
|
|
123
|
+
if (!command) throw new Error("command is required");
|
|
124
|
+
return await ui.approveCommand(command) ? ws.runCommand(command) : "The user declined to run this command.";
|
|
125
|
+
}
|
|
126
|
+
default:
|
|
127
|
+
throw new Error(`unknown tool ${name}`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
var num = (v) => typeof v == "number" && Number.isFinite(v) ? v : void 0;
|
|
131
|
+
function compact(messages) {
|
|
132
|
+
let out = structuredClone(messages), tools = out.map((m, i) => ({ m, i })).filter((x) => x.m.role === "tool" && (x.m.content?.length ?? 0) > 400);
|
|
133
|
+
for (let { m } of tools.slice(0, Math.max(1, Math.ceil(tools.length / 2))))
|
|
134
|
+
m.role === "tool" && (m.content = `${m.content.slice(0, 300)}
|
|
135
|
+
[output trimmed to save context; read again if needed]`);
|
|
136
|
+
return tools.length === 0 && out.length > 4 && out.splice(1, 2), out;
|
|
137
|
+
}
|
|
138
|
+
async function runAgent(ws, start, step, ui, opts = {}) {
|
|
139
|
+
let messages = [...start];
|
|
140
|
+
for (let i = 0; i < (opts.maxSteps ?? 25); i++) {
|
|
141
|
+
let result = null;
|
|
142
|
+
for (let attempt = 0; attempt < 4 && !result; attempt++)
|
|
143
|
+
try {
|
|
144
|
+
result = await step(messages, ui.onDelta);
|
|
145
|
+
} catch (e) {
|
|
146
|
+
if (!(e instanceof ContextTooLarge)) throw e;
|
|
147
|
+
messages = compact(messages);
|
|
148
|
+
}
|
|
149
|
+
if (!result) throw new Error("This conversation is too long. Start a new one with /clear.");
|
|
150
|
+
if (result.toolCalls.length === 0)
|
|
151
|
+
return messages.push({ role: "assistant", content: result.text }), messages;
|
|
152
|
+
messages.push({
|
|
153
|
+
role: "assistant",
|
|
154
|
+
content: result.text || null,
|
|
155
|
+
tool_calls: result.toolCalls.map((c) => ({ id: c.id, type: "function", function: { name: c.name, arguments: c.arguments } }))
|
|
156
|
+
});
|
|
157
|
+
for (let call of result.toolCalls) {
|
|
158
|
+
let args = {};
|
|
159
|
+
try {
|
|
160
|
+
args = JSON.parse(call.arguments || "{}");
|
|
161
|
+
} catch {
|
|
162
|
+
}
|
|
163
|
+
ui.onToolStart(call.name, (DETAIL[call.name] ?? (() => ""))(args));
|
|
164
|
+
let content;
|
|
165
|
+
try {
|
|
166
|
+
content = await execute(ws, call.name, args, ui), ui.onToolResult(call.name, !0);
|
|
167
|
+
} catch (e) {
|
|
168
|
+
content = `Error: ${e.message}`, ui.onToolResult(call.name, !1, e.message);
|
|
169
|
+
}
|
|
170
|
+
messages.push({ role: "tool", tool_call_id: call.id, content });
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return messages.push({ role: "assistant", content: "I stopped after many steps. Tell me how to continue." }), messages;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// src/sse.ts
|
|
177
|
+
function createSSEParser() {
|
|
178
|
+
let buffer = "";
|
|
179
|
+
return (chunk) => {
|
|
180
|
+
buffer += chunk;
|
|
181
|
+
let blocks = buffer.split(`
|
|
182
|
+
|
|
183
|
+
`);
|
|
184
|
+
return buffer = blocks.pop() ?? "", blocks.filter(Boolean).map((block) => {
|
|
185
|
+
let event = "message", data = [];
|
|
186
|
+
for (let line of block.split(`
|
|
187
|
+
`))
|
|
188
|
+
line.startsWith("event:") ? event = line.slice(6).trim() : line.startsWith("data:") && data.push(line.slice(5).replace(/^ /, ""));
|
|
189
|
+
return { event, data: data.join(`
|
|
190
|
+
`) };
|
|
191
|
+
});
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// src/api.ts
|
|
196
|
+
var FlintError = class extends Error {
|
|
197
|
+
constructor(message, reason = "error", status = 0) {
|
|
198
|
+
super(message);
|
|
199
|
+
this.reason = reason;
|
|
200
|
+
this.status = status;
|
|
201
|
+
}
|
|
202
|
+
reason;
|
|
203
|
+
status;
|
|
204
|
+
}, when = (iso) => iso ? new Date(iso).toLocaleString(void 0, { weekday: "short", hour: "2-digit", minute: "2-digit" }) : "soon";
|
|
205
|
+
async function failure(res, base) {
|
|
206
|
+
let body = await res.json().catch(() => ({})), ref = body.requestId ? ` (ref ${body.requestId})` : "";
|
|
207
|
+
switch (body.reason) {
|
|
208
|
+
case "signed_out":
|
|
209
|
+
return new FlintError("You are signed out. Run: flint login", "signed_out", res.status);
|
|
210
|
+
case "limit":
|
|
211
|
+
return new FlintError(`Out of credits until ${when(body.resetAt)}. Get more at ${base}/pricing`, "limit", res.status);
|
|
212
|
+
case "busy":
|
|
213
|
+
return new FlintError("FlintMaker is very busy right now. Please try again later.", "busy", res.status);
|
|
214
|
+
case "upgrade_required":
|
|
215
|
+
return new FlintError(`The smart model needs a paid plan: ${base}/pricing`, "upgrade_required", res.status);
|
|
216
|
+
case "in_progress":
|
|
217
|
+
return new FlintError("Another flint session is still working. Wait for it to finish.", "in_progress", res.status);
|
|
218
|
+
default:
|
|
219
|
+
return new FlintError(`Request failed (${res.status} ${body.reason ?? ""})${ref}`, body.reason ?? "error", res.status);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
var Client = class {
|
|
223
|
+
constructor(base, token) {
|
|
224
|
+
this.base = base;
|
|
225
|
+
this.token = token;
|
|
226
|
+
}
|
|
227
|
+
base;
|
|
228
|
+
token;
|
|
229
|
+
headers() {
|
|
230
|
+
return { "content-type": "application/json", "user-agent": "flint-cli", ...this.token ? { authorization: `Bearer ${this.token}` } : {} };
|
|
231
|
+
}
|
|
232
|
+
async json(path, init = {}) {
|
|
233
|
+
let res = await fetch(`${this.base}/api${path}`, { ...init, headers: { ...this.headers(), ...init.headers } });
|
|
234
|
+
if (!res.ok) throw await failure(res, this.base);
|
|
235
|
+
return await res.json();
|
|
236
|
+
}
|
|
237
|
+
me() {
|
|
238
|
+
return this.json("/me");
|
|
239
|
+
}
|
|
240
|
+
/** Device login: show the code, wait until the user approves in the browser. */
|
|
241
|
+
async deviceLogin(show) {
|
|
242
|
+
let start = await this.json("/auth/device", {
|
|
243
|
+
method: "POST",
|
|
244
|
+
body: "{}"
|
|
245
|
+
});
|
|
246
|
+
show(start.userCode, start.verifyUrl);
|
|
247
|
+
let deadline = Date.now() + start.expiresIn * 1e3;
|
|
248
|
+
for (; Date.now() < deadline; ) {
|
|
249
|
+
await new Promise((r) => setTimeout(r, start.interval * 1e3));
|
|
250
|
+
let res = await fetch(`${this.base}/api/auth/device/token`, { method: "POST", headers: this.headers(), body: JSON.stringify({ deviceCode: start.deviceCode }) });
|
|
251
|
+
if (res.status !== 428) {
|
|
252
|
+
if (res.ok) return await res.json();
|
|
253
|
+
throw new FlintError("The login code expired. Run flint login again.", "expired", res.status);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
throw new FlintError("The login code expired. Run flint login again.", "expired");
|
|
257
|
+
}
|
|
258
|
+
/** A StepFn bound to this account: one /api/agent call, streamed. */
|
|
259
|
+
stepper(model, signal, onCredits) {
|
|
260
|
+
return async (messages, onDelta) => {
|
|
261
|
+
let res = await fetch(`${this.base}/api/agent`, {
|
|
262
|
+
method: "POST",
|
|
263
|
+
headers: this.headers(),
|
|
264
|
+
body: JSON.stringify({ messages, model: model() }),
|
|
265
|
+
signal: signal()
|
|
266
|
+
});
|
|
267
|
+
if (res.status === 413) throw new ContextTooLarge();
|
|
268
|
+
if (!res.ok || !res.body) throw await failure(res, this.base);
|
|
269
|
+
let parse = createSSEParser(), decoder = new TextDecoder(), text = "", toolCalls = [];
|
|
270
|
+
for await (let chunk of res.body)
|
|
271
|
+
for (let ev of parse(decoder.decode(chunk, { stream: !0 }))) {
|
|
272
|
+
let data = ev.data ? JSON.parse(ev.data) : {};
|
|
273
|
+
if (ev.event === "delta")
|
|
274
|
+
text += data.text, onDelta(data.text);
|
|
275
|
+
else if (ev.event === "tool_call") toolCalls.push(data);
|
|
276
|
+
else if (ev.event === "done") onCredits(data.credits, data.usage);
|
|
277
|
+
else if (ev.event === "error") throw new FlintError(`The model hit a problem; no credits were used.${data.requestId ? ` (ref ${data.requestId})` : ""}`, "model");
|
|
278
|
+
}
|
|
279
|
+
return { text, toolCalls };
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
// src/config.ts
|
|
285
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
286
|
+
import { homedir } from "node:os";
|
|
287
|
+
import { join } from "node:path";
|
|
288
|
+
var DEFAULT_API = "https://flintmaker.com", configDir = () => join(process.env.XDG_CONFIG_HOME || join(homedir(), ".config"), "flintmaker"), authPath = (dir = configDir()) => join(dir, "auth.json");
|
|
289
|
+
function loadAuth(dir = configDir()) {
|
|
290
|
+
try {
|
|
291
|
+
let a = JSON.parse(readFileSync(authPath(dir), "utf8"));
|
|
292
|
+
return typeof a?.token == "string" ? a : null;
|
|
293
|
+
} catch {
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
function saveAuth(a, dir = configDir()) {
|
|
298
|
+
mkdirSync(dir, { recursive: !0, mode: 448 }), writeFileSync(authPath(dir), JSON.stringify(a, null, 2), { mode: 384 }), chmodSync(authPath(dir), 384);
|
|
299
|
+
}
|
|
300
|
+
function clearAuth(dir = configDir()) {
|
|
301
|
+
existsSync(authPath(dir)) && rmSync(authPath(dir));
|
|
302
|
+
}
|
|
303
|
+
function apiBase(auth) {
|
|
304
|
+
return (process.env.FLINT_API || auth?.api || DEFAULT_API).replace(/\/$/, "");
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// src/tools.ts
|
|
308
|
+
import { spawn, execFileSync } from "node:child_process";
|
|
309
|
+
import { existsSync as existsSync2, lstatSync, realpathSync } from "node:fs";
|
|
310
|
+
import { mkdir, readdir, readFile, stat, writeFile } from "node:fs/promises";
|
|
311
|
+
import { basename, dirname, isAbsolute, join as join2, relative, resolve, sep } from "node:path";
|
|
312
|
+
|
|
313
|
+
// node_modules/diff/libesm/diff/base.js
|
|
314
|
+
var Diff = class {
|
|
315
|
+
diff(oldStr, newStr, options = {}) {
|
|
316
|
+
let callback;
|
|
317
|
+
typeof options == "function" ? (callback = options, options = {}) : "callback" in options && (callback = options.callback);
|
|
318
|
+
let oldString = this.castInput(oldStr, options), newString = this.castInput(newStr, options), oldTokens = this.removeEmpty(this.tokenize(oldString, options)), newTokens = this.removeEmpty(this.tokenize(newString, options));
|
|
319
|
+
return this.diffWithOptionsObj(oldTokens, newTokens, options, callback);
|
|
320
|
+
}
|
|
321
|
+
diffWithOptionsObj(oldTokens, newTokens, options, callback) {
|
|
322
|
+
var _a;
|
|
323
|
+
let done = (value) => {
|
|
324
|
+
if (value = this.postProcess(value, options), callback) {
|
|
325
|
+
setTimeout(function() {
|
|
326
|
+
callback(value);
|
|
327
|
+
}, 0);
|
|
328
|
+
return;
|
|
329
|
+
} else
|
|
330
|
+
return value;
|
|
331
|
+
}, newLen = newTokens.length, oldLen = oldTokens.length, editLength = 1, maxEditLength = newLen + oldLen;
|
|
332
|
+
options.maxEditLength != null && (maxEditLength = Math.min(maxEditLength, options.maxEditLength));
|
|
333
|
+
let maxExecutionTime = (_a = options.timeout) !== null && _a !== void 0 ? _a : 1 / 0, abortAfterTimestamp = Date.now() + maxExecutionTime, bestPath = [{ oldPos: -1, lastComponent: void 0 }], newPos = this.extractCommon(bestPath[0], newTokens, oldTokens, 0, options);
|
|
334
|
+
if (bestPath[0].oldPos + 1 >= oldLen && newPos + 1 >= newLen)
|
|
335
|
+
return done(this.buildValues(bestPath[0].lastComponent, newTokens, oldTokens));
|
|
336
|
+
let minDiagonalToConsider = -1 / 0, maxDiagonalToConsider = 1 / 0, execEditLength = () => {
|
|
337
|
+
for (let diagonalPath = Math.max(minDiagonalToConsider, -editLength); diagonalPath <= Math.min(maxDiagonalToConsider, editLength); diagonalPath += 2) {
|
|
338
|
+
let basePath, removePath = bestPath[diagonalPath - 1], addPath = bestPath[diagonalPath + 1];
|
|
339
|
+
removePath && (bestPath[diagonalPath - 1] = void 0);
|
|
340
|
+
let canAdd = !1;
|
|
341
|
+
if (addPath) {
|
|
342
|
+
let addPathNewPos = addPath.oldPos - diagonalPath;
|
|
343
|
+
canAdd = addPath && 0 <= addPathNewPos && addPathNewPos < newLen;
|
|
344
|
+
}
|
|
345
|
+
let canRemove = removePath && removePath.oldPos + 1 < oldLen;
|
|
346
|
+
if (!canAdd && !canRemove) {
|
|
347
|
+
bestPath[diagonalPath] = void 0;
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
if (!canRemove || canAdd && removePath.oldPos < addPath.oldPos ? basePath = this.addToPath(addPath, !0, !1, 0, options) : basePath = this.addToPath(removePath, !1, !0, 1, options), newPos = this.extractCommon(basePath, newTokens, oldTokens, diagonalPath, options), basePath.oldPos + 1 >= oldLen && newPos + 1 >= newLen)
|
|
351
|
+
return done(this.buildValues(basePath.lastComponent, newTokens, oldTokens)) || !0;
|
|
352
|
+
bestPath[diagonalPath] = basePath, basePath.oldPos + 1 >= oldLen && (maxDiagonalToConsider = Math.min(maxDiagonalToConsider, diagonalPath - 1)), newPos + 1 >= newLen && (minDiagonalToConsider = Math.max(minDiagonalToConsider, diagonalPath + 1));
|
|
353
|
+
}
|
|
354
|
+
editLength++;
|
|
355
|
+
};
|
|
356
|
+
if (callback)
|
|
357
|
+
(function exec() {
|
|
358
|
+
setTimeout(function() {
|
|
359
|
+
if (editLength > maxEditLength || Date.now() > abortAfterTimestamp)
|
|
360
|
+
return callback(void 0);
|
|
361
|
+
execEditLength() || exec();
|
|
362
|
+
}, 0);
|
|
363
|
+
})();
|
|
364
|
+
else
|
|
365
|
+
for (; editLength <= maxEditLength && Date.now() <= abortAfterTimestamp; ) {
|
|
366
|
+
let ret = execEditLength();
|
|
367
|
+
if (ret)
|
|
368
|
+
return ret;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
addToPath(path, added, removed, oldPosInc, options) {
|
|
372
|
+
let last = path.lastComponent;
|
|
373
|
+
return last && !options.oneChangePerToken && last.added === added && last.removed === removed ? {
|
|
374
|
+
oldPos: path.oldPos + oldPosInc,
|
|
375
|
+
lastComponent: { count: last.count + 1, added, removed, previousComponent: last.previousComponent }
|
|
376
|
+
} : {
|
|
377
|
+
oldPos: path.oldPos + oldPosInc,
|
|
378
|
+
lastComponent: { count: 1, added, removed, previousComponent: last }
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
extractCommon(basePath, newTokens, oldTokens, diagonalPath, options) {
|
|
382
|
+
let newLen = newTokens.length, oldLen = oldTokens.length, oldPos = basePath.oldPos, newPos = oldPos - diagonalPath, commonCount = 0;
|
|
383
|
+
for (; newPos + 1 < newLen && oldPos + 1 < oldLen && this.equals(oldTokens[oldPos + 1], newTokens[newPos + 1], options); )
|
|
384
|
+
newPos++, oldPos++, commonCount++, options.oneChangePerToken && (basePath.lastComponent = { count: 1, previousComponent: basePath.lastComponent, added: !1, removed: !1 });
|
|
385
|
+
return commonCount && !options.oneChangePerToken && (basePath.lastComponent = { count: commonCount, previousComponent: basePath.lastComponent, added: !1, removed: !1 }), basePath.oldPos = oldPos, newPos;
|
|
386
|
+
}
|
|
387
|
+
equals(left, right, options) {
|
|
388
|
+
return options.comparator ? options.comparator(left, right) : left === right || !!options.ignoreCase && left.toLowerCase() === right.toLowerCase();
|
|
389
|
+
}
|
|
390
|
+
removeEmpty(array) {
|
|
391
|
+
let ret = [];
|
|
392
|
+
for (let i = 0; i < array.length; i++)
|
|
393
|
+
array[i] && ret.push(array[i]);
|
|
394
|
+
return ret;
|
|
395
|
+
}
|
|
396
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
397
|
+
castInput(value, options) {
|
|
398
|
+
return value;
|
|
399
|
+
}
|
|
400
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
401
|
+
tokenize(value, options) {
|
|
402
|
+
return Array.from(value);
|
|
403
|
+
}
|
|
404
|
+
join(chars) {
|
|
405
|
+
return chars.join("");
|
|
406
|
+
}
|
|
407
|
+
postProcess(changeObjects, options) {
|
|
408
|
+
return changeObjects;
|
|
409
|
+
}
|
|
410
|
+
get useLongestToken() {
|
|
411
|
+
return !1;
|
|
412
|
+
}
|
|
413
|
+
buildValues(lastComponent, newTokens, oldTokens) {
|
|
414
|
+
let components = [], nextComponent;
|
|
415
|
+
for (; lastComponent; )
|
|
416
|
+
components.push(lastComponent), nextComponent = lastComponent.previousComponent, delete lastComponent.previousComponent, lastComponent = nextComponent;
|
|
417
|
+
components.reverse();
|
|
418
|
+
let componentLen = components.length, componentPos = 0, newPos = 0, oldPos = 0;
|
|
419
|
+
for (; componentPos < componentLen; componentPos++) {
|
|
420
|
+
let component = components[componentPos];
|
|
421
|
+
if (component.removed)
|
|
422
|
+
component.value = this.join(oldTokens.slice(oldPos, oldPos + component.count)), oldPos += component.count;
|
|
423
|
+
else {
|
|
424
|
+
if (!component.added && this.useLongestToken) {
|
|
425
|
+
let value = newTokens.slice(newPos, newPos + component.count);
|
|
426
|
+
value = value.map(function(value2, i) {
|
|
427
|
+
let oldValue = oldTokens[oldPos + i];
|
|
428
|
+
return oldValue.length > value2.length ? oldValue : value2;
|
|
429
|
+
}), component.value = this.join(value);
|
|
430
|
+
} else
|
|
431
|
+
component.value = this.join(newTokens.slice(newPos, newPos + component.count));
|
|
432
|
+
newPos += component.count, component.added || (oldPos += component.count);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
return components;
|
|
436
|
+
}
|
|
437
|
+
};
|
|
438
|
+
|
|
439
|
+
// node_modules/diff/libesm/diff/line.js
|
|
440
|
+
var LineDiff = class extends Diff {
|
|
441
|
+
constructor() {
|
|
442
|
+
super(...arguments), this.tokenize = tokenize;
|
|
443
|
+
}
|
|
444
|
+
equals(left, right, options) {
|
|
445
|
+
return options.ignoreWhitespace ? ((!options.newlineIsToken || !left.includes(`
|
|
446
|
+
`)) && (left = left.trim()), (!options.newlineIsToken || !right.includes(`
|
|
447
|
+
`)) && (right = right.trim())) : options.ignoreNewlineAtEof && !options.newlineIsToken && (left.endsWith(`
|
|
448
|
+
`) && (left = left.slice(0, -1)), right.endsWith(`
|
|
449
|
+
`) && (right = right.slice(0, -1))), super.equals(left, right, options);
|
|
450
|
+
}
|
|
451
|
+
}, lineDiff = new LineDiff();
|
|
452
|
+
function diffLines(oldStr, newStr, options) {
|
|
453
|
+
return lineDiff.diff(oldStr, newStr, options);
|
|
454
|
+
}
|
|
455
|
+
function tokenize(value, options) {
|
|
456
|
+
options.stripTrailingCr && (value = value.replace(/\r\n/g, `
|
|
457
|
+
`));
|
|
458
|
+
let retLines = [], linesAndNewlines = value.split(/(\n|\r\n)/);
|
|
459
|
+
linesAndNewlines[linesAndNewlines.length - 1] || linesAndNewlines.pop();
|
|
460
|
+
for (let i = 0; i < linesAndNewlines.length; i++) {
|
|
461
|
+
let line = linesAndNewlines[i];
|
|
462
|
+
i % 2 && !options.newlineIsToken ? retLines[retLines.length - 1] += line : retLines.push(line);
|
|
463
|
+
}
|
|
464
|
+
return retLines;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// node_modules/diff/libesm/patch/create.js
|
|
468
|
+
function needsQuoting(s) {
|
|
469
|
+
for (let i = 0; i < s.length; i++)
|
|
470
|
+
if (s[i] < " " || s[i] > "~" || s[i] === '"' || s[i] === "\\")
|
|
471
|
+
return !0;
|
|
472
|
+
return !1;
|
|
473
|
+
}
|
|
474
|
+
function quoteFileNameIfNeeded(s) {
|
|
475
|
+
if (!needsQuoting(s))
|
|
476
|
+
return s;
|
|
477
|
+
let result = '"', bytes = new TextEncoder().encode(s), i = 0;
|
|
478
|
+
for (; i < bytes.length; ) {
|
|
479
|
+
let b = bytes[i];
|
|
480
|
+
b === 7 ? result += "\\a" : b === 8 ? result += "\\b" : b === 9 ? result += "\\t" : b === 10 ? result += "\\n" : b === 11 ? result += "\\v" : b === 12 ? result += "\\f" : b === 13 ? result += "\\r" : b === 34 ? result += '\\"' : b === 92 ? result += "\\\\" : b >= 32 && b <= 126 ? result += String.fromCharCode(b) : result += "\\" + b.toString(8).padStart(3, "0"), i++;
|
|
481
|
+
}
|
|
482
|
+
return result += '"', result;
|
|
483
|
+
}
|
|
484
|
+
var INCLUDE_HEADERS = {
|
|
485
|
+
includeIndex: !0,
|
|
486
|
+
includeUnderline: !0,
|
|
487
|
+
includeFileHeaders: !0
|
|
488
|
+
};
|
|
489
|
+
function structuredPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options) {
|
|
490
|
+
let optionsObj;
|
|
491
|
+
options ? typeof options == "function" ? optionsObj = { callback: options } : optionsObj = options : optionsObj = {}, typeof optionsObj.context > "u" && (optionsObj.context = 4);
|
|
492
|
+
let context = optionsObj.context;
|
|
493
|
+
if (optionsObj.newlineIsToken)
|
|
494
|
+
throw new Error("newlineIsToken may not be used with patch-generation functions, only with diffing functions");
|
|
495
|
+
if (optionsObj.callback) {
|
|
496
|
+
let { callback } = optionsObj;
|
|
497
|
+
diffLines(oldStr, newStr, Object.assign(Object.assign({}, optionsObj), { callback: (diff) => {
|
|
498
|
+
let patch = diffLinesResultToPatch(diff);
|
|
499
|
+
callback(patch);
|
|
500
|
+
} }));
|
|
501
|
+
} else
|
|
502
|
+
return diffLinesResultToPatch(diffLines(oldStr, newStr, optionsObj));
|
|
503
|
+
function diffLinesResultToPatch(diff) {
|
|
504
|
+
if (!diff)
|
|
505
|
+
return;
|
|
506
|
+
diff.push({ value: "", lines: [] });
|
|
507
|
+
function contextLines(lines) {
|
|
508
|
+
return lines.map(function(entry) {
|
|
509
|
+
return " " + entry;
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
let hunks = [], oldRangeStart = 0, newRangeStart = 0, curRange = [], oldLine = 1, newLine = 1;
|
|
513
|
+
for (let i = 0; i < diff.length; i++) {
|
|
514
|
+
let current = diff[i], lines = current.lines || splitLines(current.value);
|
|
515
|
+
if (current.lines = lines, current.added || current.removed) {
|
|
516
|
+
if (!oldRangeStart) {
|
|
517
|
+
let prev = diff[i - 1];
|
|
518
|
+
oldRangeStart = oldLine, newRangeStart = newLine, prev && (curRange = context > 0 ? contextLines(prev.lines.slice(-context)) : [], oldRangeStart -= curRange.length, newRangeStart -= curRange.length);
|
|
519
|
+
}
|
|
520
|
+
for (let line of lines)
|
|
521
|
+
curRange.push((current.added ? "+" : "-") + line);
|
|
522
|
+
current.added ? newLine += lines.length : oldLine += lines.length;
|
|
523
|
+
} else {
|
|
524
|
+
if (oldRangeStart)
|
|
525
|
+
if (lines.length <= context * 2 && i < diff.length - 2)
|
|
526
|
+
for (let line of contextLines(lines))
|
|
527
|
+
curRange.push(line);
|
|
528
|
+
else {
|
|
529
|
+
let contextSize = Math.min(lines.length, context);
|
|
530
|
+
for (let line of contextLines(lines.slice(0, contextSize)))
|
|
531
|
+
curRange.push(line);
|
|
532
|
+
let hunk = {
|
|
533
|
+
oldStart: oldRangeStart,
|
|
534
|
+
oldLines: oldLine - oldRangeStart + contextSize,
|
|
535
|
+
newStart: newRangeStart,
|
|
536
|
+
newLines: newLine - newRangeStart + contextSize,
|
|
537
|
+
lines: curRange
|
|
538
|
+
};
|
|
539
|
+
hunks.push(hunk), oldRangeStart = 0, newRangeStart = 0, curRange = [];
|
|
540
|
+
}
|
|
541
|
+
oldLine += lines.length, newLine += lines.length;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
for (let hunk of hunks)
|
|
545
|
+
for (let i = 0; i < hunk.lines.length; i++)
|
|
546
|
+
hunk.lines[i].endsWith(`
|
|
547
|
+
`) ? hunk.lines[i] = hunk.lines[i].slice(0, -1) : (hunk.lines.splice(i + 1, 0, "\"), i++);
|
|
548
|
+
return {
|
|
549
|
+
oldFileName,
|
|
550
|
+
newFileName,
|
|
551
|
+
oldHeader,
|
|
552
|
+
newHeader,
|
|
553
|
+
hunks
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
function formatPatch(patch, headerOptions) {
|
|
558
|
+
var _a, _b, _c, _d, _e, _f;
|
|
559
|
+
if (headerOptions || (headerOptions = INCLUDE_HEADERS), Array.isArray(patch)) {
|
|
560
|
+
if (patch.length > 1 && !headerOptions.includeFileHeaders && !patch.every((p) => p.isGit))
|
|
561
|
+
throw new Error("Cannot omit file headers on a multi-file patch. (The result would be unparseable; how would a tool trying to apply the patch know which changes are to which file?)");
|
|
562
|
+
return patch.map((p) => formatPatch(p, headerOptions)).join(`
|
|
563
|
+
`);
|
|
564
|
+
}
|
|
565
|
+
let ret = [];
|
|
566
|
+
if (patch.isGit) {
|
|
567
|
+
if (headerOptions = INCLUDE_HEADERS, !patch.oldFileName)
|
|
568
|
+
throw new Error("oldFileName must be specified for Git patches");
|
|
569
|
+
if (!patch.newFileName)
|
|
570
|
+
throw new Error("newFileName must be specified for Git patches");
|
|
571
|
+
let gitOldName = patch.oldFileName, gitNewName = patch.newFileName;
|
|
572
|
+
patch.isCreate && gitOldName === "/dev/null" ? gitOldName = gitNewName.replace(/^b\//, "a/") : patch.isDelete && gitNewName === "/dev/null" && (gitNewName = gitOldName.replace(/^a\//, "b/")), ret.push("diff --git " + quoteFileNameIfNeeded(gitOldName) + " " + quoteFileNameIfNeeded(gitNewName)), patch.isDelete && ret.push("deleted file mode " + ((_a = patch.oldMode) !== null && _a !== void 0 ? _a : "100644")), patch.isCreate && ret.push("new file mode " + ((_b = patch.newMode) !== null && _b !== void 0 ? _b : "100644")), patch.oldMode && patch.newMode && !patch.isDelete && !patch.isCreate && (ret.push("old mode " + patch.oldMode), ret.push("new mode " + patch.newMode)), patch.isRename && (ret.push("rename from " + quoteFileNameIfNeeded(((_c = patch.oldFileName) !== null && _c !== void 0 ? _c : "").replace(/^a\//, ""))), ret.push("rename to " + quoteFileNameIfNeeded(((_d = patch.newFileName) !== null && _d !== void 0 ? _d : "").replace(/^b\//, "")))), patch.isCopy && (ret.push("copy from " + quoteFileNameIfNeeded(((_e = patch.oldFileName) !== null && _e !== void 0 ? _e : "").replace(/^a\//, ""))), ret.push("copy to " + quoteFileNameIfNeeded(((_f = patch.newFileName) !== null && _f !== void 0 ? _f : "").replace(/^b\//, ""))));
|
|
573
|
+
} else
|
|
574
|
+
headerOptions.includeIndex && patch.oldFileName == patch.newFileName && patch.oldFileName !== void 0 && ret.push("Index: " + patch.oldFileName), headerOptions.includeUnderline && ret.push("===================================================================");
|
|
575
|
+
let hasHunks = patch.hunks.length > 0;
|
|
576
|
+
headerOptions.includeFileHeaders && patch.oldFileName !== void 0 && patch.newFileName !== void 0 && (!patch.isGit || hasHunks) && (ret.push("--- " + quoteFileNameIfNeeded(patch.oldFileName) + (patch.oldHeader ? " " + patch.oldHeader : "")), ret.push("+++ " + quoteFileNameIfNeeded(patch.newFileName) + (patch.newHeader ? " " + patch.newHeader : "")));
|
|
577
|
+
for (let i = 0; i < patch.hunks.length; i++) {
|
|
578
|
+
let hunk = patch.hunks[i], oldStart = hunk.oldLines === 0 ? hunk.oldStart - 1 : hunk.oldStart, newStart = hunk.newLines === 0 ? hunk.newStart - 1 : hunk.newStart;
|
|
579
|
+
ret.push("@@ -" + oldStart + "," + hunk.oldLines + " +" + newStart + "," + hunk.newLines + " @@");
|
|
580
|
+
for (let line of hunk.lines)
|
|
581
|
+
ret.push(line);
|
|
582
|
+
}
|
|
583
|
+
return ret.join(`
|
|
584
|
+
`) + `
|
|
585
|
+
`;
|
|
586
|
+
}
|
|
587
|
+
function createTwoFilesPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options) {
|
|
588
|
+
if (typeof options == "function" && (options = { callback: options }), options?.callback) {
|
|
589
|
+
let { callback } = options;
|
|
590
|
+
structuredPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, Object.assign(Object.assign({}, options), { callback: (patchObj) => {
|
|
591
|
+
callback(patchObj ? formatPatch(patchObj, options.headerOptions) : void 0);
|
|
592
|
+
} }));
|
|
593
|
+
} else {
|
|
594
|
+
let patchObj = structuredPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options);
|
|
595
|
+
return patchObj ? formatPatch(patchObj, options?.headerOptions) : void 0;
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
function splitLines(text) {
|
|
599
|
+
let hasTrailingNl = text.endsWith(`
|
|
600
|
+
`), result = text.split(`
|
|
601
|
+
`).map((line) => line + `
|
|
602
|
+
`);
|
|
603
|
+
return hasTrailingNl ? result.pop() : result.push(result.pop().slice(0, -1)), result;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// src/tools.ts
|
|
607
|
+
var IGNORED_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", ".next", ".nuxt", ".svelte-kit", "coverage", ".venv", "venv", "__pycache__", ".turbo", ".cache", "Pods", "DerivedData"]), SECRET = /^(\.env(\..+)?|\.envrc|\.dev\.vars|\.netrc|\.git-credentials|\.npmrc|\.pypirc|.*\.(pem|key|p12|pfx|keystore|jks|tfvars|tfstate)|id_(rsa|dsa|ecdsa|ed25519)(\.pub)?|credentials(\.json)?|.*service[-_]?account.*\.json|.*-credentials\.json)$/i, SECRET_OK = /^\.env\.(example|sample|template)$/i, MAX_READ = 1e5, MAX_LIST = 400, MAX_OUTPUT = 12e3, Workspace = class {
|
|
608
|
+
root;
|
|
609
|
+
constructor(root) {
|
|
610
|
+
this.root = realpathSync(root);
|
|
611
|
+
}
|
|
612
|
+
/** Resolve a project-relative path, refusing anything outside the root (incl. via symlinks) and secret files. */
|
|
613
|
+
resolve(path) {
|
|
614
|
+
if (typeof path != "string" || path.trim() === "") throw new Error("path is required");
|
|
615
|
+
let p = path.trim();
|
|
616
|
+
if (isAbsolute(p)) throw new Error(`${p} is outside the project`);
|
|
617
|
+
let abs = resolve(this.root, p);
|
|
618
|
+
if (abs !== this.root && !abs.startsWith(this.root + sep)) throw new Error(`${p} is outside the project`);
|
|
619
|
+
let cur = this.root;
|
|
620
|
+
for (let seg of relative(this.root, abs).split(sep).filter(Boolean)) {
|
|
621
|
+
cur = join2(cur, seg);
|
|
622
|
+
let info;
|
|
623
|
+
try {
|
|
624
|
+
info = lstatSync(cur);
|
|
625
|
+
} catch {
|
|
626
|
+
break;
|
|
627
|
+
}
|
|
628
|
+
if (info.isSymbolicLink()) {
|
|
629
|
+
let real;
|
|
630
|
+
try {
|
|
631
|
+
real = realpathSync(cur);
|
|
632
|
+
} catch {
|
|
633
|
+
throw new Error(`${p} goes through a broken symlink; refusing`);
|
|
634
|
+
}
|
|
635
|
+
if (real !== this.root && !real.startsWith(this.root + sep)) throw new Error(`${p} is outside the project`);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
let name = basename(abs);
|
|
639
|
+
if (SECRET.test(name) && !SECRET_OK.test(name)) throw new Error(`${p} looks like a secret file; flint does not read or change it`);
|
|
640
|
+
return abs;
|
|
641
|
+
}
|
|
642
|
+
rel(abs) {
|
|
643
|
+
return relative(this.root, abs) || ".";
|
|
644
|
+
}
|
|
645
|
+
async listFiles(path = ".") {
|
|
646
|
+
let base = this.resolve(path), files;
|
|
647
|
+
try {
|
|
648
|
+
files = execFileSync("git", ["ls-files", "--cached", "--others", "--exclude-standard", "--", this.rel(base)], {
|
|
649
|
+
cwd: this.root,
|
|
650
|
+
encoding: "utf8",
|
|
651
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
652
|
+
maxBuffer: 16 * 1024 * 1024
|
|
653
|
+
}).split(`
|
|
654
|
+
`).filter(Boolean);
|
|
655
|
+
} catch {
|
|
656
|
+
files = [], await this.walk(base, files);
|
|
657
|
+
}
|
|
658
|
+
return files.sort(), files.slice(0, MAX_LIST).join(`
|
|
659
|
+
`) + (files.length > MAX_LIST ? `
|
|
660
|
+
\u2026 ${files.length - MAX_LIST} more files` : "") || "(empty)";
|
|
661
|
+
}
|
|
662
|
+
async walk(dir, out) {
|
|
663
|
+
if (!(out.length > 5e3))
|
|
664
|
+
for (let entry of await readdir(dir, { withFileTypes: !0 }))
|
|
665
|
+
entry.isDirectory() ? IGNORED_DIRS.has(entry.name) || await this.walk(join2(dir, entry.name), out) : entry.isFile() && out.push(this.rel(join2(dir, entry.name)));
|
|
666
|
+
}
|
|
667
|
+
async readFile(path, start, end) {
|
|
668
|
+
let abs = this.resolve(path), info = await stat(abs);
|
|
669
|
+
if (!info.isFile()) throw new Error(`${path} is not a file`);
|
|
670
|
+
let buf = await readFile(abs);
|
|
671
|
+
if (buf.subarray(0, 8e3).includes(0)) return `[binary file, ${info.size} bytes]`;
|
|
672
|
+
let lines = buf.toString("utf8").split(`
|
|
673
|
+
`);
|
|
674
|
+
lines.at(-1) === "" && lines.pop();
|
|
675
|
+
let from = Math.max(1, start ?? 1), to = Math.min(lines.length, end ?? lines.length), out = "";
|
|
676
|
+
for (let i = from; i <= to; i++) {
|
|
677
|
+
let line = `${i} ${lines[i - 1]}`;
|
|
678
|
+
if (out.length + line.length > MAX_READ) {
|
|
679
|
+
out += `
|
|
680
|
+
[truncated at line ${i - 1} of ${lines.length}; read more with start_line]`;
|
|
681
|
+
break;
|
|
682
|
+
}
|
|
683
|
+
out += (out ? `
|
|
684
|
+
` : "") + line;
|
|
685
|
+
}
|
|
686
|
+
return out;
|
|
687
|
+
}
|
|
688
|
+
async search(pattern, path = ".") {
|
|
689
|
+
let re;
|
|
690
|
+
try {
|
|
691
|
+
re = new RegExp(pattern);
|
|
692
|
+
} catch {
|
|
693
|
+
throw new Error("invalid regular expression");
|
|
694
|
+
}
|
|
695
|
+
let files = (await this.listFiles(path)).split(`
|
|
696
|
+
`).filter((f) => f && !f.startsWith("\u2026 ")), hits = [];
|
|
697
|
+
for (let f of files) {
|
|
698
|
+
if (hits.length >= 200) break;
|
|
699
|
+
let text;
|
|
700
|
+
try {
|
|
701
|
+
let abs = this.resolve(f);
|
|
702
|
+
if ((await stat(abs)).size > 4e5) continue;
|
|
703
|
+
let buf = await readFile(abs);
|
|
704
|
+
if (buf.subarray(0, 8e3).includes(0)) continue;
|
|
705
|
+
text = buf.toString("utf8");
|
|
706
|
+
} catch {
|
|
707
|
+
continue;
|
|
708
|
+
}
|
|
709
|
+
text.split(`
|
|
710
|
+
`).forEach((line, i) => {
|
|
711
|
+
hits.length < 200 && re.test(line) && hits.push(`${f}:${i + 1}:${line.slice(0, 300)}`);
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
return hits.join(`
|
|
715
|
+
`) || "No matches.";
|
|
716
|
+
}
|
|
717
|
+
/** Prepare a file change and its diff. Nothing is written until apply(). */
|
|
718
|
+
async plan(tool, args) {
|
|
719
|
+
let abs = this.resolve(args.path), path = this.rel(abs), before = existsSync2(abs) ? await readFile(abs, "utf8") : null, after;
|
|
720
|
+
if (tool === "write_file") {
|
|
721
|
+
if (typeof args.content != "string") throw new Error("content is required");
|
|
722
|
+
after = args.content;
|
|
723
|
+
} else {
|
|
724
|
+
if (before === null) throw new Error(`${path} does not exist`);
|
|
725
|
+
let oldText = args.old_text, newText = args.new_text;
|
|
726
|
+
if (typeof oldText != "string" || oldText === "" || typeof newText != "string") throw new Error("old_text and new_text are required");
|
|
727
|
+
let count = before.split(oldText).length - 1;
|
|
728
|
+
if (count === 0) throw new Error(`old_text not found in ${path}`);
|
|
729
|
+
if (count > 1) throw new Error(`old_text matches ${count} times in ${path}; include more context`);
|
|
730
|
+
after = before.replace(oldText, () => newText);
|
|
731
|
+
}
|
|
732
|
+
let diff = createTwoFilesPatch(`a/${path}`, `b/${path}`, before ?? "", after, "", "", { context: 3 });
|
|
733
|
+
return {
|
|
734
|
+
path,
|
|
735
|
+
diff,
|
|
736
|
+
created: before === null,
|
|
737
|
+
apply: async () => {
|
|
738
|
+
await mkdir(dirname(abs), { recursive: !0 }), await writeFile(abs, after);
|
|
739
|
+
}
|
|
740
|
+
};
|
|
741
|
+
}
|
|
742
|
+
runCommand(command, timeoutMs = 12e4) {
|
|
743
|
+
return new Promise((resolvePromise) => {
|
|
744
|
+
let child = spawn("/bin/sh", ["-c", command], { cwd: this.root, detached: !0, env: { ...process.env, FORCE_COLOR: "0", CI: "1" } }), head = "", tail = "", dropped = 0, take = (chunk) => {
|
|
745
|
+
let s = chunk.toString("utf8");
|
|
746
|
+
if (head.length < 4e3) {
|
|
747
|
+
let room = 4e3 - head.length;
|
|
748
|
+
head += s.slice(0, room), tail += s.slice(room);
|
|
749
|
+
} else tail += s;
|
|
750
|
+
tail.length > 8e3 && (dropped += tail.length - 8e3, tail = tail.slice(-8e3));
|
|
751
|
+
};
|
|
752
|
+
child.stdout.on("data", take), child.stderr.on("data", take);
|
|
753
|
+
let timedOut = !1, timer = setTimeout(() => {
|
|
754
|
+
timedOut = !0;
|
|
755
|
+
try {
|
|
756
|
+
process.kill(-child.pid, "SIGKILL");
|
|
757
|
+
} catch {
|
|
758
|
+
child.kill("SIGKILL");
|
|
759
|
+
}
|
|
760
|
+
}, timeoutMs);
|
|
761
|
+
child.on("close", (code) => {
|
|
762
|
+
clearTimeout(timer);
|
|
763
|
+
let body = head + (dropped ? `
|
|
764
|
+
[\u2026 ${dropped} characters omitted \u2026]
|
|
765
|
+
` : "") + tail, status = timedOut ? `[timed out after ${Math.round(timeoutMs / 1e3)}s]` : `[exit code ${code}]`;
|
|
766
|
+
resolvePromise(`${body.slice(0, MAX_OUTPUT)}
|
|
767
|
+
${status}`.trim());
|
|
768
|
+
}), child.on("error", (err) => {
|
|
769
|
+
clearTimeout(timer), resolvePromise(`[could not run command: ${err.message}]`);
|
|
770
|
+
});
|
|
771
|
+
});
|
|
772
|
+
}
|
|
773
|
+
};
|
|
774
|
+
|
|
775
|
+
// src/ui.ts
|
|
776
|
+
var import_picocolors = __toESM(require_picocolors(), 1);
|
|
777
|
+
function sanitize(text) {
|
|
778
|
+
return text.replace(/\x1b\][^\x07\x1b]*(\x07|\x1b\\)?/g, "").replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "").replace(/\x1b[@-Z\\-_]/g, "").replace(/[\x00-\x08\x0b-\x1f\x7f-\x9f]/g, "");
|
|
779
|
+
}
|
|
780
|
+
var truecolor = /truecolor|24bit/i.test(process.env.COLORTERM ?? "") && import_picocolors.default.isColorSupported, FIRE = [
|
|
781
|
+
[123, 47, 247],
|
|
782
|
+
[232, 51, 107],
|
|
783
|
+
[255, 122, 26],
|
|
784
|
+
[255, 194, 74]
|
|
785
|
+
];
|
|
786
|
+
function fire(text) {
|
|
787
|
+
if (!truecolor) return import_picocolors.default.bold(import_picocolors.default.yellow(text));
|
|
788
|
+
let n = Math.max(1, text.length - 1);
|
|
789
|
+
return [...text].map((ch, i) => {
|
|
790
|
+
let t = i / n * (FIRE.length - 1), a = FIRE[Math.floor(t)], b = FIRE[Math.min(FIRE.length - 1, Math.floor(t) + 1)], f = t - Math.floor(t), [r, g, bl] = a.map((v, k) => Math.round(v + (b[k] - v) * f));
|
|
791
|
+
return `\x1B[1;38;2;${r};${g};${bl}m${ch}\x1B[0m`;
|
|
792
|
+
}).join("");
|
|
793
|
+
}
|
|
794
|
+
var ember = (s) => truecolor ? `\x1B[38;2;255;122;26m${s}\x1B[0m` : import_picocolors.default.yellow(s), crimson = (s) => truecolor ? `\x1B[38;2;232;51;107m${s}\x1B[0m` : import_picocolors.default.magenta(s), dim = import_picocolors.default.dim, bold = import_picocolors.default.bold;
|
|
795
|
+
function banner(version, lines) {
|
|
796
|
+
console.log(), console.log(` ${ember("\u2726")} ${fire("flintmaker")} ${dim(`v${version}`)}`);
|
|
797
|
+
for (let l of lines) console.log(` ${dim(l)}`);
|
|
798
|
+
console.log();
|
|
799
|
+
}
|
|
800
|
+
var MarkdownStream = class {
|
|
801
|
+
buf = "";
|
|
802
|
+
inCode = !1;
|
|
803
|
+
started = !1;
|
|
804
|
+
write(raw) {
|
|
805
|
+
let text = sanitize(raw);
|
|
806
|
+
this.started || (process.stdout.write(`
|
|
807
|
+
`), this.started = !0), this.buf += text;
|
|
808
|
+
let nl;
|
|
809
|
+
for (; (nl = this.buf.indexOf(`
|
|
810
|
+
`)) >= 0; )
|
|
811
|
+
this.line(this.buf.slice(0, nl)), this.buf = this.buf.slice(nl + 1);
|
|
812
|
+
}
|
|
813
|
+
end() {
|
|
814
|
+
this.buf && this.line(this.buf), this.buf = "", this.started && process.stdout.write(`
|
|
815
|
+
`), this.started = !1, this.inCode = !1;
|
|
816
|
+
}
|
|
817
|
+
line(l) {
|
|
818
|
+
if (/^\s*```/.test(l)) {
|
|
819
|
+
this.inCode = !this.inCode, process.stdout.write(dim(this.inCode ? ` \u250C\u2500 ${l.replace(/^\s*```/, "") || "code"}` : " \u2514\u2500") + `
|
|
820
|
+
`);
|
|
821
|
+
return;
|
|
822
|
+
}
|
|
823
|
+
if (this.inCode) return void process.stdout.write(` ${dim("\u2502")} ${import_picocolors.default.cyan(l)}
|
|
824
|
+
`);
|
|
825
|
+
let out = l.replace(/`([^`]+)`/g, (_, c) => import_picocolors.default.cyan(c)).replace(/\*\*([^*]+)\*\*/g, (_, b) => bold(b));
|
|
826
|
+
/^#{1,3} /.test(l) ? out = bold(ember(l.replace(/^#{1,3} /, ""))) : /^\s*[-*] /.test(l) && (out = out.replace(/^(\s*)[-*] /, (_, s) => `${s}${ember("\u2022")} `)), process.stdout.write(` ${out}
|
|
827
|
+
`);
|
|
828
|
+
}
|
|
829
|
+
}, Spinner = class {
|
|
830
|
+
timer;
|
|
831
|
+
i = 0;
|
|
832
|
+
start(label = "thinking") {
|
|
833
|
+
if (!process.stdout.isTTY || this.timer) return;
|
|
834
|
+
let frames = ["\u2726", "\u2727", "\u22C6", "\u2727"];
|
|
835
|
+
this.timer = setInterval(() => {
|
|
836
|
+
process.stdout.write(`\r ${ember(frames[this.i++ % frames.length])} ${dim(label + "\u2026")} `);
|
|
837
|
+
}, 120);
|
|
838
|
+
}
|
|
839
|
+
stop() {
|
|
840
|
+
this.timer && (clearInterval(this.timer), this.timer = void 0, process.stdout.write("\r\x1B[2K"));
|
|
841
|
+
}
|
|
842
|
+
};
|
|
843
|
+
function printDiff(diff) {
|
|
844
|
+
let lines = sanitize(diff).split(`
|
|
845
|
+
`).slice(2);
|
|
846
|
+
for (let l of lines)
|
|
847
|
+
l.startsWith("===") || l.startsWith("Index:") || (l.startsWith("+++") || l.startsWith("---") ? console.log(dim(` ${l}`)) : l.startsWith("+") ? console.log(import_picocolors.default.green(` ${l}`)) : l.startsWith("-") ? console.log(import_picocolors.default.red(` ${l}`)) : l.startsWith("@@") ? console.log(import_picocolors.default.cyan(` ${l}`)) : console.log(dim(` ${l}`)));
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
// src/index.ts
|
|
851
|
+
var VERSION = "0.1.0", HELP = `
|
|
852
|
+
${bold("flint")} \u2014 the FlintMaker coding agent for your terminal
|
|
853
|
+
|
|
854
|
+
${bold("Usage")}
|
|
855
|
+
flint start an interactive session in this folder
|
|
856
|
+
flint "task" do one task, then exit
|
|
857
|
+
flint login connect this computer to your FlintMaker account
|
|
858
|
+
flint logout disconnect
|
|
859
|
+
flint whoami show account and credits
|
|
860
|
+
|
|
861
|
+
${bold("Options")}
|
|
862
|
+
--smart use the smart model (paid plans, more credits)
|
|
863
|
+
--yes approve every change and command (careful!)
|
|
864
|
+
--help, --version
|
|
865
|
+
|
|
866
|
+
${bold("In a session")}
|
|
867
|
+
/help /credits /smart /standard /clear /exit
|
|
868
|
+
`;
|
|
869
|
+
function parseArgs(argv) {
|
|
870
|
+
let flags = new Set(argv.filter((a) => a.startsWith("--"))), rest = argv.filter((a) => !a.startsWith("--"));
|
|
871
|
+
return { flags, rest };
|
|
872
|
+
}
|
|
873
|
+
function openBrowser(url) {
|
|
874
|
+
if (!/^https:\/\/[\w.-]+\/device(\?[\w=&%-]*)?$/.test(url)) return;
|
|
875
|
+
let [cmd, args] = process.platform === "darwin" ? ["open", [url]] : process.platform === "win32" ? ["rundll32", ["url.dll,FileProtocolHandler", url]] : ["xdg-open", [url]];
|
|
876
|
+
try {
|
|
877
|
+
spawn2(cmd, args, { stdio: "ignore", detached: !0 }).unref();
|
|
878
|
+
} catch {
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
var shortPath = (p) => p.startsWith(homedir2()) ? `~${p.slice(homedir2().length)}` : p, creditsLine = (u) => u.creditsLeftToday !== null ? `${u.creditsLeftToday} credits left today \xB7 ${u.creditsLeftMonth} this month` : `${u.creditsLeftMonth.toLocaleString("en-US")} of ${u.monthlyCredits.toLocaleString("en-US")} credits left`;
|
|
882
|
+
async function login() {
|
|
883
|
+
let client = new Client(apiBase(null));
|
|
884
|
+
console.log();
|
|
885
|
+
let result = await client.deviceLogin((code, url) => {
|
|
886
|
+
console.log(` ${ember("\u2726")} Your code: ${bold(code)}`), console.log(` ${dim("Opening")} ${url}`), console.log(` ${dim("Approve it in the browser. Waiting\u2026")}`), openBrowser(url);
|
|
887
|
+
});
|
|
888
|
+
saveAuth({ token: result.token, email: result.user.email, api: client.base }), console.log(`
|
|
889
|
+
${ember("\u2713")} Signed in as ${bold(result.user.email)}. Run ${bold("flint")} in any project folder.
|
|
890
|
+
`);
|
|
891
|
+
}
|
|
892
|
+
async function main() {
|
|
893
|
+
let { flags, rest } = parseArgs(process.argv.slice(2));
|
|
894
|
+
if (flags.has("--version")) return console.log(VERSION);
|
|
895
|
+
if (flags.has("--help") || rest[0] === "help") return console.log(HELP);
|
|
896
|
+
if (rest[0] === "login") return login();
|
|
897
|
+
if (rest[0] === "logout")
|
|
898
|
+
return clearAuth(), console.log(`
|
|
899
|
+
Signed out. ${dim("Disconnect old terminals any time on your account page.")}
|
|
900
|
+
`);
|
|
901
|
+
if (!loadAuth() && (console.log(`
|
|
902
|
+
${ember("\u2726")} Welcome to flint. First, connect your FlintMaker account:
|
|
903
|
+
`), await login(), rest.length === 0 && !process.stdin.isTTY))
|
|
904
|
+
return;
|
|
905
|
+
let saved = loadAuth(), base = apiBase(saved);
|
|
906
|
+
if (base !== saved.api) throw new FlintError(`This token belongs to ${saved.api}. Run flint login to use ${base}.`);
|
|
907
|
+
let client = new Client(base, saved.token);
|
|
908
|
+
if (rest[0] === "whoami") {
|
|
909
|
+
let me2 = await client.me();
|
|
910
|
+
return console.log(`
|
|
911
|
+
${bold(me2.user.email)} \xB7 ${me2.user.plan} \xB7 ${creditsLine(me2.usage)}
|
|
912
|
+
`);
|
|
913
|
+
}
|
|
914
|
+
let ws = new Workspace(process.cwd()), model = flags.has("--smart") ? "smart" : "standard", autoYes = flags.has("--yes"), rl = process.stdin.isTTY && process.stdout.isTTY ? createInterface({ input: process.stdin, output: process.stdout, terminal: !0 }) : null, alwaysChanges = autoYes, alwaysCommands = autoYes, controller, lastUsage = null, ask = async (q, defaultYes) => {
|
|
915
|
+
if (!rl) return "n";
|
|
916
|
+
let a = (await rl.question(q)).trim().toLowerCase();
|
|
917
|
+
return a === "" ? defaultYes ? "y" : "n" : a === "a" ? "a" : a === "y" || a === "yes" ? "y" : "n";
|
|
918
|
+
}, md = new MarkdownStream(), spinner = new Spinner(), ui = {
|
|
919
|
+
onDelta: (t) => {
|
|
920
|
+
spinner.stop(), md.write(t);
|
|
921
|
+
},
|
|
922
|
+
onToolStart: (name, detail) => {
|
|
923
|
+
spinner.stop(), md.end();
|
|
924
|
+
let d = sanitize(detail);
|
|
925
|
+
console.log(` ${ember("\u23FA")} ${bold(name.replace("_", " "))} ${dim(name === "run_command" || d.length <= 80 ? d : `${d.slice(0, 77)}\u2026`)}`);
|
|
926
|
+
},
|
|
927
|
+
onToolResult: (_name, ok, summary) => {
|
|
928
|
+
ok || console.log(` ${crimson("\u2717")} ${dim(sanitize(summary ?? "failed"))}`), spinner.start("working");
|
|
929
|
+
},
|
|
930
|
+
approveChange: async (change) => {
|
|
931
|
+
if (spinner.stop(), printDiff(change.diff), alwaysChanges) return !0;
|
|
932
|
+
let a = await ask(` ${change.created ? "Create" : "Apply changes to"} ${bold(sanitize(change.path))}? ${dim("[Y]es / [n]o / [a]lways this session")} `, !0);
|
|
933
|
+
return a === "a" && (alwaysChanges = !0), a !== "n";
|
|
934
|
+
},
|
|
935
|
+
approveCommand: async (command) => {
|
|
936
|
+
if (spinner.stop(), alwaysCommands) return !0;
|
|
937
|
+
let a = await ask(` Run ${bold(JSON.stringify(sanitize(command)))}? ${dim("[y]es / [N]o / [a]lways this session")} `, !1);
|
|
938
|
+
return a === "a" && (alwaysCommands = !0), a !== "n";
|
|
939
|
+
}
|
|
940
|
+
}, step = client.stepper(
|
|
941
|
+
() => model,
|
|
942
|
+
() => controller?.signal,
|
|
943
|
+
(_credits, usage) => {
|
|
944
|
+
lastUsage = usage;
|
|
945
|
+
}
|
|
946
|
+
), history = [], run = async (task) => {
|
|
947
|
+
controller = new AbortController(), spinner.start();
|
|
948
|
+
try {
|
|
949
|
+
history = await runAgent(ws, [...history, { role: "user", content: task }], step, ui);
|
|
950
|
+
} catch (e) {
|
|
951
|
+
spinner.stop(), md.end(), e.name === "AbortError" ? console.log(dim(`
|
|
952
|
+
Stopped.`)) : console.log(`
|
|
953
|
+
${crimson("\u2717")} ${e.message}`), e instanceof FlintError && e.reason === "signed_out" && clearAuth();
|
|
954
|
+
} finally {
|
|
955
|
+
spinner.stop(), md.end(), controller = void 0;
|
|
956
|
+
}
|
|
957
|
+
lastUsage && console.log(dim(` ${creditsLine(lastUsage)}`));
|
|
958
|
+
};
|
|
959
|
+
if (rest.length > 0) {
|
|
960
|
+
await run(rest.join(" ")), rl?.close();
|
|
961
|
+
return;
|
|
962
|
+
}
|
|
963
|
+
let me = await client.me().catch((e) => {
|
|
964
|
+
throw e;
|
|
965
|
+
});
|
|
966
|
+
if (lastUsage = me.usage, banner(VERSION, [`${me.user.email} \xB7 ${me.user.plan} plan \xB7 ${creditsLine(me.usage)}`, shortPath(ws.root), "Describe a task. /help for commands, Ctrl+C to stop."]), !rl) throw new FlintError('Interactive mode needs a terminal. Pass a task: flint "fix the failing test"');
|
|
967
|
+
for (rl.on("SIGINT", () => {
|
|
968
|
+
controller ? controller.abort() : (console.log(dim(`
|
|
969
|
+
Bye.`)), process.exit(0));
|
|
970
|
+
}); ; ) {
|
|
971
|
+
let line = (await rl.question(`${crimson("\u276F")} `)).trim();
|
|
972
|
+
if (line) {
|
|
973
|
+
if (line === "/exit" || line === "/quit") break;
|
|
974
|
+
if (line === "/help") {
|
|
975
|
+
console.log(HELP);
|
|
976
|
+
continue;
|
|
977
|
+
}
|
|
978
|
+
if (line === "/clear") {
|
|
979
|
+
history = [], console.log(dim(" New conversation."));
|
|
980
|
+
continue;
|
|
981
|
+
}
|
|
982
|
+
if (line === "/smart" || line === "/standard") {
|
|
983
|
+
model = line.slice(1), console.log(dim(` Model: ${model}`));
|
|
984
|
+
continue;
|
|
985
|
+
}
|
|
986
|
+
if (line === "/credits") {
|
|
987
|
+
let u = (await client.me()).usage;
|
|
988
|
+
console.log(dim(` ${creditsLine(u)}`));
|
|
989
|
+
continue;
|
|
990
|
+
}
|
|
991
|
+
await run(line);
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
rl.close();
|
|
995
|
+
}
|
|
996
|
+
main().catch((e) => {
|
|
997
|
+
console.error(`
|
|
998
|
+
${crimson("\u2717")} ${e.message}
|
|
999
|
+
`), process.exit(1);
|
|
1000
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,33 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flintmaker",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "flint — the FlintMaker AI coding agent for your terminal.",
|
|
5
|
+
"license": "UNLICENSED",
|
|
6
|
+
"author": "PT Jiji Labs Digital",
|
|
7
|
+
"homepage": "https://flintmaker.com",
|
|
8
|
+
"type": "module",
|
|
9
|
+
"bin": {
|
|
10
|
+
"flint": "dist/flint.js"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"dist",
|
|
14
|
+
"README.md"
|
|
15
|
+
],
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=20"
|
|
18
|
+
},
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "esbuild src/index.ts --bundle --platform=node --format=esm --target=node20 --outfile=dist/flint.js --banner:js='#!/usr/bin/env node' --minify-syntax",
|
|
21
|
+
"test": "vitest run",
|
|
22
|
+
"typecheck": "tsc --noEmit"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@types/diff": "^7.0.2",
|
|
26
|
+
"@types/node": "^22.20.5",
|
|
27
|
+
"diff": "^9.0.0",
|
|
28
|
+
"esbuild": "^0.28.2",
|
|
29
|
+
"picocolors": "^1.1.1",
|
|
30
|
+
"typescript": "^7.0.2",
|
|
31
|
+
"vitest": "^5.0.4"
|
|
32
|
+
}
|
|
33
|
+
}
|