@todoforai/tfa-explore 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 -64
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -2878,11 +2878,7 @@ var require_websocket_server = __commonJS((exports, module) => {
|
|
|
2878
2878
|
|
|
2879
2879
|
// src/cli.ts
|
|
2880
2880
|
import { parseArgs } from "node:util";
|
|
2881
|
-
|
|
2882
|
-
// ../tfa-subagent/dist/index.js
|
|
2883
|
-
import { homedir, platform } from "os";
|
|
2884
|
-
import { readFileSync } from "fs";
|
|
2885
|
-
import { join } from "path";
|
|
2881
|
+
import { readFileSync } from "node:fs";
|
|
2886
2882
|
|
|
2887
2883
|
// ../node_modules/ws/wrapper.mjs
|
|
2888
2884
|
var import_stream = __toESM(require_stream(), 1);
|
|
@@ -2908,45 +2904,23 @@ function normalizeApiUrl(url) {
|
|
|
2908
2904
|
return `https://${url}`;
|
|
2909
2905
|
return url.replace(/\/+$/, "");
|
|
2910
2906
|
}
|
|
2911
|
-
function credentialsPath() {
|
|
2912
|
-
const home = homedir();
|
|
2913
|
-
if (platform() === "win32")
|
|
2914
|
-
return join(home, "AppData", "Roaming", "todoforai", "credentials.json");
|
|
2915
|
-
if (platform() === "darwin")
|
|
2916
|
-
return join(home, "Library", "Application Support", "todoforai", "credentials.json");
|
|
2917
|
-
const xdg = process.env.XDG_CONFIG_HOME;
|
|
2918
|
-
return xdg ? join(xdg, "todoforai", "credentials.json") : join(home, ".config", "todoforai", "credentials.json");
|
|
2919
|
-
}
|
|
2920
|
-
var _credsCache;
|
|
2921
|
-
function loadCredentials() {
|
|
2922
|
-
if (_credsCache !== undefined)
|
|
2923
|
-
return _credsCache;
|
|
2924
|
-
try {
|
|
2925
|
-
_credsCache = JSON.parse(readFileSync(credentialsPath(), "utf8"));
|
|
2926
|
-
} catch {
|
|
2927
|
-
_credsCache = null;
|
|
2928
|
-
}
|
|
2929
|
-
return _credsCache;
|
|
2930
|
-
}
|
|
2931
2907
|
function resolveAuth(opts = {}) {
|
|
2932
|
-
const
|
|
2933
|
-
const
|
|
2934
|
-
const apiUrl = normalizeApiUrl(opts.apiUrl || getEnv("TODOFORAI_API_URL") || credsApiUrl || DEFAULT_API_URL);
|
|
2935
|
-
const apiKey = opts.apiKey || getEnv("TODOFORAI_API_KEY") || getEnv("TODOFORAI_API_TOKEN") || creds?.apiKey || "";
|
|
2908
|
+
const apiUrl = normalizeApiUrl(opts.apiUrl || getEnv("TODOFORAI_API_URL") || DEFAULT_API_URL);
|
|
2909
|
+
const apiToken = opts.apiToken || getEnv("TODOFORAI_API_TOKEN");
|
|
2936
2910
|
const agentSettingsId = opts.agentSettingsId || getEnv("TODOFORAI_AGENT_SETTINGS_ID");
|
|
2937
|
-
if (!
|
|
2938
|
-
throw new Error("
|
|
2911
|
+
if (!apiToken)
|
|
2912
|
+
throw new Error("TODOFORAI_API_TOKEN not set (run `todoforai-bridge` to start the bridge, or pass --api-token)");
|
|
2939
2913
|
if (!agentSettingsId)
|
|
2940
2914
|
throw new Error("agent settings id required (set TODOFORAI_AGENT_SETTINGS_ID or pass --agent <id>)");
|
|
2941
|
-
const apiBase =
|
|
2942
|
-
return { apiUrl,
|
|
2915
|
+
const apiBase = apiToken.startsWith("dst_") ? "/dst/v1" : "/api/v1";
|
|
2916
|
+
return { apiUrl, apiToken, agentSettingsId, apiBase };
|
|
2943
2917
|
}
|
|
2944
|
-
async function api(path, init = {}, apiUrl,
|
|
2918
|
+
async function api(path, init = {}, apiUrl, apiToken, tabId) {
|
|
2945
2919
|
const res = await fetch(`${apiUrl}${path}`, {
|
|
2946
2920
|
...init,
|
|
2947
2921
|
headers: {
|
|
2948
2922
|
"content-type": "application/json",
|
|
2949
|
-
"x-api-key":
|
|
2923
|
+
"x-api-key": apiToken,
|
|
2950
2924
|
...tabId ? { "x-tab-id": tabId } : {},
|
|
2951
2925
|
...init.headers ?? {}
|
|
2952
2926
|
}
|
|
@@ -2998,7 +2972,7 @@ var TERMINAL = new Set([
|
|
|
2998
2972
|
]);
|
|
2999
2973
|
async function openSocket(opts) {
|
|
3000
2974
|
const wsUrl = opts.apiUrl.replace(/^http/, "ws") + "/ws/v1/frontend?tabId=" + encodeURIComponent(opts.tabId);
|
|
3001
|
-
const ws = new wrapper_default(wsUrl, [opts.
|
|
2975
|
+
const ws = new wrapper_default(wsUrl, [opts.apiToken]);
|
|
3002
2976
|
ws.on("error", (err) => process.stderr.write(`[ws error] ${err.message}
|
|
3003
2977
|
`));
|
|
3004
2978
|
await new Promise((ok, fail) => {
|
|
@@ -3022,7 +2996,6 @@ async function streamToTerminal(ws, opts = {}) {
|
|
|
3022
2996
|
return new Promise((resolve) => {
|
|
3023
2997
|
let exitCode = 0;
|
|
3024
2998
|
let result = "";
|
|
3025
|
-
let inText = false;
|
|
3026
2999
|
const finish = (code) => {
|
|
3027
3000
|
exitCode = code;
|
|
3028
3001
|
ws.close();
|
|
@@ -3039,18 +3012,14 @@ async function streamToTerminal(ws, opts = {}) {
|
|
|
3039
3012
|
if (!t)
|
|
3040
3013
|
return;
|
|
3041
3014
|
if (t === "block:message" && typeof ev.payload?.content === "string") {
|
|
3042
|
-
|
|
3043
|
-
result += ev.payload.content;
|
|
3015
|
+
result += ev.payload.content;
|
|
3044
3016
|
if (streamStdout)
|
|
3045
3017
|
process.stdout.write(ev.payload.content);
|
|
3046
3018
|
return;
|
|
3047
3019
|
}
|
|
3048
3020
|
if (t === "block:start_universal" && ev.payload?.block_type) {
|
|
3049
3021
|
const bt = ev.payload.block_type;
|
|
3050
|
-
|
|
3051
|
-
if (inText)
|
|
3052
|
-
result = "";
|
|
3053
|
-
else if (streamStdout && bt !== "REASON")
|
|
3022
|
+
if (bt !== "TEXT" && bt !== "REASON")
|
|
3054
3023
|
process.stderr.write(`
|
|
3055
3024
|
→ ${bt}
|
|
3056
3025
|
`);
|
|
@@ -3073,9 +3042,9 @@ async function streamToTerminal(ws, opts = {}) {
|
|
|
3073
3042
|
});
|
|
3074
3043
|
}
|
|
3075
3044
|
async function runTfa(spec) {
|
|
3076
|
-
const { apiUrl,
|
|
3045
|
+
const { apiUrl, apiToken, agentSettingsId, apiBase } = resolveAuth({
|
|
3077
3046
|
apiUrl: spec.apiUrl,
|
|
3078
|
-
|
|
3047
|
+
apiToken: spec.apiToken,
|
|
3079
3048
|
agentSettingsId: spec.agentSettingsId
|
|
3080
3049
|
});
|
|
3081
3050
|
const allowed = new Set(spec.allowedTools);
|
|
@@ -3087,7 +3056,7 @@ async function runTfa(spec) {
|
|
|
3087
3056
|
}
|
|
3088
3057
|
const allowTools = requested.length ? requested : spec.allowedTools;
|
|
3089
3058
|
const tabId = crypto.randomUUID();
|
|
3090
|
-
const settings = await api(`${apiBase}/agents/${agentSettingsId}`, {}, apiUrl,
|
|
3059
|
+
const settings = await api(`${apiBase}/agents/${agentSettingsId}`, {}, apiUrl, apiToken);
|
|
3091
3060
|
const patched = patchSettings(settings, {
|
|
3092
3061
|
systemMessage: spec.systemMessage,
|
|
3093
3062
|
allowTools,
|
|
@@ -3096,22 +3065,22 @@ async function runTfa(spec) {
|
|
|
3096
3065
|
});
|
|
3097
3066
|
let projectId = spec.projectId || "";
|
|
3098
3067
|
if (!projectId) {
|
|
3099
|
-
const projects = await api(`${apiBase}/projects`, {}, apiUrl,
|
|
3068
|
+
const projects = await api(`${apiBase}/projects`, {}, apiUrl, apiToken);
|
|
3100
3069
|
if (!projects?.length)
|
|
3101
3070
|
throw new Error("no project found. Pass --project <id>.");
|
|
3102
3071
|
projectId = projects[0].project?.id ?? projects[0].id;
|
|
3103
3072
|
}
|
|
3104
|
-
const ws = await openSocket({ apiUrl,
|
|
3073
|
+
const ws = await openSocket({ apiUrl, apiToken, tabId });
|
|
3105
3074
|
const visible = spec.visible === true;
|
|
3106
3075
|
const todoRes = await api(`${apiBase}/projects/${projectId}/todos`, {
|
|
3107
3076
|
method: "POST",
|
|
3108
3077
|
body: JSON.stringify({ content: spec.content, agentSettings: patched, visible })
|
|
3109
|
-
}, apiUrl,
|
|
3078
|
+
}, apiUrl, apiToken, tabId);
|
|
3110
3079
|
const todoId = todoRes.id;
|
|
3111
3080
|
await api(`${apiBase}/todos/${todoId}/subscribe`, {
|
|
3112
3081
|
method: "POST",
|
|
3113
3082
|
body: JSON.stringify({ todoId })
|
|
3114
|
-
}, apiUrl,
|
|
3083
|
+
}, apiUrl, apiToken, tabId);
|
|
3115
3084
|
process.stderr.write(`[${spec.binName}] todo=${todoId}${visible ? "" : " (hidden)"}
|
|
3116
3085
|
`);
|
|
3117
3086
|
const { exitCode, result } = await streamToTerminal(ws, { streamStdout: visible });
|
|
@@ -3119,6 +3088,15 @@ async function runTfa(spec) {
|
|
|
3119
3088
|
}
|
|
3120
3089
|
|
|
3121
3090
|
// src/cli.ts
|
|
3091
|
+
function readStdin() {
|
|
3092
|
+
if (process.stdin.isTTY)
|
|
3093
|
+
return "";
|
|
3094
|
+
try {
|
|
3095
|
+
return readFileSync(0, "utf-8");
|
|
3096
|
+
} catch {
|
|
3097
|
+
return "";
|
|
3098
|
+
}
|
|
3099
|
+
}
|
|
3122
3100
|
var EXPLORE_SYS_PROMPT = `You are a codebase exploration agent. Read files, run non-destructive shell commands (ls, grep, find, tree), and report findings.
|
|
3123
3101
|
|
|
3124
3102
|
Goals:
|
|
@@ -3146,13 +3124,11 @@ Options:
|
|
|
3146
3124
|
-a, --agent <id> Agent settings ID (default: $TODOFORAI_AGENT_SETTINGS_ID)
|
|
3147
3125
|
-p, --project <id> Project ID (default: first project the user owns)
|
|
3148
3126
|
--tools <a,b,c> Restrict to a subset of ${ALLOWED_TOOLS.join(",")} (comma-separated)
|
|
3149
|
-
--show Show this TODO in the project list and stream output to
|
|
3150
|
-
stdout as it arrives (default: hidden + final result only).
|
|
3151
3127
|
-h, --help Show this help
|
|
3152
3128
|
-v, --version Show version
|
|
3153
3129
|
|
|
3154
3130
|
Env (auto-injected by edge shell):
|
|
3155
|
-
TODOFORAI_API_URL,
|
|
3131
|
+
TODOFORAI_API_URL, TODOFORAI_API_TOKEN, TODOFORAI_AGENT_SETTINGS_ID
|
|
3156
3132
|
`);
|
|
3157
3133
|
}
|
|
3158
3134
|
async function main() {
|
|
@@ -3164,7 +3140,6 @@ async function main() {
|
|
|
3164
3140
|
agent: { type: "string", short: "a" },
|
|
3165
3141
|
project: { type: "string", short: "p" },
|
|
3166
3142
|
tools: { type: "string" },
|
|
3167
|
-
show: { type: "boolean", default: false },
|
|
3168
3143
|
help: { type: "boolean", short: "h", default: false },
|
|
3169
3144
|
version: { type: "boolean", short: "v", default: false }
|
|
3170
3145
|
},
|
|
@@ -3179,30 +3154,32 @@ async function main() {
|
|
|
3179
3154
|
console.log("tfa-explore 0.1.0");
|
|
3180
3155
|
return;
|
|
3181
3156
|
}
|
|
3182
|
-
const
|
|
3157
|
+
const requestedTools = values.tools ? String(values.tools).split(",").map((s) => s.trim()).filter(Boolean) : [];
|
|
3158
|
+
const stdinText = readStdin().trim();
|
|
3159
|
+
const ctx = stdinText ? `
|
|
3160
|
+
|
|
3161
|
+
# Context (stdin)
|
|
3162
|
+
\`\`\`
|
|
3163
|
+
${stdinText}
|
|
3164
|
+
\`\`\`` : "";
|
|
3165
|
+
const question = positionals.join(" ").trim() || (stdinText ? "Explore this codebase using the context above. Surface structure, conventions, and relevant files." : "");
|
|
3183
3166
|
if (!question) {
|
|
3184
3167
|
console.error("Error: missing question. Run `tfa-explore --help`.");
|
|
3185
3168
|
process.exit(2);
|
|
3186
3169
|
}
|
|
3187
|
-
const
|
|
3188
|
-
const visible = Boolean(values.show);
|
|
3189
|
-
const { exitCode, result } = await runTfa({
|
|
3170
|
+
const { exitCode } = await runTfa({
|
|
3190
3171
|
binName: "tfa-explore",
|
|
3191
3172
|
systemMessage: EXPLORE_SYS_PROMPT,
|
|
3192
3173
|
allowedTools: ALLOWED_TOOLS,
|
|
3193
3174
|
content: `# Question
|
|
3194
|
-
${question}`,
|
|
3175
|
+
${question}${ctx}`,
|
|
3195
3176
|
repo: values.repo,
|
|
3196
3177
|
model: values.model,
|
|
3197
3178
|
agentSettingsId: values.agent,
|
|
3198
3179
|
projectId: values.project,
|
|
3199
3180
|
requestedTools,
|
|
3200
|
-
visible
|
|
3181
|
+
visible: true
|
|
3201
3182
|
});
|
|
3202
|
-
if (!visible)
|
|
3203
|
-
process.stdout.write(result.endsWith(`
|
|
3204
|
-
`) ? result : result + `
|
|
3205
|
-
`);
|
|
3206
3183
|
process.exit(exitCode);
|
|
3207
3184
|
}
|
|
3208
3185
|
main().catch((e) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@todoforai/tfa-explore",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Lightweight TODO for AI explore — creates a real TODO with patched permissions and streams blocks to your terminal.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -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", "explore", "todoforai"],
|