@poncho-ai/cli 0.4.2 → 0.5.1

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 (58) hide show
  1. package/.turbo/turbo-build.log +6 -6
  2. package/CHANGELOG.md +30 -0
  3. package/dist/chunk-2AZ3Y6R2.js +4121 -0
  4. package/dist/chunk-2QSGKCWX.js +4194 -0
  5. package/dist/chunk-3273VMA7.js +4182 -0
  6. package/dist/chunk-3IQWS553.js +4178 -0
  7. package/dist/chunk-5JFBI2WN.js +4202 -0
  8. package/dist/chunk-6NN7D4YA.js +4179 -0
  9. package/dist/chunk-6REJ5J4T.js +4142 -0
  10. package/dist/chunk-BSW557BB.js +4058 -0
  11. package/dist/chunk-FFIQQ5RY.js +4172 -0
  12. package/dist/chunk-GJYE4S3D.js +4164 -0
  13. package/dist/chunk-HGZTVHBT.js +4089 -0
  14. package/dist/chunk-HNYADV2K.js +4164 -0
  15. package/dist/chunk-IE47LJ33.js +4166 -0
  16. package/dist/chunk-J65L5WSP.js +4187 -0
  17. package/dist/chunk-MFVXK3SX.js +4177 -0
  18. package/dist/chunk-N7ZAHMBR.js +4178 -0
  19. package/dist/chunk-RAN52NR2.js +4180 -0
  20. package/dist/chunk-RU5C6WL4.js +4186 -0
  21. package/dist/chunk-TYL4SGJE.js +4177 -0
  22. package/dist/chunk-VIX7Y2YC.js +4169 -0
  23. package/dist/chunk-WCIUVLV3.js +4171 -0
  24. package/dist/chunk-WXFCSFXF.js +4178 -0
  25. package/dist/chunk-YFNZJBPQ.js +4185 -0
  26. package/dist/chunk-ZBOX3JLJ.js +4197 -0
  27. package/dist/cli.js +1 -1
  28. package/dist/index.js +1 -1
  29. package/dist/run-interactive-ink-4EWW4AJ6.js +463 -0
  30. package/dist/run-interactive-ink-5YGICHDM.js +494 -0
  31. package/dist/run-interactive-ink-642LZIYZ.js +494 -0
  32. package/dist/run-interactive-ink-6NYRFZWP.js +494 -0
  33. package/dist/run-interactive-ink-6YNTYMPO.js +494 -0
  34. package/dist/run-interactive-ink-72H5IUTC.js +494 -0
  35. package/dist/run-interactive-ink-7DWI2HZB.js +494 -0
  36. package/dist/run-interactive-ink-7P4WWB2Y.js +494 -0
  37. package/dist/run-interactive-ink-C5NIVKAZ.js +494 -0
  38. package/dist/run-interactive-ink-EP7GIKLH.js +463 -0
  39. package/dist/run-interactive-ink-FASKW7SN.js +463 -0
  40. package/dist/run-interactive-ink-GO3OQ3BD.js +494 -0
  41. package/dist/run-interactive-ink-JTEKKDJW.js +494 -0
  42. package/dist/run-interactive-ink-LLNLDCES.js +494 -0
  43. package/dist/run-interactive-ink-MO6MGLEY.js +494 -0
  44. package/dist/run-interactive-ink-OFJCD2ZU.js +494 -0
  45. package/dist/run-interactive-ink-OQZN5DQE.js +463 -0
  46. package/dist/run-interactive-ink-QKB6CG3W.js +494 -0
  47. package/dist/run-interactive-ink-RJCA5IQA.js +494 -0
  48. package/dist/run-interactive-ink-RU2PH6R5.js +494 -0
  49. package/dist/run-interactive-ink-T36C6TJ2.js +463 -0
  50. package/dist/run-interactive-ink-XLNTYEIZ.js +494 -0
  51. package/dist/run-interactive-ink-YFY4HRAS.js +494 -0
  52. package/dist/run-interactive-ink-ZL6RAS2O.js +494 -0
  53. package/package.json +3 -2
  54. package/src/index.ts +80 -17
  55. package/src/init-onboarding.ts +4 -3
  56. package/src/run-interactive-ink.ts +45 -10
  57. package/src/web-ui.ts +235 -139
  58. package/test/cli.test.ts +115 -6
