@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.
- package/CHANGELOG.md +47 -0
- package/README.md +86 -19
- package/dist/chunks/{chunk-7LNC3FIV.js → chunk-GGJEDG3P.js} +95 -14
- package/dist/chunks/{chunk-7LNC3FIV.js.map → chunk-GGJEDG3P.js.map} +4 -4
- package/dist/chunks/{chunk-Y6GWIFHI.js → chunk-KBIQKZUK.js} +3 -53
- package/dist/chunks/chunk-KBIQKZUK.js.map +7 -0
- package/dist/chunks/{chunk-QVR5KIEQ.js → chunk-KHLGZ5IV.js} +1796 -244
- package/dist/chunks/chunk-KHLGZ5IV.js.map +7 -0
- package/dist/chunks/{chunk-NAWA6NCZ.js → chunk-NRFVKK3S.js} +37 -96
- package/dist/chunks/chunk-NRFVKK3S.js.map +7 -0
- package/dist/chunks/chunk-TP3CRQJD.js +106 -0
- package/dist/chunks/chunk-TP3CRQJD.js.map +7 -0
- package/dist/chunks/{entry-WENOOT6W.js → entry-ZAXWZSEE.js} +2023 -770
- package/dist/chunks/entry-ZAXWZSEE.js.map +7 -0
- package/dist/chunks/{mount-NGUARMCL.js → mount-36JUVX3R.js} +3 -2
- package/dist/chunks/{mount-NGUARMCL.js.map → mount-36JUVX3R.js.map} +1 -1
- package/dist/chunks/{program-O2BA5L6Z.js → program-J6GENE2V.js} +314 -375
- package/dist/chunks/program-J6GENE2V.js.map +7 -0
- package/dist/chunks/{status-I5UAQPBY.js → status-2C7BECKK.js} +3 -3
- package/dist/cli.js +1 -1
- package/package.json +4 -8
- package/dist/chunks/chunk-NAWA6NCZ.js.map +0 -7
- package/dist/chunks/chunk-QVR5KIEQ.js.map +0 -7
- package/dist/chunks/chunk-Y6GWIFHI.js.map +0 -7
- package/dist/chunks/entry-WENOOT6W.js.map +0 -7
- package/dist/chunks/program-O2BA5L6Z.js.map +0 -7
- /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-
|
|
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-
|
|
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-
|
|
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
|
|
717
|
-
|
|
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 ${
|
|
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
|
|
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
|
-
|
|
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 = () =>
|
|
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("--
|
|
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:
|
|
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.
|
|
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-
|
|
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
|
|
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(
|
|
1892
|
-
if (check.hint && check.status !== "ok") writeOut(` ${" ".repeat(
|
|
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-
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
2694
|
+
//# sourceMappingURL=program-J6GENE2V.js.map
|