@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.
Files changed (2) hide show
  1. package/dist/cli.js +41 -64
  2. 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 creds = loadCredentials();
2933
- const credsApiUrl = creds?.backendHost ? normalizeApiUrl(creds.backendHost) : "";
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 (!apiKey)
2938
- throw new Error("no API key (run `todoforai-bridge login`, or set TODOFORAI_API_KEY, or pass --api-key)");
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 = apiKey.startsWith("dst_") ? "/dst/v1" : "/api/v1";
2942
- return { apiUrl, apiKey, agentSettingsId, apiBase };
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, apiKey, tabId) {
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": apiKey,
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.apiKey]);
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
- if (inText)
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
- inText = bt === "TEXT";
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, apiKey, agentSettingsId, apiBase } = resolveAuth({
3045
+ const { apiUrl, apiToken, agentSettingsId, apiBase } = resolveAuth({
3077
3046
  apiUrl: spec.apiUrl,
3078
- apiKey: spec.apiKey,
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, apiKey);
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, apiKey);
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, apiKey, tabId });
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, apiKey, tabId);
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, apiKey, tabId);
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, TODOFORAI_API_KEY, TODOFORAI_AGENT_SETTINGS_ID
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 question = positionals.join(" ").trim();
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 requestedTools = values.tools ? String(values.tools).split(",").map((s) => s.trim()).filter(Boolean) : [];
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.0",
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": "workspace:*",
14
+ "@todoforai/tfa-subagent": "^0.1.1",
15
15
  "@types/node": "^20.11.0"
16
16
  },
17
17
  "keywords": ["cli", "ai", "explore", "todoforai"],