@todoforai/tfa-review 0.1.0 → 0.1.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/dist/cli.js +41 -62
- package/package.json +3 -3
package/dist/cli.js
CHANGED
|
@@ -2879,11 +2879,7 @@ var require_websocket_server = __commonJS((exports, module) => {
|
|
|
2879
2879
|
// src/cli.ts
|
|
2880
2880
|
import { parseArgs } from "node:util";
|
|
2881
2881
|
import { execFileSync } from "node:child_process";
|
|
2882
|
-
|
|
2883
|
-
// ../tfa-subagent/dist/index.js
|
|
2884
|
-
import { homedir, platform } from "os";
|
|
2885
|
-
import { readFileSync } from "fs";
|
|
2886
|
-
import { join } from "path";
|
|
2882
|
+
import { readFileSync } from "node:fs";
|
|
2887
2883
|
|
|
2888
2884
|
// ../node_modules/ws/wrapper.mjs
|
|
2889
2885
|
var import_stream = __toESM(require_stream(), 1);
|
|
@@ -2909,45 +2905,23 @@ function normalizeApiUrl(url) {
|
|
|
2909
2905
|
return `https://${url}`;
|
|
2910
2906
|
return url.replace(/\/+$/, "");
|
|
2911
2907
|
}
|
|
2912
|
-
function credentialsPath() {
|
|
2913
|
-
const home = homedir();
|
|
2914
|
-
if (platform() === "win32")
|
|
2915
|
-
return join(home, "AppData", "Roaming", "todoforai", "credentials.json");
|
|
2916
|
-
if (platform() === "darwin")
|
|
2917
|
-
return join(home, "Library", "Application Support", "todoforai", "credentials.json");
|
|
2918
|
-
const xdg = process.env.XDG_CONFIG_HOME;
|
|
2919
|
-
return xdg ? join(xdg, "todoforai", "credentials.json") : join(home, ".config", "todoforai", "credentials.json");
|
|
2920
|
-
}
|
|
2921
|
-
var _credsCache;
|
|
2922
|
-
function loadCredentials() {
|
|
2923
|
-
if (_credsCache !== undefined)
|
|
2924
|
-
return _credsCache;
|
|
2925
|
-
try {
|
|
2926
|
-
_credsCache = JSON.parse(readFileSync(credentialsPath(), "utf8"));
|
|
2927
|
-
} catch {
|
|
2928
|
-
_credsCache = null;
|
|
2929
|
-
}
|
|
2930
|
-
return _credsCache;
|
|
2931
|
-
}
|
|
2932
2908
|
function resolveAuth(opts = {}) {
|
|
2933
|
-
const
|
|
2934
|
-
const
|
|
2935
|
-
const apiUrl = normalizeApiUrl(opts.apiUrl || getEnv("TODOFORAI_API_URL") || credsApiUrl || DEFAULT_API_URL);
|
|
2936
|
-
const apiKey = opts.apiKey || getEnv("TODOFORAI_API_KEY") || getEnv("TODOFORAI_API_TOKEN") || creds?.apiKey || "";
|
|
2909
|
+
const apiUrl = normalizeApiUrl(opts.apiUrl || getEnv("TODOFORAI_API_URL") || DEFAULT_API_URL);
|
|
2910
|
+
const apiToken = opts.apiToken || getEnv("TODOFORAI_API_TOKEN");
|
|
2937
2911
|
const agentSettingsId = opts.agentSettingsId || getEnv("TODOFORAI_AGENT_SETTINGS_ID");
|
|
2938
|
-
if (!
|
|
2939
|
-
throw new Error("
|
|
2912
|
+
if (!apiToken)
|
|
2913
|
+
throw new Error("TODOFORAI_API_TOKEN not set (run `todoforai-bridge` to start the bridge, or pass --api-token)");
|
|
2940
2914
|
if (!agentSettingsId)
|
|
2941
2915
|
throw new Error("agent settings id required (set TODOFORAI_AGENT_SETTINGS_ID or pass --agent <id>)");
|
|
2942
|
-
const apiBase =
|
|
2943
|
-
return { apiUrl,
|
|
2916
|
+
const apiBase = apiToken.startsWith("dst_") ? "/dst/v1" : "/api/v1";
|
|
2917
|
+
return { apiUrl, apiToken, agentSettingsId, apiBase };
|
|
2944
2918
|
}
|
|
2945
|
-
async function api(path, init = {}, apiUrl,
|
|
2919
|
+
async function api(path, init = {}, apiUrl, apiToken, tabId) {
|
|
2946
2920
|
const res = await fetch(`${apiUrl}${path}`, {
|
|
2947
2921
|
...init,
|
|
2948
2922
|
headers: {
|
|
2949
2923
|
"content-type": "application/json",
|
|
2950
|
-
"x-api-key":
|
|
2924
|
+
"x-api-key": apiToken,
|
|
2951
2925
|
...tabId ? { "x-tab-id": tabId } : {},
|
|
2952
2926
|
...init.headers ?? {}
|
|
2953
2927
|
}
|
|
@@ -2999,7 +2973,7 @@ var TERMINAL = new Set([
|
|
|
2999
2973
|
]);
|
|
3000
2974
|
async function openSocket(opts) {
|
|
3001
2975
|
const wsUrl = opts.apiUrl.replace(/^http/, "ws") + "/ws/v1/frontend?tabId=" + encodeURIComponent(opts.tabId);
|
|
3002
|
-
const ws = new wrapper_default(wsUrl, [opts.
|
|
2976
|
+
const ws = new wrapper_default(wsUrl, [opts.apiToken]);
|
|
3003
2977
|
ws.on("error", (err) => process.stderr.write(`[ws error] ${err.message}
|
|
3004
2978
|
`));
|
|
3005
2979
|
await new Promise((ok, fail) => {
|
|
@@ -3023,7 +2997,6 @@ async function streamToTerminal(ws, opts = {}) {
|
|
|
3023
2997
|
return new Promise((resolve) => {
|
|
3024
2998
|
let exitCode = 0;
|
|
3025
2999
|
let result = "";
|
|
3026
|
-
let inText = false;
|
|
3027
3000
|
const finish = (code) => {
|
|
3028
3001
|
exitCode = code;
|
|
3029
3002
|
ws.close();
|
|
@@ -3040,18 +3013,14 @@ async function streamToTerminal(ws, opts = {}) {
|
|
|
3040
3013
|
if (!t)
|
|
3041
3014
|
return;
|
|
3042
3015
|
if (t === "block:message" && typeof ev.payload?.content === "string") {
|
|
3043
|
-
|
|
3044
|
-
result += ev.payload.content;
|
|
3016
|
+
result += ev.payload.content;
|
|
3045
3017
|
if (streamStdout)
|
|
3046
3018
|
process.stdout.write(ev.payload.content);
|
|
3047
3019
|
return;
|
|
3048
3020
|
}
|
|
3049
3021
|
if (t === "block:start_universal" && ev.payload?.block_type) {
|
|
3050
3022
|
const bt = ev.payload.block_type;
|
|
3051
|
-
|
|
3052
|
-
if (inText)
|
|
3053
|
-
result = "";
|
|
3054
|
-
else if (streamStdout && bt !== "REASON")
|
|
3023
|
+
if (bt !== "TEXT" && bt !== "REASON")
|
|
3055
3024
|
process.stderr.write(`
|
|
3056
3025
|
→ ${bt}
|
|
3057
3026
|
`);
|
|
@@ -3074,9 +3043,9 @@ async function streamToTerminal(ws, opts = {}) {
|
|
|
3074
3043
|
});
|
|
3075
3044
|
}
|
|
3076
3045
|
async function runTfa(spec) {
|
|
3077
|
-
const { apiUrl,
|
|
3046
|
+
const { apiUrl, apiToken, agentSettingsId, apiBase } = resolveAuth({
|
|
3078
3047
|
apiUrl: spec.apiUrl,
|
|
3079
|
-
|
|
3048
|
+
apiToken: spec.apiToken,
|
|
3080
3049
|
agentSettingsId: spec.agentSettingsId
|
|
3081
3050
|
});
|
|
3082
3051
|
const allowed = new Set(spec.allowedTools);
|
|
@@ -3088,7 +3057,7 @@ async function runTfa(spec) {
|
|
|
3088
3057
|
}
|
|
3089
3058
|
const allowTools = requested.length ? requested : spec.allowedTools;
|
|
3090
3059
|
const tabId = crypto.randomUUID();
|
|
3091
|
-
const settings = await api(`${apiBase}/agents/${agentSettingsId}`, {}, apiUrl,
|
|
3060
|
+
const settings = await api(`${apiBase}/agents/${agentSettingsId}`, {}, apiUrl, apiToken);
|
|
3092
3061
|
const patched = patchSettings(settings, {
|
|
3093
3062
|
systemMessage: spec.systemMessage,
|
|
3094
3063
|
allowTools,
|
|
@@ -3097,22 +3066,22 @@ async function runTfa(spec) {
|
|
|
3097
3066
|
});
|
|
3098
3067
|
let projectId = spec.projectId || "";
|
|
3099
3068
|
if (!projectId) {
|
|
3100
|
-
const projects = await api(`${apiBase}/projects`, {}, apiUrl,
|
|
3069
|
+
const projects = await api(`${apiBase}/projects`, {}, apiUrl, apiToken);
|
|
3101
3070
|
if (!projects?.length)
|
|
3102
3071
|
throw new Error("no project found. Pass --project <id>.");
|
|
3103
3072
|
projectId = projects[0].project?.id ?? projects[0].id;
|
|
3104
3073
|
}
|
|
3105
|
-
const ws = await openSocket({ apiUrl,
|
|
3074
|
+
const ws = await openSocket({ apiUrl, apiToken, tabId });
|
|
3106
3075
|
const visible = spec.visible === true;
|
|
3107
3076
|
const todoRes = await api(`${apiBase}/projects/${projectId}/todos`, {
|
|
3108
3077
|
method: "POST",
|
|
3109
3078
|
body: JSON.stringify({ content: spec.content, agentSettings: patched, visible })
|
|
3110
|
-
}, apiUrl,
|
|
3079
|
+
}, apiUrl, apiToken, tabId);
|
|
3111
3080
|
const todoId = todoRes.id;
|
|
3112
3081
|
await api(`${apiBase}/todos/${todoId}/subscribe`, {
|
|
3113
3082
|
method: "POST",
|
|
3114
3083
|
body: JSON.stringify({ todoId })
|
|
3115
|
-
}, apiUrl,
|
|
3084
|
+
}, apiUrl, apiToken, tabId);
|
|
3116
3085
|
process.stderr.write(`[${spec.binName}] todo=${todoId}${visible ? "" : " (hidden)"}
|
|
3117
3086
|
`);
|
|
3118
3087
|
const { exitCode, result } = await streamToTerminal(ws, { streamStdout: visible });
|
|
@@ -3136,6 +3105,15 @@ Use git diff, git status, read files, and search to understand what was done. Th
|
|
|
3136
3105
|
IMPORTANT: You are read-only. Do NOT modify, create, or delete any files. Do NOT run commands that change state. Only read, inspect, and report findings.
|
|
3137
3106
|
If a tool fails 3 times, stop retrying and report that the tools are faulty.`;
|
|
3138
3107
|
var ALLOWED_TOOLS = ["read", "grep", "bash"];
|
|
3108
|
+
function readStdinDiff() {
|
|
3109
|
+
if (process.stdin.isTTY)
|
|
3110
|
+
return "";
|
|
3111
|
+
try {
|
|
3112
|
+
return readFileSync(0, "utf-8");
|
|
3113
|
+
} catch {
|
|
3114
|
+
return "";
|
|
3115
|
+
}
|
|
3116
|
+
}
|
|
3139
3117
|
function captureGitDiff(repo, against) {
|
|
3140
3118
|
const args = against === "WORKING" ? ["-C", repo, "diff", "HEAD"] : ["-C", repo, "diff", `${against}...HEAD`];
|
|
3141
3119
|
try {
|
|
@@ -3146,7 +3124,7 @@ function captureGitDiff(repo, against) {
|
|
|
3146
3124
|
}
|
|
3147
3125
|
}
|
|
3148
3126
|
function printHelp() {
|
|
3149
|
-
console.log(`tfa-review — review a git diff
|
|
3127
|
+
console.log(`tfa-review — review a git diff via a sub-agent TODO.
|
|
3150
3128
|
|
|
3151
3129
|
Usage:
|
|
3152
3130
|
tfa-review [options] <goal>
|
|
@@ -3159,11 +3137,12 @@ Options:
|
|
|
3159
3137
|
-a, --agent <id> Agent settings ID (default: $TODOFORAI_AGENT_SETTINGS_ID)
|
|
3160
3138
|
-p, --project <id> Project ID (default: first project the user owns)
|
|
3161
3139
|
--tools <a,b,c> Restrict to a subset of ${ALLOWED_TOOLS.join(",")} (comma-separated)
|
|
3140
|
+
--visible List the TODO in the dashboard project view (otherwise only its id is logged).
|
|
3162
3141
|
-h, --help Show this help
|
|
3163
3142
|
-v, --version Show version
|
|
3164
3143
|
|
|
3165
3144
|
Env (auto-injected by edge shell):
|
|
3166
|
-
TODOFORAI_API_URL,
|
|
3145
|
+
TODOFORAI_API_URL, TODOFORAI_API_TOKEN, TODOFORAI_AGENT_SETTINGS_ID
|
|
3167
3146
|
`);
|
|
3168
3147
|
}
|
|
3169
3148
|
async function main() {
|
|
@@ -3176,6 +3155,7 @@ async function main() {
|
|
|
3176
3155
|
agent: { type: "string", short: "a" },
|
|
3177
3156
|
project: { type: "string", short: "p" },
|
|
3178
3157
|
tools: { type: "string" },
|
|
3158
|
+
visible: { type: "boolean", default: false },
|
|
3179
3159
|
help: { type: "boolean", short: "h", default: false },
|
|
3180
3160
|
version: { type: "boolean", short: "v", default: false }
|
|
3181
3161
|
},
|
|
@@ -3190,30 +3170,28 @@ async function main() {
|
|
|
3190
3170
|
console.log("tfa-review 0.1.0");
|
|
3191
3171
|
return;
|
|
3192
3172
|
}
|
|
3193
|
-
const goal = positionals.join(" ").trim();
|
|
3194
|
-
if (!goal) {
|
|
3195
|
-
console.error("Error: missing goal. Run `tfa-review --help`.");
|
|
3196
|
-
process.exit(2);
|
|
3197
|
-
}
|
|
3198
3173
|
const repo = values.repo || process.cwd();
|
|
3199
3174
|
const against = values.against || "WORKING";
|
|
3200
|
-
const
|
|
3175
|
+
const stdinDiff = readStdinDiff();
|
|
3176
|
+
const diff = stdinDiff.trim() ? stdinDiff : captureGitDiff(repo, against);
|
|
3177
|
+
const goal = positionals.join(" ").trim() || "Review this diff. Assess correctness, flag issues, suggest simpler approaches.";
|
|
3201
3178
|
if (!diff.trim()) {
|
|
3202
3179
|
console.error(`No changes between ${against} and HEAD in ${repo}.`);
|
|
3203
3180
|
process.exit(1);
|
|
3204
3181
|
}
|
|
3182
|
+
const diffLabel = stdinDiff.trim() ? "stdin" : `${against}...HEAD`;
|
|
3205
3183
|
const content = `# Goal
|
|
3206
3184
|
${goal}
|
|
3207
3185
|
|
|
3208
3186
|
# Repo
|
|
3209
3187
|
${repo}
|
|
3210
3188
|
|
|
3211
|
-
# Diff (${
|
|
3189
|
+
# Diff (${diffLabel})
|
|
3212
3190
|
\`\`\`diff
|
|
3213
3191
|
${diff}
|
|
3214
3192
|
\`\`\``;
|
|
3215
3193
|
const requestedTools = values.tools ? String(values.tools).split(",").map((s) => s.trim()).filter(Boolean) : [];
|
|
3216
|
-
const
|
|
3194
|
+
const { exitCode } = await runTfa({
|
|
3217
3195
|
binName: "tfa-review",
|
|
3218
3196
|
systemMessage: REVIEW_SYS_PROMPT,
|
|
3219
3197
|
allowedTools: ALLOWED_TOOLS,
|
|
@@ -3222,9 +3200,10 @@ ${diff}
|
|
|
3222
3200
|
model: values.model,
|
|
3223
3201
|
agentSettingsId: values.agent,
|
|
3224
3202
|
projectId: values.project,
|
|
3225
|
-
requestedTools
|
|
3203
|
+
requestedTools,
|
|
3204
|
+
visible: values.visible === true
|
|
3226
3205
|
});
|
|
3227
|
-
process.exit(
|
|
3206
|
+
process.exit(exitCode);
|
|
3228
3207
|
}
|
|
3229
3208
|
main().catch((e) => {
|
|
3230
3209
|
console.error(`Error: ${e?.message ?? e}`);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@todoforai/tfa-review",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Review a git diff via a sub-agent TODO. Not listed in the dashboard unless --visible.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"tfa-review": "./dist/cli.js"
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"dev": "bun run src/cli.ts"
|
|
12
12
|
},
|
|
13
13
|
"devDependencies": {
|
|
14
|
-
"@todoforai/tfa-subagent": "
|
|
14
|
+
"@todoforai/tfa-subagent": "^0.1.1",
|
|
15
15
|
"@types/node": "^20.11.0"
|
|
16
16
|
},
|
|
17
17
|
"keywords": ["cli", "ai", "review", "todoforai"],
|