@tabbio-technologies/cli 1.2.8 → 1.3.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 (27) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/README.md +86 -19
  3. package/dist/chunks/{chunk-7LNC3FIV.js → chunk-GGJEDG3P.js} +95 -14
  4. package/dist/chunks/{chunk-7LNC3FIV.js.map → chunk-GGJEDG3P.js.map} +4 -4
  5. package/dist/chunks/{chunk-Y6GWIFHI.js → chunk-KBIQKZUK.js} +3 -53
  6. package/dist/chunks/chunk-KBIQKZUK.js.map +7 -0
  7. package/dist/chunks/{chunk-QVR5KIEQ.js → chunk-KHLGZ5IV.js} +1796 -244
  8. package/dist/chunks/chunk-KHLGZ5IV.js.map +7 -0
  9. package/dist/chunks/{chunk-NAWA6NCZ.js → chunk-NRFVKK3S.js} +37 -96
  10. package/dist/chunks/chunk-NRFVKK3S.js.map +7 -0
  11. package/dist/chunks/chunk-TP3CRQJD.js +106 -0
  12. package/dist/chunks/chunk-TP3CRQJD.js.map +7 -0
  13. package/dist/chunks/{entry-WENOOT6W.js → entry-ZAXWZSEE.js} +2023 -770
  14. package/dist/chunks/entry-ZAXWZSEE.js.map +7 -0
  15. package/dist/chunks/{mount-NGUARMCL.js → mount-36JUVX3R.js} +3 -2
  16. package/dist/chunks/{mount-NGUARMCL.js.map → mount-36JUVX3R.js.map} +1 -1
  17. package/dist/chunks/{program-O2BA5L6Z.js → program-J6GENE2V.js} +314 -375
  18. package/dist/chunks/program-J6GENE2V.js.map +7 -0
  19. package/dist/chunks/{status-I5UAQPBY.js → status-2C7BECKK.js} +3 -3
  20. package/dist/cli.js +1 -1
  21. package/package.json +4 -8
  22. package/dist/chunks/chunk-NAWA6NCZ.js.map +0 -7
  23. package/dist/chunks/chunk-QVR5KIEQ.js.map +0 -7
  24. package/dist/chunks/chunk-Y6GWIFHI.js.map +0 -7
  25. package/dist/chunks/entry-WENOOT6W.js.map +0 -7
  26. package/dist/chunks/program-O2BA5L6Z.js.map +0 -7
  27. /package/dist/chunks/{status-I5UAQPBY.js.map → status-2C7BECKK.js.map} +0 -0
@@ -1,27 +1,35 @@
1
1
  import { createRequire as __tabbioCreateRequire } from 'node:module';
2
2
  const require = __tabbioCreateRequire(import.meta.url);
