@todoforai/tfa-surface 0.1.1 → 0.1.3

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 +79 -64
  2. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -37,9 +37,13 @@ function writeSecure(data) {
37
37
  }
38
38
  function readStoredAuth(apiUrl) {
39
39
  const c = read();
40
- const apiToken = c[apiUrl] || c.apiToken || c.apiKey || "";
41
40
  const host = c.backendHost || "";
42
41
  const storedApiUrl = typeof c.apiUrl === "string" && c.apiUrl ? c.apiUrl : host ? `${host === "localhost" || host === "127.0.0.1" ? "http" : "https"}://${host}` : undefined;
42
+ const dstIsForThisUrl = !storedApiUrl || normalizeApiUrl(storedApiUrl) === normalizeApiUrl(apiUrl);
43
+ const isDst = typeof c.apiToken === "string" && c.apiToken.startsWith("dst_");
44
+ const dst = dstIsForThisUrl && isDst ? c.apiToken : "";
45
+ const fallbackApiToken = isDst && !dstIsForThisUrl ? "" : c.apiToken;
46
+ const apiToken = dst || c[apiUrl] || fallbackApiToken || c.apiKey || "";
43
47
  return {
44
48
  apiToken: typeof apiToken === "string" ? apiToken : "",
45
49
  apiUrl: storedApiUrl,
@@ -63,6 +67,8 @@ async function refreshDeviceToken(opts = {}) {
63
67
  const apiUrl = normalizeApiUrl(opts.apiUrl || envUrl || stored.apiUrl || DEFAULT_API_URL);
64
68
  if (!stored.deviceId || !stored.deviceSecret)
65
69
  return "";
70
+ if (stored.apiUrl && normalizeApiUrl(stored.apiUrl) !== apiUrl)
71
+ return "";
66
72
  try {
67
73
  const res = await fetch(`${apiUrl}/api/v1/cli/device/token`, {
68
74
  method: "POST",
@@ -89,7 +95,7 @@ async function fetchWithRenew(url, init, opts) {
89
95
  return fetch(url, { ...init, headers });
90
96
  };
91
97
  const res = await send(opts.token);
92
- if (renewOn.includes(res.status) && opts.token.startsWith("dst_")) {
98
+ if (renewOn.includes(res.status)) {
93
99
  const fresh = await refreshDeviceToken({ apiUrl: opts.apiUrl });
94
100
  if (fresh && fresh !== opts.token)
95
101
  return { res: await send(fresh), token: fresh };
@@ -110,7 +116,7 @@ function resolveToken(opts = {}) {
110
116
  // package.json
111
117
  var package_default = {
112
118
  name: "@todoforai/tfa-surface",
113
- version: "0.1.1",
119
+ version: "0.1.3",
114
120
  description: "TODOforAI surface CLI — see and drive what is shown in the user's live UI (describe, todo/panel views, renderer cards, surface render, controls). Replaces tfa-frontend. Reuses bridge credentials.",
115
121
  type: "module",
116
122
  bin: {
@@ -150,22 +156,23 @@ Usage:
150
156
  tfa-surface <command> [args...]
151
157
 
152
158
  Discovery:
153
- describe What's open in the target frontend:
154
- todos, focus, panel surfaces, sidebar,
155
- embedded-browser CDP URL, show/card
156
- instances in loaded chats. YAML; --json
157
- for the raw structure.
158
-
159
- Todos (views):
160
- todo open <todoId> [--popout] Open a todo (full-page overlay); --popout
161
- forces a floating preview window.
162
- todo focus <todoId> Focus an open todo's window.
163
- todo close <todoId> Close an open todo view.
159
+ describe Every surface in the target frontend — kind, id,
160
+ ref, focus; plus sidebar, embedded-browser CDP
161
+ URL and the show instances loaded in each todo.
162
+ YAML; --json for the raw structure.
164
163
 
165
- Panels (surfaces inside a todo; --todo defaults to $TODOFORAI_TODO_ID):
166
- panel open <surface> [--todo <id>] [--url <url>]
167
- surface: changes|preview|browser|terminal
168
- panel close [--todo <id>]
164
+ Surfaces — a ref is a todo id, <todoId>:<alias> (a shown artifact, live) or an http(s) url:
165
+ open overlay <todoId> The full-page todo (todos only).
166
+ open window <ref> A floating preview window (any ref).
167
+ open panel <view> [--todo <id>] [--url <url>]
168
+ A todo's side panel: changes|agents|preview|browser|terminal
169
+ (--todo defaults to $TODOFORAI_TODO_ID).
170
+ open card <ref> [--id <id>] [--title <t>] [--group <slug>]
171
+ A card on the project board, for every member —
172
+ its own card, or a tile inside a group's card.
173
+ Persisted; the same --id updates in place.
174
+ close <kind> <id> overlay: no id · window: the ref · panel: the todo · card: its id
175
+ focus window <ref> Bring a window (or its docked tab) to front.
169
176
 
170
177
  Renderer cards (data plane — works headless, no frontend needed;
171
178
  --project defaults to $TODOFORAI_PROJECT_ID):
@@ -187,6 +194,7 @@ Content (inherited from tfa-frontend, broadcast to all tabs):
187
194
 
188
195
  Options:
189
196
  --frontend <tabId> Target frontend (overrides $TODOFORAI_FRONTEND_ID)
197
+ --id <surfaceId> Card id (open card); --group <slug> files it into a group card
190
198
  --api-url <url> Backend URL (default: env/credentials)
191
199
  --api-token <tok> Token (default: env/credentials)
192
200
  --json Machine-readable output
@@ -196,9 +204,12 @@ Options:
196
204
  Examples:
197
205
  tfa-surface describe
198
206
  tfa-surface card add kpi-tile dashboards/kpi.html --title "KPI tile"
199
- tfa-surface todo open 3f2a91... --popout
200
- tfa-surface panel open browser --url https://example.com
201
- agent-browser connect "$(tfa-surface describe --json | jq -r '.todos[0].browser.cdp')"
207
+ tfa-surface open window 3f2a91...
208
+ tfa-surface open window $TODOFORAI_TODO_ID:kpi # a shown artifact, in its own window
209
+ tfa-surface open card $TODOFORAI_TODO_ID:kpi --title "Weekly KPIs" --group marketing
210
+ tfa-surface open panel browser --url https://example.com
211
+ tfa-surface close card weekly-kpis
212
+ agent-browser connect "$(tfa-surface describe --json | jq -r '.surfaces[] | select(.browser) | .browser.cdp' | head -1)"
202
213
  git diff --stat | tfa-surface show chat.panel $TODOFORAI_TODO_ID
203
214
 
204
215
  Exit codes: 0 ok · 1 error · 2 frontend not reachable / no frontend id`);
@@ -278,24 +289,18 @@ function printResult(ctx, data) {
278
289
  function printDescribe(d) {
279
290
  console.log(`frontend: ${d.frontendId} (${d.platform}, ${d.kind})`);
280
291
  console.log(`sidebar: ${d.sidebar}`);
281
- console.log(d.todos.length ? "todos:" : "todos: []");
282
- for (const t of d.todos) {
283
- const flags = [t.mode === "window" ? "window" : "", t.focused ? "focused" : ""].filter(Boolean).join(", ");
284
- console.log(` ${t.todoId}:${flags ? ` ${flags}` : ""}${t.title ? ` # ${t.title}` : ""}`);
285
- if (t.panel && t.panel !== "browser")
286
- console.log(` ${t.panel}`);
287
- if (t.browser)
288
- console.log(` browser: ${t.browser.open ? t.browser.url : "(idle)"} cdp: ${t.browser.cdp}`);
289
- if (t.shows?.length) {
292
+ console.log(d.surfaces.length ? "surfaces:" : "surfaces: []");
293
+ for (const s of d.surfaces) {
294
+ const flags = [s.focused ? "focused" : "", s.docked ? "docked" : "", s.view ?? "", s.group ? `in ${s.group}` : ""].filter(Boolean).join(", ");
295
+ const id = s.kind === "overlay" || s.id === s.ref ? "" : ` ${s.id}`;
296
+ console.log(` ${s.kind}${id}: ${s.ref}${flags ? ` ${flags}` : ""}${s.title ? ` # ${s.title}` : ""}`);
297
+ if (s.browser)
298
+ console.log(` browser: ${s.browser.open ? s.browser.url : "(idle)"} cdp: ${s.browser.cdp}`);
299
+ if (s.shows?.length) {
290
300
  console.log(" shows:");
291
- for (const s of t.shows) {
292
- const bits = [
293
- s.card ? `card=${s.card}` : "",
294
- s.mime || "",
295
- s.url ? `url ${s.url}` : "",
296
- s.title || ""
297
- ].filter(Boolean);
298
- console.log(` ${s.ref}${bits.length ? ` ${bits.join(" ")}` : ""}`);
301
+ for (const x of s.shows) {
302
+ const bits = [x.card ? `card=${x.card}` : "", x.mime || "", x.url ? `url ${x.url}` : "", x.title || ""].filter(Boolean);
303
+ console.log(` ${x.ref}${bits.length ? ` ${bits.join(" ")}` : ""}`);
299
304
  }
300
305
  }
301
306
  }
@@ -309,7 +314,8 @@ async function main() {
309
314
  project: { type: "string" },
310
315
  title: { type: "string" },
311
316
  description: { type: "string" },
312
- popout: { type: "boolean", default: false },
317
+ id: { type: "string" },
318
+ group: { type: "string" },
313
319
  "api-url": { type: "string" },
314
320
  "api-token": { type: "string" },
315
321
  json: { type: "boolean", default: false },
@@ -348,37 +354,46 @@ async function main() {
348
354
  printDescribe(result);
349
355
  break;
350
356
  }
351
- case "todo": {
352
- const [verb, todoId] = args;
353
- if (!verb || !todoId)
354
- die("usage: tfa-surface todo open|focus|close <todoId>");
355
- if (verb === "open") {
356
- const result = await uiCall(ctx, "todo.open", { todoId, ...values.popout ? { mode: "window" } : {} });
357
- console.log(ctx.json ? JSON.stringify(result) : `${result.created ? "opened" : "already open"} (${result.mode})`);
358
- } else if (verb === "focus" || verb === "close") {
359
- await uiCall(ctx, `todo.${verb}`, { todoId });
360
- console.log(ctx.json ? '{"ok":true}' : "ok");
361
- } else {
362
- die(`unknown todo verb: ${verb}`);
363
- }
364
- break;
365
- }
366
- case "panel": {
367
- const [verb, surface] = args;
368
- if (verb === "open") {
369
- if (!surface)
370
- die("usage: tfa-surface panel open changes|preview|browser|terminal [--todo <id>] [--url <url>]");
371
- const result = await uiCall(ctx, "panel.open", { todoId: todoArg(), surface, ...values.url ? { url: values.url } : {} });
357
+ case "open": {
358
+ const [kind, arg] = args;
359
+ const usage = "usage: tfa-surface open overlay <todoId> | window <ref> | panel <view> [--todo <id>] [--url <url>] | card <ref> [--id <id>] [--title <t>] [--group <slug>]";
360
+ if (kind === "overlay" || kind === "window") {
361
+ if (!arg)
362
+ die(usage);
363
+ const result = await uiCall(ctx, "surface.open", { kind, ref: arg });
364
+ console.log(ctx.json ? JSON.stringify(result) : `${result.created ? "opened" : "already open"} (${kind})`);
365
+ } else if (kind === "panel") {
366
+ if (!arg)
367
+ die(usage);
368
+ const result = await uiCall(ctx, "surface.open", { kind, id: todoArg(), view: arg, ref: values.url ?? arg });
372
369
  const pending = result?.browserReady === false;
373
370
  console.log(ctx.json ? JSON.stringify(result ?? { ok: true }) : pending ? "ok (browser opening — its CDP url appears in `device_list` shortly)" : "ok");
374
- } else if (verb === "close") {
375
- await uiCall(ctx, "panel.close", { todoId: todoArg() });
376
- console.log(ctx.json ? '{"ok":true}' : "ok");
371
+ } else if (kind === "card") {
372
+ if (!arg)
373
+ die(usage);
374
+ const result = await uiCall(ctx, "surface.open", {
375
+ kind,
376
+ ref: arg,
377
+ ...values.id ? { id: values.id } : {},
378
+ ...values.title !== undefined ? { title: values.title } : {},
379
+ ...values.group !== undefined ? { group: values.group } : {}
380
+ });
381
+ console.log(ctx.json ? JSON.stringify(result) : `card ${result.id} ${result.created ? "added to" : "updated on"} the board`);
377
382
  } else {
378
- die("usage: tfa-surface panel open|close ...");
383
+ die(usage);
379
384
  }
380
385
  break;
381
386
  }
387
+ case "close":
388
+ case "focus": {
389
+ const [kind, arg] = args;
390
+ const id = kind === "overlay" ? "" : kind === "panel" ? arg || todoArg() : arg;
391
+ if (!kind || kind !== "overlay" && !id)
392
+ die(`usage: tfa-surface ${cmd} overlay | window <ref> | panel [<todoId>] | card <id>`);
393
+ await uiCall(ctx, `surface.${cmd}`, { kind, id });
394
+ console.log(ctx.json ? '{"ok":true}' : "ok");
395
+ break;
396
+ }
382
397
  case "card": {
383
398
  const [verb, name] = args;
384
399
  const cardPath = (n) => `/resources/cards/${projectArg()}/${encodeURIComponent(n)}`;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@todoforai/tfa-surface",
3
- "version": "0.1.1",
4
- "description": "TODOforAI surface CLI \u2014 see and drive what is shown in the user's live UI (describe, todo/panel views, renderer cards, surface render, controls). Replaces tfa-frontend. Reuses bridge credentials.",
3
+ "version": "0.1.3",
4
+ "description": "TODOforAI surface CLI — see and drive what is shown in the user's live UI (describe, todo/panel views, renderer cards, surface render, controls). Replaces tfa-frontend. Reuses bridge credentials.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "tfa-surface": "dist/cli.js"