@algolia/wizard 0.3.0 → 0.4.0-rc.48.12

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.
Files changed (2) hide show
  1. package/dist/main.js +305 -78
  2. package/package.json +1 -1
package/dist/main.js CHANGED
@@ -4,10 +4,11 @@
4
4
  import { render } from "ink";
5
5
 
6
6
  // src/ui/App.tsx
7
- import { Box as Box12, Text as Text12, useApp, useInput as useInput5, useWindowSize as useWindowSize4 } from "ink";
7
+ import { Box as Box13, Text as Text13, useApp, useInput as useInput6, useWindowSize as useWindowSize6 } from "ink";
8
8
 
9
9
  // src/core/store.ts
10
10
  import { create } from "zustand";
11
+ import { nanoid } from "nanoid";
11
12
 
12
13
  // src/lib/algoliaCli.ts
13
14
  import { spawn } from "node:child_process";
@@ -165,6 +166,10 @@ async function markInteraction() {
165
166
  function toUserInfo({ user_id, ...rest }) {
166
167
  return { userId: user_id, ...rest };
167
168
  }
169
+ function describeInputValue(value) {
170
+ if (typeof value === "boolean") return value ? "Yes" : "No";
171
+ return Array.isArray(value) ? value.join(", ") : value;
172
+ }
168
173
  var NOTICE_INTERVAL_MS = 2e3;
169
174
  var useWizard = create((set, get) => ({
170
175
  phase: "idle",
@@ -177,6 +182,7 @@ var useWizard = create((set, get) => ({
177
182
  notices: [],
178
183
  _noticeQueue: [],
179
184
  _noticeTimer: null,
185
+ logs: [],
180
186
  error: null,
181
187
  inputReq: null,
182
188
  _resolve: null,
@@ -255,6 +261,21 @@ var useWizard = create((set, get) => ({
255
261
  get()._clearNoticeQueue();
256
262
  set({ notices: [] });
257
263
  },
264
+ logStart: (kind, name, input) => {
265
+ const id = nanoid();
266
+ set((s) => ({
267
+ logs: [
268
+ ...s.logs,
269
+ { id, kind, name, input, status: "running", startedAt: Date.now() }
270
+ ]
271
+ }));
272
+ return id;
273
+ },
274
+ logEnd: (id, status) => set((s) => ({
275
+ logs: s.logs.map(
276
+ (t) => t.id === id ? { ...t, status, durationMs: Date.now() - t.startedAt } : t
277
+ )
278
+ })),
258
279
  requestUserInput: (req) => new Promise((resolve4) => {
259
280
  set({
260
281
  phase: "awaitingInput",
@@ -262,10 +283,15 @@ var useWizard = create((set, get) => ({
262
283
  _resolve: resolve4
263
284
  });
264
285
  }),
286
+ // Logs what the user picked — not the prompt text that was shown, which
287
+ // may repeat or duplicate on-screen content and isn't the useful signal
288
+ // here.
265
289
  submitInput: async (value) => {
266
290
  await markInteraction();
267
291
  get()._resolve?.(value);
268
292
  set({ inputReq: null, _resolve: null, phase: "running" });
293
+ const id = get().logStart("prompt", `User input: ${describeInputValue(value)}`);
294
+ get().logEnd(id, "success");
269
295
  },
270
296
  setDone: () => set({ phase: "done" }),
271
297
  setError: (message) => set({ phase: "error", error: message }),
@@ -279,6 +305,7 @@ var useWizard = create((set, get) => ({
279
305
  currentStepIndex: 0,
280
306
  output: "",
281
307
  notices: [],
308
+ logs: [],
282
309
  error: null,
283
310
  inputReq: null,
284
311
  _resolve: null
@@ -722,7 +749,7 @@ function PromptInput() {
722
749
  // src/ui/Welcome.tsx
723
750
  import { dirname as dirname2, join as join3 } from "node:path";
724
751
  import { fileURLToPath } from "node:url";
725
- import { Box as Box6, Spacer, Text as Text6, useInput as useInput3 } from "ink";
752
+ import { Box as Box6, Spacer, Text as Text6, useInput as useInput3, useWindowSize as useWindowSize3 } from "ink";
726
753
 
727
754
  // src/ui/copy/welcome.ts
728
755
  var sidebarItems = [
@@ -770,36 +797,61 @@ function SidebarItem({
770
797
  function Welcome() {
771
798
  const confirmStart = useWizard((s) => s.confirmStart);
772
799
  const openLearnMore = useWizard((s) => s.openLearnMore);
800
+ const { rows } = useWindowSize3();
773
801
  useInput3((input) => {
774
802
  if (input === " ") confirmStart();
775
803
  else if (input === "i") openLearnMore();
776
804
  });
805
+ const scales = {
806
+ large: {
807
+ sidebar: { padding: { x: 4, y: 2 }, gap: 2 },
808
+ main: { padding: { x: 8, y: 4 } }
809
+ },
810
+ small: {
811
+ sidebar: { padding: { x: 2, y: 1 }, gap: 1 },
812
+ main: { padding: { x: 4, y: 2 } }
813
+ }
814
+ };
815
+ let layout = scales["large"];
816
+ if (rows < 30) {
817
+ layout = scales["small"];
818
+ }
777
819
  return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "row", justifyContent: "space-between", width: "100%", children: [
778
- /* @__PURE__ */ jsx6(Box6, { padding: 8, flexDirection: "column", justifyContent: "center", children: /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", gap: 4, children: [
779
- /* @__PURE__ */ jsx6(InkPictureProvider, { children: /* @__PURE__ */ jsx6(
780
- Image,
781
- {
782
- src: IMAGE_PATH,
783
- objectFit: "contain",
784
- alt: "Algolia",
785
- width: 30,
786
- height: 15,
787
- protocol: "halfBlock"
788
- }
789
- ) }),
790
- /* @__PURE__ */ jsx6(Text6, { color: COLORS.muted, children: "\u2726 From zero \u2192 working search in ~10 minutes" }),
791
- /* @__PURE__ */ jsxs5(Box6, { gap: 1, flexDirection: "column", children: [
792
- /* @__PURE__ */ jsx6(NextAction, { action: "start wizard", keyHint: "space" }),
793
- /* @__PURE__ */ jsx6(NextAction, { action: "learn more", keyHint: "i", hierarchy: "secondary" })
794
- ] })
795
- ] }) }),
820
+ /* @__PURE__ */ jsx6(
821
+ Box6,
822
+ {
823
+ paddingY: layout.main.padding.y,
824
+ paddingX: layout.main.padding.x,
825
+ flexDirection: "column",
826
+ justifyContent: "center",
827
+ children: /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", gap: 2, children: [
828
+ /* @__PURE__ */ jsx6(InkPictureProvider, { children: /* @__PURE__ */ jsx6(
829
+ Image,
830
+ {
831
+ src: IMAGE_PATH,
832
+ objectFit: "contain",
833
+ alt: "Algolia",
834
+ width: 20,
835
+ height: 10,
836
+ protocol: "halfBlock"
837
+ }
838
+ ) }),
839
+ /* @__PURE__ */ jsx6(Text6, { color: COLORS.muted, children: "\u2726 From zero \u2192 working search in ~10 minutes" }),
840
+ /* @__PURE__ */ jsxs5(Box6, { gap: 1, flexDirection: "column", children: [
841
+ /* @__PURE__ */ jsx6(NextAction, { action: "start wizard", keyHint: "space" }),
842
+ /* @__PURE__ */ jsx6(NextAction, { action: "learn more", keyHint: "i", hierarchy: "secondary" })
843
+ ] })
844
+ ] })
845
+ }
846
+ ),
796
847
  /* @__PURE__ */ jsxs5(
797
848
  Box6,
798
849
  {
799
850
  backgroundColor: COLORS.bg.sidebar,
800
851
  width: 40,
801
- padding: 4,
802
- gap: 2,
852
+ paddingY: layout.sidebar.padding.y,
853
+ paddingX: layout.sidebar.padding.x,
854
+ gap: layout.sidebar.gap,
803
855
  flexDirection: "column",
804
856
  justifyContent: "center",
805
857
  children: [
@@ -813,7 +865,7 @@ function Welcome() {
813
865
 
814
866
  // src/ui/LearnMore.tsx
815
867
  import { Fragment as Fragment2 } from "react";
816
- import { Box as Box7, Text as Text7, useInput as useInput4, useWindowSize as useWindowSize3 } from "ink";
868
+ import { Box as Box7, Text as Text7, useInput as useInput4, useWindowSize as useWindowSize4 } from "ink";
817
869
 
818
870
  // src/ui/copy/learn-more.ts
819
871
  var accessIntro = "Everything runs locally on your machine. Nothing is written or sent without an explicit yes from you.";
@@ -877,7 +929,7 @@ function NeverLine({
877
929
  function LearnMore() {
878
930
  const confirmStart = useWizard((s) => s.confirmStart);
879
931
  const backToHome = useWizard((s) => s.backToHome);
880
- const { columns } = useWindowSize3();
932
+ const { columns } = useWindowSize4();
881
933
  const dividerWidth = Math.max(0, columns - PADDING_X * 2);
882
934
  useInput4((input, key) => {
883
935
  if (key.escape) backToHome();
@@ -1087,53 +1139,205 @@ function Ribbon() {
1087
1139
  }
1088
1140
 
1089
1141
  // src/ui/App.tsx
1142
+ import { useState as useState6 } from "react";
1143
+
1144
+ // src/ui/Logs.tsx
1145
+ import { useLayoutEffect, useRef as useRef2, useState as useState5 } from "react";
1146
+ import { Box as Box12, Text as Text12, measureElement as measureElement2, useInput as useInput5, useWindowSize as useWindowSize5 } from "ink";
1090
1147
  import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
1148
+ var KIND_COLOR = {
1149
+ tool: COLORS.primary,
1150
+ prompt: COLORS.badge
1151
+ };
1152
+ var STATUS_COLOR = {
1153
+ running: COLORS.status.running,
1154
+ error: COLORS.danger
1155
+ };
1156
+ function logNameColor(entry) {
1157
+ return STATUS_COLOR[entry.status] ?? KIND_COLOR[entry.kind];
1158
+ }
1159
+ var ROW_GAP = 1;
1160
+ function truncate2(str, maxWidth) {
1161
+ if (maxWidth <= 0) return "";
1162
+ return str.length > maxWidth ? `${str.slice(0, maxWidth - 1)}\u2026` : str;
1163
+ }
1164
+ function rawInputText(input) {
1165
+ if (input === void 0) return "";
1166
+ const str = typeof input === "string" ? input : JSON.stringify(input);
1167
+ if (!str || str === "{}") return "";
1168
+ return str.replace(/\s+/g, " ").trim();
1169
+ }
1170
+ function formatTimestamp(ms) {
1171
+ const d = new Date(ms);
1172
+ const pad = (n) => String(n).padStart(2, "0");
1173
+ return `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
1174
+ }
1175
+ function Logs() {
1176
+ const logs = useWizard((s) => s.logs);
1177
+ const { rows, columns } = useWindowSize5();
1178
+ const viewportRef = useRef2(null);
1179
+ const [viewportHeight, setViewportHeight] = useState5(0);
1180
+ const [viewportWidth, setViewportWidth] = useState5(0);
1181
+ const [scrollOffset, setScrollOffset] = useState5(0);
1182
+ const prevMaxOffsetRef = useRef2(0);
1183
+ useLayoutEffect(() => {
1184
+ if (!viewportRef.current) return;
1185
+ const { width, height } = measureElement2(viewportRef.current);
1186
+ setViewportHeight(height);
1187
+ setViewportWidth(width);
1188
+ }, [rows, columns, logs.length === 0]);
1189
+ let capacity = viewportHeight;
1190
+ for (let i = 0; i < 2; i++) {
1191
+ const hasAbove = scrollOffset > 0;
1192
+ const hasBelow = scrollOffset + capacity < logs.length;
1193
+ capacity = Math.max(
1194
+ viewportHeight - (hasAbove ? 1 : 0) - (hasBelow ? 1 : 0),
1195
+ 0
1196
+ );
1197
+ }
1198
+ const capacityAtBottom = logs.length > viewportHeight ? Math.max(viewportHeight - 1, 0) : viewportHeight;
1199
+ const maxOffset = Math.max(logs.length - capacityAtBottom, 0);
1200
+ useLayoutEffect(() => {
1201
+ const wasAtBottom = scrollOffset >= prevMaxOffsetRef.current;
1202
+ prevMaxOffsetRef.current = maxOffset;
1203
+ setScrollOffset((o) => wasAtBottom ? maxOffset : Math.min(o, maxOffset));
1204
+ }, [maxOffset]);
1205
+ useInput5((_input, key) => {
1206
+ if (!key.upArrow && !key.downArrow) return;
1207
+ setScrollOffset(
1208
+ (o) => key.upArrow ? Math.max(o - 1, 0) : Math.min(o + 1, maxOffset)
1209
+ );
1210
+ });
1211
+ const visible = logs.slice(scrollOffset, scrollOffset + capacity);
1212
+ const hiddenAbove = scrollOffset;
1213
+ const hiddenBelow = logs.length - scrollOffset - visible.length;
1214
+ return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", paddingX: 4, paddingY: 2, flexGrow: 1, children: [
1215
+ logs.length === 0 && /* @__PURE__ */ jsx12(Text12, { color: COLORS.dim, children: "No logs yet." }),
1216
+ /* @__PURE__ */ jsxs11(Box12, { ref: viewportRef, flexDirection: "column", flexGrow: 1, children: [
1217
+ hiddenAbove > 0 && /* @__PURE__ */ jsxs11(Text12, { color: COLORS.dim, children: [
1218
+ "\u2191 ",
1219
+ hiddenAbove,
1220
+ " more"
1221
+ ] }),
1222
+ visible.map((entry) => {
1223
+ const timestamp = `[${formatTimestamp(entry.startedAt)}]`;
1224
+ const durationText = entry.kind === "tool" && entry.durationMs !== void 0 ? `${entry.durationMs}ms` : "";
1225
+ const rawPreview = rawInputText(entry.input);
1226
+ const partCount = 2 + (rawPreview ? 1 : 0) + (durationText ? 1 : 0);
1227
+ const gaps = (partCount - 1) * ROW_GAP;
1228
+ let budget = viewportWidth - timestamp.length - durationText.length - gaps;
1229
+ const name = truncate2(entry.name, budget);
1230
+ budget -= name.length;
1231
+ const preview = rawPreview ? truncate2(rawPreview, budget) : "";
1232
+ return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "row", gap: ROW_GAP, children: [
1233
+ /* @__PURE__ */ jsx12(Text12, { color: COLORS.dim, children: timestamp }),
1234
+ /* @__PURE__ */ jsx12(Text12, { color: logNameColor(entry), wrap: "truncate", children: name }),
1235
+ preview && /* @__PURE__ */ jsx12(Text12, { color: COLORS.dim, wrap: "truncate", children: preview }),
1236
+ durationText && /* @__PURE__ */ jsx12(Text12, { color: COLORS.dim, children: durationText })
1237
+ ] }, entry.id);
1238
+ }),
1239
+ hiddenBelow > 0 && /* @__PURE__ */ jsxs11(Text12, { color: COLORS.dim, children: [
1240
+ "\u2193 ",
1241
+ hiddenBelow,
1242
+ " more"
1243
+ ] })
1244
+ ] }),
1245
+ /* @__PURE__ */ jsx12(Text12, { color: COLORS.dim, children: "\u2191/\u2193 scroll" })
1246
+ ] });
1247
+ }
1248
+
1249
+ // src/lib/events.ts
1250
+ import "zod";
1251
+ function track(event, payload) {
1252
+ const token = getAuthToken();
1253
+ if (!token) return;
1254
+ const userId = useWizard.getState().user?.userId;
1255
+ if (!userId) return;
1256
+ void proxyFetch(`${PROXY_BASE_URL}/events`, {
1257
+ method: "POST",
1258
+ headers: {
1259
+ "content-type": "application/json",
1260
+ authorization: `Bearer ${token}`
1261
+ },
1262
+ body: JSON.stringify({ userId, event, properties: payload })
1263
+ }).catch((err) => {
1264
+ logger.warn({ err, event }, "failed to send analytics event");
1265
+ });
1266
+ }
1267
+
1268
+ // src/ui/App.tsx
1269
+ import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
1091
1270
  function App() {
1092
- const { phase, error, homeScreen } = useWizard();
1271
+ const { phase, error, homeScreen, currentStepIndex, steps, inputReq } = useWizard();
1093
1272
  const { exit } = useApp();
1094
- const { columns, rows } = useWindowSize4();
1273
+ const { columns, rows } = useWindowSize6();
1274
+ const [showLogs, setShowLogs] = useState6(false);
1095
1275
  const finished = phase === "done" || phase === "error";
1096
- useInput5(
1276
+ const currentStep = steps[currentStepIndex];
1277
+ useInput6(
1097
1278
  (_input, key) => {
1098
- if (key.return || key.escape) {
1279
+ if (key.return) {
1099
1280
  exit();
1100
1281
  }
1101
1282
  },
1102
1283
  { isActive: finished }
1103
1284
  );
1285
+ useInput6((_input, key) => {
1286
+ if (phase === "idle" || phase === "preflight") return;
1287
+ if (key.tab) {
1288
+ setShowLogs(!showLogs);
1289
+ track("AI Wizard Interaction", {
1290
+ context: "global",
1291
+ key: "tab",
1292
+ currentStep: currentStep?.id
1293
+ });
1294
+ }
1295
+ });
1296
+ const escOwnedElsewhere = phase === "idle" || phase === "awaitingInput" && inputReq?.promptType === "spaceToContinue";
1297
+ useInput6((_input, key) => {
1298
+ if (escOwnedElsewhere) return;
1299
+ if (key.escape) {
1300
+ track("AI Wizard Interaction", {
1301
+ context: "global",
1302
+ key: "esc",
1303
+ // No step is active until `startWorkflow` — report the phase instead.
1304
+ currentStep: currentStep?.id ?? phase
1305
+ });
1306
+ exit();
1307
+ }
1308
+ });
1104
1309
  const mainWindowVisible = phase === "running" || phase === "awaitingInput" || phase === "error" || phase === "done";
1105
1310
  const flexDirection = columns > 90 ? "row" : "column";
1106
1311
  const showSidebar = flexDirection === "row";
1107
- return /* @__PURE__ */ jsxs11(
1108
- Box12,
1312
+ return /* @__PURE__ */ jsxs12(
1313
+ Box13,
1109
1314
  {
1110
1315
  backgroundColor: COLORS.bg.main,
1111
1316
  flexDirection: "row",
1112
1317
  width: columns,
1113
1318
  minHeight: rows,
1114
1319
  children: [
1115
- mainWindowVisible && /* @__PURE__ */ jsxs11(
1116
- Box12,
1320
+ mainWindowVisible && /* @__PURE__ */ jsxs12(
1321
+ Box13,
1117
1322
  {
1118
1323
  flexDirection,
1119
1324
  width: "100%",
1120
1325
  justifyContent: "space-between",
1121
1326
  children: [
1122
- /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", paddingX: 4, paddingY: 2, width: 70, children: [
1123
- /* @__PURE__ */ jsx12(Notices, {}),
1124
- /* @__PURE__ */ jsx12(PromptInput, {}),
1125
- phase === "running" && showSidebar && /* @__PURE__ */ jsx12(Box12, { marginTop: 1, children: /* @__PURE__ */ jsx12(CurrentStep, {}) }),
1126
- phase === "error" && error && /* @__PURE__ */ jsx12(Box12, { marginTop: 1, children: /* @__PURE__ */ jsxs11(Text12, { color: COLORS.status.error, children: [
1327
+ showLogs ? /* @__PURE__ */ jsx13(Logs, {}) : /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", paddingX: 4, paddingY: 2, width: 70, children: [
1328
+ /* @__PURE__ */ jsx13(Notices, {}),
1329
+ /* @__PURE__ */ jsx13(PromptInput, {}),
1330
+ phase === "running" && showSidebar && /* @__PURE__ */ jsx13(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx13(CurrentStep, {}) }),
1331
+ phase === "error" && error && /* @__PURE__ */ jsx13(Box13, { marginTop: 1, children: /* @__PURE__ */ jsxs12(Text13, { color: COLORS.status.error, children: [
1127
1332
  "\u2716 ",
1128
1333
  error
1129
1334
  ] }) })
1130
1335
  ] }),
1131
- /* @__PURE__ */ jsx12(Text12, { color: "white", backgroundColor: "#14171E" }),
1132
- showSidebar ? /* @__PURE__ */ jsx12(Sidebar, {}) : /* @__PURE__ */ jsx12(Ribbon, {})
1336
+ showSidebar ? /* @__PURE__ */ jsx13(Sidebar, {}) : /* @__PURE__ */ jsx13(Ribbon, {})
1133
1337
  ]
1134
1338
  }
1135
1339
  ),
1136
- (phase === "idle" || phase === "preflight") && (homeScreen === "learnMore" ? /* @__PURE__ */ jsx12(LearnMore, {}) : /* @__PURE__ */ jsx12(Welcome, {}))
1340
+ (phase === "idle" || phase === "preflight") && (homeScreen === "learnMore" ? /* @__PURE__ */ jsx13(LearnMore, {}) : /* @__PURE__ */ jsx13(Welcome, {}))
1137
1341
  ]
1138
1342
  }
1139
1343
  );
@@ -1331,25 +1535,6 @@ function trackWorkflowError(ctx) {
1331
1535
  );
1332
1536
  }
1333
1537
 
1334
- // src/lib/events.ts
1335
- import "zod";
1336
- function track(event, payload) {
1337
- const token = getAuthToken();
1338
- if (!token) return;
1339
- const userId = useWizard.getState().user?.userId;
1340
- if (!userId) return;
1341
- void proxyFetch(`${PROXY_BASE_URL}/events`, {
1342
- method: "POST",
1343
- headers: {
1344
- "content-type": "application/json",
1345
- authorization: `Bearer ${token}`
1346
- },
1347
- body: JSON.stringify({ userId, event, properties: payload })
1348
- }).catch((err) => {
1349
- logger.warn({ err, event }, "failed to send analytics event");
1350
- });
1351
- }
1352
-
1353
1538
  // src/core/orchestrator.ts
1354
1539
  function defineStep(step) {
1355
1540
  return { visible: true, ...step };
@@ -1423,6 +1608,8 @@ async function makeContext(state) {
1423
1608
  requestUserInput: (prompt) => useWizard.getState().requestUserInput(prompt),
1424
1609
  notify: (notice) => useWizard.getState().pushNotice(notice),
1425
1610
  clearNotices: () => useWizard.getState().clearNotices(),
1611
+ logStart: (name, input) => useWizard.getState().logStart("tool", name, input),
1612
+ logEnd: (id, status) => useWizard.getState().logEnd(id, status),
1426
1613
  updateAlgoliaState: (key, value) => {
1427
1614
  state.algoliaState[key] = value;
1428
1615
  },
@@ -2075,7 +2262,7 @@ function verifyImplementationTool() {
2075
2262
  // src/lib/tools/generateRecord.ts
2076
2263
  import { tool as tool9, generateText, Output, NoObjectGeneratedError } from "ai";
2077
2264
  import { createAnthropic } from "@ai-sdk/anthropic";
2078
- import { nanoid } from "nanoid";
2265
+ import { nanoid as nanoid2 } from "nanoid";
2079
2266
  import { mkdir as mkdir5, writeFile as writeFile5 } from "node:fs/promises";
2080
2267
  import { dirname as dirname6 } from "node:path";
2081
2268
  import z12 from "zod";
@@ -2121,7 +2308,7 @@ function generateRecordTool(ctx) {
2121
2308
  // would otherwise drift to the same high-probability values and
2122
2309
  // collide across batches. This per-batch seed pushes each call
2123
2310
  // into a different region of the output space.
2124
- `Variety seed: ${nanoid()}. Use it to diversify values.`
2311
+ `Variety seed: ${nanoid2()}. Use it to diversify values.`
2125
2312
  ].filter(Boolean).join("\n")
2126
2313
  });
2127
2314
  return output.records;
@@ -2143,7 +2330,7 @@ function generateRecordTool(ctx) {
2143
2330
  const batches = await Promise.all(batchSizes.map(generateBatch));
2144
2331
  const records = batches.flat().map((record) => ({
2145
2332
  ...record,
2146
- objectID: nanoid()
2333
+ objectID: nanoid2()
2147
2334
  }));
2148
2335
  const slug = entityName.toLowerCase().replace(/[^a-z0-9]+/g, "-");
2149
2336
  const relPath = `${DATA_DIR}/${slug}.json`;
@@ -2203,18 +2390,42 @@ function createToolContext(limits = DEFAULT_TOOL_LIMITS, cwd = process.cwd()) {
2203
2390
  }
2204
2391
 
2205
2392
  // src/lib/tools/index.ts
2393
+ function withLogging(name, def) {
2394
+ const execute = def.execute;
2395
+ if (!execute) return def;
2396
+ return {
2397
+ ...def,
2398
+ execute: async (input, options) => {
2399
+ const id = useWizard.getState().logStart("tool", name, input);
2400
+ try {
2401
+ const output = await execute(input, options);
2402
+ useWizard.getState().logEnd(id, "success");
2403
+ return output;
2404
+ } catch (err) {
2405
+ useWizard.getState().logEnd(id, "error");
2406
+ throw err;
2407
+ }
2408
+ }
2409
+ };
2410
+ }
2206
2411
  function createTools(ctx, { output, tools }) {
2207
2412
  const all = {
2208
- listFiles: listFilesTool(ctx),
2209
- changeDirectory: changeDirectoryTool(ctx),
2210
- reportStatus: reportStatusTool(output),
2211
- readFile: readFileTool(ctx),
2212
- writeFile: writeFileTool(ctx),
2213
- writeCredentials: writeCredentialsTool(ctx),
2214
- searchFiles: searchFilesTool(ctx),
2215
- verifyImplementation: verifyImplementationTool(),
2216
- generateRecord: generateRecordTool(ctx),
2217
- notifyUser: notifyUserTool()
2413
+ listFiles: withLogging("listFiles", listFilesTool(ctx)),
2414
+ changeDirectory: withLogging("changeDirectory", changeDirectoryTool(ctx)),
2415
+ reportStatus: withLogging("reportStatus", reportStatusTool(output)),
2416
+ readFile: withLogging("readFile", readFileTool(ctx)),
2417
+ writeFile: withLogging("writeFile", writeFileTool(ctx)),
2418
+ writeCredentials: withLogging(
2419
+ "writeCredentials",
2420
+ writeCredentialsTool(ctx)
2421
+ ),
2422
+ searchFiles: withLogging("searchFiles", searchFilesTool(ctx)),
2423
+ verifyImplementation: withLogging(
2424
+ "verifyImplementation",
2425
+ verifyImplementationTool()
2426
+ ),
2427
+ generateRecord: withLogging("generateRecord", generateRecordTool(ctx)),
2428
+ notifyUser: withLogging("notifyUser", notifyUserTool())
2218
2429
  };
2219
2430
  if (!tools) return all;
2220
2431
  const selection = /* @__PURE__ */ new Set([...tools, "reportStatus", "notifyUser"]);
@@ -2437,7 +2648,7 @@ async function runAnalysis(mode, extraInstructions = []) {
2437
2648
  // package.json
2438
2649
  var package_default = {
2439
2650
  name: "@algolia/wizard",
2440
- version: "0.3.0",
2651
+ version: "0.4.0-rc.48.12",
2441
2652
  description: "Magically implement Algolia functionality in your codebase",
2442
2653
  type: "module",
2443
2654
  engines: {
@@ -3386,13 +3597,16 @@ function searchInstructions(input) {
3386
3597
  doc,
3387
3598
  `Add the search UI at ${input.searchLocation ? `"${input.searchLocation}"` : "the best shared, always-rendered layout location (e.g. a header/nav component)"} so it is reachable across the app \u2014 at least a working SearchBox and Hits against the "${input.targetIndex}" index.`,
3388
3599
  "Read the App ID and a search-only API key from public env vars; never hardcode them. A search-only key is safe to expose client-side.",
3389
- `Values: App ID ${input.appId ? `"${input.appId}"` : "(placeholder for the developer to fill in)"}, search-only key ${input.searchKey ? `"${input.searchKey}"` : "(placeholder for the developer to fill in)"}.`,
3600
+ // appId always resolves (loadActiveProfile throws otherwise); only the
3601
+ // search-only key is best-effort and can fall back to a placeholder.
3602
+ `Values: App ID "${input.appId}", search-only key ${input.searchKey ? `"${input.searchKey}"` : "(placeholder for the developer to fill in)"}.`,
3390
3603
  // Names are fixed, not the agent's to rename: the wizard writes the
3391
3604
  // resolved app id / search-only key into ".env" under these exact names
3392
3605
  // right after this step, so a renamed prefix here would leave the code
3393
3606
  // reading a var the wizard never wrote.
3394
3607
  `Use exactly these public env var names in the code: ${input.searchEnvVars.map(({ name }) => name).join(", ")}.`,
3395
- 'Add any Algolia/InstantSearch packages you import to package.json "dependencies" with a valid version range; the wizard installs them in the worktree after you finish.'
3608
+ 'Add any Algolia/InstantSearch packages you import to package.json "dependencies" with a valid version range; the wizard installs them in the worktree after you finish.',
3609
+ "The summary should be extremely concise; do not mention env var setup or manual testing steps \u2014 the wizard writes the resolved credentials to .env and reports that separately."
3396
3610
  ];
3397
3611
  }
3398
3612
  function verificationInstructions(input) {
@@ -3613,7 +3827,14 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
3613
3827
  tools: toolsForUseCase(currentUseCase, input.ingestionSource),
3614
3828
  outputSchema: implementationOutputSchema
3615
3829
  });
3830
+ ctx.notify({
3831
+ messages: [`Installing dependencies for ${currentUseCase}\u2026`]
3832
+ });
3833
+ const installLogId = ctx.logStart("installWorktreeDeps", {
3834
+ useCase: currentUseCase
3835
+ });
3616
3836
  const install = await installWorktreeDeps(worktree);
3837
+ ctx.logEnd(installLogId, install.ok ? "success" : "error");
3617
3838
  if (!install.ok) {
3618
3839
  installFailed = true;
3619
3840
  logger.warn(
@@ -3647,6 +3868,11 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
3647
3868
  }) === true;
3648
3869
  if (runNow) {
3649
3870
  const profile2 = await loadActiveProfile();
3871
+ ctx.notify({ messages: [`Writing records to "${targetIndex}"\u2026`] });
3872
+ const scriptLogId = ctx.logStart("runIngestScript", {
3873
+ runtime: ingestRuntime,
3874
+ entrypoint: ingestEntrypoint
3875
+ });
3650
3876
  const startedAt = Date.now();
3651
3877
  const run = await runIngestScript(
3652
3878
  worktree,
@@ -3657,6 +3883,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES, existingWo
3657
3883
  [API_KEY_VAR]: profile2.apiKey
3658
3884
  }
3659
3885
  );
3886
+ ctx.logEnd(scriptLogId, run.ok ? "success" : "error");
3660
3887
  ingestScriptRan = run.ran && run.ok;
3661
3888
  if (ingestScriptRan) {
3662
3889
  ingestDurationMs = Date.now() - startedAt;
@@ -3934,7 +4161,7 @@ function getWorkflow(id) {
3934
4161
  }
3935
4162
 
3936
4163
  // src/main.tsx
3937
- import { jsx as jsx13 } from "react/jsx-runtime";
4164
+ import { jsx as jsx14 } from "react/jsx-runtime";
3938
4165
  var requestedId = process.argv[2] ?? defaultWorkflow.id;
3939
4166
  var workflow = getWorkflow(requestedId);
3940
4167
  if (!workflow) {
@@ -3943,7 +4170,7 @@ if (!workflow) {
3943
4170
  process.exit(1);
3944
4171
  }
3945
4172
  var store = useWizard.getState();
3946
- var instance = render(/* @__PURE__ */ jsx13(App, {}), { incrementalRendering: true });
4173
+ var instance = render(/* @__PURE__ */ jsx14(App, {}), { incrementalRendering: true });
3947
4174
  var user = await getUser();
3948
4175
  if (!user) {
3949
4176
  await instance.waitUntilRenderFlush();
@@ -3954,7 +4181,7 @@ if (!user) {
3954
4181
  console.error(err instanceof Error ? err.message : String(err));
3955
4182
  process.exit(1);
3956
4183
  }
3957
- instance = render(/* @__PURE__ */ jsx13(App, {}), { incrementalRendering: true });
4184
+ instance = render(/* @__PURE__ */ jsx14(App, {}), { incrementalRendering: true });
3958
4185
  user = await getUser();
3959
4186
  if (!user) {
3960
4187
  store.setError(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@algolia/wizard",
3
- "version": "0.3.0",
3
+ "version": "0.4.0-rc.48.12",
4
4
  "description": "Magically implement Algolia functionality in your codebase",
5
5
  "type": "module",
6
6
  "engines": {