3
3
  import {
4
- createCommandContext,
5
4
  mcpSummary,
6
- readGlobalOptions,
7
5
  registerStatusCommand,
8
6
  registerWhoamiCommand
9
- } from "./chunk-Y6GWIFHI.js";
7
+ } from "./chunk-KBIQKZUK.js";
10
8
  import {
11
9
  ARTIFACT_KINDS,
12
10
  ARTIFACT_PRODUCING_TOOL_IDS,
13
11
  PREVIEW_VARIANTS,
12
+ PRODUCING_TOOLS,
14
13
  RESERVED_GLOBAL_FLAGS,
14
+ artifactBaseName,
15
15
  artifactFromResult,
16
+ artifactsDir,
16
17
  assertInteractive,
17
18
  autoFilledProperties,
19
+ autoSaveDisabled,
18
20
  canUseInteractiveUi,
19
21
  capabilityDenial,
20
22
  capabilityRequestFields,
23
+ cardFromToolResult,
24
+ cardLines,
25
+ collapseRepeat,
26
+ confirm,
27
+ cvExportFormat,
21
28
  defaultVariantFor,
22
29
  deleteArtifact,
23
30
  describeSchema,
24
31
  didYouMean,
32
+ displayPath,
25
33
  displayWidth,
26
34
  downloadToFile,
27
35
  downloadUrl,
@@ -36,6 +44,7 @@ import {
36
44
  getInteractiveHooks,
37
45
  hyperlink,
38
46
  isInteractiveTerminal,
47
+ isTextFile,
39
48
  jsonEnvelope,
40
49
  listArtifacts,
41
50
  listVersions,
@@ -43,16 +52,22 @@ import {
43
52
  mergeSources,
44
53
  parseFields,
45
54
  parseToolInput,
55
+ planFinal,
46
56
  previewUrl,
47
57
  previewVariantFor,
48
58
  printEmptyNotice,
59
+ readSavedPreview,
60
+ registerLoginCommand,
49
61
  renderResult,
50
62
  resolveCapabilities,
51
63
  resolveOutputMode,
52
64
  resolveOutputPath,
53
65
  restoreVersion,
54
66
  safeFileName,
67
+ saveArtifactToDisk,
55
68
  saveCapabilityPrefs,
69
+ saveCvExportToDisk,
70
+ savedLines,
56
71
  schemaToFlags,
57
72
  schemaToOptions,
58
73
  setInteractiveHooks,
@@ -63,9 +78,14 @@ import {
63
78
  supportsHyperlinks,
64
79
  toTsv,
65
80
  truncate,
81
+ uniquePath,
66
82
  waitForArtifact,
67
83
  writeJson
68
- } from "./chunk-QVR5KIEQ.js";
84
+ } from "./chunk-KHLGZ5IV.js";
85
+ import {
86
+ TerminalTextSanitizer,
87
+ safeTerminalText
88
+ } from "./chunk-NRFVKK3S.js";
69
89
  import {
70
90
  ApiClient,
71
91
  CLI_PACKAGE_NAME,
@@ -80,20 +100,15 @@ import {
80
100
  assertJwtSession,
81
101
  assertProfileName,
82
102
  checkForUpdate,
83
- chooseLoginMethod,
84
- clearCatalogCache,
85
103
  configPaths,
104
+ createCommandContext,
86
105
  credentialPermissionIssues,
87
- credentialsFromLogin,
88
106
  debug,
89
107
  describeMcpBearer,
90
- deviceLabel,
91
108
  findTool,
92
- fingerprint,
93
109
  getGlobalOptions,
94
110
  getMcpAccessState,
95
111
  groupCatalog,
96
- hasDisplay,
97
112
  info,
98
113
  interpretToolPayload,
99
114
  interruptedError,
@@ -104,20 +119,17 @@ import {
104
119
  loadConfig,
105
120
  loadCredentials,
106
121
  loadStoredCredentials,
107
- loginWithBrowser,
108
- loginWithEmailOtp,
109
122
  logoutProfile,
110
123
  normalizeBaseUrl,
111
124
  notSignedInError,
112
125
  printJson,
113
126
  printKeyValues,
114
127
  readCachedCatalog,
128
+ readGlobalOptions,
115
129
  readRequestId,
116
130
  redactSecrets,
117
131
  resolveProfile,
118
132
  resolveProfileWithSources,
119
- revokeSession,
120
- saveCredentials,
121
133
  selectMcpBearer,
122
134
  setGlobalOptions,
123
135
  successLine,
@@ -126,9 +138,10 @@ import {
126
138
  usageError,
127
139
  warn,
128
140
  writeOut
129
- } from "./chunk-7LNC3FIV.js";
141
+ } from "./chunk-GGJEDG3P.js";
130
142
  import {
131
- activeCommandSignal
143
+ activeCommandSignal,
144
+ cancelActiveCommand
132
145
  } from "./chunk-NK5RPNSV.js";
133
146
  import {
134
147
  configureTheme,
@@ -138,78 +151,6 @@ import {
138
151
  // src/program.ts
139
152
  import { Command as Command2, CommanderError as CommanderError2 } from "commander";
140
153
 
141
- // src/core/prompt.ts
142
- import { createInterface } from "node:readline";
143
- function cancelled() {
144
- return interruptedError();
145
- }
146
- function promptLine(question) {
147
- return new Promise((resolve, reject2) => {
148
- const rl = createInterface({ input: process.stdin, output: process.stderr, terminal: Boolean(process.stdin.isTTY) });
149
- let answered = false;
150
- rl.once("SIGINT", () => {
151
- rl.close();
152
- reject2(cancelled());
153
- });
154
- rl.once("close", () => {
155
- if (!answered) reject2(usageError("No input received"));
156
- });
157
- rl.question(question, (answer) => {
158
- answered = true;
159
- rl.close();
160
- resolve(answer.trim());
161
- });
162
- });
163
- }
164
- function promptSecret(question) {
165
- const stdin = process.stdin;
166
- if (!stdin.isTTY || typeof stdin.setRawMode !== "function") return promptLine(question);
167
- return new Promise((resolve, reject2) => {
168
- let value = "";
169
- process.stderr.write(question);
170
- const wasRaw = stdin.isRaw;
171
- stdin.setRawMode(true);
172
- stdin.resume();
173
- stdin.setEncoding("utf8");
174
- const finish = (error) => {
175
- stdin.off("data", onData);
176
- stdin.setRawMode(wasRaw);
177
- stdin.pause();
178
- process.stderr.write("\n");
179
- if (error) reject2(error);
180
- else resolve(value.trim());
181
- };
182
- const onData = (chunk) => {
183
- for (const char of chunk) {
184
- if (char === "") return finish(cancelled());
185
- if (char === "" && value === "") return finish(cancelled());
186
- if (char === "\r" || char === "\n") return finish();
187
- if (char === "\x7F" || char === "\b") {
188
- if (value.length > 0) {
189
- value = value.slice(0, -1);
190
- process.stderr.write("\b \b");
191
- }
192
- continue;
193
- }
194
- if (char < " ") continue;
195
- value += char;
196
- process.stderr.write("\u2022");
197
- }
198
- };
199
- stdin.on("data", onData);
200
- });
201
- }
202
- async function confirm(question, opts = {}) {
203
- if (opts.assumeYes) return true;
204
- if (!process.stdin.isTTY) {
205
- throw usageError(`${question} (needs confirmation)`, "Re-run with --yes to confirm non-interactively.");
206
- }
207
- const suffix = opts.defaultYes ? " [Y/n] " : " [y/N] ";
208
- const answer = (await promptLine(`${question}${suffix}`)).toLowerCase();
209
- if (!answer) return Boolean(opts.defaultYes);
210
- return answer === "y" || answer === "yes";
211
- }
212
-
213
154
  // src/core/approvals.ts
214
155
  var DEFAULT_WAIT_TIMEOUT_MS = 10 * 6e4;
215
156
  var POLL_START_MS = 3e3;
@@ -600,6 +541,8 @@ function registerApprovalsCommand(program) {
600
541
  import { Option } from "commander";
601
542
 
602
543
  // src/commands/artifacts-files.ts
544
+ import { mkdirSync } from "node:fs";
545
+ import { basename, join } from "node:path";
603
546
  import open from "open";
604
547
  var DEFAULT_WAIT = 5 * 6e4;
605
548
  function outMode(ctx) {
@@ -713,8 +656,16 @@ async function downloadCommand(ctx, id, opts) {
713
656
  }
714
657
  const variant = opts.format ? formatToVariant(opts.format) : defaultVariantFor(artifact);
715
658
  const signed = await mintDownload(ctx, id, variant, { version: opts.version, wait: Boolean(opts.wait), timeoutMs });
716
- const fileName = safeFileName(artifact.title, extensionFor(variant, signed.contentType));
717
- const dest = resolveOutputPath(opts.out, fileName);
659
+ const ext = extensionFor(variant, signed.contentType);
660
+ let dest;
661
+ if (opts.out) dest = resolveOutputPath(opts.out, safeFileName(artifact.title, ext));
662
+ else {
663
+ const dir = artifactsDir();
664
+ mkdirSync(dir, { recursive: true, mode: 448 });
665
+ const base = artifactBaseName(artifact.title, artifact.id || id);
666
+ dest = opts.force ? join(dir, `${base}.${ext}`) : uniquePath(dir, base, ext);
667
+ }
668
+ const fileName = basename(dest);
718
669
  const tty = out !== "json" && process.stderr.isTTY && !ctx.globals.quiet && !process.env.CI;
719
670
  let lastPaint = 0;
720
671
  const saved = await downloadToFile(signed.url, dest, {
@@ -733,7 +684,7 @@ async function downloadCommand(ctx, id, opts) {
733
684
  writeJson(jsonEnvelope({ id: "artifacts.download" }, data, { durationMs: Date.now() - started }));
734
685
  return;
735
686
  }
736
- status(ctx, out, `${theme.success(theme.symbols.success)} Saved ${variant.toUpperCase()} ${theme.dim(`${humanBytes(saved.bytes)} ${theme.symbols.middot} ${formatDuration(Date.now() - started)}`)}`);
687
+ status(ctx, out, `${theme.success(theme.symbols.success)} Saved ${displayPath(saved.path)} ${theme.dim(`${humanBytes(saved.bytes)} ${theme.symbols.middot} ${formatDuration(Date.now() - started)}`)}`);
737
688
  process.stdout.write(`${saved.path}
738
689
  `);
739
690
  }
@@ -890,7 +841,7 @@ function registerArtifactsCommand(program) {
890
841
  artifacts.command("open").description("Print a 15-minute preview link and open it in the browser (on a terminal)").argument("<id>", "Artifact id").addOption(new Option("--variant <variant>", "What to preview (default: image for images, else html)").choices(["html", "pdf", "image", "thumbnail"])).addOption(new Option("--theme <theme>", "HTML preview theme").choices(["light", "dark"])).option("--version <versionId>", "A version other than the newest").option("--print", "Only print the link, never open a browser").action(
891
842
  (id, opts, cmd) => withSession(cmd, (ctx) => openCommand(ctx, id, opts))
892
843
  );
893
- artifacts.command("download").description("Save a file to disk (default name from its title; never overwrites without --force)").argument("<id>", "Artifact id").option("--format <format>", "pdf | pptx | html | png (image formats save the stored image). Default: pdf for documents, pptx for slides, html for pages, the image for images").option("-O, --out <path>", "File or directory to save to (default: the current directory)").option("--force", "Overwrite an existing file").option("--version <versionId>", "A version other than the newest").option("--wait", "If it is still rendering, wait for it").option("--timeout <duration>", "How long --wait waits (default 5m)").action(
844
+ artifacts.command("download").description("Save a file to disk, by default into ./tabbio-artifacts (never overwrites without --force)").argument("<id>", "Artifact id").option("--format <format>", "pdf | pptx | html | png (image formats save the stored image). Default: pdf for documents, pptx for slides, html for pages, the image for images").option("-O, --out <path>", "File or directory to save to (default: ./tabbio-artifacts, or TABBIO_ARTIFACTS_DIR)").option("--force", "Overwrite an existing file").option("--version <versionId>", "A version other than the newest").option("--wait", "If it is still rendering, wait for it").option("--timeout <duration>", "How long --wait waits (default 5m)").action(
894
845
  (id, opts, cmd) => withSession(cmd, (ctx) => downloadCommand(ctx, id, opts))
895
846
  );
896
847
  artifacts.command("versions").description("Every version of a file, newest first").argument("<id>", "Artifact id").action((id, _opts, cmd) => withSession(cmd, (ctx) => versions(ctx, id)));
@@ -1125,6 +1076,203 @@ function wantsHelp(args) {
1125
1076
  return args.includes("--help") || args.includes("-h");
1126
1077
  }
1127
1078
 
1079
+ // src/commands/ask-chat.ts
1080
+ function width() {
1081
+ return Math.min(100, process.stdout.columns || 80);
1082
+ }
1083
+ async function saveChatFiles(ctx, artifacts, exports, mode2) {
1084
+ const results = [];
1085
+ for (const artifact of artifacts) {
1086
+ const base = { artifactId: artifact.id, title: artifact.title, kind: artifact.kind };
1087
+ try {
1088
+ let status2 = artifact.status;
1089
+ if (status2 === "pending") {
1090
+ if (mode2 !== "json" && !ctx.globals.quiet) process.stderr.write(theme.dim(`${theme.symbols.pending} Waiting for "${safeTerminalText(artifact.title)}" to finish (ctrl+c stops)
1091
+ `));
1092
+ const waited = await waitForArtifact(() => getArtifact(ctx.api, artifact.id), { signal: activeCommandSignal });
1093
+ status2 = waited.artifact?.status ?? "pending";
1094
+ }
1095
+ if (status2 !== "ready") {
1096
+ results.push({ ...base, files: [], error: status2 === "failed" ? "the file failed to render" : `still rendering; try tabbio artifacts wait ${artifact.id}` });
1097
+ continue;
1098
+ }
1099
+ const files = await saveArtifactToDisk(ctx, artifact.id, { versionId: artifact.versionId });
1100
+ const text = files.find((file) => isTextFile(file.path));
1101
+ const preview = text ? { path: text.path, ...await readSavedPreview(text.path, 12) } : void 0;
1102
+ results.push({ ...base, files, ...preview ? { preview } : {} });
1103
+ } catch (error) {
1104
+ if (activeCommandSignal.aborted) throw interruptedError("File save cancelled");
1105
+ if (isCliError(error) && error.exitCode === ExitCode.Interrupted) throw error;
1106
+ results.push({ ...base, files: [], error: toCliError(error).message });
1107
+ }
1108
+ }
1109
+ for (const cv of exports) {
1110
+ try {
1111
+ results.push({ cvId: cv.cvId, title: cv.title, kind: "cv", files: [await saveCvExportToDisk(ctx, cv.cvId, { format: cv.format })] });
1112
+ } catch (error) {
1113
+ if (activeCommandSignal.aborted) throw interruptedError("File save cancelled");
1114
+ if (isCliError(error) && error.exitCode === ExitCode.Interrupted) throw error;
1115
+ results.push({ cvId: cv.cvId, title: cv.title, kind: "cv", files: [], error: toCliError(error).message });
1116
+ }
1117
+ }
1118
+ return results;
1119
+ }
1120
+ async function askViaChat(ctx, question, mode2, opts) {
1121
+ const started = Date.now();
1122
+ const controller = new AbortController();
1123
+ const onSigint = () => controller.abort();
1124
+ process.on("SIGINT", onSigint);
1125
+ const clear = mode2 === "json" ? () => void 0 : startProgress(ctx, mode2, "Asking Tabbio\u2026");
1126
+ let cleared = false;
1127
+ const settle = () => {
1128
+ if (!cleared) clear();
1129
+ cleared = true;
1130
+ };
1131
+ const out = (text2) => {
1132
+ if (mode2 === "json") return;
1133
+ settle();
1134
+ process.stdout.write(text2);
1135
+ };
1136
+ let text = "";
1137
+ let printedEnd = "\n";
1138
+ const print = (chunk) => {
1139
+ out(chunk);
1140
+ if (chunk) printedEnd = chunk;
1141
+ };
1142
+ const block = (lines) => {
1143
+ if (!printedEnd.endsWith("\n")) print("\n");
1144
+ print(`${lines.join("\n")}
1145
+ `);
1146
+ };
1147
+ let finishReason;
1148
+ let threadId;
1149
+ let finalText;
1150
+ let approval;
1151
+ let errorMessage;
1152
+ let sources = [];
1153
+ const artifacts = /* @__PURE__ */ new Map();
1154
+ const made = /* @__PURE__ */ new Set();
1155
+ const exports = [];
1156
+ const cards = [];
1157
+ const notices = [];
1158
+ const links = supportsHyperlinks();
1159
+ const answerSanitizer = new TerminalTextSanitizer();
1160
+ try {
1161
+ const capabilities = capabilityRequestFields(opts.agentMode, loadCapabilityPrefs(ctx.profile.name));
1162
+ const events = streamChat(ctx, { mode: opts.agentMode, message: question, ...capabilities }, { signal: controller.signal });
1163
+ for await (const event of events) {
1164
+ switch (event.type) {
1165
+ case "start":
1166
+ threadId = event.payload.threadId ?? threadId;
1167
+ break;
1168
+ case "text-delta":
1169
+ if (!event.payload.text) break;
1170
+ text += event.payload.text;
1171
+ print(answerSanitizer.write(event.payload.text));
1172
+ break;
1173
+ case "tool-call-approval":
1174
+ approval = { approvalId: event.payload.approvalId, name: event.payload.name || "A tool" };
1175
+ break;
1176
+ case "tool-result": {
1177
+ const payload = event.payload;
1178
+ sources = mergeSources(sources, sourcesFromResult(payload.result));
1179
+ const artifact = artifactFromResult(payload.result);
1180
+ if (artifact) {
1181
+ artifacts.set(artifact.id, artifact);
1182
+ if (PRODUCING_TOOLS.has(payload.name)) made.add(artifact.id);
1183
+ }
1184
+ const denial = capabilityDenial(payload.result);
1185
+ if (denial) notices.push(`${denial.message}${denial.reason ? ` (${denial.reason})` : ""}`);
1186
+ const record = payload.result;
1187
+ if (payload.name === "cvExport" && record && typeof record === "object") {
1188
+ const format = cvExportFormat(payload.args.format, record.formats);
1189
+ const cvId = typeof record.cvId === "string" && record.cvId ? record.cvId : "master";
1190
+ if (format) exports.push({ cvId, title: typeof record.cvTitle === "string" ? record.cvTitle : "My CV", format });
1191
+ }
1192
+ const card = cardFromToolResult(payload, { appUrl: ctx.profile.appUrl });
1193
+ if (card) {
1194
+ cards.push(card);
1195
+ block(["", ...cardLines(card, { width: width(), links }), ""]);
1196
+ }
1197
+ break;
1198
+ }
1199
+ case "error":
1200
+ errorMessage = event.payload.message || "The agent reported an error";
1201
+ break;
1202
+ case "finish":
1203
+ finishReason = event.payload.finishReason ?? finishReason;
1204
+ threadId = event.payload.threadId ?? threadId;
1205
+ finalText = event.payload.text;
1206
+ break;
1207
+ default:
1208
+ break;
1209
+ }
1210
+ }
1211
+ } finally {
1212
+ settle();
1213
+ process.off("SIGINT", onSigint);
1214
+ }
1215
+ if (finalText && finishReason !== "error") {
1216
+ const plan = planFinal(text, finalText);
1217
+ if (plan.kind === "append") {
1218
+ text += plan.text;
1219
+ print(answerSanitizer.write(plan.text));
1220
+ } else if (plan.kind === "different") {
1221
+ if (text.trim()) block(["", theme.dim("Tabbio revised this answer:")]);
1222
+ text = plan.text;
1223
+ answerSanitizer.reset();
1224
+ print(answerSanitizer.write(plan.text));
1225
+ }
1226
+ }
1227
+ if (errorMessage || finishReason === "error") throw new CliError({ code: "AGENT_ERROR", message: errorMessage ?? "The agent could not finish this answer", exitCode: ExitCode.Server });
1228
+ const files = [...artifacts.values()].map(({ id, kind, title, status: status2 }) => ({ id, kind, title, status: status2 }));
1229
+ const toSave = opts.save ? [...artifacts.values()].filter((a) => made.has(a.id) && a.status !== "failed") : [];
1230
+ if (!printedEnd.endsWith("\n")) print("\n");
1231
+ for (const note of notices) process.stderr.write(`${theme.dim(`${theme.symbols.arrowRight} ${safeTerminalText(note)}`)}
1232
+ `);
1233
+ const saved = toSave.length || opts.save && exports.length ? await saveChatFiles(ctx, toSave, opts.save ? exports : [], mode2) : [];
1234
+ if (mode2 === "json") {
1235
+ writeJson({
1236
+ data: {
1237
+ text: collapseRepeat(text.trimEnd()),
1238
+ ...finishReason ? { finishReason } : {},
1239
+ ...threadId ? { threadId } : {},
1240
+ ...sources.length ? { sources } : {},
1241
+ ...files.length ? { artifacts: files } : {},
1242
+ ...cards.length ? { cards } : {},
1243
+ ...saved.length ? { saved } : {}
1244
+ },
1245
+ meta: { schemaVersion: 1, tool: "chat", durationMs: Date.now() - started, ...approval ? { approval } : {} }
1246
+ });
1247
+ } else {
1248
+ printExtras(sources, files.filter((f) => !saved.some((s) => s.artifactId === f.id && s.files.length)));
1249
+ for (const entry of saved) block(["", ...savedLines(entry, { width: width() })]);
1250
+ if (approval) {
1251
+ process.stderr.write(
1252
+ `${theme.warn(`${theme.symbols.pending} ${safeTerminalText(approval.name)} is waiting for your approval`)} ${theme.dim(safeTerminalText(approval.approvalId))}
1253
+ ${theme.dim("Run:")} tabbio approvals approve ${safeTerminalText(approval.approvalId)}
1254
+ `
1255
+ );
1256
+ }
1257
+ }
1258
+ if (saved.some((entry) => entry.error)) process.exitCode = ExitCode.Error;
1259
+ else if (approval) process.exitCode = ExitCode.ApprovalPending;
1260
+ }
1261
+ function printExtras(sources, files) {
1262
+ if (sources.length) {
1263
+ process.stdout.write(`
1264
+ ${theme.dim("Sources")}
1265
+ `);
1266
+ sources.forEach((source, i) => process.stdout.write(`${i + 1}. ${safeTerminalText(source.title)} ${theme.dim(safeTerminalText(source.url))}
1267
+ `));
1268
+ }
1269
+ for (const file of files) {
1270
+ const next = file.status === "pending" ? `tabbio artifacts wait ${file.id}` : `tabbio artifacts download ${file.id}`;
1271
+ process.stdout.write(`${theme.dim(`${safeTerminalText(file.kind)} ${safeTerminalText(file.status)}:`)} ${safeTerminalText(file.title)} ${theme.dim(safeTerminalText(next))}
1272
+ `);
1273
+ }
1274
+ }
1275
+
1128
1276
  // src/commands/ask.ts
1129
1277
  function askInputField(schema) {
1130
1278
  const properties = schema?.properties ?? {};
@@ -1157,7 +1305,7 @@ async function ask(ctx, words, opts = {}) {
1157
1305
  if (!question && !process.stdin.isTTY) question = (await readStdin()).trim();
1158
1306
  if (!question) throw usageError("Ask a question", 'e.g. tabbio ask "Which of my CVs fits product roles best?"');
1159
1307
  if (!opts.viaMcp && describeMcpBearer(ctx.credentials)?.kind === "app-session") {
1160
- await askViaChat(ctx, question, mode2, opts.mode ?? "seeker");
1308
+ await askViaChat(ctx, question, mode2, { agentMode: opts.mode ?? "seeker", save: opts.save !== false && !autoSaveDisabled() });
1161
1309
  return;
1162
1310
  }
1163
1311
  const session = await ctx.mcp();
@@ -1199,121 +1347,23 @@ async function ask(ctx, words, opts = {}) {
1199
1347
  process.stdout.write(text ? `${text}
1200
1348
  ` : "");
1201
1349
  }
1202
- async function askViaChat(ctx, question, mode2, agentMode) {
1203
- const started = Date.now();
1204
- const controller = new AbortController();
1205
- const onSigint = () => controller.abort();
1206
- process.on("SIGINT", onSigint);
1207
- const clear = mode2 === "json" ? () => void 0 : startProgress(ctx, mode2, "Asking Tabbio\u2026");
1208
- let cleared = false;
1209
- let text = "";
1210
- let finishReason;
1211
- let threadId;
1212
- let approval;
1213
- let errorMessage;
1214
- let sources = [];
1215
- const artifacts = /* @__PURE__ */ new Map();
1216
- const notices = [];
1217
- try {
1218
- const capabilities = capabilityRequestFields(agentMode, loadCapabilityPrefs(ctx.profile.name));
1219
- const events = streamChat(ctx, { mode: agentMode, message: question, ...capabilities }, { signal: controller.signal });
1220
- for await (const event of events) {
1221
- const payload = event.payload ?? {};
1222
- switch (event.type) {
1223
- case "start":
1224
- if (typeof payload.threadId === "string") threadId = payload.threadId;
1225
- break;
1226
- case "text-delta": {
1227
- const delta = typeof payload.text === "string" ? payload.text : "";
1228
- if (!delta) break;
1229
- if (!cleared) {
1230
- clear();
1231
- cleared = true;
1232
- }
1233
- text += delta;
1234
- if (mode2 !== "json") process.stdout.write(delta);
1235
- break;
1236
- }
1237
- case "tool-call-approval":
1238
- approval = { approvalId: String(payload.approvalId ?? ""), name: String(payload.name ?? "A tool") };
1239
- break;
1240
- case "tool-result": {
1241
- sources = mergeSources(sources, sourcesFromResult(payload.result));
1242
- const artifact = artifactFromResult(payload.result);
1243
- if (artifact) artifacts.set(artifact.id, artifact);
1244
- const denial = capabilityDenial(payload.result);
1245
- if (denial) notices.push(`${denial.message}${denial.reason ? ` (${denial.reason})` : ""}`);
1246
- break;
1247
- }
1248
- case "error":
1249
- errorMessage = typeof payload.message === "string" ? payload.message : "The agent reported an error";
1250
- break;
1251
- case "finish":
1252
- if (typeof payload.finishReason === "string") finishReason = payload.finishReason;
1253
- if (typeof payload.threadId === "string") threadId = payload.threadId;
1254
- if (!text.trim() && typeof payload.text === "string") text = payload.text;
1255
- break;
1256
- default:
1257
- break;
1258
- }
1259
- }
1260
- } finally {
1261
- if (!cleared) clear();
1262
- process.off("SIGINT", onSigint);
1263
- }
1264
- if (errorMessage && !text.trim()) {
1265
- throw new CliError({ code: "AGENT_ERROR", message: errorMessage, exitCode: ExitCode.Server });
1266
- }
1267
- const durationMs = Date.now() - started;
1268
- const files = [...artifacts.values()].map(({ id, kind, title, status: status2 }) => ({ id, kind, title, status: status2 }));
1269
- if (mode2 === "json") {
1270
- writeJson({
1271
- data: {
1272
- text: text.trimEnd(),
1273
- ...finishReason ? { finishReason } : {},
1274
- ...threadId ? { threadId } : {},
1275
- ...sources.length ? { sources } : {},
1276
- ...files.length ? { artifacts: files } : {}
1277
- },
1278
- meta: { schemaVersion: 1, tool: "chat", durationMs, ...approval ? { approval } : {} }
1279
- });
1280
- } else {
1281
- if (text && !text.endsWith("\n")) process.stdout.write("\n");
1282
- printAskExtras({ sources, files, notices });
1283
- if (approval) {
1284
- process.stderr.write(
1285
- `${theme.warn(`${theme.symbols.pending} ${approval.name} is waiting for your approval`)} ${theme.dim(approval.approvalId)}
1286
- ${theme.dim("Run:")} tabbio approvals approve ${approval.approvalId}
1287
- `
1288
- );
1289
- }
1290
- }
1291
- if (approval) process.exitCode = ExitCode.ApprovalPending;
1292
- }
1293
- function printAskExtras(extras) {
1294
- for (const note of extras.notices) process.stderr.write(`${theme.dim(`${theme.symbols.arrowRight} ${note}`)}
1295
- `);
1296
- if (extras.sources.length) {
1297
- process.stdout.write(`
1298
- ${theme.dim("Sources")}
1299
- `);
1300
- extras.sources.forEach((source, i) => process.stdout.write(`${i + 1}. ${source.title} ${theme.dim(source.url)}
1301
- `));
1302
- }
1303
- for (const file of extras.files) {
1304
- process.stdout.write(`${theme.dim(`${file.kind} ${file.status}:`)} ${file.title} ${theme.dim(`tabbio artifacts open ${file.id}`)}
1305
- `);
1306
- }
1307
- }
1308
1350
  function registerAskCommand(program) {
1309
- program.command("ask").description("Ask Tabbio one question (streams through your session; MCP ask tool with a personal token)").argument("[question...]", "Your question; read from stdin when omitted and piped").option("--mode <mode>", "seeker or employer (app session only)", "seeker").option("--via-mcp", "Force the MCP ask_tabbio tool even with an app session").addHelpText(
1351
+ program.command("ask").description("Ask Tabbio one question (streams through your session; MCP ask tool with a personal token)").argument("[question...]", "Your question; read from stdin when omitted and piped").option("--mode <mode>", "seeker or employer (app session only)", "seeker").option("--via-mcp", "Force the MCP ask_tabbio tool even with an app session").option("--no-save", "Do not save files Tabbio makes (pages, documents, images, CV exports) to ./tabbio-artifacts").addHelpText(
1310
1352
  "after",
1311
- '\nExamples:\n $ tabbio ask "What jobs fit my CV?"\n $ echo "Summarise my applications" | tabbio ask\n $ tabbio ask "Draft a headline" --json'
1353
+ [
1354
+ "",
1355
+ "Files Tabbio makes are saved to ./tabbio-artifacts (TABBIO_ARTIFACTS_DIR to change it; --no-save or TABBIO_NO_ARTIFACT_SAVE=1 to skip).",
1356
+ "",
1357
+ "Examples:",
1358
+ ' $ tabbio ask "What jobs fit my CV?"',
1359
+ ' $ echo "Summarise my applications" | tabbio ask',
1360
+ ' $ tabbio ask "Draft a headline" --json'
1361
+ ].join("\n")
1312
1362
  ).action(async (words, opts, cmd) => {
1313
1363
  const ctx = createCommandContext(cmd);
1314
1364
  const agentMode = opts.mode === "employer" ? "employer" : "seeker";
1315
1365
  try {
1316
- await ask(ctx, words, { mode: agentMode, viaMcp: Boolean(opts.viaMcp) });
1366
+ await ask(ctx, words, { mode: agentMode, viaMcp: Boolean(opts.viaMcp), save: opts.save !== false });
1317
1367
  } finally {
1318
1368
  await ctx.close();
1319
1369
  }
@@ -1322,6 +1372,9 @@ function registerAskCommand(program) {
1322
1372
 
1323
1373
  // src/commands/chat.ts
1324
1374
  import { Option as Option3 } from "commander";
1375
+ function resolveSaveArtifacts(flag, env = process.env) {
1376
+ return flag !== false && !autoSaveDisabled(env);
1377
+ }
1325
1378
  async function readStdin2() {
1326
1379
  if (process.stdin.isTTY) return void 0;
1327
1380
  const chunks = [];
@@ -1337,9 +1390,15 @@ async function runChatNdjson(ctx, message, opts) {
1337
1390
  throw usageError("Chat needs a message when it is not interactive", 'Pass it as an argument, or pipe it in: echo "hi" | tabbio chat --json');
1338
1391
  }
1339
1392
  const controller = new AbortController();
1340
- const onSigint = () => controller.abort();
1393
+ const onSigint = () => {
1394
+ controller.abort();
1395
+ cancelActiveCommand();
1396
+ };
1341
1397
  process.on("SIGINT", onSigint);
1342
1398
  let failed = false;
1399
+ const artifacts = /* @__PURE__ */ new Map();
1400
+ const made = /* @__PURE__ */ new Set();
1401
+ const exports = /* @__PURE__ */ new Map();
1343
1402
  try {
1344
1403
  const capabilities = opts.capabilities ?? loadCapabilityPrefs(ctx.api.profile.name);
1345
1404
  const frames = streamChatFrames(
@@ -1358,6 +1417,31 @@ async function runChatNdjson(ctx, message, opts) {
1358
1417
  process.stdout.write(`${ndjsonLine(frame.data)}
1359
1418
  `);
1360
1419
  if (frame.event && isFailureEvent(frame.event)) failed = true;
1420
+ if (frame.event?.type === "tool-result") {
1421
+ const payload = frame.event.payload;
1422
+ const artifact = artifactFromResult(payload.result);
1423
+ if (artifact) {
1424
+ artifacts.set(artifact.id, artifact);
1425
+ if (PRODUCING_TOOLS.has(payload.name)) made.add(artifact.id);
1426
+ }
1427
+ const record = payload.result;
1428
+ if (payload.name === "cvExport" && record && typeof record === "object") {
1429
+ const format = cvExportFormat(payload.args.format, record.formats);
1430
+ if (format) {
1431
+ const cvId = typeof record.cvId === "string" && record.cvId ? record.cvId : "master";
1432
+ exports.set(payload.id, { cvId, title: typeof record.cvTitle === "string" ? record.cvTitle : "My CV", format });
1433
+ }
1434
+ }
1435
+ }
1436
+ }
1437
+ if (opts.saveArtifacts ?? resolveSaveArtifacts(opts.save)) {
1438
+ const toSave = [...artifacts.values()].filter((artifact) => made.has(artifact.id) && artifact.status !== "failed");
1439
+ const saved = await saveChatFiles(ctx, toSave, [...exports.values()], "json");
1440
+ for (const entry of saved) {
1441
+ process.stdout.write(`${JSON.stringify({ type: "files-saved", payload: entry })}
1442
+ `);
1443
+ if (entry.error) failed = true;
1444
+ }
1361
1445
  }
1362
1446
  } finally {
1363
1447
  process.off("SIGINT", onSigint);
@@ -1381,12 +1465,16 @@ function registerChatCommand(program) {
1381
1465
  "page",
1382
1466
  "image"
1383
1467
  ])
1384
- ).option("--save", "Remember --research/--image/--result for this profile (like /research in the chat)").addHelpText(
1468
+ ).option("--remember", "Remember --research/--image/--result for this profile (like /research in the chat)").option("--no-save", "Do not save generated files into ./tabbio-artifacts (also TABBIO_NO_ARTIFACT_SAVE=1)").addHelpText(
1385
1469
  "after",
1386
1470
  [
1387
1471
  "",
1388
- "In the chat: /new \xB7 /threads \xB7 /mode seeker|employer \xB7 /model <id> \xB7 /research \xB7 /image \xB7 /result \xB7 /help \xB7 /exit",
1472
+ "In the chat:",
1473
+ " /new \xB7 /threads \xB7 /mode seeker|employer \xB7 /model <id> \xB7 /research \xB7 /image \xB7 /result \xB7 /help \xB7 /exit",
1474
+ " /open <n|id> \xB7 /apply <n> \xB7 /save <id> [folder] \xB7 /show <id> \xB7 /download [cvId] [pdf|docx]",
1475
+ " /login [email|token] \xB7 /logout \xB7 type / to list commands and your Tabbio skills (/<skill> runs one)",
1389
1476
  "Research, image and result modes are sent with every message (seeker mode) with your timezone.",
1477
+ "Generated files are saved to ./tabbio-artifacts unless --no-save or TABBIO_NO_ARTIFACT_SAVE=1.",
1390
1478
  "Keys: enter send \xB7 ctrl+j newline \xB7 esc interrupt \xB7 ctrl+o expand tool \xB7 ctrl+c twice quit",
1391
1479
  "",
1392
1480
  "Examples:",
@@ -1401,20 +1489,22 @@ function registerChatCommand(program) {
1401
1489
  try {
1402
1490
  assertJwtSession(ctx.credentials, ctx.profile, "Chat");
1403
1491
  opts.capabilities = resolveCapabilities(loadCapabilityPrefs(ctx.profile.name), opts);
1404
- if (opts.save) saveCapabilityPrefs(ctx.profile.name, opts.capabilities);
1492
+ if (opts.remember) saveCapabilityPrefs(ctx.profile.name, opts.capabilities);
1493
+ opts.saveArtifacts = resolveSaveArtifacts(opts.save);
1405
1494
  if (ctx.globals.json || !isInteractiveTerminal() || process.env.CI) {
1406
1495
  await runChatNdjson(ctx, message, opts);
1407
1496
  return;
1408
1497
  }
1409
1498
  assertInteractive("Chat");
1410
- const { runChat } = await import("./entry-WENOOT6W.js");
1499
+ const { runChat } = await import("./entry-ZAXWZSEE.js");
1411
1500
  await runChat(ctx, {
1412
1501
  mode: opts.mode,
1413
1502
  companyId: opts.company,
1414
1503
  threadId: opts.thread,
1415
1504
  modelId: opts.model,
1416
1505
  message,
1417
- capabilities: opts.capabilities
1506
+ capabilities: opts.capabilities,
1507
+ saveArtifacts: opts.saveArtifacts
1418
1508
  });
1419
1509
  } finally {
1420
1510
  await ctx.close();
@@ -1886,10 +1976,10 @@ function registerDoctorCommand(program) {
1886
1976
  if (ctx.globals.json) {
1887
1977
  printJson({ ok: !failed, checks });
1888
1978
  } else {
1889
- const width = Math.max(...checks.map((c) => c.name.length));
1979
+ const width2 = Math.max(...checks.map((c) => c.name.length));
1890
1980
  for (const check of checks) {
1891
- writeOut(`${SYMBOL[check.status]()} ${check.name.padEnd(width)} ${theme.dim(check.detail)}`);
1892
- if (check.hint && check.status !== "ok") writeOut(` ${" ".repeat(width)} ${check.hint}`);
1981
+ writeOut(`${SYMBOL[check.status]()} ${check.name.padEnd(width2)} ${theme.dim(check.detail)}`);
1982
+ if (check.hint && check.status !== "ok") writeOut(` ${" ".repeat(width2)} ${check.hint}`);
1893
1983
  }
1894
1984
  }
1895
1985
  if (failed) process.exitCode = ExitCode.Error;
@@ -1899,8 +1989,8 @@ function registerDoctorCommand(program) {
1899
1989
  // src/commands/home.ts
1900
1990
  function registerHome() {
1901
1991
  setHomeHandler(async (ctx) => {
1902
- const { runHome } = await import("./entry-WENOOT6W.js");
1903
- await runHome(ctx);
1992
+ const { runHome } = await import("./entry-ZAXWZSEE.js");
1993
+ await runHome(ctx, { saveArtifacts: resolveSaveArtifacts(void 0) });
1904
1994
  });
1905
1995
  }
1906
1996
 
@@ -1977,157 +2067,6 @@ function registerGeneratedCommands(program, catalog) {
1977
2067
  }
1978
2068
  }
1979
2069
 
1980
- // src/commands/login.ts
1981
- import open2 from "open";
1982
- async function readTokenFromStdin(stdin = process.stdin) {
1983
- if (stdin.isTTY) {
1984
- throw usageError("--with-token reads the token from stdin", "Pipe it in: `tabbio login --with-token < token.txt`.");
1985
- }
1986
- const chunks = [];
1987
- for await (const chunk of stdin) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk)));
1988
- const token = Buffer.concat(chunks).toString("utf8").trim();
1989
- if (!token) throw usageError("No token on stdin");
1990
- return token;
1991
- }
1992
- function persistProfileUrls(profile, sources) {
1993
- if (sources.apiUrl !== "flag" && sources.appUrl !== "flag") return;
1994
- updateConfig((config) => {
1995
- const current = config.profiles[profile.name] ?? {};
1996
- config.profiles[profile.name] = {
1997
- ...current,
1998
- ...sources.apiUrl === "flag" ? { apiUrl: profile.apiUrl } : {},
1999
- ...sources.appUrl === "flag" ? { appUrl: profile.appUrl } : {}
2000
- };
2001
- });
2002
- }
2003
- async function revokePrevious(profile, refreshToken, keep) {
2004
- if (!refreshToken || refreshToken === keep) return;
2005
- await revokeSession(new ApiClient(profile, null, { persist: false }), refreshToken).catch(
2006
- (error) => debug(`previous session not revoked: ${error.message}`)
2007
- );
2008
- }
2009
- async function loginWithToken(ctx, rawToken) {
2010
- const token = rawToken.trim();
2011
- if (!token.startsWith("tabbio_mcp_")) {
2012
- warn("This is not a tabbio_mcp_ token; treating it as an OAuth access token.");
2013
- }
2014
- const probe = { profile: ctx.profile.name, mcpToken: token };
2015
- const session = await McpSession.connect(ctx.profile, probe);
2016
- let toolCount = 0;
2017
- try {
2018
- toolCount = (await session.listTools({ refresh: true })).length;
2019
- } finally {
2020
- await session.close();
2021
- }
2022
- const previous = loadStoredCredentials(ctx.profile.name);
2023
- saveCredentials(probe);
2024
- persistProfileUrls(ctx.profile, ctx.sources);
2025
- await revokePrevious(ctx.profile, previous?.refreshToken);
2026
- if (ctx.globals.json) {
2027
- printJson({
2028
- profile: ctx.profile.name,
2029
- method: "token",
2030
- user: null,
2031
- mcp: { kind: "personal-token", token: fingerprint(token) },
2032
- tools: toolCount
2033
- });
2034
- return;
2035
- }
2036
- writeOut(successLine(`Token accepted \xB7 ${toolCount} tools available \xB7 profile ${ctx.profile.name}`));
2037
- writeOut(theme.dim(" MCP-only mode: `tabbio chat` and approving actions need `tabbio login` (browser or --email)."));
2038
- }
2039
- async function obtainSession(ctx, method, opts) {
2040
- const api = new ApiClient(ctx.profile, null, { persist: false });
2041
- if (method === "email") {
2042
- const email = opts.email?.trim() || await promptLine("Email: ");
2043
- info(`Sending a sign-in code to ${email}\u2026`);
2044
- return loginWithEmailOtp(api, {
2045
- email,
2046
- promptCode: (attempt) => promptSecret(attempt === 1 ? "Enter the 6-digit code: " : "Try the code again: "),
2047
- onInvalidCode: (left) => warn(`That code did not match. ${left} attempt${left === 1 ? "" : "s"} left.`)
2048
- });
2049
- }
2050
- const agentMode = Boolean(opts.nonInteractive);
2051
- if (!agentMode) info(`Connecting this computer (${deviceLabel()}) to Tabbio\u2026`);
2052
- const controller = new AbortController();
2053
- const onSigint = () => controller.abort();
2054
- process.once("SIGINT", onSigint);
2055
- try {
2056
- return await loginWithBrowser(api, {
2057
- device: deviceLabel(),
2058
- signal: controller.signal,
2059
- openUrl: async (url) => {
2060
- if (agentMode) return false;
2061
- try {
2062
- await open2(url);
2063
- return true;
2064
- } catch {
2065
- return false;
2066
- }
2067
- },
2068
- onWaiting: (url, opened, details) => {
2069
- if (agentMode) {
2070
- printJson({ url, state: details.state, expiresAt: details.expiresAt });
2071
- return;
2072
- }
2073
- info(opened ? theme.dim("If your browser did not open, visit:") : "Open this URL to continue:");
2074
- info(` ${theme.accent(url)}`);
2075
- info(theme.dim("Waiting for you to approve in the browser\u2026 (Ctrl-C to cancel)"));
2076
- }
2077
- });
2078
- } finally {
2079
- process.off("SIGINT", onSigint);
2080
- }
2081
- }
2082
- async function runLogin(opts, cmd) {
2083
- const ctx = createCommandContext(cmd);
2084
- if (opts.nonInteractive) ctx.globals.json = true;
2085
- const method = chooseLoginMethod({
2086
- browser: opts.browser,
2087
- email: opts.email,
2088
- token: opts.token,
2089
- withToken: opts.withToken,
2090
- nonInteractive: opts.nonInteractive,
2091
- isTTY: Boolean(process.stdin.isTTY && process.stderr.isTTY),
2092
- hasDisplay: hasDisplay()
2093
- });
2094
- if (method === "token") {
2095
- return loginWithToken(ctx, opts.withToken ? await readTokenFromStdin() : opts.token ?? "");
2096
- }
2097
- const previous = loadStoredCredentials(ctx.profile.name);
2098
- const payload = await obtainSession(ctx, method, opts);
2099
- const creds = credentialsFromLogin(ctx.profile, payload);
2100
- saveCredentials(creds);
2101
- clearCatalogCache(ctx.profile);
2102
- persistProfileUrls(ctx.profile, ctx.sources);
2103
- await revokePrevious(ctx.profile, previous?.refreshToken, payload.refreshToken);
2104
- if (ctx.globals.json) {
2105
- printJson({
2106
- profile: ctx.profile.name,
2107
- method,
2108
- apiUrl: ctx.profile.apiUrl,
2109
- user: creds.user ?? null,
2110
- mcp: { kind: "app-session" }
2111
- });
2112
- return;
2113
- }
2114
- const who = creds.user?.name ? `${creds.user.name} <${creds.user.email}>` : creds.user?.email ?? "your account";
2115
- writeOut(successLine(`Signed in as ${theme.bold(who)} \xB7 profile ${ctx.profile.name}`));
2116
- if (process.env.TABBIO_TOKEN) {
2117
- warn("TABBIO_TOKEN is set, so tool commands keep using that personal token.");
2118
- }
2119
- writeOut(theme.dim(" Next: tabbio status \xB7 tabbio tools \xB7 tabbio chat"));
2120
- }
2121
- function registerLoginCommand(program) {
2122
- program.command("login").description("Sign in (browser by default; --email for SSH/headless; --token for MCP-only)").option("--browser", "Approve this computer in your browser").option("--email <address>", "Sign in with a one-time code sent to this email").option("--token <token>", "Use a personal MCP token (tabbio_mcp_\u2026); tool commands only").option("--with-token", "Read a personal MCP token from stdin (CI)").option(
2123
- "--non-interactive",
2124
- 'For agents: print {"url","state","expiresAt"} as JSON, wait for the browser approval, never prompt'
2125
- ).addHelpText(
2126
- "after",
2127
- "\nThe browser flow opens <app-url>/cli/connect and receives the session on 127.0.0.1.\nEnvironment: TABBIO_TOKEN / TABBIO_ACCESS_TOKEN override stored credentials (never saved)."
2128
- ).action(async (opts, cmd) => runLogin(opts, cmd));
2129
- }
2130
-
2131
2070
  // src/commands/logout.ts
2132
2071
  async function logoutOne(name, apiUrl, appUrl) {
2133
2072
  const profile = resolveProfile({ profile: name, apiUrl, appUrl });
@@ -2279,7 +2218,7 @@ var WRITES_LABEL = " \xB7 writes";
2279
2218
  function readWrite(tool) {
2280
2219
  return tool.readOnly ? theme.muted(theme.symbols.dot) : theme.warn(theme.symbols.dot);
2281
2220
  }
2282
- function formatToolList(tools, width = process.stdout.columns ?? 100) {
2221
+ function formatToolList(tools, width2 = process.stdout.columns ?? 100) {
2283
2222
  if (tools.length === 0) return [theme.dim("No tools match.")];
2284
2223
  const idWidth = Math.min(36, Math.max(...tools.map((t) => displayWidth(t.id))));
2285
2224
  const lines = [];
@@ -2288,7 +2227,7 @@ function formatToolList(tools, width = process.stdout.columns ?? 100) {
2288
2227
  lines.push(`${theme.bold(group)} ${theme.dim(`(${list3.length})`)}`);
2289
2228
  for (const tool of list3) {
2290
2229
  const id = truncate(tool.id, idWidth);
2291
- const room = Math.max(10, width - idWidth - 6 - (tool.readOnly ? 0 : WRITES_LABEL.length));
2230
+ const room = Math.max(10, width2 - idWidth - 6 - (tool.readOnly ? 0 : WRITES_LABEL.length));
2292
2231
  const writes = tool.readOnly ? "" : theme.dim(WRITES_LABEL);
2293
2232
  lines.push(` ${readWrite(tool)} ${id}${" ".repeat(idWidth - displayWidth(id))} ${truncate(tool.title, room)}${writes}`);
2294
2233
  }
@@ -2341,7 +2280,7 @@ function describeTool(tool) {
2341
2280
  autoFilled: autoFilledProperties(tool, tool.inputSchema)
2342
2281
  };
2343
2282
  }
2344
- function formatToolDescription(info2, width = process.stdout.columns ?? 100) {
2283
+ function formatToolDescription(info2, width2 = process.stdout.columns ?? 100) {
2345
2284
  const lines = [
2346
2285
  theme.bold(info2.title),
2347
2286
  theme.dim(`${info2.id} \xB7 MCP ${info2.name} \xB7 ${info2.kind} \xB7 ${info2.readOnly ? "read-only" : "writes"}`)
@@ -2358,7 +2297,7 @@ function formatToolDescription(info2, width = process.stdout.columns ?? 100) {
2358
2297
  const auto = info2.autoFilled.includes(row2.property) ? "filled from your session" : null;
2359
2298
  const notes = [row2.required && !auto ? "required" : null, auto, ...row2.notes].filter(Boolean).join("; ");
2360
2299
  const text = [row2.description, notes ? `(${notes})` : ""].filter(Boolean).join(" ");
2361
- const room = Math.max(20, width - flagWidth - typeWidth - 6);
2300
+ const room = Math.max(20, width2 - flagWidth - typeWidth - 6);
2362
2301
  lines.push(` ${row2.flag.padEnd(flagWidth)} ${theme.dim(truncate(row2.type, typeWidth).padEnd(typeWidth))} ${truncate(text, room)}`.trimEnd());
2363
2302
  }
2364
2303
  lines.push(
@@ -2466,13 +2405,13 @@ function toWorkflowEntry(tool) {
2466
2405
  requiredFlags: flagsForProperties(tool.inputSchema, required)
2467
2406
  };
2468
2407
  }
2469
- function formatWorkflowList(entries, width = process.stdout.columns ?? 100) {
2408
+ function formatWorkflowList(entries, width2 = process.stdout.columns ?? 100) {
2470
2409
  if (entries.length === 0) return [theme.dim("No workflows are exposed over MCP; multi-step flows run through `tabbio chat`.")];
2471
2410
  const keyWidth = Math.max(...entries.map((e) => e.key.length));
2472
2411
  const lines = [`${theme.bold("Workflows")} ${theme.dim(`(${entries.length})`)}`];
2473
2412
  for (const entry of entries) {
2474
2413
  const needs = entry.requiredFlags.length ? theme.dim(` needs ${entry.requiredFlags.join(" ")}`) : "";
2475
- const room = Math.max(20, width - keyWidth - 4 - (entry.requiredFlags.join(" ").length + 8));
2414
+ const room = Math.max(20, width2 - keyWidth - 4 - (entry.requiredFlags.join(" ").length + 8));
2476
2415
  lines.push(` ${entry.key.padEnd(keyWidth)} ${truncate(entry.description, room)}${needs}`);
2477
2416
  }
2478
2417
  lines.push(theme.dim("Run one: tabbio workflows run <key> [flags] \xB7 its flags: tabbio workflows run <key> --help"));
@@ -2511,19 +2450,19 @@ function registerWorkflowsCommand(program) {
2511
2450
  function installInteractiveUi() {
2512
2451
  const hooks = {
2513
2452
  async browseTools(ctx, tools, options) {
2514
- const { runBrowseTools } = await import("./entry-WENOOT6W.js");
2453
+ const { runBrowseTools } = await import("./entry-ZAXWZSEE.js");
2515
2454
  return runBrowseTools(ctx, tools, options);
2516
2455
  },
2517
2456
  async runToolForm(ctx, tool, initial, options) {
2518
- const { runToolForm } = await import("./entry-WENOOT6W.js");
2457
+ const { runToolForm } = await import("./entry-ZAXWZSEE.js");
2519
2458
  return runToolForm(ctx, tool, initial, options);
2520
2459
  },
2521
2460
  async approvalPrompt(ctx, approval) {
2522
- const { runApprovalPrompt } = await import("./entry-WENOOT6W.js");
2461
+ const { runApprovalPrompt } = await import("./entry-ZAXWZSEE.js");
2523
2462
  return runApprovalPrompt(ctx, approval);
2524
2463
  },
2525
2464
  async showResult(ctx, tool, result, meta) {
2526
- const { runShowResult } = await import("./entry-WENOOT6W.js");
2465
+ const { runShowResult } = await import("./entry-ZAXWZSEE.js");
2527
2466
  return runShowResult(ctx, tool, result, meta);
2528
2467
  }
2529
2468
  };
@@ -2752,4 +2691,4 @@ export {
2752
2691
  scanArgv,
2753
2692
  setHomeHandler
2754
2693
  };
2755
- //# sourceMappingURL=program-O2BA5L6Z.js.map
2694
+ //# sourceMappingURL=program-J6GENE2V.js.map