@todoforai/cli 0.1.27 → 0.1.29
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/bin/todoforai-cli.js +1 -1
- package/dist/{todoai.js → todoforai-cli.js} +437 -244
- package/package.json +5 -11
package/bin/todoforai-cli.js
CHANGED
|
@@ -5,5 +5,5 @@ import { existsSync } from "node:fs";
|
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
|
|
7
7
|
const src = fileURLToPath(new URL("../src/index.ts", import.meta.url));
|
|
8
|
-
const dist = fileURLToPath(new URL("../dist/
|
|
8
|
+
const dist = fileURLToPath(new URL("../dist/todoforai-cli.js", import.meta.url));
|
|
9
9
|
await import(existsSync(src) ? src : dist);
|
|
@@ -41771,9 +41771,9 @@ var require_route = __commonJS((exports, module) => {
|
|
|
41771
41771
|
var conversions = require_conversions();
|
|
41772
41772
|
function buildGraph() {
|
|
41773
41773
|
const graph = {};
|
|
41774
|
-
const
|
|
41775
|
-
for (let len =
|
|
41776
|
-
graph[
|
|
41774
|
+
const models2 = Object.keys(conversions);
|
|
41775
|
+
for (let len = models2.length, i = 0;i < len; i++) {
|
|
41776
|
+
graph[models2[i]] = {
|
|
41777
41777
|
distance: -1,
|
|
41778
41778
|
parent: null
|
|
41779
41779
|
};
|
|
@@ -41805,23 +41805,23 @@ var require_route = __commonJS((exports, module) => {
|
|
|
41805
41805
|
};
|
|
41806
41806
|
}
|
|
41807
41807
|
function wrapConversion(toModel, graph) {
|
|
41808
|
-
const
|
|
41808
|
+
const path3 = [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
|
+
path3.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 = path3;
|
|
41817
41817
|
return fn;
|
|
41818
41818
|
}
|
|
41819
41819
|
module.exports = function(fromModel) {
|
|
41820
41820
|
const graph = deriveBFS(fromModel);
|
|
41821
41821
|
const conversion = {};
|
|
41822
|
-
const
|
|
41823
|
-
for (let len =
|
|
41824
|
-
const toModel =
|
|
41822
|
+
const models2 = Object.keys(graph);
|
|
41823
|
+
for (let len = models2.length, i = 0;i < len; i++) {
|
|
41824
|
+
const toModel = models2[i];
|
|
41825
41825
|
const node = graph[toModel];
|
|
41826
41826
|
if (node.parent === null) {
|
|
41827
41827
|
continue;
|
|
@@ -41837,7 +41837,7 @@ var require_color_convert = __commonJS((exports, module) => {
|
|
|
41837
41837
|
var conversions = require_conversions();
|
|
41838
41838
|
var route = require_route();
|
|
41839
41839
|
var convert = {};
|
|
41840
|
-
var
|
|
41840
|
+
var models2 = Object.keys(conversions);
|
|
41841
41841
|
function wrapRaw(fn) {
|
|
41842
41842
|
const wrappedFn = function(...args) {
|
|
41843
41843
|
const arg0 = args[0];
|
|
@@ -41876,7 +41876,7 @@ var require_color_convert = __commonJS((exports, module) => {
|
|
|
41876
41876
|
}
|
|
41877
41877
|
return wrappedFn;
|
|
41878
41878
|
}
|
|
41879
|
-
|
|
41879
|
+
models2.forEach((fromModel) => {
|
|
41880
41880
|
convert[fromModel] = {};
|
|
41881
41881
|
Object.defineProperty(convert[fromModel], "channels", { value: conversions[fromModel].channels });
|
|
41882
41882
|
Object.defineProperty(convert[fromModel], "labels", { value: conversions[fromModel].labels });
|
|
@@ -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 os3 = __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 = os3.release().split(".");
|
|
42091
42091
|
if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) {
|
|
42092
42092
|
return Number(osRelease[2]) >= 14931 ? 3 : 2;
|
|
42093
42093
|
}
|
|
@@ -42665,7 +42665,7 @@ import { realpathSync, readFileSync as readFileSync3 } from "fs";
|
|
|
42665
42665
|
import { resolve as resolve3 } from "path";
|
|
42666
42666
|
import { fileURLToPath } from "url";
|
|
42667
42667
|
import { homedir as homedir3 } from "os";
|
|
42668
|
-
import
|
|
42668
|
+
import path3 from "path";
|
|
42669
42669
|
|
|
42670
42670
|
// node_modules/@todoforai/update-notifier/src/index.ts
|
|
42671
42671
|
import fs from "fs";
|
|
@@ -42731,7 +42731,7 @@ function randomTip() {
|
|
|
42731
42731
|
return TIPS[Math.floor(Math.random() * TIPS.length)];
|
|
42732
42732
|
}
|
|
42733
42733
|
|
|
42734
|
-
// ../
|
|
42734
|
+
// ../packages/shared-api/src/api.ts
|
|
42735
42735
|
function restBasePath(apiKey) {
|
|
42736
42736
|
return apiKey.startsWith("dst_") ? "/dst/v1" : "/api/v1";
|
|
42737
42737
|
}
|
|
@@ -42812,8 +42812,8 @@ class ApiClient {
|
|
|
42812
42812
|
deleteProject(projectId) {
|
|
42813
42813
|
return this.request("DELETE", `/api/v1/projects/${projectId}`);
|
|
42814
42814
|
}
|
|
42815
|
-
createTodo(projectId, content, agentSettings) {
|
|
42816
|
-
return this.request("POST", `/api/v1/projects/${projectId}/todos`, { content, agentSettings });
|
|
42815
|
+
createTodo(projectId, content, agentSettings, groupTag, groupName) {
|
|
42816
|
+
return this.request("POST", `/api/v1/projects/${projectId}/todos`, { content, agentSettings, groupTag, groupName });
|
|
42817
42817
|
}
|
|
42818
42818
|
listTodos(projectId, opts) {
|
|
42819
42819
|
if (!projectId)
|
|
@@ -42851,12 +42851,16 @@ class ApiClient {
|
|
|
42851
42851
|
getAgentSettings(id) {
|
|
42852
42852
|
return this.request("GET", `/api/v1/agents/${id}`);
|
|
42853
42853
|
}
|
|
42854
|
-
addMessage(projectId, content, agentSettings, todoId, attachments, scheduledTimestamp) {
|
|
42854
|
+
addMessage(projectId, content, agentSettings, todoId, attachments, scheduledTimestamp, groupTag, groupName) {
|
|
42855
42855
|
const payload = { content, agentSettings, attachments: attachments || [] };
|
|
42856
42856
|
if (todoId)
|
|
42857
42857
|
payload.todoId = todoId;
|
|
42858
42858
|
if (scheduledTimestamp)
|
|
42859
42859
|
payload.scheduledTimestamp = scheduledTimestamp;
|
|
42860
|
+
if (!todoId && groupTag)
|
|
42861
|
+
payload.groupTag = groupTag;
|
|
42862
|
+
if (!todoId && groupName)
|
|
42863
|
+
payload.groupName = groupName;
|
|
42860
42864
|
return this.request("POST", `/api/v1/projects/${projectId}/todos`, payload);
|
|
42861
42865
|
}
|
|
42862
42866
|
async showFile(todoId, file, filename, opts = {}) {
|
|
@@ -42889,6 +42893,65 @@ class ApiClient {
|
|
|
42889
42893
|
throw new Error(`API POST /resources/open failed: ${res.status} ${await res.text()}`);
|
|
42890
42894
|
return res.json();
|
|
42891
42895
|
}
|
|
42896
|
+
async listShows(opts = {}) {
|
|
42897
|
+
const qs = new URLSearchParams;
|
|
42898
|
+
if (opts.todoId)
|
|
42899
|
+
qs.set("todoId", opts.todoId);
|
|
42900
|
+
if (opts.projectId)
|
|
42901
|
+
qs.set("projectId", opts.projectId);
|
|
42902
|
+
if (opts.card)
|
|
42903
|
+
qs.set("card", opts.card);
|
|
42904
|
+
const url = `${this.apiUrl}${restBasePath(this.apiKey)}/resources/show?${qs}`;
|
|
42905
|
+
const res = await fetch(url, { headers: { "x-api-key": this.apiKey }, signal: AbortSignal.timeout(30000) });
|
|
42906
|
+
if (!res.ok)
|
|
42907
|
+
throw new Error(`API GET /resources/show failed: ${res.status} ${await res.text()}`);
|
|
42908
|
+
return res.json();
|
|
42909
|
+
}
|
|
42910
|
+
async upsertCard(projectId, name, html, opts = {}) {
|
|
42911
|
+
const form = new FormData;
|
|
42912
|
+
form.append("file", html, `${name}.html`);
|
|
42913
|
+
for (const [k, v] of Object.entries(opts))
|
|
42914
|
+
if (v)
|
|
42915
|
+
form.append(k, v);
|
|
42916
|
+
const url = `${this.apiUrl}${restBasePath(this.apiKey)}/resources/cards/${projectId}/${name}`;
|
|
42917
|
+
const res = await fetch(url, {
|
|
42918
|
+
method: "PUT",
|
|
42919
|
+
headers: { "x-api-key": this.apiKey },
|
|
42920
|
+
body: form,
|
|
42921
|
+
signal: AbortSignal.timeout(60000)
|
|
42922
|
+
});
|
|
42923
|
+
if (!res.ok)
|
|
42924
|
+
throw new Error(`API PUT cards failed: ${res.status} ${await res.text()}`);
|
|
42925
|
+
return res.json();
|
|
42926
|
+
}
|
|
42927
|
+
async listCards(projectId) {
|
|
42928
|
+
const url = `${this.apiUrl}${restBasePath(this.apiKey)}/resources/cards/${projectId}`;
|
|
42929
|
+
const res = await fetch(url, { headers: { "x-api-key": this.apiKey }, signal: AbortSignal.timeout(30000) });
|
|
42930
|
+
if (!res.ok)
|
|
42931
|
+
throw new Error(`API GET cards failed: ${res.status} ${await res.text()}`);
|
|
42932
|
+
return res.json();
|
|
42933
|
+
}
|
|
42934
|
+
async getCard(projectId, name) {
|
|
42935
|
+
const url = `${this.apiUrl}${restBasePath(this.apiKey)}/resources/cards/${projectId}/${name}`;
|
|
42936
|
+
const res = await fetch(url, { headers: { "x-api-key": this.apiKey }, signal: AbortSignal.timeout(30000) });
|
|
42937
|
+
if (!res.ok)
|
|
42938
|
+
throw new Error(`API GET card failed: ${res.status} ${await res.text()}`);
|
|
42939
|
+
return res.json();
|
|
42940
|
+
}
|
|
42941
|
+
async getCardBody(projectId, name) {
|
|
42942
|
+
const url = `${this.apiUrl}${restBasePath(this.apiKey)}/resources/cards/${projectId}/${name}?body=1`;
|
|
42943
|
+
const res = await fetch(url, { headers: { "x-api-key": this.apiKey }, signal: AbortSignal.timeout(30000) });
|
|
42944
|
+
if (!res.ok)
|
|
42945
|
+
throw new Error(`API GET card body failed: ${res.status} ${await res.text()}`);
|
|
42946
|
+
return res.text();
|
|
42947
|
+
}
|
|
42948
|
+
async deleteCard(projectId, name) {
|
|
42949
|
+
const url = `${this.apiUrl}${restBasePath(this.apiKey)}/resources/cards/${projectId}/${name}`;
|
|
42950
|
+
const res = await fetch(url, { method: "DELETE", headers: { "x-api-key": this.apiKey }, signal: AbortSignal.timeout(30000) });
|
|
42951
|
+
if (!res.ok)
|
|
42952
|
+
throw new Error(`API DELETE card failed: ${res.status} ${await res.text()}`);
|
|
42953
|
+
return res.json();
|
|
42954
|
+
}
|
|
42892
42955
|
patchEdgeConfig(edgeId, updates) {
|
|
42893
42956
|
return this.request("PATCH", `/api/v1/edges/${edgeId}`, { updates });
|
|
42894
42957
|
}
|
|
@@ -42898,6 +42961,9 @@ class ApiClient {
|
|
|
42898
42961
|
updateAgentSettings(agentId, agentSettingsId, updates) {
|
|
42899
42962
|
return this.request("PUT", `/api/v1/agents/${agentId}/settings`, { agentSettingsId, updates });
|
|
42900
42963
|
}
|
|
42964
|
+
deleteAgentSettings(agentSettingsId) {
|
|
42965
|
+
return this.request("DELETE", `/api/v1/agents/${agentSettingsId}`);
|
|
42966
|
+
}
|
|
42901
42967
|
getGlobalAgentSettings() {
|
|
42902
42968
|
return this.request("GET", "/api/v1/agents/global");
|
|
42903
42969
|
}
|
|
@@ -42933,11 +42999,11 @@ class ApiClient {
|
|
|
42933
42999
|
throw new Error(`Spec '${specId}' not found (${res.status})`);
|
|
42934
43000
|
return res.json();
|
|
42935
43001
|
}
|
|
42936
|
-
async initDeviceLogin(clientName = "edge") {
|
|
43002
|
+
async initDeviceLogin(clientName = "edge", opts = {}) {
|
|
42937
43003
|
const res = await fetch(`${this.apiUrl}/api/v1/cli/login/init`, {
|
|
42938
43004
|
method: "POST",
|
|
42939
43005
|
headers: { "content-type": "application/json" },
|
|
42940
|
-
body: JSON.stringify({ clientName })
|
|
43006
|
+
body: JSON.stringify({ clientName, ...opts })
|
|
42941
43007
|
});
|
|
42942
43008
|
if (!res.ok)
|
|
42943
43009
|
throw new Error(`Device login init failed: ${res.status}`);
|
|
@@ -42957,35 +43023,50 @@ class ApiClient {
|
|
|
42957
43023
|
return res.json();
|
|
42958
43024
|
}
|
|
42959
43025
|
}
|
|
42960
|
-
|
|
42961
|
-
// ../edge/bun/src/frontend-ws.ts
|
|
43026
|
+
// ../packages/shared-api/src/frontend-ws.ts
|
|
42962
43027
|
import WebSocket from "ws";
|
|
42963
43028
|
|
|
42964
|
-
// ../
|
|
42965
|
-
import
|
|
42966
|
-
import
|
|
43029
|
+
// ../packages/shared-credentials/src/index.ts
|
|
43030
|
+
import { readFileSync, writeFileSync, mkdirSync, chmodSync } from "fs";
|
|
43031
|
+
import { join, dirname } from "path";
|
|
43032
|
+
import { homedir, platform } from "os";
|
|
42967
43033
|
function normalizeApiUrl(url) {
|
|
42968
43034
|
if (url.startsWith("localhost"))
|
|
42969
43035
|
return `http://${url}`;
|
|
42970
43036
|
if (!url.startsWith("http://") && !url.startsWith("https://"))
|
|
42971
43037
|
return `https://${url}`;
|
|
42972
|
-
return url;
|
|
43038
|
+
return url.replace(/\/+$/, "");
|
|
42973
43039
|
}
|
|
42974
|
-
var SUBCOMMANDS = new Set(["login", "logout", "update"]);
|
|
42975
43040
|
function credentialsPath() {
|
|
42976
|
-
|
|
42977
|
-
|
|
42978
|
-
|
|
42979
|
-
|
|
42980
|
-
|
|
42981
|
-
|
|
43041
|
+
return platform() === "win32" ? join(homedir(), "AppData", "Roaming", "todoforai", "credentials.json") : platform() === "darwin" ? join(homedir(), "Library", "Application Support", "todoforai", "credentials.json") : join(process.env.XDG_CONFIG_HOME || join(homedir(), ".config"), "todoforai", "credentials.json");
|
|
43042
|
+
}
|
|
43043
|
+
function read() {
|
|
43044
|
+
try {
|
|
43045
|
+
return JSON.parse(readFileSync(credentialsPath(), "utf-8"));
|
|
43046
|
+
} catch {
|
|
43047
|
+
return {};
|
|
42982
43048
|
}
|
|
42983
|
-
const xdg = process.env.XDG_CONFIG_HOME || path2.join(os2.homedir(), ".config");
|
|
42984
|
-
return path2.join(xdg, "todoforai", "credentials.json");
|
|
42985
43049
|
}
|
|
42986
|
-
|
|
43050
|
+
function writeSecure(data) {
|
|
43051
|
+
const p = credentialsPath();
|
|
43052
|
+
mkdirSync(dirname(p), { recursive: true });
|
|
43053
|
+
writeFileSync(p, JSON.stringify(data, null, 2), { encoding: "utf-8", mode: 384 });
|
|
43054
|
+
try {
|
|
43055
|
+
chmodSync(p, 384);
|
|
43056
|
+
} catch {}
|
|
43057
|
+
}
|
|
43058
|
+
function readCredential(apiUrl) {
|
|
43059
|
+
const c = read();
|
|
43060
|
+
const v = c[apiUrl] || c.apiToken || c.apiKey;
|
|
43061
|
+
return typeof v === "string" ? v : "";
|
|
43062
|
+
}
|
|
43063
|
+
function writeCredential(apiUrl, apiKey) {
|
|
43064
|
+
const c = read();
|
|
43065
|
+
c[apiUrl] = apiKey;
|
|
43066
|
+
writeSecure(c);
|
|
43067
|
+
}
|
|
42987
43068
|
|
|
42988
|
-
// ../
|
|
43069
|
+
// ../packages/shared-api/src/frontend-ws.ts
|
|
42989
43070
|
var log = (level, ...args) => console.log(`[frontend-ws:${level}]`, ...args);
|
|
42990
43071
|
|
|
42991
43072
|
class FrontendWebSocket {
|
|
@@ -43174,51 +43255,11 @@ class FrontendWebSocket {
|
|
|
43174
43255
|
});
|
|
43175
43256
|
}
|
|
43176
43257
|
}
|
|
43177
|
-
|
|
43178
|
-
// src/args.ts
|
|
43179
|
-
import { parseArgs } from "util";
|
|
43180
|
-
// package.json
|
|
43181
|
-
var package_default = {
|
|
43182
|
-
name: "@todoforai/cli",
|
|
43183
|
-
version: "0.1.27",
|
|
43184
|
-
type: "module",
|
|
43185
|
-
bin: {
|
|
43186
|
-
"todoforai-cli": "bin/todoforai-cli.js",
|
|
43187
|
-
todoai: "bin/todoforai-cli.js"
|
|
43188
|
-
},
|
|
43189
|
-
files: [
|
|
43190
|
-
"bin",
|
|
43191
|
-
"dist/todoai.js"
|
|
43192
|
-
],
|
|
43193
|
-
scripts: {
|
|
43194
|
-
build: "bun build src/index.ts --target=bun --outfile dist/todoai.js --external ws",
|
|
43195
|
-
prepublishOnly: "bun run build",
|
|
43196
|
-
start: "bun run src/index.ts",
|
|
43197
|
-
dev: "bun run src/index.ts"
|
|
43198
|
-
},
|
|
43199
|
-
dependencies: {
|
|
43200
|
-
"@shared/credentials": "file:../packages/shared-credentials",
|
|
43201
|
-
"@todoforai/update-notifier": "^0.1.0",
|
|
43202
|
-
"cli-highlight": "^2.1.11",
|
|
43203
|
-
"diff-match-patch": "^1.0.5",
|
|
43204
|
-
ws: "^8.18.0"
|
|
43205
|
-
},
|
|
43206
|
-
peerDependencies: {
|
|
43207
|
-
"@todoforai/edge": ">=0.12.0"
|
|
43208
|
-
},
|
|
43209
|
-
peerDependenciesMeta: {
|
|
43210
|
-
"@todoforai/edge": {
|
|
43211
|
-
optional: true
|
|
43212
|
-
}
|
|
43213
|
-
},
|
|
43214
|
-
devDependencies: {
|
|
43215
|
-
"@types/ws": "^8.5.13",
|
|
43216
|
-
typescript: "^5.7.0"
|
|
43217
|
-
}
|
|
43218
|
-
};
|
|
43219
43258
|
// ../packages/shared-fbe/src/enums.ts
|
|
43220
43259
|
var TodoStatus;
|
|
43221
43260
|
((TodoStatus2) => {
|
|
43261
|
+
TodoStatus2["PROPOSED"] = "PROPOSED";
|
|
43262
|
+
TodoStatus2["DISMISSED"] = "DISMISSED";
|
|
43222
43263
|
TodoStatus2["TODO"] = "TODO";
|
|
43223
43264
|
TodoStatus2["SCHEDULED"] = "SCHEDULED";
|
|
43224
43265
|
TodoStatus2["PAUSED"] = "PAUSED";
|
|
@@ -43238,6 +43279,59 @@ var TodoStatus;
|
|
|
43238
43279
|
TodoStatus2["ARCHIVED"] = "ARCHIVED";
|
|
43239
43280
|
TodoStatus2["DELETED"] = "DELETED";
|
|
43240
43281
|
})(TodoStatus ||= {});
|
|
43282
|
+
// ../packages/shared-fbe/src/models.ts
|
|
43283
|
+
var DEFAULT_MODEL = "anthropic:anthropic/claude-sonnet-5";
|
|
43284
|
+
var MODEL_ALIASES = {
|
|
43285
|
+
claude: DEFAULT_MODEL
|
|
43286
|
+
};
|
|
43287
|
+
var PROVIDER_BY_AUTHOR = {
|
|
43288
|
+
anthropic: "anthropic",
|
|
43289
|
+
openai: "openai",
|
|
43290
|
+
deepseek: "deepseek",
|
|
43291
|
+
poolside: "poolside",
|
|
43292
|
+
"x-ai": "xai",
|
|
43293
|
+
"z-ai": "z.ai",
|
|
43294
|
+
google: "google-ai-studio",
|
|
43295
|
+
moonshotai: "opencode_go"
|
|
43296
|
+
};
|
|
43297
|
+
function isQualifiedModel(model) {
|
|
43298
|
+
const id = model.trim();
|
|
43299
|
+
const colon = id.indexOf(":");
|
|
43300
|
+
const slash = id.indexOf("/");
|
|
43301
|
+
return colon > 0 && colon < id.length - 1 && (slash === -1 || colon < slash);
|
|
43302
|
+
}
|
|
43303
|
+
function qualifyModel(model) {
|
|
43304
|
+
const id = model?.trim();
|
|
43305
|
+
if (!id)
|
|
43306
|
+
return { model: DEFAULT_MODEL };
|
|
43307
|
+
if (isQualifiedModel(id))
|
|
43308
|
+
return { model: id };
|
|
43309
|
+
const suffix = id.match(/\([^)]+\)$/)?.[0] ?? "";
|
|
43310
|
+
const bare = suffix ? id.slice(0, -suffix.length) : id;
|
|
43311
|
+
const aliased = MODEL_ALIASES[bare.toLowerCase()];
|
|
43312
|
+
if (aliased)
|
|
43313
|
+
return { model: aliased + suffix };
|
|
43314
|
+
const lower = bare.toLowerCase();
|
|
43315
|
+
const provider = PROVIDER_BY_AUTHOR[lower.split("/")[0]];
|
|
43316
|
+
if (provider && lower.includes("/"))
|
|
43317
|
+
return { model: `${provider}:${lower}${suffix}` };
|
|
43318
|
+
return { model: DEFAULT_MODEL, fallback: id };
|
|
43319
|
+
}
|
|
43320
|
+
function qualifiedModelIds(listing, filter) {
|
|
43321
|
+
const q = filter?.toLowerCase();
|
|
43322
|
+
const ids = [];
|
|
43323
|
+
for (const m of listing?.data ?? []) {
|
|
43324
|
+
const id = String(m.id);
|
|
43325
|
+
if (m.provider) {
|
|
43326
|
+
ids.push(`${m.provider.toLowerCase()}:${id}`);
|
|
43327
|
+
continue;
|
|
43328
|
+
}
|
|
43329
|
+
const r = qualifyModel(id);
|
|
43330
|
+
if (!r.fallback)
|
|
43331
|
+
ids.push(r.model);
|
|
43332
|
+
}
|
|
43333
|
+
return q ? ids.filter((id) => id.toLowerCase().includes(q)) : ids;
|
|
43334
|
+
}
|
|
43241
43335
|
// ../packages/shared-fbe/src/billing.ts
|
|
43242
43336
|
var USAGE_WINDOWS = {
|
|
43243
43337
|
SESSION_MS: 5 * 60 * 60 * 1000,
|
|
@@ -43288,7 +43382,7 @@ var mimetypes_default = {
|
|
|
43288
43382
|
gif: { mime: "image/gif", type: "image" },
|
|
43289
43383
|
webp: { mime: "image/webp", type: "image" },
|
|
43290
43384
|
bmp: { mime: "image/bmp", type: "image" },
|
|
43291
|
-
svg: { mime: "image/svg+xml", type: "
|
|
43385
|
+
svg: { mime: "image/svg+xml", type: "image" },
|
|
43292
43386
|
heic: { mime: "image/heic", type: "image", conversionProfile: "to_jpeg_90" },
|
|
43293
43387
|
heif: { mime: "image/heif", type: "image", conversionProfile: "to_jpeg_90" },
|
|
43294
43388
|
mp3: { mime: "audio/mpeg", type: "audio" },
|
|
@@ -43446,9 +43540,6 @@ var SERVER_TO_FRONTENDS = {
|
|
|
43446
43540
|
hexGrid: {
|
|
43447
43541
|
updated: (projectId) => `${"hexgrid:updated" /* HEXGRID_UPDATED */}:${projectId}`
|
|
43448
43542
|
},
|
|
43449
|
-
placement: {
|
|
43450
|
-
updated: (projectId) => `${"placement:updated" /* PLACEMENT_UPDATED */}:${projectId}`
|
|
43451
|
-
},
|
|
43452
43543
|
sandbox: {
|
|
43453
43544
|
state: (userId) => `${"sandbox:state" /* SANDBOX_STATE */}:${userId}`
|
|
43454
43545
|
},
|
|
@@ -43496,7 +43587,7 @@ var SERVER_TO_FRONTENDS = {
|
|
|
43496
43587
|
predicted_reply_updated: (todoId) => `${"predict:reply_updated" /* PREDICTED_REPLY_UPDATED */}:${todoId}`,
|
|
43497
43588
|
error_result: (todoId) => `${"block:error_result" /* BLOCK_ERROR_RESULT */}:${todoId}`,
|
|
43498
43589
|
meta_result: (todoId) => `${"block:meta_result" /* BLOCK_META_RESULT */}:${todoId}`,
|
|
43499
|
-
file_changed: (edgeId,
|
|
43590
|
+
file_changed: (edgeId, path2) => `${"block:file_changed" /* BLOCK_FILE_CHANGED */}:${edgeId}:${path2}`,
|
|
43500
43591
|
notify: (todoId) => `${"NOTIFY" /* NOTIFY */}:${todoId}`
|
|
43501
43592
|
},
|
|
43502
43593
|
notify: {
|
|
@@ -43744,13 +43835,10 @@ function getBlockServerPrefix(block) {
|
|
|
43744
43835
|
const prefixMatch = agentPattern.match(/^(.*?:)BASH(?:\(|$)/);
|
|
43745
43836
|
return prefixMatch ? prefixMatch[1] : "";
|
|
43746
43837
|
}
|
|
43747
|
-
var BASH_LIKE_BLOCK_TYPES = new Set(["bash", "machine_exec"]);
|
|
43748
43838
|
function getBlockPatterns(block) {
|
|
43749
43839
|
if (Array.isArray(block.generalized_pattern))
|
|
43750
43840
|
return block.generalized_pattern;
|
|
43751
|
-
|
|
43752
|
-
const isBashLike = BASH_LIKE_BLOCK_TYPES.has(block.type.toLowerCase()) || /:BASH(\(|$)/.test(agentPattern);
|
|
43753
|
-
if (isBashLike && block.cmd) {
|
|
43841
|
+
if (block.type.toLowerCase() === "bash" && block.cmd) {
|
|
43754
43842
|
const prefix = getBlockServerPrefix(block);
|
|
43755
43843
|
return generalizeBashCmd(block.cmd).map((p) => prefix + p);
|
|
43756
43844
|
}
|
|
@@ -43848,8 +43936,7 @@ function getBlockNewPatterns(block, permissions) {
|
|
|
43848
43936
|
if (Array.isArray(block.generalized_pattern)) {
|
|
43849
43937
|
return getNewPatterns(block.generalized_pattern, permissions);
|
|
43850
43938
|
}
|
|
43851
|
-
const
|
|
43852
|
-
const isBash = (block.type.toLowerCase() === "bash" || agentPattern.includes(":BASH(")) && !!block.cmd;
|
|
43939
|
+
const isBash = block.type.toLowerCase() === "bash" && !!block.cmd;
|
|
43853
43940
|
if (!isBash) {
|
|
43854
43941
|
return getBlockPatterns(block).filter((p) => !isPatternAllowed(permissions, p));
|
|
43855
43942
|
}
|
|
@@ -43910,7 +43997,7 @@ Rules for the Presence block:
|
|
|
43910
43997
|
- \`score\` is an integer 0-100, or \`null\` when you checked but found no presence. Base scores on real findings, not guesses. Include every channel you actually checked.
|
|
43911
43998
|
- Keep it consistent with the prose Online Presence section \u2014 the JSON is the structured mirror of those findings.
|
|
43912
43999
|
|
|
43913
|
-
Growth recommendations are NOT part of this block \u2014 they live
|
|
44000
|
+
Growth recommendations are NOT part of this block \u2014 they live on the project board as proposed todos (\u2727 rows on their group cards; see "Recommending actionable TODOs" below). After any analysis that surfaces growth opportunities, materialize your top recommendations as proposals there; the prose Next Steps section stays human-readable advice.
|
|
43914
44001
|
|
|
43915
44002
|
Document order for the saved brand page: human-readable sections first, then the **Next Steps** section, then Open Questions (if any), and finally exactly one \`growth-plan\` block as the very last element. (Your chat reply can still end with a short spoken summary; only the saved markdown must end with the machine block.)
|
|
43916
44003
|
|
|
@@ -43922,8 +44009,14 @@ In follow-up conversations, act as an ongoing advisor: answer growth and digital
|
|
|
43922
44009
|
|
|
43923
44010
|
When the user asks you to recommend how to grow (e.g. "recommend growth TODOs", the brand page's guided suggestion, the board's Recommend button), read the current business context (including the presence block), research only where information is missing or stale, refresh the presence block if findings changed, then materialize your top growth actions as recommendation cards (see below). The cards ARE the growth plan \u2014 do not write a separate recommendations list into the markdown.
|
|
43924
44011
|
|
|
44012
|
+
Strategic workstreams (board groups):
|
|
44013
|
+
The project board is organized into group cards, and each group should be a strategic workstream for THIS company \u2014 a front the business is (or should be) pushing on, e.g. "SEO & Content", "Outbound to agencies", "Community on Reddit", "Retention & Email". Your recommendation-generation prompt includes the board's existing groups as JSON (\`slug\`, \`name\`, and \`description\` when set \u2014 the description states the workstream's strategic intent). Treat them as the company's current strategic map:
|
|
44014
|
+
- FIRST fit each recommendation into an existing group when it genuinely advances that workstream \u2014 pass its slug as the \`group\` parameter of \`api_recommend_template\`. Match on strategy (read the descriptions), not keywords.
|
|
44015
|
+
- When your analysis surfaces a strategic direction the board doesn't cover yet, introduce it: pass a new short kebab-case \`group\` slug plus a human \`groupName\` (2-4 words, strategy-level \u2014 "Partner Channel", not "Misc" or "Growth") and a one-sentence \`groupDescription\` stating the strategic bet and its target outcome for THIS company (e.g. "Win comparison searches against Zapier and n8n to capture switchers"). The group is created with the card.
|
|
44016
|
+
- Keep the map tight: 3-7 workstreams total is healthy. Never invent a new group when an existing one fits, never create catch-all groups, and never mirror priority or channel-lists as groups \u2014 a group is a strategic bet, its cards are the moves.
|
|
44017
|
+
|
|
43925
44018
|
Recommending actionable TODOs from your recommendations:
|
|
43926
|
-
To materialize growth recommendations,
|
|
44019
|
+
To materialize growth recommendations, propose todos on the project board. A proposal is a real todo in PROPOSED state \u2014 it sits as a \u2727 row on its group card until the user starts (accepts) or dismisses it. You NEVER create running todos \u2014 you only propose. Two tools, strict roles:
|
|
43927
44020
|
|
|
43928
44021
|
1. GET A TEMPLATE (api_create_template) \u2014 generate-first, reuse only on a close fit:
|
|
43929
44022
|
- Default: CREATE a template tailored to THIS business with the \`api_create_template\` tool. It returns the new template id.
|
|
@@ -43936,12 +44029,12 @@ To materialize growth recommendations, create recommendation cards in the projec
|
|
|
43936
44029
|
- Then the request, using these exact standalone heading lines (letters only, each on its own line, ending with a colon): \`Your task:\`, \`Steps:\`, \`Data needed:\`. Everything under these headings becomes the first user turn.
|
|
43937
44030
|
- Headings must match exactly (e.g. \`Your task:\`, not \`Your task (SEO):\`) or the split breaks. Put the business-specific detail in the body text under each heading, never in the heading itself.
|
|
43938
44031
|
|
|
43939
|
-
2. RECOMMEND (api_recommend_template \u2014
|
|
43940
|
-
Call \`api_recommend_template\` with the template id, a one-line \`note\` (the WHY for THIS business \u2014 it becomes the
|
|
44032
|
+
2. RECOMMEND (api_recommend_template \u2014 proposes the template as a PROPOSED todo on the project; never creates a running todo):
|
|
44033
|
+
Call \`api_recommend_template\` with the template id, a one-line \`note\` (the WHY for THIS business \u2014 it becomes the proposal's tooltip), a \`priority\` of high, medium or low, and the \`group\` slug of the strategic workstream it belongs to (see "Strategic workstreams" above \u2014 reuse an existing slug when one fits; a new slug + \`groupName\` creates the workstream). Project and business context default to the current session, so pass them only when targeting a different one. The proposal appears as a \u2727 row on that group's board card for the user to review before any credits are spent. Re-proposing the same template refreshes the open proposal instead of duplicating it; dismissed proposals since your last run are fed back to you \u2014 don't re-propose those, steer elsewhere.
|
|
43941
44034
|
|
|
43942
44035
|
Both tools run in-process and need no machine or shell \u2014 never shell out to \`todoregistry-cli\` / \`todoforai-cli\` for this.
|
|
43943
44036
|
|
|
43944
|
-
3.
|
|
44037
|
+
3. Propose 3-6 todos (highest priority first). Then reply with a one-line summary per proposal grouped by workstream, and tell the user they're waiting on the board for review.`,
|
|
43945
44038
|
mcpConfigs: {},
|
|
43946
44039
|
edgesMcpConfigs: {},
|
|
43947
44040
|
permissions: {
|
|
@@ -43985,6 +44078,9 @@ var OUTPUT_POLICIES = {
|
|
|
43985
44078
|
full: { firstLimit: Infinity, lastLimit: 0, hardCap: RUN_OUTPUT_CAP, lineLimit: MAX_LINE_LEN },
|
|
43986
44079
|
raw: { firstLimit: Infinity, lastLimit: 0, hardCap: Infinity, lineLimit: Infinity }
|
|
43987
44080
|
};
|
|
44081
|
+
// ../packages/shared-fbe/src/toolInstallCommand.ts
|
|
44082
|
+
var PYTHON_MIN_MINOR = 10;
|
|
44083
|
+
var PYTHON_VERSION_CHECK = `-c 'import sys; sys.exit(0 if sys.version_info[:2] >= (3, ${PYTHON_MIN_MINOR}) else 1)'`;
|
|
43988
44084
|
// ../packages/shared-fbe/src/realtime/topics.ts
|
|
43989
44085
|
var TOPICS = {
|
|
43990
44086
|
["todo:new" /* NEW_TODO */]: {
|
|
@@ -44023,10 +44119,6 @@ var TOPICS = {
|
|
|
44023
44119
|
channel: (p) => SERVER_TO_FRONTENDS.hexGrid.updated(p.projectId),
|
|
44024
44120
|
audience: "frontend"
|
|
44025
44121
|
},
|
|
44026
|
-
["placement:updated" /* PLACEMENT_UPDATED */]: {
|
|
44027
|
-
channel: (p) => SERVER_TO_FRONTENDS.placement.updated(p.projectId),
|
|
44028
|
-
audience: "frontend"
|
|
44029
|
-
},
|
|
44030
44122
|
["sandbox:state" /* SANDBOX_STATE */]: {
|
|
44031
44123
|
channel: (p) => SERVER_TO_FRONTENDS.sandbox.state(p.userId),
|
|
44032
44124
|
audience: "frontend"
|
|
@@ -44124,15 +44216,51 @@ var TOPICS = {
|
|
|
44124
44216
|
audience: "frontend"
|
|
44125
44217
|
}
|
|
44126
44218
|
};
|
|
44219
|
+
// src/args.ts
|
|
44220
|
+
import { parseArgs } from "util";
|
|
44221
|
+
// package.json
|
|
44222
|
+
var package_default = {
|
|
44223
|
+
name: "@todoforai/cli",
|
|
44224
|
+
version: "0.1.29",
|
|
44225
|
+
type: "module",
|
|
44226
|
+
bin: {
|
|
44227
|
+
"todoforai-cli": "bin/todoforai-cli.js",
|
|
44228
|
+
"tfa-cli": "bin/todoforai-cli.js",
|
|
44229
|
+
todoai: "bin/todoforai-cli.js"
|
|
44230
|
+
},
|
|
44231
|
+
files: [
|
|
44232
|
+
"bin",
|
|
44233
|
+
"dist/todoforai-cli.js"
|
|
44234
|
+
],
|
|
44235
|
+
scripts: {
|
|
44236
|
+
build: "bun build src/index.ts --target=bun --outfile dist/todoforai-cli.js --external ws",
|
|
44237
|
+
prepublishOnly: "bun run build",
|
|
44238
|
+
start: "bun run src/index.ts",
|
|
44239
|
+
dev: "bun run src/index.ts"
|
|
44240
|
+
},
|
|
44241
|
+
dependencies: {
|
|
44242
|
+
"@shared/api": "file:../packages/shared-api",
|
|
44243
|
+
"@shared/credentials": "file:../packages/shared-credentials",
|
|
44244
|
+
"@todoforai/update-notifier": "^0.1.0",
|
|
44245
|
+
"cli-highlight": "^2.1.11",
|
|
44246
|
+
"diff-match-patch": "^1.0.5",
|
|
44247
|
+
ws: "^8.18.0"
|
|
44248
|
+
},
|
|
44249
|
+
devDependencies: {
|
|
44250
|
+
"@types/ws": "^8.5.13",
|
|
44251
|
+
typescript: "^5.7.0"
|
|
44252
|
+
}
|
|
44253
|
+
};
|
|
44254
|
+
|
|
44127
44255
|
// src/args.ts
|
|
44128
44256
|
var DEFAULT_API_URL = "https://api.todofor.ai";
|
|
44129
44257
|
var VERSION = package_default.version;
|
|
44130
44258
|
function getEnv(name) {
|
|
44131
|
-
return process.env[`TODOFORAI_${name}`] || process.env[`TODO4AI_${name}`] || "";
|
|
44259
|
+
return (process.env[`TODOFORAI_${name}`] || process.env[`TODO4AI_${name}`] || "").trim();
|
|
44132
44260
|
}
|
|
44133
44261
|
function printUsage() {
|
|
44134
44262
|
process.stderr.write(`
|
|
44135
|
-
todoforai-cli \u2014 TODOforAI CLI (Bun)
|
|
44263
|
+
todoforai-cli \u2014 TODOforAI CLI (Bun). Aliases: tfa-cli, todoai.
|
|
44136
44264
|
|
|
44137
44265
|
Usage:
|
|
44138
44266
|
todoforai-cli login # Browser-based device auth
|
|
@@ -44152,7 +44280,10 @@ Usage:
|
|
|
44152
44280
|
todoforai-cli delete <todo-id> # Permanently delete a todo
|
|
44153
44281
|
todoforai-cli addmessage <todo-id> "text" # Add a message to an existing todo
|
|
44154
44282
|
todoforai-cli show <file|-> [todo-id] # Show a file in the chat (rendered by mimetype; - reads stdin)
|
|
44155
|
-
# [--title T] [--alias A] [--mime M] [--json]
|
|
44283
|
+
# [--title T] [--alias A] [--mime M] [--card <name>] [--json]
|
|
44284
|
+
todoforai-cli show list [todo-id] # List show blocks (ref, title, mime/url, card)
|
|
44285
|
+
# [--project <id>] [--card <name>] [--json]
|
|
44286
|
+
# no todo-id + --project (or $TODOFORAI_PROJECT_ID) = every todo
|
|
44156
44287
|
todoforai-cli open <url> [todo-id] # Show a live http(s) url in the chat as a preview
|
|
44157
44288
|
# [--title T] [--alias A] [--json]
|
|
44158
44289
|
todoforai-cli recommend --template <id> # Add a template as a recommendation card (see 'todoregistry-cli create')
|
|
@@ -44163,6 +44294,8 @@ Options:
|
|
|
44163
44294
|
--path <dir> Workspace path (default: cwd)
|
|
44164
44295
|
--project <id> Project ID
|
|
44165
44296
|
--agent, -a <name> Agent name (partial match)
|
|
44297
|
+
--group <slug> Group new todo; omitted inherits TODOFORAI_GROUP_ID
|
|
44298
|
+
--group-name <name> Display name for --group (last write wins)
|
|
44166
44299
|
--model <model> Override the agent's model for this todo
|
|
44167
44300
|
(e.g. anthropic:anthropic/claude-opus-5, openai:openai/gpt-5.6-sol)
|
|
44168
44301
|
--list-agents List available agents (name, id, workspace paths) and exit
|
|
@@ -44221,6 +44354,9 @@ function parseCliArgs() {
|
|
|
44221
44354
|
project: { type: "string" },
|
|
44222
44355
|
agent: { type: "string", short: "a" },
|
|
44223
44356
|
model: { type: "string" },
|
|
44357
|
+
group: { type: "string" },
|
|
44358
|
+
"group-name": { type: "string" },
|
|
44359
|
+
"group-description": { type: "string" },
|
|
44224
44360
|
"list-agents": { type: "boolean", default: false },
|
|
44225
44361
|
"list-models": { type: "boolean", default: false },
|
|
44226
44362
|
"api-url": { type: "string" },
|
|
@@ -44233,6 +44369,7 @@ function parseCliArgs() {
|
|
|
44233
44369
|
title: { type: "string" },
|
|
44234
44370
|
alias: { type: "string" },
|
|
44235
44371
|
mime: { type: "string" },
|
|
44372
|
+
card: { type: "string" },
|
|
44236
44373
|
direction: { type: "string" },
|
|
44237
44374
|
"business-context": { type: "string" },
|
|
44238
44375
|
seed: { type: "string" },
|
|
@@ -44601,8 +44738,8 @@ import { resolve, basename } from "path";
|
|
|
44601
44738
|
// src/ensure-bridge.ts
|
|
44602
44739
|
import { spawn, spawnSync } from "child_process";
|
|
44603
44740
|
import fs2 from "fs";
|
|
44604
|
-
import
|
|
44605
|
-
import
|
|
44741
|
+
import path2 from "path";
|
|
44742
|
+
import os2 from "os";
|
|
44606
44743
|
function hasBridge() {
|
|
44607
44744
|
const probe = spawnSync("todoforai-bridge", ["--version"], { stdio: "ignore" });
|
|
44608
44745
|
return probe.status === 0;
|
|
@@ -44671,31 +44808,44 @@ function ensureBridgeCredentials(apiUrl) {
|
|
|
44671
44808
|
const login = spawnSync("todoforai-bridge", bridgeLoginArgs(apiUrl), { stdio: "inherit" });
|
|
44672
44809
|
return login.status === 0;
|
|
44673
44810
|
}
|
|
44674
|
-
function
|
|
44675
|
-
|
|
44676
|
-
|
|
44677
|
-
|
|
44678
|
-
|
|
44679
|
-
|
|
44680
|
-
|
|
44681
|
-
r.on("error", () => resolve(false));
|
|
44682
|
-
r.on("close", (code) => resolve(code === 0 && out.trim().length > 0));
|
|
44683
|
-
});
|
|
44811
|
+
async function bridgeState(apiUrl, apiKey, deviceId) {
|
|
44812
|
+
try {
|
|
44813
|
+
const devices = await new ApiClient(apiUrl, apiKey).listDevices();
|
|
44814
|
+
return Array.isArray(devices) && devices.some((device) => device.id === deviceId && device.status === "ONLINE") ? "online" : "offline";
|
|
44815
|
+
} catch {
|
|
44816
|
+
return "unknown";
|
|
44817
|
+
}
|
|
44684
44818
|
}
|
|
44685
|
-
async function
|
|
44686
|
-
|
|
44687
|
-
|
|
44819
|
+
async function waitForBridgeOnline(apiUrl, apiKey, deviceId, timeoutMs = 5000) {
|
|
44820
|
+
const deadline = Date.now() + timeoutMs;
|
|
44821
|
+
do {
|
|
44822
|
+
const state = await bridgeState(apiUrl, apiKey, deviceId);
|
|
44823
|
+
if (state !== "offline")
|
|
44824
|
+
return state === "online";
|
|
44825
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
44826
|
+
} while (Date.now() < deadline);
|
|
44827
|
+
return false;
|
|
44828
|
+
}
|
|
44829
|
+
async function ensureBridgeRunning(apiUrl, apiKey) {
|
|
44688
44830
|
if (!hasBridge()) {
|
|
44689
44831
|
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");
|
|
44690
|
-
return;
|
|
44832
|
+
return false;
|
|
44691
44833
|
}
|
|
44692
44834
|
if (!ensureBridgeCredentials(apiUrl)) {
|
|
44693
44835
|
console.error("\x1B[33mBridge not started: `todoforai-bridge login` did not complete successfully.\x1B[0m");
|
|
44694
|
-
return;
|
|
44836
|
+
return false;
|
|
44695
44837
|
}
|
|
44696
|
-
const
|
|
44838
|
+
const deviceId = bridgeDeviceId(apiUrl);
|
|
44839
|
+
if (!deviceId) {
|
|
44840
|
+
console.error("\x1B[33mBridge not started: could not determine this device's id.\x1B[0m");
|
|
44841
|
+
return false;
|
|
44842
|
+
}
|
|
44843
|
+
const initialState = await bridgeState(apiUrl, apiKey, deviceId);
|
|
44844
|
+
if (initialState === "online" || initialState === "unknown")
|
|
44845
|
+
return initialState === "online";
|
|
44846
|
+
const logDir = path2.join(os2.homedir(), ".todoforai");
|
|
44697
44847
|
fs2.mkdirSync(logDir, { recursive: true });
|
|
44698
|
-
const logFile =
|
|
44848
|
+
const logFile = path2.join(logDir, "bridge.log");
|
|
44699
44849
|
const out = fs2.openSync(logFile, "a");
|
|
44700
44850
|
const child = spawn("todoforai-bridge", bridgeRunArgs(apiUrl), {
|
|
44701
44851
|
detached: true,
|
|
@@ -44712,13 +44862,13 @@ async function ensureBridgeRunning(apiUrl, _apiKey) {
|
|
|
44712
44862
|
});
|
|
44713
44863
|
child.unref();
|
|
44714
44864
|
if (!child.pid)
|
|
44715
|
-
return;
|
|
44716
|
-
|
|
44717
|
-
|
|
44718
|
-
|
|
44719
|
-
|
|
44720
|
-
|
|
44721
|
-
|
|
44865
|
+
return false;
|
|
44866
|
+
if (await waitForBridgeOnline(apiUrl, apiKey, deviceId))
|
|
44867
|
+
return true;
|
|
44868
|
+
const shortLog = logFile.replace(os2.homedir(), "~");
|
|
44869
|
+
const reason = exited ? `exit ${exitCode}` : "startup timed out";
|
|
44870
|
+
console.error(`\x1B[33mBridge not ready (${reason}), see ${shortLog}\x1B[0m`);
|
|
44871
|
+
return false;
|
|
44722
44872
|
}
|
|
44723
44873
|
|
|
44724
44874
|
// src/agent.ts
|
|
@@ -44752,9 +44902,13 @@ async function autoCreateAgent(api, resolvedPath) {
|
|
|
44752
44902
|
return resp;
|
|
44753
44903
|
}
|
|
44754
44904
|
const edges = await api.listEdges();
|
|
44755
|
-
|
|
44756
|
-
if (!edgeId)
|
|
44905
|
+
if (!Array.isArray(edges) || edges.length === 0) {
|
|
44757
44906
|
throw new Error("No bridge device or edge available to configure workspace path");
|
|
44907
|
+
}
|
|
44908
|
+
if (edges.length > 1) {
|
|
44909
|
+
throw new Error("No local bridge device found and multiple legacy edges are available; choose an existing agent explicitly");
|
|
44910
|
+
}
|
|
44911
|
+
const edgeId = edges[0].id;
|
|
44758
44912
|
await api.setAgentEdgeMcpConfig(agentId, agentSettingsId, edgeId, "todoai_edge", { workspacePaths: [resolvedPath] });
|
|
44759
44913
|
const matches = await api.listAgentSettings({ workspacePath: resolvedPath });
|
|
44760
44914
|
if (matches.length)
|
|
@@ -44765,20 +44919,29 @@ async function autoCreateAgent(api, resolvedPath) {
|
|
|
44765
44919
|
}
|
|
44766
44920
|
|
|
44767
44921
|
// src/config.ts
|
|
44768
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
44769
|
-
import { dirname, join, resolve as resolve2 } from "path";
|
|
44770
|
-
import { homedir, platform } from "os";
|
|
44922
|
+
import { existsSync, mkdirSync as mkdirSync2, readFileSync as readFileSync2, renameSync, writeFileSync as writeFileSync2 } from "fs";
|
|
44923
|
+
import { dirname as dirname2, join as join2, resolve as resolve2 } from "path";
|
|
44924
|
+
import { homedir as homedir2, platform as platform2 } from "os";
|
|
44925
|
+
function getConfigBase() {
|
|
44926
|
+
const sys = platform2();
|
|
44927
|
+
if (sys === "win32")
|
|
44928
|
+
return process.env.APPDATA || join2(homedir2(), "AppData", "Roaming");
|
|
44929
|
+
if (sys === "darwin")
|
|
44930
|
+
return join2(homedir2(), "Library", "Application Support");
|
|
44931
|
+
return process.env.XDG_CONFIG_HOME || join2(homedir2(), ".config");
|
|
44932
|
+
}
|
|
44771
44933
|
function getConfigDir() {
|
|
44772
|
-
const
|
|
44773
|
-
|
|
44774
|
-
|
|
44775
|
-
|
|
44776
|
-
|
|
44777
|
-
|
|
44778
|
-
|
|
44934
|
+
const base = getConfigBase();
|
|
44935
|
+
const dir = join2(base, "todoforai-cli");
|
|
44936
|
+
const legacy = join2(base, "todoai-cli");
|
|
44937
|
+
if (!existsSync(dir) && existsSync(legacy)) {
|
|
44938
|
+
try {
|
|
44939
|
+
renameSync(legacy, dir);
|
|
44940
|
+
} catch {
|
|
44941
|
+
return legacy;
|
|
44942
|
+
}
|
|
44779
44943
|
}
|
|
44780
|
-
|
|
44781
|
-
return join(xdg, "todoai-cli");
|
|
44944
|
+
return dir;
|
|
44782
44945
|
}
|
|
44783
44946
|
function defaultScope() {
|
|
44784
44947
|
return {
|
|
@@ -44830,10 +44993,10 @@ class ConfigStore {
|
|
|
44830
44993
|
data;
|
|
44831
44994
|
constructor(pathArg) {
|
|
44832
44995
|
if (pathArg) {
|
|
44833
|
-
const p = resolve2(pathArg.replace(/^~/,
|
|
44834
|
-
this.path = p.endsWith(".json") ? p :
|
|
44996
|
+
const p = resolve2(pathArg.replace(/^~/, homedir2()));
|
|
44997
|
+
this.path = p.endsWith(".json") ? p : join2(p, "config.json");
|
|
44835
44998
|
} else {
|
|
44836
|
-
this.path =
|
|
44999
|
+
this.path = join2(getConfigDir(), "config.json");
|
|
44837
45000
|
}
|
|
44838
45001
|
this.data = this.load();
|
|
44839
45002
|
}
|
|
@@ -44841,7 +45004,7 @@ class ConfigStore {
|
|
|
44841
45004
|
if (!existsSync(this.path))
|
|
44842
45005
|
return defaultConfig();
|
|
44843
45006
|
try {
|
|
44844
|
-
const raw = JSON.parse(
|
|
45007
|
+
const raw = JSON.parse(readFileSync2(this.path, "utf-8"));
|
|
44845
45008
|
delete raw.default_api_key;
|
|
44846
45009
|
const cfg = migrate(raw);
|
|
44847
45010
|
const needsMigration = raw.per_api_url == null;
|
|
@@ -44856,8 +45019,8 @@ class ConfigStore {
|
|
|
44856
45019
|
}
|
|
44857
45020
|
save() {
|
|
44858
45021
|
try {
|
|
44859
|
-
|
|
44860
|
-
|
|
45022
|
+
mkdirSync2(dirname2(this.path), { recursive: true });
|
|
45023
|
+
writeFileSync2(this.path, JSON.stringify(this.data, null, 2), "utf-8");
|
|
44861
45024
|
} catch {}
|
|
44862
45025
|
}
|
|
44863
45026
|
scope(apiUrl) {
|
|
@@ -44917,39 +45080,6 @@ class ScopedConfig {
|
|
|
44917
45080
|
this.store.save();
|
|
44918
45081
|
}
|
|
44919
45082
|
}
|
|
44920
|
-
|
|
44921
|
-
// ../packages/shared-credentials/src/index.ts
|
|
44922
|
-
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, chmodSync } from "fs";
|
|
44923
|
-
import { join as join2, dirname as dirname2 } from "path";
|
|
44924
|
-
import { homedir as homedir2, platform as platform2 } from "os";
|
|
44925
|
-
function credentialsPath2() {
|
|
44926
|
-
return platform2() === "win32" ? join2(homedir2(), "AppData", "Roaming", "todoforai", "credentials.json") : platform2() === "darwin" ? join2(homedir2(), "Library", "Application Support", "todoforai", "credentials.json") : join2(process.env.XDG_CONFIG_HOME || join2(homedir2(), ".config"), "todoforai", "credentials.json");
|
|
44927
|
-
}
|
|
44928
|
-
function read() {
|
|
44929
|
-
try {
|
|
44930
|
-
return JSON.parse(readFileSync2(credentialsPath2(), "utf-8"));
|
|
44931
|
-
} catch {
|
|
44932
|
-
return {};
|
|
44933
|
-
}
|
|
44934
|
-
}
|
|
44935
|
-
function writeSecure(data) {
|
|
44936
|
-
const p = credentialsPath2();
|
|
44937
|
-
mkdirSync2(dirname2(p), { recursive: true });
|
|
44938
|
-
writeFileSync2(p, JSON.stringify(data, null, 2), { encoding: "utf-8", mode: 384 });
|
|
44939
|
-
try {
|
|
44940
|
-
chmodSync(p, 384);
|
|
44941
|
-
} catch {}
|
|
44942
|
-
}
|
|
44943
|
-
function readCredential(apiUrl) {
|
|
44944
|
-
const c2 = read();
|
|
44945
|
-
const v = c2[apiUrl] || c2.apiToken || c2.apiKey;
|
|
44946
|
-
return typeof v === "string" ? v : "";
|
|
44947
|
-
}
|
|
44948
|
-
function writeCredential(apiUrl, apiKey) {
|
|
44949
|
-
const c2 = read();
|
|
44950
|
-
c2[apiUrl] = apiKey;
|
|
44951
|
-
writeSecure(c2);
|
|
44952
|
-
}
|
|
44953
45083
|
// src/logo.ts
|
|
44954
45084
|
var LETTERS = {
|
|
44955
45085
|
t: [" x ", "xxxx", " x ", " xll", " xll", " xxx"],
|
|
@@ -45023,6 +45153,12 @@ function printLogo() {
|
|
|
45023
45153
|
`);
|
|
45024
45154
|
}
|
|
45025
45155
|
|
|
45156
|
+
// src/urls.ts
|
|
45157
|
+
function getFrontendUrl(apiUrl, todoId) {
|
|
45158
|
+
const local = apiUrl.includes("localhost:4000") || apiUrl.includes("127.0.0.1:4000");
|
|
45159
|
+
return `${local ? "http://localhost:3000" : "https://todofor.ai"}/t/${todoId}`;
|
|
45160
|
+
}
|
|
45161
|
+
|
|
45026
45162
|
// src/inspect.ts
|
|
45027
45163
|
var xmlAttr = (v) => JSON.stringify(String(v));
|
|
45028
45164
|
var DROP_IF_EMPTY = new Set(["runMeta", "meta", "attachments", "blocks"]);
|
|
@@ -45448,7 +45584,16 @@ function selectProject(projects, defaultId, setDefault) {
|
|
|
45448
45584
|
return Promise.resolve({ id: defaultId, name });
|
|
45449
45585
|
}
|
|
45450
45586
|
}
|
|
45587
|
+
const accountDefault = projects.find((p) => p?.project?.isDefault) || projects[0];
|
|
45588
|
+
if (!process.stdin.isTTY) {
|
|
45589
|
+
const id = getItemId(accountDefault);
|
|
45590
|
+
const name = getDisplayName(accountDefault);
|
|
45591
|
+
process.stderr.write(`Using account default project: ${name}
|
|
45592
|
+
`);
|
|
45593
|
+
return Promise.resolve({ id, name });
|
|
45594
|
+
}
|
|
45451
45595
|
return (async () => {
|
|
45596
|
+
const defIdx = projects.indexOf(accountDefault);
|
|
45452
45597
|
process.stderr.write(`
|
|
45453
45598
|
Please choose a project:
|
|
45454
45599
|
|
|
@@ -45456,7 +45601,7 @@ Please choose a project:
|
|
|
45456
45601
|
for (let i = 0;i < projects.length; i++) {
|
|
45457
45602
|
const name = getDisplayName(projects[i]);
|
|
45458
45603
|
const id = getItemId(projects[i]);
|
|
45459
|
-
process.stderr.write(` [${i + 1}] ${name}
|
|
45604
|
+
process.stderr.write(` [${i + 1}] ${name}${i === defIdx ? " (default)" : ""}
|
|
45460
45605
|
`);
|
|
45461
45606
|
if (id && id !== name)
|
|
45462
45607
|
process.stderr.write(` ${id}
|
|
@@ -45465,8 +45610,8 @@ Please choose a project:
|
|
|
45465
45610
|
process.stderr.write(`
|
|
45466
45611
|
`);
|
|
45467
45612
|
while (true) {
|
|
45468
|
-
const choice = await terminalLine(
|
|
45469
|
-
const idx = parseInt(choice, 10) - 1;
|
|
45613
|
+
const choice = await terminalLine(`Please enter your numeric choice [${defIdx + 1}]: `);
|
|
45614
|
+
const idx = choice === "" ? defIdx : parseInt(choice, 10) - 1;
|
|
45470
45615
|
if (idx >= 0 && idx < projects.length) {
|
|
45471
45616
|
const id = getItemId(projects[idx]);
|
|
45472
45617
|
const name = getDisplayName(projects[idx]);
|
|
@@ -45854,17 +45999,6 @@ async function watchTodo(ws, todoId, projectId, opts = {}) {
|
|
|
45854
45999
|
"block:start_catfile"
|
|
45855
46000
|
]);
|
|
45856
46001
|
const signalActivity = () => opts.activityEvent?.set();
|
|
45857
|
-
let edgeId;
|
|
45858
|
-
let rootPath = "";
|
|
45859
|
-
if (opts.agentSettings) {
|
|
45860
|
-
const emc = opts.agentSettings.edgesMcpConfigs || {};
|
|
45861
|
-
edgeId = Object.keys(emc)[0];
|
|
45862
|
-
if (edgeId) {
|
|
45863
|
-
const ec = emc[edgeId];
|
|
45864
|
-
const tc = ec?.todoai_edge || ec?.todoai || {};
|
|
45865
|
-
rootPath = (tc.workspacePaths || [])[0] || "";
|
|
45866
|
-
}
|
|
45867
|
-
}
|
|
45868
46002
|
const blocksStore = new Map;
|
|
45869
46003
|
const diffStore = diffStoreByWs.get(ws) ?? new Map;
|
|
45870
46004
|
diffStoreByWs.set(ws, diffStore);
|
|
@@ -46051,6 +46185,12 @@ ${YELLOW}*${RESET} ${YELLOW}${blockType}${RESET}${extra}
|
|
|
46051
46185
|
`);
|
|
46052
46186
|
signalActivity();
|
|
46053
46187
|
}
|
|
46188
|
+
} else if (msgType === "NOTIFY") {
|
|
46189
|
+
const sevColor = payload.severity === "error" ? RED : YELLOW;
|
|
46190
|
+
process.stderr.write(`
|
|
46191
|
+
${sevColor}\u26A0 ${payload.title || "Notice"}${RESET}${payload.message ? ` ${DIM}${payload.message}${RESET}` : ""}
|
|
46192
|
+
`);
|
|
46193
|
+
signalActivity();
|
|
46054
46194
|
} else if (msgType === "todo:status") {
|
|
46055
46195
|
const status = payload.status || "";
|
|
46056
46196
|
process.stderr.write(`
|
|
@@ -46081,6 +46221,9 @@ ${DIM}[todo:status] ${status}${RESET}
|
|
|
46081
46221
|
const status = result?.payload?.status || result?.type || "unknown";
|
|
46082
46222
|
process.stderr.write(`Warning: Stopped: ${status}
|
|
46083
46223
|
`);
|
|
46224
|
+
process.exitCode = 1;
|
|
46225
|
+
} else {
|
|
46226
|
+
process.exitCode = 0;
|
|
46084
46227
|
}
|
|
46085
46228
|
return true;
|
|
46086
46229
|
} catch (e) {
|
|
@@ -46124,20 +46267,6 @@ async function listAgentsCommand(api, opts) {
|
|
|
46124
46267
|
}
|
|
46125
46268
|
}
|
|
46126
46269
|
|
|
46127
|
-
// src/list-models.ts
|
|
46128
|
-
async function listModelsCommand(api, opts) {
|
|
46129
|
-
const res = await api.listModels();
|
|
46130
|
-
const q = opts.filter?.toLowerCase();
|
|
46131
|
-
const ids = (res?.data || []).map((m) => String(m.id).includes("/") ? `${String(m.id).split("/")[0]}:${m.id}` : String(m.id)).filter((id) => !q || id.toLowerCase().includes(q));
|
|
46132
|
-
if (opts.json) {
|
|
46133
|
-
console.log(JSON.stringify(ids, null, 2));
|
|
46134
|
-
return;
|
|
46135
|
-
}
|
|
46136
|
-
for (const id of ids)
|
|
46137
|
-
process.stderr.write(`${BRAND}${id}${RESET}
|
|
46138
|
-
`);
|
|
46139
|
-
}
|
|
46140
|
-
|
|
46141
46270
|
// src/agent-command.ts
|
|
46142
46271
|
function printAgentHelp() {
|
|
46143
46272
|
process.stderr.write(`
|
|
@@ -46147,6 +46276,7 @@ Usage:
|
|
|
46147
46276
|
todoforai-cli agent list List agents (name, model, id, paths)
|
|
46148
46277
|
todoforai-cli agent get <agent> Show a single agent's settings
|
|
46149
46278
|
todoforai-cli agent update <agent> <field=value>\u2026 Update one or more settings
|
|
46279
|
+
todoforai-cli agent delete <agent> Delete an agent configuration (asks to confirm)
|
|
46150
46280
|
|
|
46151
46281
|
<agent> is a name or id (unique partial name also works).
|
|
46152
46282
|
Fields map directly to agent settings; values are parsed as JSON when possible
|
|
@@ -46244,6 +46374,32 @@ async function agentCommand(api, positionals, args, formatPath) {
|
|
|
46244
46374
|
}
|
|
46245
46375
|
const summary = Object.keys(updates).map((k) => `${k}=${JSON.stringify(updated[k])}`).join(" ");
|
|
46246
46376
|
process.stderr.write(`${GREEN}\u2705 ${getDisplayName(agent)} updated: ${summary}${RESET}
|
|
46377
|
+
`);
|
|
46378
|
+
return;
|
|
46379
|
+
}
|
|
46380
|
+
if (sub === "delete" || sub === "rm") {
|
|
46381
|
+
const query = positionals[2];
|
|
46382
|
+
if (!query) {
|
|
46383
|
+
process.stderr.write(`${RED}Usage: todoforai-cli agent delete <name|id>${RESET}
|
|
46384
|
+
`);
|
|
46385
|
+
process.exit(2);
|
|
46386
|
+
}
|
|
46387
|
+
const agents = await api.listAgentSettings();
|
|
46388
|
+
const agent = resolveAgent(agents, query);
|
|
46389
|
+
const name = getDisplayName(agent);
|
|
46390
|
+
if (!process.stdin.isTTY) {
|
|
46391
|
+
process.stderr.write(`${RED}Refusing to delete '${name}' non-interactively (delete needs an interactive confirm).${RESET}
|
|
46392
|
+
`);
|
|
46393
|
+
process.exit(2);
|
|
46394
|
+
}
|
|
46395
|
+
const ok = await singleChar(`${RED}Delete agent '${name}'? [y/N] ${RESET}`);
|
|
46396
|
+
if (ok !== "y") {
|
|
46397
|
+
process.stderr.write(`Aborted
|
|
46398
|
+
`);
|
|
46399
|
+
return;
|
|
46400
|
+
}
|
|
46401
|
+
await api.deleteAgentSettings(getItemId(agent));
|
|
46402
|
+
process.stderr.write(`${GREEN}\u2705 Deleted ${name}${RESET}
|
|
46247
46403
|
`);
|
|
46248
46404
|
return;
|
|
46249
46405
|
}
|
|
@@ -46449,18 +46605,12 @@ async function listTodosCommand(api, defaultProjectId, argv) {
|
|
|
46449
46605
|
|
|
46450
46606
|
// src/index.ts
|
|
46451
46607
|
try {
|
|
46452
|
-
const pkgPath =
|
|
46608
|
+
const pkgPath = path3.resolve(fileURLToPath(import.meta.url), "../../package.json");
|
|
46453
46609
|
checkForUpdates(JSON.parse(readFileSync3(pkgPath, "utf-8")));
|
|
46454
46610
|
} catch {}
|
|
46455
|
-
function formatPathWithTilde(
|
|
46611
|
+
function formatPathWithTilde(path4) {
|
|
46456
46612
|
const home = homedir3();
|
|
46457
|
-
return
|
|
46458
|
-
}
|
|
46459
|
-
function getFrontendUrl(apiUrl, projectId, todoId) {
|
|
46460
|
-
if (apiUrl.includes("localhost:4000") || apiUrl.includes("127.0.0.1:4000")) {
|
|
46461
|
-
return `http://localhost:3000/${projectId}/${todoId}`;
|
|
46462
|
-
}
|
|
46463
|
-
return `https://todofor.ai/${projectId}/${todoId}`;
|
|
46613
|
+
return path4.startsWith(home) ? path4.replace(home, "~") : path4;
|
|
46464
46614
|
}
|
|
46465
46615
|
async function interactiveLoop(ws, api, todoId, projectId, agent, json, autoApprove, cfg) {
|
|
46466
46616
|
while (true) {
|
|
@@ -46537,6 +46687,8 @@ Cancelled by user (Ctrl+C)
|
|
|
46537
46687
|
process.exit(130);
|
|
46538
46688
|
});
|
|
46539
46689
|
const { values: args, positionals } = parseCliArgs();
|
|
46690
|
+
const groupTag = args.group === undefined ? getEnv("GROUP_ID") : String(args.group);
|
|
46691
|
+
const groupName = args["group-name"] === undefined ? undefined : String(args["group-name"]);
|
|
46540
46692
|
if (positionals[0] === "start") {
|
|
46541
46693
|
if (!positionals[1] && !args.template) {
|
|
46542
46694
|
process.stderr.write(`${RED}Usage: todoforai-cli start <todo-id>${RESET}
|
|
@@ -46699,7 +46851,8 @@ Cancelled by user (Ctrl+C)
|
|
|
46699
46851
|
process.exit(2);
|
|
46700
46852
|
}
|
|
46701
46853
|
const todo2 = await api.getTodo(todoId);
|
|
46702
|
-
const
|
|
46854
|
+
const agent2 = todo2.agentSettings || await api.getAgentSettings(todo2.agentSettingsId);
|
|
46855
|
+
const msg = await api.addMessage(todo2.projectId, content2, agent2, todoId);
|
|
46703
46856
|
if (args.json)
|
|
46704
46857
|
console.log(JSON.stringify(msg, null, 2));
|
|
46705
46858
|
else
|
|
@@ -46707,6 +46860,32 @@ Cancelled by user (Ctrl+C)
|
|
|
46707
46860
|
`);
|
|
46708
46861
|
return;
|
|
46709
46862
|
}
|
|
46863
|
+
if (positionals[0] === "show" && positionals[1] === "list") {
|
|
46864
|
+
const explicitProject = !!args.project;
|
|
46865
|
+
const todoId = positionals[2] || (!explicitProject ? getEnv("TODO_ID") || cfgScope.data.last_todo_id : undefined);
|
|
46866
|
+
const projectId2 = todoId ? undefined : args.project || getEnv("PROJECT_ID") || cfgScope.data.default_project_id;
|
|
46867
|
+
if (!todoId && !projectId2) {
|
|
46868
|
+
process.stderr.write(`${RED}Usage: todoforai-cli show list [todo-id] [--project <id>] [--card <name>]${RESET}
|
|
46869
|
+
`);
|
|
46870
|
+
process.exit(2);
|
|
46871
|
+
}
|
|
46872
|
+
const { items } = await api.listShows({ todoId, projectId: projectId2, card: args.card });
|
|
46873
|
+
if (args.json) {
|
|
46874
|
+
console.log(JSON.stringify(items, null, 2));
|
|
46875
|
+
return;
|
|
46876
|
+
}
|
|
46877
|
+
if (items.length === 0) {
|
|
46878
|
+
process.stderr.write(`${DIM}No show blocks in ${todoId || `project ${projectId2}`}${RESET}
|
|
46879
|
+
`);
|
|
46880
|
+
return;
|
|
46881
|
+
}
|
|
46882
|
+
for (const it of items) {
|
|
46883
|
+
const kind = it.url ? `url ${it.url}` : it.mime || "";
|
|
46884
|
+
const card = it.cardRef ? ` card=${it.cardRef}` : "";
|
|
46885
|
+
console.log(`${it.ref} ${it.title || it.filename || ""} ${kind}${card}`);
|
|
46886
|
+
}
|
|
46887
|
+
return;
|
|
46888
|
+
}
|
|
46710
46889
|
if (positionals[0] === "show") {
|
|
46711
46890
|
const [, filePath, todoArg] = positionals;
|
|
46712
46891
|
const todoId = todoArg || getEnv("TODO_ID") || cfgScope.data.last_todo_id;
|
|
@@ -46732,9 +46911,9 @@ Cancelled by user (Ctrl+C)
|
|
|
46732
46911
|
process.exit(1);
|
|
46733
46912
|
}
|
|
46734
46913
|
blob = file;
|
|
46735
|
-
name =
|
|
46914
|
+
name = path3.basename(filePath);
|
|
46736
46915
|
}
|
|
46737
|
-
const res = await api.showFile(todoId, blob, name, { title: args.title, alias: args.alias, mime: args.mime });
|
|
46916
|
+
const res = await api.showFile(todoId, blob, name, { title: args.title, alias: args.alias, mime: args.mime, card: args.card });
|
|
46738
46917
|
if (args.json)
|
|
46739
46918
|
console.log(JSON.stringify(res, null, 2));
|
|
46740
46919
|
else
|
|
@@ -46759,13 +46938,13 @@ Cancelled by user (Ctrl+C)
|
|
|
46759
46938
|
if (positionals[0] === "recommend") {
|
|
46760
46939
|
const templateId = args.template || positionals[1];
|
|
46761
46940
|
if (!templateId) {
|
|
46762
|
-
process.stderr.write(`${RED}Usage: todoforai-cli recommend --template <id> [--note "why"] [--priority high|medium|low] [--title "..."] [--project <id>]${RESET}
|
|
46941
|
+
process.stderr.write(`${RED}Usage: todoforai-cli recommend --template <id> [--note "why"] [--priority high|medium|low] [--title "..."] [--group <slug> [--group-name "..."] [--group-description "..."]] [--project <id>]${RESET}
|
|
46763
46942
|
`);
|
|
46764
46943
|
process.stderr.write(`${DIM}Create a template first with: todoregistry-cli create --name ... --description ... --body @prompt.md${RESET}
|
|
46765
46944
|
`);
|
|
46766
46945
|
process.exit(2);
|
|
46767
46946
|
}
|
|
46768
|
-
let projectId2 = args.project || cfgScope.data.default_project_id;
|
|
46947
|
+
let projectId2 = args.project || getEnv("PROJECT_ID") || cfgScope.data.default_project_id;
|
|
46769
46948
|
if (!projectId2) {
|
|
46770
46949
|
const projects2 = await api.listProjects();
|
|
46771
46950
|
projectId2 = projects2.find((p) => p.project?.isDefault)?.project?.id || projects2[0]?.project?.id;
|
|
@@ -46787,7 +46966,10 @@ Cancelled by user (Ctrl+C)
|
|
|
46787
46966
|
...args.title ? { title: args.title } : {},
|
|
46788
46967
|
...args.note ? { note: args.note } : {},
|
|
46789
46968
|
...priority ? { priority } : {},
|
|
46790
|
-
...args["business-context"] ? { businessContextId: args["business-context"] } : {}
|
|
46969
|
+
...args["business-context"] ? { businessContextId: args["business-context"] } : {},
|
|
46970
|
+
...args.group ? { group: args.group } : {},
|
|
46971
|
+
...args["group-name"] ? { groupName: args["group-name"] } : {},
|
|
46972
|
+
...args["group-description"] ? { groupDescription: args["group-description"] } : {}
|
|
46791
46973
|
});
|
|
46792
46974
|
if (args.json)
|
|
46793
46975
|
console.log(JSON.stringify(rec, null, 2));
|
|
@@ -46797,7 +46979,7 @@ Cancelled by user (Ctrl+C)
|
|
|
46797
46979
|
return;
|
|
46798
46980
|
}
|
|
46799
46981
|
if (positionals[0] === "claim" && positionals[1] === "mint") {
|
|
46800
|
-
let seedProjectId = args.seed || args.project || cfgScope.data.default_project_id;
|
|
46982
|
+
let seedProjectId = args.seed || args.project || getEnv("PROJECT_ID") || cfgScope.data.default_project_id;
|
|
46801
46983
|
if (!seedProjectId) {
|
|
46802
46984
|
const projects2 = await api.listProjects();
|
|
46803
46985
|
seedProjectId = projects2.find((p) => p.project?.isDefault)?.project?.id || projects2[0]?.project?.id;
|
|
@@ -46839,7 +47021,7 @@ Cancelled by user (Ctrl+C)
|
|
|
46839
47021
|
return;
|
|
46840
47022
|
}
|
|
46841
47023
|
if (positionals[0] === "next") {
|
|
46842
|
-
let projectId2 = args.project || cfgScope.data.default_project_id;
|
|
47024
|
+
let projectId2 = args.project || getEnv("PROJECT_ID") || cfgScope.data.default_project_id;
|
|
46843
47025
|
if (!projectId2) {
|
|
46844
47026
|
const projects2 = await api.listProjects();
|
|
46845
47027
|
projectId2 = projects2.find((p) => p.project?.isDefault)?.project?.id || projects2[0]?.project?.id;
|
|
@@ -46875,7 +47057,13 @@ Cancelled by user (Ctrl+C)
|
|
|
46875
47057
|
return;
|
|
46876
47058
|
}
|
|
46877
47059
|
if (args["list-models"]) {
|
|
46878
|
-
|
|
47060
|
+
const ids = qualifiedModelIds(await api.listModels(), positionals[0]);
|
|
47061
|
+
if (args.json)
|
|
47062
|
+
console.log(JSON.stringify(ids, null, 2));
|
|
47063
|
+
else
|
|
47064
|
+
for (const id of ids)
|
|
47065
|
+
process.stderr.write(`${BRAND}${id}${RESET}
|
|
47066
|
+
`);
|
|
46879
47067
|
return;
|
|
46880
47068
|
}
|
|
46881
47069
|
if (positionals[0] === "agent") {
|
|
@@ -46883,7 +47071,7 @@ Cancelled by user (Ctrl+C)
|
|
|
46883
47071
|
return;
|
|
46884
47072
|
}
|
|
46885
47073
|
if (positionals[0] === "list" || positionals[0] === "ls") {
|
|
46886
|
-
let defaultProjectId = args.project || cfgScope.data.default_project_id;
|
|
47074
|
+
let defaultProjectId = args.project || getEnv("PROJECT_ID") || cfgScope.data.default_project_id;
|
|
46887
47075
|
if (!defaultProjectId) {
|
|
46888
47076
|
const projects2 = await api.listProjects();
|
|
46889
47077
|
defaultProjectId = projects2.find((p) => p.project?.isDefault)?.project?.id || projects2[0]?.project?.id;
|
|
@@ -46922,14 +47110,14 @@ Cancelled by user (Ctrl+C)
|
|
|
46922
47110
|
}
|
|
46923
47111
|
const mode = args.debug ? "debug" : args.detailed ? "detailed" : "default";
|
|
46924
47112
|
const format = args["format-anthropic"] ? "anthropic" : "compact";
|
|
46925
|
-
printFullChat(todo2, getFrontendUrl(apiUrl,
|
|
47113
|
+
printFullChat(todo2, getFrontendUrl(apiUrl, todoId), slice, mode, format);
|
|
46926
47114
|
return;
|
|
46927
47115
|
}
|
|
46928
47116
|
if (process.stderr.isTTY)
|
|
46929
47117
|
printLogo();
|
|
46930
47118
|
if (args.template) {
|
|
46931
47119
|
if (!args["no-bridge"] && !args["no-watch"])
|
|
46932
|
-
ensureBridgeRunning(apiUrl, apiKey);
|
|
47120
|
+
await ensureBridgeRunning(apiUrl, apiKey);
|
|
46933
47121
|
const templateId = args.template;
|
|
46934
47122
|
const spec = await api.getRegistrySpec(templateId);
|
|
46935
47123
|
process.stderr.write(`${DIM}Template:${RESET} ${BRAND}${spec.name}${RESET}
|
|
@@ -46938,7 +47126,7 @@ Cancelled by user (Ctrl+C)
|
|
|
46938
47126
|
process.stderr.write(`${DIM}${spec.description}${RESET}
|
|
46939
47127
|
`);
|
|
46940
47128
|
const projects2 = await api.listProjects();
|
|
46941
|
-
let projectId2 = args.project;
|
|
47129
|
+
let projectId2 = args.project || getEnv("PROJECT_ID");
|
|
46942
47130
|
if (!projectId2) {
|
|
46943
47131
|
const cached = cfgScope.data.default_project_id;
|
|
46944
47132
|
projectId2 = (cached && projects2.some((p) => getItemId(p) === cached) ? cached : null) || projects2.find((p) => p.project?.isDefault)?.project?.id || projects2[0]?.project?.id;
|
|
@@ -46948,10 +47136,13 @@ Cancelled by user (Ctrl+C)
|
|
|
46948
47136
|
`);
|
|
46949
47137
|
process.exit(1);
|
|
46950
47138
|
}
|
|
46951
|
-
const todo2 = await api.startFromSpec(projectId2, templateId
|
|
47139
|
+
const todo2 = await api.startFromSpec(projectId2, templateId, {
|
|
47140
|
+
...groupTag ? { groupTag } : {},
|
|
47141
|
+
...groupName ? { groupName } : {}
|
|
47142
|
+
});
|
|
46952
47143
|
const todoId = todo2.id;
|
|
46953
47144
|
cfgScope.setLastTodoId(todoId);
|
|
46954
|
-
const frontendUrl2 = getFrontendUrl(apiUrl,
|
|
47145
|
+
const frontendUrl2 = getFrontendUrl(apiUrl, todoId);
|
|
46955
47146
|
if (args.json) {
|
|
46956
47147
|
console.log(JSON.stringify({ ...todo2, frontend_url: frontendUrl2 }, null, 2));
|
|
46957
47148
|
} else if (args["no-watch"]) {
|
|
@@ -46996,7 +47187,7 @@ ${"\u2500".repeat(40)}
|
|
|
46996
47187
|
}
|
|
46997
47188
|
if (args.resume || args.continue) {
|
|
46998
47189
|
if (!args["no-bridge"])
|
|
46999
|
-
ensureBridgeRunning(apiUrl, apiKey);
|
|
47190
|
+
await ensureBridgeRunning(apiUrl, apiKey);
|
|
47000
47191
|
const todoId = args.resume || cfgScope.data.last_todo_id;
|
|
47001
47192
|
if (!todoId) {
|
|
47002
47193
|
process.stderr.write(`Error: No recent todo found
|
|
@@ -47013,7 +47204,7 @@ ${"\u2500".repeat(40)}
|
|
|
47013
47204
|
}
|
|
47014
47205
|
process.stderr.write(`
|
|
47015
47206
|
${"\u2500".repeat(40)}
|
|
47016
|
-
Resumed: ${CYAN}${getFrontendUrl(apiUrl,
|
|
47207
|
+
Resumed: ${CYAN}${getFrontendUrl(apiUrl, todoId)}${RESET}
|
|
47017
47208
|
`);
|
|
47018
47209
|
const ws2 = new FrontendWebSocket(apiUrl, apiKey);
|
|
47019
47210
|
await ws2.connect();
|
|
@@ -47037,8 +47228,7 @@ Resumed: ${CYAN}${getFrontendUrl(apiUrl, projectId2, todoId)}${RESET}
|
|
|
47037
47228
|
}
|
|
47038
47229
|
const ws = args["no-watch"] ? null : new FrontendWebSocket(apiUrl, apiKey);
|
|
47039
47230
|
const wsReady = ws ? ws.connect() : null;
|
|
47040
|
-
|
|
47041
|
-
ensureBridgeRunning(apiUrl, apiKey);
|
|
47231
|
+
const bridgeReady = !args["no-bridge"] && !args["no-watch"] ? ensureBridgeRunning(apiUrl, apiKey) : null;
|
|
47042
47232
|
let preMatchedAgent = null;
|
|
47043
47233
|
let agents = null;
|
|
47044
47234
|
if (args.agent) {
|
|
@@ -47096,7 +47286,8 @@ Resumed: ${CYAN}${getFrontendUrl(apiUrl, projectId2, todoId)}${RESET}
|
|
|
47096
47286
|
} else {
|
|
47097
47287
|
content = await readStdin();
|
|
47098
47288
|
}
|
|
47099
|
-
const
|
|
47289
|
+
const envProjectId = getEnv("PROJECT_ID");
|
|
47290
|
+
const hasProject = args.project || envProjectId || cfgScope.data.default_project_id;
|
|
47100
47291
|
const storedAgent = cfgScope.data.default_agent_settings;
|
|
47101
47292
|
const hasAgent = preMatchedAgent || storedAgent?.id && !args.agent;
|
|
47102
47293
|
let projects = null;
|
|
@@ -47107,8 +47298,8 @@ Resumed: ${CYAN}${getFrontendUrl(apiUrl, projectId2, todoId)}${RESET}
|
|
|
47107
47298
|
}
|
|
47108
47299
|
let projectId;
|
|
47109
47300
|
let projectName;
|
|
47110
|
-
if (args.project) {
|
|
47111
|
-
projectId = args.project;
|
|
47301
|
+
if (args.project || envProjectId) {
|
|
47302
|
+
projectId = args.project || envProjectId;
|
|
47112
47303
|
projectName = projectId;
|
|
47113
47304
|
if (projects) {
|
|
47114
47305
|
const match = projects.find((p) => getItemId(p) === projectId);
|
|
@@ -47133,6 +47324,8 @@ Resumed: ${CYAN}${getFrontendUrl(apiUrl, projectId2, todoId)}${RESET}
|
|
|
47133
47324
|
}
|
|
47134
47325
|
if (wsReady)
|
|
47135
47326
|
await wsReady;
|
|
47327
|
+
if (bridgeReady)
|
|
47328
|
+
await bridgeReady;
|
|
47136
47329
|
if (args.model)
|
|
47137
47330
|
agent = { ...agent, model: args.model };
|
|
47138
47331
|
if (args["raw-sysmsg"]) {
|
|
@@ -47146,9 +47339,9 @@ Resumed: ${CYAN}${getFrontendUrl(apiUrl, projectId2, todoId)}${RESET}
|
|
|
47146
47339
|
cfg.addToHistory(content);
|
|
47147
47340
|
let todo;
|
|
47148
47341
|
try {
|
|
47149
|
-
todo = await api.addMessage(projectId, content, agent);
|
|
47342
|
+
todo = await api.addMessage(projectId, content, agent, undefined, undefined, undefined, groupTag || undefined, groupName);
|
|
47150
47343
|
} catch (e) {
|
|
47151
|
-
if (!args.project && cfgScope.data.default_project_id === projectId && /failed: 403/.test(e.message || "")) {
|
|
47344
|
+
if (!args.project && !envProjectId && cfgScope.data.default_project_id === projectId && /failed: 403/.test(e.message || "")) {
|
|
47152
47345
|
cfgScope.clearDefaultProject();
|
|
47153
47346
|
process.stderr.write(`${RED}Not authorized for cached default project ${projectName} (${projectId}) \u2014 cleared it. Re-run to pick a project.${RESET}
|
|
47154
47347
|
`);
|
|
@@ -47158,7 +47351,7 @@ Resumed: ${CYAN}${getFrontendUrl(apiUrl, projectId2, todoId)}${RESET}
|
|
|
47158
47351
|
}
|
|
47159
47352
|
const actualTodoId = todo.id || crypto.randomUUID();
|
|
47160
47353
|
cfgScope.setLastTodoId(actualTodoId);
|
|
47161
|
-
const frontendUrl = getFrontendUrl(apiUrl,
|
|
47354
|
+
const frontendUrl = getFrontendUrl(apiUrl, actualTodoId);
|
|
47162
47355
|
if (args.json) {
|
|
47163
47356
|
console.log(JSON.stringify({ ...todo, frontend_url: frontendUrl }, null, 2));
|
|
47164
47357
|
} else if (!ws) {
|
|
@@ -47183,7 +47376,7 @@ ${"\u2500".repeat(40)}
|
|
|
47183
47376
|
await ws.close();
|
|
47184
47377
|
}
|
|
47185
47378
|
}
|
|
47186
|
-
main().then(() => process.exit(0)).catch((e) => {
|
|
47379
|
+
main().then(() => process.exit(process.exitCode ?? 0)).catch((e) => {
|
|
47187
47380
|
process.stderr.write(`Error: ${e.message}
|
|
47188
47381
|
`);
|
|
47189
47382
|
process.exit(1);
|
package/package.json
CHANGED
|
@@ -1,36 +1,30 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@todoforai/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.29",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"todoforai-cli": "bin/todoforai-cli.js",
|
|
7
|
+
"tfa-cli": "bin/todoforai-cli.js",
|
|
7
8
|
"todoai": "bin/todoforai-cli.js"
|
|
8
9
|
},
|
|
9
10
|
"files": [
|
|
10
11
|
"bin",
|
|
11
|
-
"dist/
|
|
12
|
+
"dist/todoforai-cli.js"
|
|
12
13
|
],
|
|
13
14
|
"scripts": {
|
|
14
|
-
"build": "bun build src/index.ts --target=bun --outfile dist/
|
|
15
|
+
"build": "bun build src/index.ts --target=bun --outfile dist/todoforai-cli.js --external ws",
|
|
15
16
|
"prepublishOnly": "bun run build",
|
|
16
17
|
"start": "bun run src/index.ts",
|
|
17
18
|
"dev": "bun run src/index.ts"
|
|
18
19
|
},
|
|
19
20
|
"dependencies": {
|
|
21
|
+
"@shared/api": "file:../packages/shared-api",
|
|
20
22
|
"@shared/credentials": "file:../packages/shared-credentials",
|
|
21
23
|
"@todoforai/update-notifier": "^0.1.0",
|
|
22
24
|
"cli-highlight": "^2.1.11",
|
|
23
25
|
"diff-match-patch": "^1.0.5",
|
|
24
26
|
"ws": "^8.18.0"
|
|
25
27
|
},
|
|
26
|
-
"peerDependencies": {
|
|
27
|
-
"@todoforai/edge": ">=0.12.0"
|
|
28
|
-
},
|
|
29
|
-
"peerDependenciesMeta": {
|
|
30
|
-
"@todoforai/edge": {
|
|
31
|
-
"optional": true
|
|
32
|
-
}
|
|
33
|
-
},
|
|
34
28
|
"devDependencies": {
|
|
35
29
|
"@types/ws": "^8.5.13",
|
|
36
30
|
"typescript": "^5.7.0"
|