@aayambansal/squint 0.8.2 → 0.9.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.
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  App
4
- } from "./chunk-PPC2KM7P.js";
5
- import "./chunk-B356UR44.js";
4
+ } from "./chunk-IANXZQ6Z.js";
5
+ import "./chunk-ZPX5FADM.js";
6
6
  import "./chunk-YKEAKVIS.js";
7
7
  import "./chunk-LNJ7O5O4.js";
8
8
  import "./chunk-3ZHETLUC.js";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  Session
4
- } from "./chunk-B356UR44.js";
4
+ } from "./chunk-ZPX5FADM.js";
5
5
  import {
6
6
  completeCommand
7
7
  } from "./chunk-LNJ7O5O4.js";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  Session
4
- } from "./chunk-B356UR44.js";
4
+ } from "./chunk-ZPX5FADM.js";
5
5
 
6
6
  // src/daemon/client.ts
7
7
  import net from "net";
@@ -57,7 +57,7 @@ function socketPath(cwd) {
57
57
  const hash = crypto.createHash("sha256").update(cwd).digest("hex").slice(0, 12);
58
58
  return path.join(os.tmpdir(), `squint-${hash}.sock`);
59
59
  }
60
- function startDaemon(opts) {
60
+ async function startDaemon(opts) {
61
61
  const session = new Session(opts);
62
62
  const sock = socketPath(opts.cwd);
63
63
  fs.mkdirSync(path.dirname(sock), { recursive: true });
@@ -132,6 +132,54 @@ ${failures.join("\n")}`);
132
132
  }
133
133
  }, opts.intervalSweepMs ?? 6e4);
134
134
  sweep.unref?.();
135
+ let relayServer = null;
136
+ let relayUrl;
137
+ let relayUnsub = null;
138
+ if (opts.approvalWebhook) {
139
+ const http = await import("http");
140
+ const crypto2 = await import("crypto");
141
+ const tokens = /* @__PURE__ */ new Map();
142
+ relayServer = http.createServer((req, res) => {
143
+ const token = (req.url ?? "").replace(/^\//, "");
144
+ const verdict = tokens.get(token);
145
+ if (!verdict) {
146
+ res.writeHead(404).end("unknown or already-used token");
147
+ return;
148
+ }
149
+ tokens.clear();
150
+ session.command(verdict === "yes" ? "/yes approved via webhook" : "/no rejected via webhook");
151
+ res.writeHead(200, { "content-type": "text/plain" });
152
+ res.end(verdict === "yes" ? "approved \u2014 the engine proceeds" : "rejected \u2014 the engine stands down");
153
+ });
154
+ await new Promise((resolve) => relayServer?.listen(opts.relayPort ?? 0, "127.0.0.1", resolve));
155
+ const addr = relayServer.address();
156
+ relayUrl = `http://127.0.0.1:${addr.port}`;
157
+ let lastSeen = null;
158
+ relayUnsub = session.subscribe(() => {
159
+ const pending = session.getState().pendingApproval;
160
+ if (!pending || pending === lastSeen) {
161
+ if (!pending) lastSeen = null;
162
+ return;
163
+ }
164
+ lastSeen = pending;
165
+ const approve = crypto2.randomBytes(16).toString("hex");
166
+ const reject = crypto2.randomBytes(16).toString("hex");
167
+ tokens.clear();
168
+ tokens.set(approve, "yes");
169
+ tokens.set(reject, "no");
170
+ fetch(opts.approvalWebhook, {
171
+ method: "POST",
172
+ headers: { "content-type": "application/json" },
173
+ body: JSON.stringify({
174
+ text: `squint approval requested: ${pending}`,
175
+ summary: pending,
176
+ approveUrl: `${relayUrl}/${approve}`,
177
+ rejectUrl: `${relayUrl}/${reject}`
178
+ }),
179
+ signal: AbortSignal.timeout(5e3)
180
+ }).catch(() => null);
181
+ });
182
+ }
135
183
  const unsubscribe = session.subscribe(() => {
136
184
  const payload = serialize();
137
185
  for (const client of clients) {
@@ -143,9 +191,12 @@ ${failures.join("\n")}`);
143
191
  server.listen(sock, () => {
144
192
  resolve({
145
193
  session,
194
+ relayUrl,
146
195
  clientCount: () => clients.length,
147
196
  close: () => {
148
197
  clearInterval(sweep);
198
+ relayUnsub?.();
199
+ relayServer?.close();
149
200
  unsubscribe();
150
201
  for (const client of clients) client.destroy();
151
202
  server.close();
@@ -117,6 +117,7 @@ var Session = class {
117
117
  engineId: opts.engineId,
118
118
  model: opts.model,
119
119
  devState: "stopped",
120
+ pendingApproval: null,
120
121
  devUrl: null,
121
122
  totals: { costUsd: 0, turns: 0 },
122
123
  queue: [],
@@ -155,7 +156,6 @@ var Session = class {
155
156
  checkpoints = [];
156
157
  fixAttempts = 0;
157
158
  reviewTipShown = false;
158
- pendingApproval = null;
159
159
  goal = null;
160
160
  laneEnabled = false;
161
161
  inLane = false;
@@ -496,7 +496,7 @@ ${question}`,
496
496
  const req = JSON.parse(fs2.readFileSync(reqPath, "utf8"));
497
497
  fs2.rmSync(reqPath, { force: true });
498
498
  if (typeof req?.summary === "string" && req.summary.length > 0) {
499
- this.pendingApproval = req.summary;
499
+ this.notify({ pendingApproval: req.summary });
500
500
  const shot = typeof req.screenshot === "string" ? path2.resolve(this.execCwd(), req.screenshot) : null;
501
501
  if (shot && fs2.existsSync(shot)) this.push("image", shot);
502
502
  this.push("status", `\u23F8 approval requested: ${req.summary}
@@ -1069,12 +1069,12 @@ every ask now carries it; auto-fix presses to ${MAX_GOAL_FIX_ATTEMPTS} attempts
1069
1069
  }
1070
1070
  case "yes":
1071
1071
  case "no": {
1072
- if (!this.pendingApproval) {
1072
+ if (!this.state.pendingApproval) {
1073
1073
  this.push("status", `nothing awaiting approval \u2014 /${name} answers an engine's approval request`);
1074
1074
  break;
1075
1075
  }
1076
- const summary = this.pendingApproval;
1077
- this.pendingApproval = null;
1076
+ const summary = this.state.pendingApproval;
1077
+ this.notify({ pendingApproval: null });
1078
1078
  const approved = name === "yes";
1079
1079
  appendDecision(this.opts.cwd, {
1080
1080
  decision: `${approved ? "approved" : "rejected"}: ${summary}${arg ? ` \u2014 ${arg}` : ""}`,
package/dist/cli.js CHANGED
@@ -3,11 +3,11 @@ import {
3
3
  connectDaemon,
4
4
  socketPath,
5
5
  startDaemon
6
- } from "./chunk-MDLNTOGX.js";
6
+ } from "./chunk-RXVMB47Q.js";
7
7
  import {
8
8
  App
9
- } from "./chunk-PPC2KM7P.js";
10
- import "./chunk-B356UR44.js";
9
+ } from "./chunk-IANXZQ6Z.js";
10
+ import "./chunk-ZPX5FADM.js";
11
11
  import "./chunk-YKEAKVIS.js";
12
12
  import "./chunk-LNJ7O5O4.js";
13
13
  import "./chunk-3ZHETLUC.js";
@@ -66,7 +66,9 @@ var ConfigSchema = z.object({
66
66
  /** Cheaper model used for auto-fix and /fix turns (mechanical work). */
67
67
  fixModel: z.string().optional(),
68
68
  /** TUI theme name (amber, ocean, moss, rose, mono). */
69
- theme: z.string().optional()
69
+ theme: z.string().optional(),
70
+ /** POSTed when an engine requests visual approval under squint serve. */
71
+ approvalWebhook: z.string().url().optional()
70
72
  });
71
73
  function defaultPaths(cwd) {
72
74
  const xdg = process.env.XDG_CONFIG_HOME;
@@ -114,6 +116,8 @@ function setConfigValue(file, key, value) {
114
116
  throw new Error(`"${key}" must be true or false`);
115
117
  }
116
118
  next = { ...current, [key]: value === "true" };
119
+ } else if (key === "approvalWebhook") {
120
+ next = { ...current, approvalWebhook: value };
117
121
  } else if (key === "budgetUsd") {
118
122
  const budget = Number.parseFloat(value);
119
123
  if (!Number.isFinite(budget) || budget <= 0) {
@@ -126,7 +130,7 @@ function setConfigValue(file, key, value) {
126
130
  next = { ...current, models: { ...current.models, [engineId]: value } };
127
131
  } else {
128
132
  throw new Error(
129
- `Unknown config key "${key}". Supported: engine, theme, autoDev, autoFix, autoProbe, autoCheck, autoReview, bell, budgetUsd, fixModel, models.<engineId>`
133
+ `Unknown config key "${key}". Supported: engine, theme, autoDev, autoFix, autoProbe, autoCheck, autoReview, bell, budgetUsd, fixModel, approvalWebhook, models.<engineId>`
130
134
  );
131
135
  }
132
136
  fs.mkdirSync(path.dirname(file), { recursive: true });
@@ -173,6 +177,7 @@ function registerDaemon(program2) {
173
177
  autoReview: config.autoReview,
174
178
  fixModel: config.fixModel,
175
179
  budgetUsd: config.budgetUsd,
180
+ approvalWebhook: config.approvalWebhook,
176
181
  onQuit: () => {
177
182
  daemon.close();
178
183
  process.exit(0);
@@ -180,6 +185,7 @@ function registerDaemon(program2) {
180
185
  });
181
186
  console.log(`squint daemon on ${socketPath(cwd)} (engine: ${engineId})`);
182
187
  console.log("attach from another terminal with: squint attach");
188
+ if (daemon.relayUrl) console.log(`approval relay on ${daemon.relayUrl} \u2192 ${config.approvalWebhook}`);
183
189
  const stop = () => {
184
190
  daemon.close();
185
191
  process.exit(0);
@@ -188,18 +194,18 @@ function registerDaemon(program2) {
188
194
  process.on("SIGTERM", stop);
189
195
  });
190
196
  program2.command("mcp").description("serve the gates as MCP tools over stdio (check, shot, flows, context)").action(async () => {
191
- const { runMcpServer } = await import("./server-WOYEQLAA.js");
197
+ const { runMcpServer } = await import("./server-IIIH4OZZ.js");
192
198
  runMcpServer(process.cwd());
193
199
  });
194
200
  program2.command("attach").description("attach this terminal to a running squint daemon (full TUI; --plain for line mode)").option("--plain", "line-mode attach instead of the full TUI").action(async (opts) => {
195
201
  const cwd = process.cwd();
196
202
  if (!opts.plain) {
197
203
  try {
198
- const { RemoteSession } = await import("./remote-ICNK2PPK.js");
204
+ const { RemoteSession } = await import("./remote-WHMW4CGK.js");
199
205
  const remote = await RemoteSession.connect(cwd);
200
206
  const config = loadConfig(defaultPaths(cwd));
201
207
  const { render: render2 } = await import("ink");
202
- const { App: App2 } = await import("./App-C3LOYRTR.js");
208
+ const { App: App2 } = await import("./App-22JCZNP6.js");
203
209
  const React = await import("react");
204
210
  render2(
205
211
  React.createElement(App2, {
@@ -605,7 +611,7 @@ function registerQuality(program2) {
605
611
  report.ok = !failed;
606
612
  report.finishedAt = (/* @__PURE__ */ new Date()).toISOString();
607
613
  try {
608
- const { writeReceipt } = await import("./receipts-7PERXBJO.js");
614
+ const { writeReceipt } = await import("./receipts-JE6WVFYH.js");
609
615
  console.log(pc5.dim(`receipt \u2192 ${writeReceipt(cwd, report)}`));
610
616
  } catch {
611
617
  }
@@ -804,7 +810,7 @@ function registerTui(program2) {
804
810
  }
805
811
 
806
812
  // src/cli.tsx
807
- var VERSION = true ? "0.8.2" : "0.0.0-dev";
813
+ var VERSION = true ? "0.9.0" : "0.0.0-dev";
808
814
  var program = new Command();
809
815
  program.name("squint").description("Lovable for your terminal \u2014 a frontend harness on top of Claude Code, Codex, and friends.").version(VERSION);
810
816
  registerRun(program);
@@ -31,7 +31,7 @@ function buildReceipt(cwd, report) {
31
31
  }
32
32
  }
33
33
  const unsigned = {
34
- version: true ? "0.8.2" : "0.0.0-dev",
34
+ version: true ? "0.9.0" : "0.0.0-dev",
35
35
  node: process.version,
36
36
  gitHead,
37
37
  report,
@@ -2,8 +2,8 @@
2
2
  import {
3
3
  connectDaemon,
4
4
  socketPath
5
- } from "./chunk-MDLNTOGX.js";
6
- import "./chunk-B356UR44.js";
5
+ } from "./chunk-RXVMB47Q.js";
6
+ import "./chunk-ZPX5FADM.js";
7
7
  import "./chunk-YKEAKVIS.js";
8
8
  import "./chunk-3ZHETLUC.js";
9
9
  import "./chunk-UBADCBPT.js";
@@ -38,7 +38,8 @@ var RemoteSession = class _RemoteSession {
38
38
  queue: [],
39
39
  mode: "safe",
40
40
  problems: [],
41
- sandbox: false
41
+ sandbox: false,
42
+ pendingApproval: null
42
43
  };
43
44
  client.onMessage((msg) => {
44
45
  if (msg.type === "state" && msg.state) {
@@ -112,7 +112,7 @@ ${result.leaks.map((l) => ` ${l}`).join("\n")}` : ""}` : `\u2717 ${flow.name} a
112
112
  async run(args, cwd) {
113
113
  const fs = await import("fs");
114
114
  const path = await import("path");
115
- const { verifyReceipt } = await import("./receipts-7PERXBJO.js");
115
+ const { verifyReceipt } = await import("./receipts-JE6WVFYH.js");
116
116
  const file = path.isAbsolute(String(args.path)) ? String(args.path) : path.join(cwd, String(args.path));
117
117
  let receipt;
118
118
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aayambansal/squint",
3
- "version": "0.8.2",
3
+ "version": "0.9.0",
4
4
  "description": "Lovable for your terminal \u2014 a frontend harness on top of Claude Code, Codex, and other coding agents.",
5
5
  "type": "module",
6
6
  "license": "MIT",