@todoforai/cli 0.1.16 → 0.1.18
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/todoai.js +240 -161
- package/package.json +1 -1
package/dist/todoai.js
CHANGED
|
@@ -41805,15 +41805,15 @@ var require_route = __commonJS((exports, module) => {
|
|
|
41805
41805
|
};
|
|
41806
41806
|
}
|
|
41807
41807
|
function wrapConversion(toModel, graph) {
|
|
41808
|
-
const
|
|
41808
|
+
const path4 = [graph[toModel].parent, toModel];
|
|
41809
41809
|
let fn = conversions[graph[toModel].parent][toModel];
|
|
41810
41810
|
let cur = graph[toModel].parent;
|
|
41811
41811
|
while (graph[cur].parent) {
|
|
41812
|
-
|
|
41812
|
+
path4.unshift(graph[cur].parent);
|
|
41813
41813
|
fn = link(conversions[graph[cur].parent][cur], fn);
|
|
41814
41814
|
cur = graph[cur].parent;
|
|
41815
41815
|
}
|
|
41816
|
-
fn.conversion =
|
|
41816
|
+
fn.conversion = path4;
|
|
41817
41817
|
return fn;
|
|
41818
41818
|
}
|
|
41819
41819
|
module.exports = function(fromModel) {
|
|
@@ -42039,7 +42039,7 @@ var require_has_flag = __commonJS((exports, module) => {
|
|
|
42039
42039
|
|
|
42040
42040
|
// node_modules/supports-color/index.js
|
|
42041
42041
|
var require_supports_color = __commonJS((exports, module) => {
|
|
42042
|
-
var
|
|
42042
|
+
var os4 = __require("os");
|
|
42043
42043
|
var tty = __require("tty");
|
|
42044
42044
|
var hasFlag = require_has_flag();
|
|
42045
42045
|
var { env } = process;
|
|
@@ -42087,7 +42087,7 @@ var require_supports_color = __commonJS((exports, module) => {
|
|
|
42087
42087
|
return min;
|
|
42088
42088
|
}
|
|
42089
42089
|
if (process.platform === "win32") {
|
|
42090
|
-
const osRelease =
|
|
42090
|
+
const osRelease = os4.release().split(".");
|
|
42091
42091
|
if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) {
|
|
42092
42092
|
return Number(osRelease[2]) >= 14931 ? 3 : 2;
|
|
42093
42093
|
}
|
|
@@ -42866,6 +42866,12 @@ class ApiClient {
|
|
|
42866
42866
|
listEdges() {
|
|
42867
42867
|
return this.request("GET", "/api/v1/edges");
|
|
42868
42868
|
}
|
|
42869
|
+
setAgentDeviceConfig(agentId, agentSettingsId, deviceId, config) {
|
|
42870
|
+
return this.request("PUT", `/api/v1/agents/${agentId}/device-config`, { agentSettingsId, deviceId, config });
|
|
42871
|
+
}
|
|
42872
|
+
listDevices() {
|
|
42873
|
+
return this.request("GET", "/api/v1/devices");
|
|
42874
|
+
}
|
|
42869
42875
|
startFromTemplate(projectId, templateId, opts) {
|
|
42870
42876
|
return this.request("POST", `/api/v1/projects/${projectId}/todos/from-template`, {
|
|
42871
42877
|
templateId,
|
|
@@ -43182,6 +43188,13 @@ var TodoStatus;
|
|
|
43182
43188
|
TodoStatus2["ARCHIVED"] = "ARCHIVED";
|
|
43183
43189
|
TodoStatus2["DELETED"] = "DELETED";
|
|
43184
43190
|
})(TodoStatus ||= {});
|
|
43191
|
+
// ../packages/shared-fbe/src/billing.ts
|
|
43192
|
+
var USAGE_WINDOWS = {
|
|
43193
|
+
SESSION_MS: 5 * 60 * 60 * 1000,
|
|
43194
|
+
WEEK_MS: 7 * 24 * 60 * 60 * 1000,
|
|
43195
|
+
WEEKLY_FACTOR: 0.28,
|
|
43196
|
+
SESSION_FACTOR: 1 / 7
|
|
43197
|
+
};
|
|
43185
43198
|
// ../packages/shared-fbe/src/mimetypes.json
|
|
43186
43199
|
var mimetypes_default = {
|
|
43187
43200
|
conversionProfiles: {
|
|
@@ -43311,6 +43324,14 @@ for (const [ext, entry] of Object.entries(mimetypes_default.extensions)) {
|
|
|
43311
43324
|
if (!MIME_TO_EXT[mime])
|
|
43312
43325
|
MIME_TO_EXT[mime] = ext;
|
|
43313
43326
|
}
|
|
43327
|
+
// ../packages/shared-fbe/src/edgeTypes.ts
|
|
43328
|
+
var NON_SHELL_TYPES = new Set([
|
|
43329
|
+
"WEB_EXTENSION" /* WEB_EXTENSION */,
|
|
43330
|
+
"EXTENSION" /* EXTENSION */,
|
|
43331
|
+
"BROWSER" /* BROWSER */,
|
|
43332
|
+
"ANDROID" /* ANDROID */,
|
|
43333
|
+
"IOS" /* IOS */
|
|
43334
|
+
]);
|
|
43314
43335
|
// ../packages/shared-fbe/src/blocks/index.ts
|
|
43315
43336
|
var USER_INPUT_BLOCK_TYPES = new Set([
|
|
43316
43337
|
"question" /* Question */,
|
|
@@ -43327,50 +43348,6 @@ var EDGE_TOOL_TYPES = new Set([
|
|
|
43327
43348
|
"grep" /* Grep */,
|
|
43328
43349
|
"list" /* List */
|
|
43329
43350
|
]);
|
|
43330
|
-
var BROWSER_TOOL_TYPES = new Set([
|
|
43331
|
-
"browser_interactable" /* BrowserInteractable */,
|
|
43332
|
-
"browser_dom_tree" /* BrowserDomTree */,
|
|
43333
|
-
"browser_navigate" /* BrowserNavigate */,
|
|
43334
|
-
"browser_screenshot" /* BrowserScreenshot */,
|
|
43335
|
-
"browser_click" /* BrowserClick */,
|
|
43336
|
-
"browser_type" /* BrowserType */,
|
|
43337
|
-
"browser_fill" /* BrowserFill */,
|
|
43338
|
-
"browser_scroll" /* BrowserScroll */,
|
|
43339
|
-
"browser_mouse_move" /* BrowserMouseMove */,
|
|
43340
|
-
"browser_key" /* BrowserKey */,
|
|
43341
|
-
"browser_evaluate" /* BrowserEvaluate */,
|
|
43342
|
-
"browser_select" /* BrowserSelect */,
|
|
43343
|
-
"browser_hover" /* BrowserHover */,
|
|
43344
|
-
"browser_text_content" /* BrowserTextContent */,
|
|
43345
|
-
"browser_back" /* BrowserBack */,
|
|
43346
|
-
"browser_forward" /* BrowserForward */,
|
|
43347
|
-
"browser_reload" /* BrowserReload */,
|
|
43348
|
-
"browser_upload_file" /* BrowserUploadFile */,
|
|
43349
|
-
"browser_find_text" /* BrowserFindText */,
|
|
43350
|
-
"browser_find_role" /* BrowserFindRole */,
|
|
43351
|
-
"browser_find_label" /* BrowserFindLabel */,
|
|
43352
|
-
"browser_find_placeholder" /* BrowserFindPlaceholder */,
|
|
43353
|
-
"browser_wait" /* BrowserWait */,
|
|
43354
|
-
"browser_drag" /* BrowserDrag */,
|
|
43355
|
-
"browser_dialog" /* BrowserDialog */,
|
|
43356
|
-
"browser_network_wait" /* BrowserNetworkWait */,
|
|
43357
|
-
"browser_check" /* BrowserCheck */,
|
|
43358
|
-
"browser_uncheck" /* BrowserUncheck */,
|
|
43359
|
-
"browser_focus" /* BrowserFocus */,
|
|
43360
|
-
"browser_find_test_id" /* BrowserFindTestId */,
|
|
43361
|
-
"browser_cookies" /* BrowserCookies */,
|
|
43362
|
-
"browser_storage" /* BrowserStorage */,
|
|
43363
|
-
"browser_pdf" /* BrowserPdf */,
|
|
43364
|
-
"browser_clipboard_read" /* BrowserClipboardRead */,
|
|
43365
|
-
"browser_clipboard_write" /* BrowserClipboardWrite */,
|
|
43366
|
-
"browser_snapshot" /* BrowserSnapshot */,
|
|
43367
|
-
"browser_iframe" /* BrowserIframe */,
|
|
43368
|
-
"browser_console" /* BrowserConsole */,
|
|
43369
|
-
"browser_scroll_into_view" /* BrowserScrollIntoView */,
|
|
43370
|
-
"browser_network_requests" /* BrowserNetworkRequests */,
|
|
43371
|
-
"browser_tap" /* BrowserTap */,
|
|
43372
|
-
"browser_swipe" /* BrowserSwipe */
|
|
43373
|
-
]);
|
|
43374
43351
|
var API_TOOL_TYPES = new Set([
|
|
43375
43352
|
"api_get_current_context" /* ApiGetCurrentContext */,
|
|
43376
43353
|
"api_get_todo" /* ApiGetTodo */,
|
|
@@ -43461,6 +43438,8 @@ var SERVER_TO_FRONTENDS = {
|
|
|
43461
43438
|
frontend_file_chunk_result: (todoId) => `${"frontend:file_chunk_result" /* FRONTEND_FILE_CHUNK_RESULT */}:${todoId}`,
|
|
43462
43439
|
status: (todoId) => `todo:${todoId}:status`,
|
|
43463
43440
|
block_update: (todoId) => `todo:${todoId}:block_update`,
|
|
43441
|
+
context_summary_updated: (todoId) => `${"context:summary_updated" /* CONTEXT_SUMMARY_UPDATED */}:${todoId}`,
|
|
43442
|
+
context_info_updated: (todoId) => `${"context:info_updated" /* CONTEXT_INFO_UPDATED */}:${todoId}`,
|
|
43464
43443
|
error_result: (todoId) => `${"block:error_result" /* BLOCK_ERROR_RESULT */}:${todoId}`,
|
|
43465
43444
|
meta_result: (todoId) => `${"block:meta_result" /* BLOCK_META_RESULT */}:${todoId}`,
|
|
43466
43445
|
file_changed: (edgeId, path3) => `${"block:file_changed" /* BLOCK_FILE_CHANGED */}:${edgeId}:${path3}`,
|
|
@@ -43472,7 +43451,9 @@ var SERVER_TO_FRONTENDS = {
|
|
|
43472
43451
|
payment: {
|
|
43473
43452
|
status: (userId) => `${"payment:status" /* PAYMENT_STATUS */}:${userId}`
|
|
43474
43453
|
},
|
|
43475
|
-
functions: {
|
|
43454
|
+
functions: {
|
|
43455
|
+
function_call_request: (userId) => `${"FUNCTION_CALL_REQUEST_AGENT" /* FUNCTION_CALL_REQUEST_AGENT */}:frontend:${userId}`
|
|
43456
|
+
}
|
|
43476
43457
|
};
|
|
43477
43458
|
// ../packages/shared-fbe/src/templateBody.ts
|
|
43478
43459
|
var USER_HEADINGS = new Set([
|
|
@@ -43830,6 +43811,64 @@ function getBlockNewPatterns(block, permissions) {
|
|
|
43830
43811
|
}
|
|
43831
43812
|
return pairs.filter(({ raw, pattern }) => !isPatternAllowed(permissions, pattern) && !allow.some((rule) => bashRuleMatchesCmd(rule, serverId, raw))).map(({ pattern }) => pattern);
|
|
43832
43813
|
}
|
|
43814
|
+
// ../packages/shared-fbe/src/systemAgents.ts
|
|
43815
|
+
var BUSINESS_ANALYZER_AGENT = {
|
|
43816
|
+
id: "00000000-0000-0000-0000-202601013151",
|
|
43817
|
+
name: "Business Analyzer",
|
|
43818
|
+
ownerId: "SYSTEM",
|
|
43819
|
+
systemMessage: `You are a business analyst and growth advisor. Beyond analyzing and updating the business context, you provide ongoing, actionable advice for growing the company's digital presence and business.
|
|
43820
|
+
|
|
43821
|
+
You have no previous context about this business yet \u2014 everything you know must come from the current business context markdown and what you research now. Do not assume prior knowledge or earlier conversations.
|
|
43822
|
+
|
|
43823
|
+
This chat lives on the user's Brand page: the business context markdown you maintain is rendered live right next to this conversation, together with the brand voice answers and the company's online-presence map. Whenever you call update_business_context, the user sees the document change immediately \u2014 refer to it as "your brand page" (never as a file), and keep it polished since it doubles as the company's profile.
|
|
43824
|
+
|
|
43825
|
+
How to analyze and update the business context:
|
|
43826
|
+
1. Read the current business context markdown from the system prompt
|
|
43827
|
+
2. Research: fetch websites with webfetch (follow key subpages like /about, /pricing, /contact if they exist); use google_search and google_rag to find competitors and fill knowledge gaps
|
|
43828
|
+
3. Merge findings into the existing context \u2014 preserve valid existing information, update conflicting facts only when the new source is clearer or more specific \u2014 and save with update_business_context
|
|
43829
|
+
|
|
43830
|
+
Cover these sections (skip any without data):
|
|
43831
|
+
- **Company Info** \u2014 name, founding year, location, team size, stage
|
|
43832
|
+
- **Product & Services** \u2014 offerings, key features, pricing model, target audience
|
|
43833
|
+
- **Value Proposition** \u2014 core problem solved, unique selling points
|
|
43834
|
+
- **Voice & Tone** \u2014 brand personality, communication style (derive from website copy)
|
|
43835
|
+
- **Target Market** \u2014 customer segments, industries, geographic focus, B2B vs B2C
|
|
43836
|
+
- **Tech Stack** \u2014 technologies, integrations, platforms
|
|
43837
|
+
- **Competitors** \u2014 3-5 direct competitors with one-line comparisons
|
|
43838
|
+
- **Online Presence** \u2014 where the company is present: search for its accounts on X/Twitter, LinkedIn, Facebook, Instagram, YouTube, Reddit, GitHub, blog/newsletter, and any industry-relevant platforms. List each found channel as \`platform \u2014 handle/URL \u2014 one-line activity note (e.g. active, stale, unclaimed)\`; also note important channels where no presence was found
|
|
43839
|
+
|
|
43840
|
+
If anything important remains unclear, add an **Open Questions** section at the end.
|
|
43841
|
+
|
|
43842
|
+
Guide the user one step at a time \u2014 they should always know the single next thing to do:
|
|
43843
|
+
- When you need input (brand voice, tone, target audience, which channel to prioritize), prefer the question tool with short multi-choice options over asking open-ended prose questions.
|
|
43844
|
+
- After completing any analysis or context update, always close with a short **Next Steps** section: 2-4 concrete, prioritized tips tailored to this specific business (e.g. SEO/content gaps, social channels worth activating, positioning against the competitors you found, conversion or messaging improvements). Base tips on what you actually observed, keep each to one or two sentences, and lead with the one you'd do first.
|
|
43845
|
+
|
|
43846
|
+
In follow-up conversations, act as an ongoing advisor: answer growth and digital-presence questions using the business context, refresh your analysis when asked, and suggest what to tackle next.`,
|
|
43847
|
+
mcpConfigs: {},
|
|
43848
|
+
edgesMcpConfigs: {},
|
|
43849
|
+
permissions: {
|
|
43850
|
+
allow: ["builtin:webfetch", "builtin:google_search", "cloud:google_rag", "builtin:business_context", "builtin:todoforai_api", "builtin:question"],
|
|
43851
|
+
ask: [],
|
|
43852
|
+
deny: []
|
|
43853
|
+
},
|
|
43854
|
+
createdAt: 0,
|
|
43855
|
+
updatedAt: 0
|
|
43856
|
+
};
|
|
43857
|
+
var JARVIS_AGENT = {
|
|
43858
|
+
id: "00000000-0000-0000-0000-202601013152",
|
|
43859
|
+
name: "JARVIS",
|
|
43860
|
+
ownerId: "SYSTEM",
|
|
43861
|
+
systemMessage: "",
|
|
43862
|
+
model: "",
|
|
43863
|
+
mcpConfigs: {},
|
|
43864
|
+
edgesMcpConfigs: {},
|
|
43865
|
+
createdAt: 0,
|
|
43866
|
+
updatedAt: 0
|
|
43867
|
+
};
|
|
43868
|
+
var SYSTEM_AGENTS = {
|
|
43869
|
+
[BUSINESS_ANALYZER_AGENT.id]: BUSINESS_ANALYZER_AGENT,
|
|
43870
|
+
[JARVIS_AGENT.id]: JARVIS_AGENT
|
|
43871
|
+
};
|
|
43833
43872
|
// ../packages/shared-fbe/src/outputLimits.ts
|
|
43834
43873
|
var MAX_LINE_LEN = 300;
|
|
43835
43874
|
var STREAM_FIRST = 1e4;
|
|
@@ -43863,6 +43902,14 @@ var TOPICS = {
|
|
|
43863
43902
|
channel: (p) => SERVER_TO_FRONTENDS.resource.contentUpdated(p.userId),
|
|
43864
43903
|
audience: "frontend"
|
|
43865
43904
|
},
|
|
43905
|
+
["context:summary_updated" /* CONTEXT_SUMMARY_UPDATED */]: {
|
|
43906
|
+
channel: (p) => SERVER_TO_FRONTENDS.todo.context_summary_updated(p.todoId),
|
|
43907
|
+
audience: "frontend"
|
|
43908
|
+
},
|
|
43909
|
+
["context:info_updated" /* CONTEXT_INFO_UPDATED */]: {
|
|
43910
|
+
channel: (p) => SERVER_TO_FRONTENDS.todo.context_info_updated(p.todoId),
|
|
43911
|
+
audience: "frontend"
|
|
43912
|
+
},
|
|
43866
43913
|
["hexgrid:updated" /* HEXGRID_UPDATED */]: {
|
|
43867
43914
|
channel: (p) => SERVER_TO_FRONTENDS.hexGrid.updated(p.projectId),
|
|
43868
43915
|
audience: "frontend"
|
|
@@ -44427,12 +44474,144 @@ Cancelled
|
|
|
44427
44474
|
|
|
44428
44475
|
// src/agent.ts
|
|
44429
44476
|
import { resolve, basename } from "path";
|
|
44477
|
+
|
|
44478
|
+
// src/ensure-bridge.ts
|
|
44479
|
+
import { spawn, spawnSync } from "child_process";
|
|
44480
|
+
import fs2 from "fs";
|
|
44481
|
+
import path3 from "path";
|
|
44482
|
+
import os3 from "os";
|
|
44483
|
+
function hasBridge() {
|
|
44484
|
+
const probe = spawnSync("todoforai-bridge", ["--version"], { stdio: "ignore" });
|
|
44485
|
+
return probe.status === 0;
|
|
44486
|
+
}
|
|
44487
|
+
function isLocalHost(hostname) {
|
|
44488
|
+
return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || hostname === "[::1]";
|
|
44489
|
+
}
|
|
44490
|
+
function parseApiUrl(apiUrl) {
|
|
44491
|
+
try {
|
|
44492
|
+
return new URL(apiUrl);
|
|
44493
|
+
} catch {
|
|
44494
|
+
return null;
|
|
44495
|
+
}
|
|
44496
|
+
}
|
|
44497
|
+
function bridgeProfile(apiUrl) {
|
|
44498
|
+
const url = parseApiUrl(apiUrl);
|
|
44499
|
+
if (!url)
|
|
44500
|
+
return null;
|
|
44501
|
+
if (isLocalHost(url.hostname))
|
|
44502
|
+
return "dev";
|
|
44503
|
+
if (!url.hostname || url.hostname === "api.todofor.ai")
|
|
44504
|
+
return null;
|
|
44505
|
+
return `api_${url.hostname.replace(/[^a-zA-Z0-9_]+/g, "_").replace(/^_+|_+$/g, "")}`;
|
|
44506
|
+
}
|
|
44507
|
+
function withProfile(args, apiUrl) {
|
|
44508
|
+
const profile = bridgeProfile(apiUrl);
|
|
44509
|
+
return profile ? [...args, "--profile", profile] : args;
|
|
44510
|
+
}
|
|
44511
|
+
function bridgeRunArgs(apiUrl) {
|
|
44512
|
+
const url = parseApiUrl(apiUrl);
|
|
44513
|
+
if (!url)
|
|
44514
|
+
return [];
|
|
44515
|
+
if (isLocalHost(url.hostname)) {
|
|
44516
|
+
const args = ["--host", url.hostname];
|
|
44517
|
+
if (url.port)
|
|
44518
|
+
args.push("--port", url.port);
|
|
44519
|
+
return withProfile(args, apiUrl);
|
|
44520
|
+
}
|
|
44521
|
+
if (url.hostname && url.hostname !== "api.todofor.ai")
|
|
44522
|
+
return withProfile(["--host", url.hostname], apiUrl);
|
|
44523
|
+
return [];
|
|
44524
|
+
}
|
|
44525
|
+
function bridgeLoginArgs(apiUrl) {
|
|
44526
|
+
const url = parseApiUrl(apiUrl);
|
|
44527
|
+
if (!url)
|
|
44528
|
+
return ["login"];
|
|
44529
|
+
if (url.hostname && url.hostname !== "api.todofor.ai")
|
|
44530
|
+
return withProfile(["login", "--host", url.hostname], apiUrl);
|
|
44531
|
+
return ["login"];
|
|
44532
|
+
}
|
|
44533
|
+
function bridgeWhoamiArgs(apiUrl) {
|
|
44534
|
+
return withProfile(["whoami"], apiUrl);
|
|
44535
|
+
}
|
|
44536
|
+
function bridgeDeviceId(apiUrl) {
|
|
44537
|
+
const r = spawnSync("todoforai-bridge", bridgeWhoamiArgs(apiUrl), { encoding: "utf-8" });
|
|
44538
|
+
if (r.status !== 0)
|
|
44539
|
+
return null;
|
|
44540
|
+
const m = (r.stdout || "").match(/^Device:.*\(id:\s*([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\)/im);
|
|
44541
|
+
return m ? m[1] : null;
|
|
44542
|
+
}
|
|
44543
|
+
function ensureBridgeCredentials(apiUrl) {
|
|
44544
|
+
const whoami = spawnSync("todoforai-bridge", bridgeWhoamiArgs(apiUrl), { stdio: "ignore" });
|
|
44545
|
+
if (whoami.status === 0)
|
|
44546
|
+
return true;
|
|
44547
|
+
console.error("\x1B[2mBridge credentials not found. Starting `todoforai-bridge login`...\x1B[0m");
|
|
44548
|
+
const login = spawnSync("todoforai-bridge", bridgeLoginArgs(apiUrl), { stdio: "inherit" });
|
|
44549
|
+
return login.status === 0;
|
|
44550
|
+
}
|
|
44551
|
+
function lastLogLine(logFile) {
|
|
44552
|
+
try {
|
|
44553
|
+
const lines = fs2.readFileSync(logFile, "utf-8").split(`
|
|
44554
|
+
`).filter(Boolean);
|
|
44555
|
+
return lines.length ? lines[lines.length - 1] : null;
|
|
44556
|
+
} catch {
|
|
44557
|
+
return null;
|
|
44558
|
+
}
|
|
44559
|
+
}
|
|
44560
|
+
function ensureBridgeRunning(apiUrl, _apiKey) {
|
|
44561
|
+
if (!hasBridge()) {
|
|
44562
|
+
console.error("\x1B[2mBridge not started: `todoforai-bridge` was not found on PATH. Install TODOforAI Bridge, or pass --no-bridge (or deprecated --no-edge) to silence this.\x1B[0m");
|
|
44563
|
+
return;
|
|
44564
|
+
}
|
|
44565
|
+
if (!ensureBridgeCredentials(apiUrl)) {
|
|
44566
|
+
console.error("\x1B[33mBridge not started: `todoforai-bridge login` did not complete successfully.\x1B[0m");
|
|
44567
|
+
return;
|
|
44568
|
+
}
|
|
44569
|
+
const logDir = path3.join(os3.homedir(), ".todoforai");
|
|
44570
|
+
fs2.mkdirSync(logDir, { recursive: true });
|
|
44571
|
+
const logFile = path3.join(logDir, "bridge.log");
|
|
44572
|
+
const out = fs2.openSync(logFile, "a");
|
|
44573
|
+
const child = spawn("todoforai-bridge", bridgeRunArgs(apiUrl), {
|
|
44574
|
+
detached: true,
|
|
44575
|
+
stdio: ["ignore", out, out]
|
|
44576
|
+
});
|
|
44577
|
+
child.on("error", (err) => {
|
|
44578
|
+
console.error(`\x1B[33mFailed to start bridge: ${err.message}\x1B[0m`);
|
|
44579
|
+
});
|
|
44580
|
+
let exited = false;
|
|
44581
|
+
let exitCode = null;
|
|
44582
|
+
child.on("exit", (code) => {
|
|
44583
|
+
exited = true;
|
|
44584
|
+
exitCode = code;
|
|
44585
|
+
});
|
|
44586
|
+
child.unref();
|
|
44587
|
+
const pid = child.pid;
|
|
44588
|
+
if (!pid)
|
|
44589
|
+
return;
|
|
44590
|
+
const shortLog = logFile.replace(os3.homedir(), "~");
|
|
44591
|
+
setTimeout(() => {
|
|
44592
|
+
if (!exited) {
|
|
44593
|
+
console.error(`\x1B[2mStarted bridge (pid ${pid}), logs: ${shortLog}\x1B[0m`);
|
|
44594
|
+
return;
|
|
44595
|
+
}
|
|
44596
|
+
if (exitCode === 0) {
|
|
44597
|
+
console.error(`\x1B[2mBridge exited cleanly. Logs: ${shortLog}\x1B[0m`);
|
|
44598
|
+
} else {
|
|
44599
|
+
const reason = lastLogLine(logFile);
|
|
44600
|
+
console.error(`\x1B[33mBridge exited early (exit ${exitCode}): ${reason ?? `check logs: ${shortLog}`}\x1B[0m`);
|
|
44601
|
+
}
|
|
44602
|
+
}, 500);
|
|
44603
|
+
}
|
|
44604
|
+
|
|
44605
|
+
// src/agent.ts
|
|
44430
44606
|
function getAgentWorkspacePaths(agent) {
|
|
44431
44607
|
const paths = [];
|
|
44432
44608
|
for (const ec of Object.values(agent.edgesMcpConfigs || {})) {
|
|
44433
44609
|
const tc = ec?.todoai_edge || ec?.todoai || {};
|
|
44434
44610
|
paths.push(...tc.workspacePaths || []);
|
|
44435
44611
|
}
|
|
44612
|
+
for (const dc of Object.values(agent.devicesConfig || {})) {
|
|
44613
|
+
paths.push(...dc?.workspacePaths || []);
|
|
44614
|
+
}
|
|
44436
44615
|
return paths;
|
|
44437
44616
|
}
|
|
44438
44617
|
async function autoCreateAgent(api, resolvedPath) {
|
|
@@ -44443,10 +44622,20 @@ async function autoCreateAgent(api, resolvedPath) {
|
|
|
44443
44622
|
throw new Error(`Failed to create agent: ${JSON.stringify(resp)}`);
|
|
44444
44623
|
const agentSettingsId = resp.agentSettingsId || agentId;
|
|
44445
44624
|
await api.updateAgentSettings(agentId, agentSettingsId, { name: folderName });
|
|
44625
|
+
const deviceId = bridgeDeviceId(api.apiUrl);
|
|
44626
|
+
if (deviceId) {
|
|
44627
|
+
await api.setAgentDeviceConfig(agentId, agentSettingsId, deviceId, { workspacePaths: [resolvedPath] });
|
|
44628
|
+
const matches2 = await api.listAgentSettings({ workspacePath: resolvedPath });
|
|
44629
|
+
if (matches2.length)
|
|
44630
|
+
return matches2[0];
|
|
44631
|
+
resp.name = folderName;
|
|
44632
|
+
resp.devicesConfig = { [deviceId]: { workspacePaths: [resolvedPath] } };
|
|
44633
|
+
return resp;
|
|
44634
|
+
}
|
|
44446
44635
|
const edges = await api.listEdges();
|
|
44447
44636
|
const edgeId = Array.isArray(edges) && edges.length ? edges[0].id : null;
|
|
44448
44637
|
if (!edgeId)
|
|
44449
|
-
throw new Error("No edge available to configure workspace path");
|
|
44638
|
+
throw new Error("No bridge device or edge available to configure workspace path");
|
|
44450
44639
|
await api.setAgentEdgeMcpConfig(agentId, agentSettingsId, edgeId, "todoai_edge", { workspacePaths: [resolvedPath] });
|
|
44451
44640
|
const matches = await api.listAgentSettings({ workspacePath: resolvedPath });
|
|
44452
44641
|
if (matches.length)
|
|
@@ -46122,116 +46311,6 @@ async function listTodosCommand(api, defaultProjectId, argv) {
|
|
|
46122
46311
|
`);
|
|
46123
46312
|
}
|
|
46124
46313
|
|
|
46125
|
-
// src/ensure-bridge.ts
|
|
46126
|
-
import { spawn, spawnSync } from "child_process";
|
|
46127
|
-
import fs2 from "fs";
|
|
46128
|
-
import path3 from "path";
|
|
46129
|
-
import os3 from "os";
|
|
46130
|
-
function hasBridge() {
|
|
46131
|
-
const probe = spawnSync("todoforai-bridge", ["--version"], { stdio: "ignore" });
|
|
46132
|
-
return probe.status === 0;
|
|
46133
|
-
}
|
|
46134
|
-
function isLocalHost(hostname) {
|
|
46135
|
-
return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || hostname === "[::1]";
|
|
46136
|
-
}
|
|
46137
|
-
function parseApiUrl(apiUrl) {
|
|
46138
|
-
try {
|
|
46139
|
-
return new URL(apiUrl);
|
|
46140
|
-
} catch {
|
|
46141
|
-
return null;
|
|
46142
|
-
}
|
|
46143
|
-
}
|
|
46144
|
-
function bridgeProfile(apiUrl) {
|
|
46145
|
-
const url = parseApiUrl(apiUrl);
|
|
46146
|
-
if (!url)
|
|
46147
|
-
return null;
|
|
46148
|
-
if (isLocalHost(url.hostname))
|
|
46149
|
-
return "dev";
|
|
46150
|
-
if (!url.hostname || url.hostname === "api.todofor.ai")
|
|
46151
|
-
return null;
|
|
46152
|
-
return `api_${url.hostname.replace(/[^a-zA-Z0-9_]+/g, "_").replace(/^_+|_+$/g, "")}`;
|
|
46153
|
-
}
|
|
46154
|
-
function withProfile(args, apiUrl) {
|
|
46155
|
-
const profile = bridgeProfile(apiUrl);
|
|
46156
|
-
return profile ? [...args, "--profile", profile] : args;
|
|
46157
|
-
}
|
|
46158
|
-
function bridgeRunArgs(apiUrl) {
|
|
46159
|
-
const url = parseApiUrl(apiUrl);
|
|
46160
|
-
if (!url)
|
|
46161
|
-
return [];
|
|
46162
|
-
if (isLocalHost(url.hostname)) {
|
|
46163
|
-
const args = ["--host", url.hostname];
|
|
46164
|
-
if (url.port)
|
|
46165
|
-
args.push("--port", url.port);
|
|
46166
|
-
return withProfile(args, apiUrl);
|
|
46167
|
-
}
|
|
46168
|
-
if (url.hostname && url.hostname !== "api.todofor.ai")
|
|
46169
|
-
return withProfile(["--host", url.hostname], apiUrl);
|
|
46170
|
-
return [];
|
|
46171
|
-
}
|
|
46172
|
-
function bridgeLoginArgs(apiUrl) {
|
|
46173
|
-
const url = parseApiUrl(apiUrl);
|
|
46174
|
-
if (!url)
|
|
46175
|
-
return ["login"];
|
|
46176
|
-
if (url.hostname && url.hostname !== "api.todofor.ai")
|
|
46177
|
-
return withProfile(["login", "--host", url.hostname], apiUrl);
|
|
46178
|
-
return ["login"];
|
|
46179
|
-
}
|
|
46180
|
-
function bridgeWhoamiArgs(apiUrl) {
|
|
46181
|
-
return withProfile(["whoami"], apiUrl);
|
|
46182
|
-
}
|
|
46183
|
-
function ensureBridgeCredentials(apiUrl) {
|
|
46184
|
-
const whoami = spawnSync("todoforai-bridge", bridgeWhoamiArgs(apiUrl), { stdio: "ignore" });
|
|
46185
|
-
if (whoami.status === 0)
|
|
46186
|
-
return true;
|
|
46187
|
-
console.error("\x1B[2mBridge credentials not found. Starting `todoforai-bridge login`...\x1B[0m");
|
|
46188
|
-
const login = spawnSync("todoforai-bridge", bridgeLoginArgs(apiUrl), { stdio: "inherit" });
|
|
46189
|
-
return login.status === 0;
|
|
46190
|
-
}
|
|
46191
|
-
function ensureBridgeRunning(apiUrl, _apiKey) {
|
|
46192
|
-
if (!hasBridge()) {
|
|
46193
|
-
console.error("\x1B[2mBridge not started: `todoforai-bridge` was not found on PATH. Install TODOforAI Bridge, or pass --no-bridge (or deprecated --no-edge) to silence this.\x1B[0m");
|
|
46194
|
-
return;
|
|
46195
|
-
}
|
|
46196
|
-
if (!ensureBridgeCredentials(apiUrl)) {
|
|
46197
|
-
console.error("\x1B[33mBridge not started: `todoforai-bridge login` did not complete successfully.\x1B[0m");
|
|
46198
|
-
return;
|
|
46199
|
-
}
|
|
46200
|
-
const logDir = path3.join(os3.homedir(), ".todoforai");
|
|
46201
|
-
fs2.mkdirSync(logDir, { recursive: true });
|
|
46202
|
-
const logFile = path3.join(logDir, "bridge.log");
|
|
46203
|
-
const out = fs2.openSync(logFile, "a");
|
|
46204
|
-
const child = spawn("todoforai-bridge", bridgeRunArgs(apiUrl), {
|
|
46205
|
-
detached: true,
|
|
46206
|
-
stdio: ["ignore", out, out]
|
|
46207
|
-
});
|
|
46208
|
-
child.on("error", (err) => {
|
|
46209
|
-
console.error(`\x1B[33mFailed to start bridge: ${err.message}\x1B[0m`);
|
|
46210
|
-
});
|
|
46211
|
-
let exited = false;
|
|
46212
|
-
let exitCode = null;
|
|
46213
|
-
child.on("exit", (code) => {
|
|
46214
|
-
exited = true;
|
|
46215
|
-
exitCode = code;
|
|
46216
|
-
});
|
|
46217
|
-
child.unref();
|
|
46218
|
-
const pid = child.pid;
|
|
46219
|
-
if (!pid)
|
|
46220
|
-
return;
|
|
46221
|
-
const shortLog = logFile.replace(os3.homedir(), "~");
|
|
46222
|
-
setTimeout(() => {
|
|
46223
|
-
if (!exited) {
|
|
46224
|
-
console.error(`\x1B[2mStarted bridge (pid ${pid}), logs: ${shortLog}\x1B[0m`);
|
|
46225
|
-
return;
|
|
46226
|
-
}
|
|
46227
|
-
if (exitCode === 0) {
|
|
46228
|
-
console.error(`\x1B[2mBridge exited cleanly. Logs: ${shortLog}\x1B[0m`);
|
|
46229
|
-
} else {
|
|
46230
|
-
console.error(`\x1B[33mBridge exited early (exit ${exitCode}). Check logs: ${shortLog}. Another instance may already be running.\x1B[0m`);
|
|
46231
|
-
}
|
|
46232
|
-
}, 500);
|
|
46233
|
-
}
|
|
46234
|
-
|
|
46235
46314
|
// src/index.ts
|
|
46236
46315
|
try {
|
|
46237
46316
|
const pkgPath = path4.resolve(fileURLToPath(import.meta.url), "../../package.json");
|