package/src/index.ts CHANGED
@@ -830,11 +830,30 @@ export const createRequestHandler = async (options?: {
830
830
  const conversationStore = createConversationStore(resolveStateConfig(config), { workingDir });
831
831
  const sessionStore = new SessionStore();
832
832
  const loginRateLimiter = new LoginRateLimiter();
833
- const passphrase = process.env.AGENT_UI_PASSPHRASE ?? "";
833
+
834
+ // Unified authentication using PONCHO_AUTH_TOKEN for both Web UI and API
835
+ const authToken = process.env.PONCHO_AUTH_TOKEN ?? "";
836
+ const authRequired = config?.auth?.required ?? false;
837
+ const requireAuth = authRequired && authToken.length > 0;
838
+
834
839
  const isProduction = resolveHarnessEnvironment() === "production";
835
- const requireUiAuth = passphrase.length > 0;
836
840
  const secureCookies = isProduction;
837
841
 
842
+ // Helper to extract and validate Bearer token from Authorization header
843
+ const validateBearerToken = (authHeader: string | string[] | undefined): boolean => {
844
+ if (!requireAuth || !authToken) {
845
+ return true; // No auth required
846
+ }
847
+ if (!authHeader || typeof authHeader !== "string") {
848
+ return false;
849
+ }
850
+ const match = authHeader.match(/^Bearer\s+(.+)$/i);
851
+ if (!match || !match[1]) {
852
+ return false;
853
+ }
854
+ return verifyPassphrase(match[1], authToken);
855
+ };
856
+
838
857
  return async (request: IncomingMessage, response: ServerResponse) => {
839
858
  if (!request.url || !request.method) {
840
859
  writeJson(response, 404, { error: "Not found" });
@@ -888,7 +907,7 @@ export const createRequestHandler = async (options?: {
888
907
  request.method !== "GET" && request.method !== "HEAD" && request.method !== "OPTIONS";
889
908
 
890
909
  if (pathname === "/api/auth/session" && request.method === "GET") {
891
- if (!requireUiAuth) {
910
+ if (!requireAuth) {
892
911
  writeJson(response, 200, { authenticated: true, csrfToken: "" });
893
912
  return;
894
913
  }
@@ -906,7 +925,7 @@ export const createRequestHandler = async (options?: {
906
925
  }
907
926
 
908
927
  if (pathname === "/api/auth/login" && request.method === "POST") {
909
- if (!requireUiAuth) {
928
+ if (!requireAuth) {
910
929
  writeJson(response, 200, { authenticated: true, csrfToken: "" });
911
930
  return;
912
931
  }
@@ -922,7 +941,7 @@ export const createRequestHandler = async (options?: {
922
941
  }
923
942
  const body = (await readRequestBody(request)) as { passphrase?: string };
924
943
  const provided = body.passphrase ?? "";
925
- if (!verifyPassphrase(provided, passphrase)) {
944
+ if (!verifyPassphrase(provided, authToken)) {
926
945
  const failure = loginRateLimiter.registerFailure(ip);
927
946
  writeJson(response, 401, {
928
947
  code: "AUTH_ERROR",
@@ -963,15 +982,23 @@ export const createRequestHandler = async (options?: {
963
982
  }
964
983
 
965
984
  if (pathname.startsWith("/api/")) {
966
- if (requireUiAuth && !session) {
985
+ // Check authentication: either valid session (Web UI) or valid Bearer token (API)
986
+ const hasBearerToken = request.headers.authorization?.startsWith("Bearer ");
987
+ const isAuthenticated = !requireAuth || session || validateBearerToken(request.headers.authorization);
988
+
989
+ if (!isAuthenticated) {
967
990
  writeJson(response, 401, {
968
991
  code: "AUTH_ERROR",
969
992
  message: "Authentication required",
970
993
  });
971
994
  return;
972
995
  }
996
+
997
+ // CSRF validation only for session-based requests (not Bearer token requests)
973
998
  if (
974
- requireUiAuth &&
999
+ requireAuth &&
1000
+ session &&
1001
+ !hasBearerToken &&
975
1002
  requiresCsrfValidation &&
976
1003
  pathname !== "/api/auth/login" &&
977
1004
  request.headers["x-csrf-token"] !== session?.csrfToken
@@ -1090,6 +1117,9 @@ export const createRequestHandler = async (options?: {
1090
1117
  let latestRunId = conversation.runtimeRunId ?? "";
1091
1118
  let assistantResponse = "";
1092
1119
  const toolTimeline: string[] = [];
1120
+ const sections: Array<{ type: "text" | "tools"; content: string | string[] }> = [];
1121
+ let currentText = "";
1122
+ let currentTools: string[] = [];
1093
1123
  try {
1094
1124
  const recallCorpus = (await conversationStore.list(ownerId))
1095
1125
  .filter((item) => item.conversationId !== conversationId)
@@ -1106,7 +1136,7 @@ export const createRequestHandler = async (options?: {
1106
1136
  }))
1107
1137
  .filter((item) => item.content.length > 0);
1108
1138
 
1109
- for await (const event of harness.run({
1139
+ for await (const event of harness.runWithTelemetry({
1110
1140
  task: messageText,
1111
1141
  parameters: {
1112
1142
  ...(body.parameters ?? {}),
@@ -1119,25 +1149,48 @@ export const createRequestHandler = async (options?: {
1119
1149
  latestRunId = event.runId;
1120
1150
  }
1121
1151
  if (event.type === "model:chunk") {
1152
+ // If we have tools accumulated and text starts again, push tools as a section
1153
+ if (currentTools.length > 0) {
1154
+ sections.push({ type: "tools", content: currentTools });
1155
+ currentTools = [];
1156
+ }
1122
1157
  assistantResponse += event.content;
1158
+ currentText += event.content;
1123
1159
  }
1124
1160
  if (event.type === "tool:started") {
1125
- toolTimeline.push(`- start \`${event.tool}\``);
1161
+ // If we have text accumulated, push it as a text section
1162
+ if (currentText.length > 0) {
1163
+ sections.push({ type: "text", content: currentText });
1164
+ currentText = "";
1165
+ }
1166
+ const toolText = `- start \`${event.tool}\``;
1167
+ toolTimeline.push(toolText);
1168
+ currentTools.push(toolText);
1126
1169
  }
1127
1170
  if (event.type === "tool:completed") {
1128
- toolTimeline.push(`- done \`${event.tool}\` (${event.duration}ms)`);
1171
+ const toolText = `- done \`${event.tool}\` (${event.duration}ms)`;
1172
+ toolTimeline.push(toolText);
1173
+ currentTools.push(toolText);
1129
1174
  }
1130
1175
  if (event.type === "tool:error") {
1131
- toolTimeline.push(`- error \`${event.tool}\`: ${event.error}`);
1176
+ const toolText = `- error \`${event.tool}\`: ${event.error}`;
1177
+ toolTimeline.push(toolText);
1178
+ currentTools.push(toolText);
1132
1179
  }
1133
1180
  if (event.type === "tool:approval:required") {
1134
- toolTimeline.push(`- approval required \`${event.tool}\``);
1181
+ const toolText = `- approval required \`${event.tool}\``;
1182
+ toolTimeline.push(toolText);
1183
+ currentTools.push(toolText);
1135
1184
  }
1136
1185
  if (event.type === "tool:approval:granted") {
1137
- toolTimeline.push(`- approval granted (${event.approvalId})`);
1186
+ const toolText = `- approval granted (${event.approvalId})`;
1187
+ toolTimeline.push(toolText);
1188
+ currentTools.push(toolText);
1138
1189
  }
1139
1190
  if (event.type === "tool:approval:denied") {
1140
- toolTimeline.push(`- approval denied (${event.approvalId})`);
1191
+ const toolText = `- approval denied (${event.approvalId})`;
1192
+ toolTimeline.push(toolText);
1193
+ currentTools.push(toolText);
1141
1194
  }
1142
1195
  if (
1143
1196
  event.type === "run:completed" &&
@@ -1149,6 +1202,13 @@ export const createRequestHandler = async (options?: {
1149
1202
  await telemetry.emit(event);
1150
1203
  response.write(formatSseEvent(event));
1151
1204
  }
1205
+ // Finalize sections
1206
+ if (currentTools.length > 0) {
1207
+ sections.push({ type: "tools", content: currentTools });
1208
+ }
1209
+ if (currentText.length > 0) {
1210
+ sections.push({ type: "text", content: currentText });
1211
+ }
1152
1212
  conversation.messages = [
1153
1213
  ...conversation.messages,
1154
1214
  { role: "user", content: messageText },
@@ -1156,8 +1216,11 @@ export const createRequestHandler = async (options?: {
1156
1216
  role: "assistant",
1157
1217
  content: assistantResponse,
1158
1218
  metadata:
1159
- toolTimeline.length > 0
1160
- ? ({ toolActivity: toolTimeline } as Message["metadata"])
1219
+ toolTimeline.length > 0 || sections.length > 0
1220
+ ? ({
1221
+ toolActivity: toolTimeline,
1222
+ sections: sections.length > 0 ? sections : undefined,
1223
+ } as Message["metadata"])
1161
1224
  : undefined,
1162
1225
  },
1163
1226
  ];
@@ -1246,7 +1309,7 @@ export const runOnce = async (
1246
1309
  return;
1247
1310
  }
1248
1311
 
1249
- for await (const event of harness.run(input)) {
1312
+ for await (const event of harness.runWithTelemetry(input)) {
1250
1313
  await telemetry.emit(event);
1251
1314
  if (event.type === "model:chunk") {
1252
1315
  process.stdout.write(event.content);
@@ -441,11 +441,12 @@ const collectEnvFileLines = (answers: OnboardingAnswers): string[] => {
441
441
  (answers["auth.type"] as "bearer" | "header" | "custom" | undefined) ?? "bearer";
442
442
  const authHeaderName = String(answers["auth.headerName"] ?? "x-poncho-key");
443
443
  if (authRequired) {
444
- lines.push("# Auth (API request authentication)");
444
+ lines.push("# Auth (protects both Web UI and API)");
445
+ lines.push("# Web UI: enter this token as the passphrase");
445
446
  if (authType === "bearer") {
446
- lines.push("# Requests should include: Authorization: Bearer <token>");
447
+ lines.push("# API: include Authorization: Bearer <token> header");
447
448
  } else if (authType === "header") {
448
- lines.push(`# Requests should include: ${authHeaderName}: <token>`);
449
+ lines.push(`# API: include ${authHeaderName}: <token> header`);
449
450
  } else {
450
451
  lines.push("# Custom auth mode: read this token in your auth.validate function.");
451
452
  }
@@ -443,6 +443,9 @@ export const runInteractiveInk = async ({
443
443
  let sawChunk = false;
444
444
  let toolEvents = 0;
445
445
  const toolTimeline: string[] = [];
446
+ const sections: Array<{ type: "text" | "tools"; content: string | string[] }> = [];
447
+ let currentText = "";
448
+ let currentTools: string[] = [];
446
449
  let runFailed = false;
447
450
  let usage: TokenUsage | undefined;
448
451
  let latestRunId = "";
@@ -459,8 +462,14 @@ export const runInteractiveInk = async ({
459
462
  }
460
463
  if (event.type === "model:chunk") {
461
464
  sawChunk = true;
465
+ // If we have tools accumulated and text starts again, push tools as a section
466
+ if (currentTools.length > 0) {
467
+ sections.push({ type: "tools", content: currentTools });
468
+ currentTools = [];
469
+ }
462
470
  responseText += event.content;
463
471
  streamedText += event.content;
472
+ currentText += event.content;
464
473
 
465
474
  if (!thinkingCleared) {
466
475
  clearThinking();
@@ -485,11 +494,18 @@ export const runInteractiveInk = async ({
485
494
  clearThinking();
486
495
 
487
496
  if (event.type === "tool:started") {
497
+ // If we have text accumulated, push it as a text section
498
+ if (currentText.length > 0) {
499
+ sections.push({ type: "text", content: currentText });
500
+ currentText = "";
501
+ }
488
502
  const preview = showToolPayloads
489
503
  ? compactPreview(event.input, 400)
490
504
  : compactPreview(event.input, 100);
491
505
  console.log(yellow(`tools> start ${event.tool} input=${preview}`));
492
- toolTimeline.push(`- start \`${event.tool}\``);
506
+ const toolText = `- start \`${event.tool}\``;
507
+ toolTimeline.push(toolText);
508
+ currentTools.push(toolText);
493
509
  toolEvents += 1;
494
510
  } else if (event.type === "tool:completed") {
495
511
  const preview = showToolPayloads
@@ -503,29 +519,37 @@ export const runInteractiveInk = async ({
503
519
  if (showToolPayloads) {
504
520
  console.log(yellow(`tools> output ${preview}`));
505
521
  }
506
- toolTimeline.push(
507
- `- done \`${event.tool}\` in ${formatDuration(event.duration)}`,
508
- );
522
+ const toolText = `- done \`${event.tool}\` in ${formatDuration(event.duration)}`;
523
+ toolTimeline.push(toolText);
524
+ currentTools.push(toolText);
509
525
  } else if (event.type === "tool:error") {
510
526
  console.log(
511
527
  red(`tools> error ${event.tool}: ${event.error}`),
512
528
  );
513
- toolTimeline.push(`- error \`${event.tool}\`: ${event.error}`);
529
+ const toolText = `- error \`${event.tool}\`: ${event.error}`;
530
+ toolTimeline.push(toolText);
531
+ currentTools.push(toolText);
514
532
  } else if (event.type === "tool:approval:required") {
515
533
  console.log(
516
534
  magenta(`tools> approval required for ${event.tool}`),
517
535
  );
518
- toolTimeline.push(`- approval required \`${event.tool}\``);
536
+ const toolText = `- approval required \`${event.tool}\``;
537
+ toolTimeline.push(toolText);
538
+ currentTools.push(toolText);
519
539
  } else if (event.type === "tool:approval:granted") {
520
540
  console.log(
521
541
  gray(`tools> approval granted (${event.approvalId})`),
522
542
  );
523
- toolTimeline.push(`- approval granted (${event.approvalId})`);
543
+ const toolText = `- approval granted (${event.approvalId})`;
544
+ toolTimeline.push(toolText);
545
+ currentTools.push(toolText);
524
546
  } else if (event.type === "tool:approval:denied") {
525
547
  console.log(
526
548
  magenta(`tools> approval denied (${event.approvalId})`),
527
549
  );
528
- toolTimeline.push(`- approval denied (${event.approvalId})`);
550
+ const toolText = `- approval denied (${event.approvalId})`;
551
+ toolTimeline.push(toolText);
552
+ currentTools.push(toolText);
529
553
  }
530
554
  } else if (event.type === "run:error") {
531
555
  clearThinking();
@@ -588,13 +612,24 @@ export const runInteractiveInk = async ({
588
612
  activeConversationId = created.conversationId;
589
613
  }
590
614
 
615
+ // Finalize sections
616
+ if (currentTools.length > 0) {
617
+ sections.push({ type: "tools", content: currentTools });
618
+ }
619
+ if (currentText.length > 0) {
620
+ sections.push({ type: "text", content: currentText });
621
+ }
622
+
591
623
  messages.push({ role: "user", content: trimmed });
592
624
  messages.push({
593
625
  role: "assistant",
594
626
  content: responseText,
595
627
  metadata:
596
- toolTimeline.length > 0
597
- ? ({ toolActivity: toolTimeline } as Message["metadata"])
628
+ toolTimeline.length > 0 || sections.length > 0
629
+ ? ({
630
+ toolActivity: toolTimeline,
631
+ sections: sections.length > 0 ? sections : undefined,
632
+ } as Message["metadata"])
598
633
  : undefined,
599
634
  });
600
635
  turn = computeTurn(messages);