@datename/fastcode-termux 0.0.0-stage → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (197) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +400 -2
  3. package/bin/fastcode.mjs +59 -0
  4. package/dist/agent/guards.d.ts +35 -0
  5. package/dist/agent/guards.js +74 -0
  6. package/dist/agent/limits.d.ts +24 -0
  7. package/dist/agent/limits.js +71 -0
  8. package/dist/agent/protocol.d.ts +4 -0
  9. package/dist/agent/protocol.js +126 -0
  10. package/dist/agent/tools.d.ts +1 -0
  11. package/dist/agent/tools.js +377 -0
  12. package/dist/app.d.ts +4 -0
  13. package/dist/app.js +476 -0
  14. package/dist/cli.d.ts +1 -0
  15. package/dist/cli.js +518 -0
  16. package/dist/commands/about.d.ts +8 -0
  17. package/dist/commands/about.js +16 -0
  18. package/dist/commands/agent.d.ts +11 -0
  19. package/dist/commands/agent.js +18 -0
  20. package/dist/commands/clear.d.ts +11 -0
  21. package/dist/commands/clear.js +9 -0
  22. package/dist/commands/config.d.ts +7 -0
  23. package/dist/commands/config.js +24 -0
  24. package/dist/commands/cost.d.ts +9 -0
  25. package/dist/commands/cost.js +13 -0
  26. package/dist/commands/cwd.d.ts +7 -0
  27. package/dist/commands/cwd.js +7 -0
  28. package/dist/commands/exit.d.ts +9 -0
  29. package/dist/commands/exit.js +8 -0
  30. package/dist/commands/help.d.ts +6 -0
  31. package/dist/commands/help.js +18 -0
  32. package/dist/commands/index.d.ts +9 -0
  33. package/dist/commands/index.js +58 -0
  34. package/dist/commands/login.d.ts +16 -0
  35. package/dist/commands/login.js +61 -0
  36. package/dist/commands/logout.d.ts +9 -0
  37. package/dist/commands/logout.js +53 -0
  38. package/dist/commands/model.d.ts +11 -0
  39. package/dist/commands/model.js +30 -0
  40. package/dist/commands/plugin.d.ts +11 -0
  41. package/dist/commands/plugin.js +74 -0
  42. package/dist/commands/provider.d.ts +11 -0
  43. package/dist/commands/provider.js +28 -0
  44. package/dist/commands/reset.d.ts +9 -0
  45. package/dist/commands/reset.js +13 -0
  46. package/dist/commands/skills.d.ts +11 -0
  47. package/dist/commands/skills.js +106 -0
  48. package/dist/commands/system.d.ts +11 -0
  49. package/dist/commands/system.js +20 -0
  50. package/dist/commands/theme.d.ts +11 -0
  51. package/dist/commands/theme.js +22 -0
  52. package/dist/commands/tools.d.ts +7 -0
  53. package/dist/commands/tools.js +29 -0
  54. package/dist/commands/whoami.d.ts +9 -0
  55. package/dist/commands/whoami.js +51 -0
  56. package/dist/config/paths.d.ts +4 -0
  57. package/dist/config/paths.js +59 -0
  58. package/dist/config/store.d.ts +69 -0
  59. package/dist/config/store.js +208 -0
  60. package/dist/config/themes.d.ts +73 -0
  61. package/dist/config/themes.js +77 -0
  62. package/dist/config/trust.d.ts +9 -0
  63. package/dist/config/trust.js +67 -0
  64. package/dist/index.d.ts +1 -0
  65. package/dist/index.js +31 -0
  66. package/dist/lib/diagnostics.d.ts +41 -0
  67. package/dist/lib/diagnostics.js +139 -0
  68. package/dist/lib/http.d.ts +13 -0
  69. package/dist/lib/http.js +70 -0
  70. package/dist/lib/markdown.d.ts +1 -0
  71. package/dist/lib/markdown.js +32 -0
  72. package/dist/lib/pkg.d.ts +5 -0
  73. package/dist/lib/pkg.js +27 -0
  74. package/dist/lib/platform.d.ts +7 -0
  75. package/dist/lib/platform.js +57 -0
  76. package/dist/lib/startup.d.ts +6 -0
  77. package/dist/lib/startup.js +25 -0
  78. package/dist/lib/uiMode.d.ts +2 -0
  79. package/dist/lib/uiMode.js +7 -0
  80. package/dist/plugins/manager.d.ts +23 -0
  81. package/dist/plugins/manager.js +101 -0
  82. package/dist/providers/anthropic.d.ts +21 -0
  83. package/dist/providers/anthropic.js +107 -0
  84. package/dist/providers/custom.d.ts +24 -0
  85. package/dist/providers/custom.js +25 -0
  86. package/dist/providers/deepseek.d.ts +24 -0
  87. package/dist/providers/deepseek.js +15 -0
  88. package/dist/providers/google.d.ts +24 -0
  89. package/dist/providers/google.js +19 -0
  90. package/dist/providers/groq.d.ts +24 -0
  91. package/dist/providers/groq.js +20 -0
  92. package/dist/providers/index.d.ts +201 -0
  93. package/dist/providers/index.js +37 -0
  94. package/dist/providers/mistral.d.ts +24 -0
  95. package/dist/providers/mistral.js +19 -0
  96. package/dist/providers/moonshot.d.ts +24 -0
  97. package/dist/providers/moonshot.js +22 -0
  98. package/dist/providers/ollama.d.ts +24 -0
  99. package/dist/providers/ollama.js +22 -0
  100. package/dist/providers/openai.d.ts +24 -0
  101. package/dist/providers/openai.js +25 -0
  102. package/dist/providers/openaiCompatible.d.ts +17 -0
  103. package/dist/providers/openaiCompatible.js +110 -0
  104. package/dist/providers/openrouter.d.ts +24 -0
  105. package/dist/providers/openrouter.js +26 -0
  106. package/dist/providers/qwen.d.ts +24 -0
  107. package/dist/providers/qwen.js +23 -0
  108. package/dist/providers/xai.d.ts +24 -0
  109. package/dist/providers/xai.js +18 -0
  110. package/dist/providers/zhipu.d.ts +24 -0
  111. package/dist/providers/zhipu.js +23 -0
  112. package/dist/session/log.d.ts +29 -0
  113. package/dist/session/log.js +135 -0
  114. package/dist/skills/manager.d.ts +18 -0
  115. package/dist/skills/manager.js +201 -0
  116. package/dist/skills/parser.d.ts +18 -0
  117. package/dist/skills/parser.js +65 -0
  118. package/dist/types.d.ts +146 -0
  119. package/dist/types.js +1 -0
  120. package/dist/ui/Banner.d.ts +5 -0
  121. package/dist/ui/Banner.js +65 -0
  122. package/dist/ui/Message.d.ts +7 -0
  123. package/dist/ui/Message.js +13 -0
  124. package/dist/ui/Prompt.d.ts +10 -0
  125. package/dist/ui/Prompt.js +38 -0
  126. package/dist/ui/Spinner.d.ts +5 -0
  127. package/dist/ui/Spinner.js +33 -0
  128. package/dist/ui/StatusBar.d.ts +10 -0
  129. package/dist/ui/StatusBar.js +19 -0
  130. package/dist/ui/TrustGate.d.ts +5 -0
  131. package/dist/ui/TrustGate.js +26 -0
  132. package/package.json +96 -4
  133. package/src/agent/guards.ts +74 -0
  134. package/src/agent/limits.ts +71 -0
  135. package/src/agent/protocol.ts +126 -0
  136. package/src/agent/tools.ts +377 -0
  137. package/src/app.ts +476 -0
  138. package/src/cli.ts +308 -0
  139. package/src/commands/about.ts +16 -0
  140. package/src/commands/agent.ts +18 -0
  141. package/src/commands/clear.ts +9 -0
  142. package/src/commands/config.ts +24 -0
  143. package/src/commands/cost.ts +13 -0
  144. package/src/commands/cwd.ts +7 -0
  145. package/src/commands/exit.ts +8 -0
  146. package/src/commands/help.ts +18 -0
  147. package/src/commands/index.ts +58 -0
  148. package/src/commands/login.ts +61 -0
  149. package/src/commands/logout.ts +53 -0
  150. package/src/commands/model.ts +30 -0
  151. package/src/commands/plugin.ts +74 -0
  152. package/src/commands/provider.ts +28 -0
  153. package/src/commands/reset.ts +13 -0
  154. package/src/commands/skills.ts +106 -0
  155. package/src/commands/system.ts +20 -0
  156. package/src/commands/theme.ts +22 -0
  157. package/src/commands/tools.ts +29 -0
  158. package/src/commands/whoami.ts +51 -0
  159. package/src/config/paths.ts +59 -0
  160. package/src/config/store.ts +240 -0
  161. package/src/config/themes.ts +77 -0
  162. package/src/config/trust.ts +67 -0
  163. package/src/index.ts +31 -0
  164. package/src/lib/diagnostics.ts +139 -0
  165. package/src/lib/http.ts +70 -0
  166. package/src/lib/markdown.ts +32 -0
  167. package/src/lib/pkg.ts +35 -0
  168. package/src/lib/platform.ts +57 -0
  169. package/src/lib/startup.ts +25 -0
  170. package/src/lib/uiMode.ts +7 -0
  171. package/src/plugins/manager.ts +101 -0
  172. package/src/providers/anthropic.ts +107 -0
  173. package/src/providers/custom.ts +25 -0
  174. package/src/providers/deepseek.ts +15 -0
  175. package/src/providers/google.ts +19 -0
  176. package/src/providers/groq.ts +20 -0
  177. package/src/providers/index.ts +37 -0
  178. package/src/providers/mistral.ts +19 -0
  179. package/src/providers/moonshot.ts +22 -0
  180. package/src/providers/ollama.ts +22 -0
  181. package/src/providers/openai.ts +25 -0
  182. package/src/providers/openaiCompatible.ts +110 -0
  183. package/src/providers/openrouter.ts +26 -0
  184. package/src/providers/qwen.ts +23 -0
  185. package/src/providers/xai.ts +18 -0
  186. package/src/providers/zhipu.ts +23 -0
  187. package/src/session/log.ts +135 -0
  188. package/src/skills/manager.ts +201 -0
  189. package/src/skills/parser.ts +65 -0
  190. package/src/types.ts +143 -0
  191. package/src/ui/Banner.ts +68 -0
  192. package/src/ui/Message.ts +13 -0
  193. package/src/ui/Prompt.ts +38 -0
  194. package/src/ui/Spinner.ts +33 -0
  195. package/src/ui/StatusBar.ts +19 -0
  196. package/src/ui/TrustGate.ts +26 -0
  197. package/tsconfig.json +22 -0
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Built-in color themes. The `banner` triple is fed to gradient-string
3
+ * (start → mid → end) so each theme produces a recognizable big-text logo.
4
+ */
5
+ export declare const THEMES: {
6
+ fastcode: {
7
+ id: string;
8
+ label: string;
9
+ banner: string[];
10
+ user: string;
11
+ assistant: string;
12
+ system: string;
13
+ border: string;
14
+ accent: string;
15
+ muted: string;
16
+ error: string;
17
+ success: string;
18
+ };
19
+ neon: {
20
+ id: string;
21
+ label: string;
22
+ banner: string[];
23
+ user: string;
24
+ assistant: string;
25
+ system: string;
26
+ border: string;
27
+ accent: string;
28
+ muted: string;
29
+ error: string;
30
+ success: string;
31
+ };
32
+ sunset: {
33
+ id: string;
34
+ label: string;
35
+ banner: string[];
36
+ user: string;
37
+ assistant: string;
38
+ system: string;
39
+ border: string;
40
+ accent: string;
41
+ muted: string;
42
+ error: string;
43
+ success: string;
44
+ };
45
+ matrix: {
46
+ id: string;
47
+ label: string;
48
+ banner: string[];
49
+ user: string;
50
+ assistant: string;
51
+ system: string;
52
+ border: string;
53
+ accent: string;
54
+ muted: string;
55
+ error: string;
56
+ success: string;
57
+ };
58
+ mono: {
59
+ id: string;
60
+ label: string;
61
+ banner: string[];
62
+ user: string;
63
+ assistant: string;
64
+ system: string;
65
+ border: string;
66
+ accent: string;
67
+ muted: string;
68
+ error: string;
69
+ success: string;
70
+ };
71
+ };
72
+ export declare const DEFAULT_THEME = "fastcode";
73
+ export declare function getTheme(id: any): any;
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Built-in color themes. The `banner` triple is fed to gradient-string
3
+ * (start → mid → end) so each theme produces a recognizable big-text logo.
4
+ */
5
+ export const THEMES = {
6
+ fastcode: {
7
+ id: "fastcode",
8
+ label: "FastCode (default)",
9
+ banner: ["#ff7a00", "#ff2d95", "#7c4dff"],
10
+ user: "cyan",
11
+ assistant: "white",
12
+ system: "gray",
13
+ border: "magenta",
14
+ accent: "magenta",
15
+ muted: "gray",
16
+ error: "red",
17
+ success: "green",
18
+ },
19
+ neon: {
20
+ id: "neon",
21
+ label: "Neon",
22
+ banner: ["#00ffd0", "#00b3ff", "#7c4dff"],
23
+ user: "cyanBright",
24
+ assistant: "whiteBright",
25
+ system: "gray",
26
+ border: "cyan",
27
+ accent: "cyan",
28
+ muted: "gray",
29
+ error: "red",
30
+ success: "greenBright",
31
+ },
32
+ sunset: {
33
+ id: "sunset",
34
+ label: "Sunset",
35
+ banner: ["#ff5f6d", "#ffc371", "#ff9966"],
36
+ user: "yellow",
37
+ assistant: "whiteBright",
38
+ system: "gray",
39
+ border: "yellow",
40
+ accent: "yellow",
41
+ muted: "gray",
42
+ error: "redBright",
43
+ success: "green",
44
+ },
45
+ matrix: {
46
+ id: "matrix",
47
+ label: "Matrix",
48
+ banner: ["#00ff41", "#008f11", "#003b00"],
49
+ user: "greenBright",
50
+ assistant: "green",
51
+ system: "gray",
52
+ border: "green",
53
+ accent: "green",
54
+ muted: "gray",
55
+ error: "redBright",
56
+ success: "greenBright",
57
+ },
58
+ mono: {
59
+ id: "mono",
60
+ label: "Monochrome",
61
+ banner: ["#ffffff", "#cccccc", "#888888"],
62
+ user: "white",
63
+ assistant: "whiteBright",
64
+ system: "gray",
65
+ border: "white",
66
+ accent: "white",
67
+ muted: "gray",
68
+ error: "red",
69
+ success: "white",
70
+ },
71
+ };
72
+ export const DEFAULT_THEME = "fastcode";
73
+ export function getTheme(id) {
74
+ if (id && THEMES[id])
75
+ return THEMES[id];
76
+ return THEMES[DEFAULT_THEME];
77
+ }
@@ -0,0 +1,9 @@
1
+ export declare function normalizeProjectPath(dir: any): string;
2
+ export declare function loadTrust(): {
3
+ trusted: any;
4
+ };
5
+ export declare function trustAllEnabled(): boolean;
6
+ export declare function isTrusted(dir: any): any;
7
+ export declare function trustProject(dir: any): string;
8
+ export declare function untrustProject(dir: any): boolean;
9
+ export declare function listTrusted(): any;
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Project trust store.
3
+ *
4
+ * Agent tools can read, write, delete and execute inside the workspace, so a
5
+ * folder is only enabled after the user confirms it once. The list lives in
6
+ * ~/.fastcode/trusted.json and is compared by real path.
7
+ *
8
+ * FASTCODE_TRUST_ALL=1 marks every directory trusted (scripts, CI).
9
+ */
10
+ import { existsSync, readFileSync, realpathSync, writeFileSync } from "node:fs";
11
+ import { resolve } from "node:path";
12
+ import { ensureDir, getPaths } from "./paths.js";
13
+ const TRUTHY = ["1", "true", "yes", "on"];
14
+ export function normalizeProjectPath(dir) {
15
+ const absolute = resolve(dir);
16
+ try {
17
+ return realpathSync(absolute);
18
+ }
19
+ catch {
20
+ return absolute;
21
+ }
22
+ }
23
+ export function loadTrust() {
24
+ const { trust } = getPaths();
25
+ if (!existsSync(trust))
26
+ return { trusted: [] };
27
+ try {
28
+ const raw = JSON.parse(readFileSync(trust, "utf8"));
29
+ return { trusted: Array.isArray(raw.trusted) ? raw.trusted.filter((entry) => entry && typeof entry.path === "string") : [] };
30
+ }
31
+ catch {
32
+ return { trusted: [] };
33
+ }
34
+ }
35
+ function saveTrust(state) {
36
+ const paths = getPaths();
37
+ ensureDir(paths.root);
38
+ writeFileSync(paths.trust, JSON.stringify(state, null, 2), "utf8");
39
+ }
40
+ export function trustAllEnabled() {
41
+ return TRUTHY.includes((process.env["FASTCODE_TRUST_ALL"] ?? "").trim().toLowerCase());
42
+ }
43
+ export function isTrusted(dir) {
44
+ if (trustAllEnabled())
45
+ return true;
46
+ const target = normalizeProjectPath(dir);
47
+ return loadTrust().trusted.some((entry) => entry.path === target);
48
+ }
49
+ export function trustProject(dir) {
50
+ const target = normalizeProjectPath(dir);
51
+ const state = loadTrust();
52
+ if (!state.trusted.some((entry) => entry.path === target)) {
53
+ state.trusted.push({ path: target, addedAt: new Date().toISOString() });
54
+ saveTrust(state);
55
+ }
56
+ return target;
57
+ }
58
+ export function untrustProject(dir) {
59
+ const target = normalizeProjectPath(dir);
60
+ const state = loadTrust();
61
+ const next = state.trusted.filter((entry) => entry.path !== target);
62
+ saveTrust({ trusted: next });
63
+ return state.trusted.length !== next.length;
64
+ }
65
+ export function listTrusted() {
66
+ return loadTrust().trusted;
67
+ }
@@ -0,0 +1 @@
1
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,31 @@
1
+ /**
2
+ * FastCode entrypoint.
3
+ *
4
+ * Parses CLI args, then either runs a one-shot subcommand or boots the
5
+ * Ink-based TUI. We keep this file tiny so the slow imports (Ink, React)
6
+ * are not loaded for `fastcode skills list`.
7
+ */
8
+ import { parseCli } from "./cli.js";
9
+ async function main() {
10
+ const cli = await parseCli();
11
+ if (cli.done)
12
+ return;
13
+ if (!process.stdout.isTTY) {
14
+ console.error("FastCode TUI requires a TTY. Pipe-mode prompts aren't supported yet — try `fastcode \"<prompt>\"` from a real terminal.");
15
+ process.exit(1);
16
+ }
17
+ // Lazy-load Ink so subcommands don't pay the React boot cost.
18
+ const [{ render }, React, { App }] = await Promise.all([
19
+ import("ink"),
20
+ import("react"),
21
+ import("./app.js"),
22
+ ]);
23
+ // React.createElement avoids needing a separate .tsx loader for this file.
24
+ // exitOnCtrlC: false lets the app own Ctrl+C so the documented
25
+ // "press Ctrl+C twice to quit" behavior actually works.
26
+ render(React.createElement(App, { initialPrompt: cli.prompt, resume: cli.resume }), { exitOnCtrlC: false });
27
+ }
28
+ main().catch((err) => {
29
+ console.error(err);
30
+ process.exit(1);
31
+ });
@@ -0,0 +1,41 @@
1
+ export declare function collectDiagnostics(): {
2
+ version: string;
3
+ node: string;
4
+ nodeRequired: string;
5
+ nodeOk: boolean;
6
+ platform: string;
7
+ platformLabel: string;
8
+ termux: boolean;
9
+ termuxVersion: string;
10
+ stableUi: boolean;
11
+ tty: boolean;
12
+ configRoot: any;
13
+ configFile: any;
14
+ authFile: any;
15
+ providerId: any;
16
+ model: any;
17
+ keyPresent: boolean;
18
+ keyStatus: "ok" | "unbound" | "missing";
19
+ keyEnv: any;
20
+ skillsInstalled: number;
21
+ pluginsInstalled: number;
22
+ enabledSkills: any;
23
+ enabledPlugins: any;
24
+ sessionLogEnabled: boolean;
25
+ sessionCacheMb: number;
26
+ sessionsDir: any;
27
+ sessionsCount: number;
28
+ sessionsBytes: number;
29
+ trustFile: any;
30
+ trusted: any;
31
+ shellDenyPatterns: number;
32
+ allowDangerous: boolean;
33
+ toolOutputLimit: number;
34
+ providers: {
35
+ id: string;
36
+ label: string;
37
+ env: any[] | string[];
38
+ keyPresent: boolean;
39
+ }[];
40
+ };
41
+ export declare function formatDiagnostics(info: any): string;
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Environment diagnostics for mobile / Termux troubleshooting.
3
+ *
4
+ * `fastcode doctor` prints the values below so a broken mobile install can be
5
+ * diagnosed without opening the TUI: Node version, Termux detection, whether
6
+ * stdout is a real terminal, the config root, and which provider keys exist.
7
+ */
8
+ import { detectPlatform, platformLabel } from "./platform.js";
9
+ import { readPackageMeta } from "./pkg.js";
10
+ import { shouldUseStableTerminalUi } from "./uiMode.js";
11
+ import { getPaths } from "../config/paths.js";
12
+ import { loadConfig, resolveApiKey, resolveProviderKey } from "../config/store.js";
13
+ import { PROVIDERS } from "../providers/index.js";
14
+ import { listInstalledSkills } from "../skills/manager.js";
15
+ import { listInstalledPlugins } from "../plugins/manager.js";
16
+ import { loggingEnabled, listSessions, sessionCacheLimitBytes } from "../session/log.js";
17
+ import { isTrusted } from "../config/trust.js";
18
+ import { DEFAULT_DENY_PATTERNS } from "../agent/guards.js";
19
+ import { toolOutputLimit } from "../agent/limits.js";
20
+ const NODE_REQUIRED = "18.17";
21
+ function readVersion() {
22
+ return readPackageMeta().version;
23
+ }
24
+ function nodeIsSupported() {
25
+ const [major = 0, minor = 0] = process.versions.node.split(".").map(Number);
26
+ const [reqMajor = 0, reqMinor = 0] = NODE_REQUIRED.split(".").map(Number);
27
+ return major > reqMajor || (major === reqMajor && minor >= reqMinor);
28
+ }
29
+ export function collectDiagnostics() {
30
+ const config = loadConfig();
31
+ let paths;
32
+ try {
33
+ paths = getPaths();
34
+ }
35
+ catch {
36
+ paths = undefined;
37
+ }
38
+ const provider = PROVIDERS.find((p) => p.id === config.providerId);
39
+ const keyLookup = provider && provider.authEnv.length > 0
40
+ ? resolveApiKey(provider.id, config.model, provider.authEnv)
41
+ : { status: "ok" };
42
+ const keyPresent = Boolean(provider) && (provider.authEnv.length === 0 || keyLookup.status === "ok");
43
+ let skillsInstalled = 0;
44
+ let pluginsInstalled = 0;
45
+ try {
46
+ skillsInstalled = listInstalledSkills().length;
47
+ }
48
+ catch {
49
+ // A broken skills dir must not break the whole report.
50
+ }
51
+ try {
52
+ pluginsInstalled = listInstalledPlugins().length;
53
+ }
54
+ catch {
55
+ // Same for plugins.
56
+ }
57
+ const platform = detectPlatform();
58
+ return {
59
+ version: readVersion(),
60
+ node: process.version,
61
+ nodeRequired: NODE_REQUIRED,
62
+ nodeOk: nodeIsSupported(),
63
+ platform,
64
+ platformLabel: platformLabel(),
65
+ termux: platform === "termux",
66
+ termuxVersion: process.env["TERMUX_VERSION"] ?? null,
67
+ stableUi: shouldUseStableTerminalUi(),
68
+ tty: Boolean(process.stdout.isTTY),
69
+ configRoot: paths?.root,
70
+ configFile: paths?.config,
71
+ authFile: paths?.auth,
72
+ providerId: config.providerId,
73
+ model: config.model,
74
+ keyPresent,
75
+ keyStatus: provider ? keyLookup.status : "missing",
76
+ keyEnv: provider?.authEnv?.[0],
77
+ skillsInstalled,
78
+ pluginsInstalled,
79
+ enabledSkills: (config.enabledSkills ?? []).length,
80
+ enabledPlugins: (config.enabledPlugins ?? []).length,
81
+ sessionLogEnabled: loggingEnabled(),
82
+ sessionCacheMb: Math.round(sessionCacheLimitBytes() / (1024 * 1024)),
83
+ sessionsDir: paths?.sessions,
84
+ sessionsCount: listSessions().length,
85
+ sessionsBytes: listSessions().reduce((sum, entry) => sum + entry.bytes, 0),
86
+ trustFile: paths?.trust,
87
+ trusted: isTrusted(process.cwd()),
88
+ shellDenyPatterns: DEFAULT_DENY_PATTERNS.length,
89
+ allowDangerous: ["1", "true", "yes", "on"].includes((process.env["FASTCODE_ALLOW_DANGEROUS"] ?? "").trim().toLowerCase()),
90
+ toolOutputLimit: toolOutputLimit(),
91
+ providers: PROVIDERS.map((p) => ({
92
+ id: p.id,
93
+ label: p.label,
94
+ env: p.authEnv,
95
+ keyPresent: p.authEnv.length === 0 || Boolean(resolveProviderKey(p.id, p.authEnv).key),
96
+ })),
97
+ };
98
+ }
99
+ export function formatDiagnostics(info) {
100
+ const lines = [];
101
+ lines.push(`FastCode ${info.version} — environment check`);
102
+ lines.push("");
103
+ lines.push(` node ${info.node} (needs >= ${info.nodeRequired}) [${info.nodeOk ? "ok" : "TOO OLD"}]`);
104
+ lines.push(` platform ${info.platformLabel}${info.termux ? " (Termux)" : ""}`);
105
+ lines.push(` terminal ${info.tty ? "interactive TTY" : "NOT a TTY — the TUI needs a real terminal"}`);
106
+ lines.push(` stable UI ${info.stableUi ? "on (anti-flicker)" : "off"}`);
107
+ lines.push(" config root " + (info.configRoot ?? "(unavailable)"));
108
+ lines.push(" config file " + (info.configFile ?? "(unavailable)"));
109
+ lines.push(" keys file " + (info.authFile ?? "(unavailable)") + " (separate, chmod 0600)");
110
+ lines.push(" trusted cwd " + (info.trusted ? "yes" : "NO - agent tools ask first or stay off") + " (" + (info.trustFile ?? "?") + ")");
111
+ lines.push(" session log " + (info.sessionLogEnabled ? "on" : "off") + " cache cap " + info.sessionCacheMb + "MB, " + info.sessionsCount + " file(s), " + (info.sessionsBytes / 1024 / 1024).toFixed(2) + "MB used");
112
+ lines.push(" session dir " + (info.sessionsDir ?? "(unavailable)"));
113
+ lines.push(" shell guard " + info.shellDenyPatterns + " deny patterns" + (info.allowDangerous ? " [FASTCODE_ALLOW_DANGEROUS is ON]" : ""));
114
+ lines.push(" tool output " + info.toolOutputLimit + " chars max (streaming cap)");
115
+ lines.push(` provider ${info.providerId} · model ${info.model}`);
116
+ let apiKeyStatus;
117
+ if (info.keyPresent)
118
+ apiKeyStatus = "present";
119
+ else if (info.keyStatus === "unbound")
120
+ apiKeyStatus = `UNBOUND — a stored ${info.providerId} key isn't tied to a model; re-bind with /login ${info.providerId} ${info.model} <key>`;
121
+ else
122
+ apiKeyStatus = `MISSING — set ${info.keyEnv ?? "the provider env var"} or /login ${info.providerId} ${info.model} <key>`;
123
+ lines.push(` api key ${apiKeyStatus}`);
124
+ lines.push(` skills ${info.skillsInstalled} installed, ${info.enabledSkills} enabled`);
125
+ lines.push(` plugins ${info.pluginsInstalled} installed, ${info.enabledPlugins} enabled`);
126
+ lines.push("");
127
+ lines.push(" provider keys:");
128
+ for (const p of info.providers) {
129
+ lines.push(` ${p.keyPresent ? "●" : "○"} ${p.id.padEnd(12)} ${p.env.join(" / ") || "(no key needed)"}`);
130
+ }
131
+ if (info.termux) {
132
+ lines.push("");
133
+ lines.push(" Termux tips:");
134
+ lines.push(" - pkg install nodejs git");
135
+ lines.push(" - keep the session alive with: termux-wake-lock");
136
+ lines.push(" - add an extra-keys row for Esc/Ctrl in ~/.termux/termux.properties");
137
+ }
138
+ return lines.join("\n");
139
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Tiny fetch helpers. We standardize on globalThis.fetch (Node 18+).
3
+ *
4
+ * The `streamSSE` helper parses Server-Sent Events for any provider that uses
5
+ * the OpenAI-style `data: {...}\n\n` chunk format. Non-SSE providers (e.g.
6
+ * Anthropic's older streaming) handle their own parsing.
7
+ */
8
+ export declare function fetchJson(url: string, init?: any): Promise<any>;
9
+ /** Async iterator over SSE events from a streaming HTTP response. */
10
+ export declare function streamSSE(res: any): AsyncGenerator<{
11
+ event: any;
12
+ data: string;
13
+ }, void, unknown>;
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Tiny fetch helpers. We standardize on globalThis.fetch (Node 18+).
3
+ *
4
+ * The `streamSSE` helper parses Server-Sent Events for any provider that uses
5
+ * the OpenAI-style `data: {...}\n\n` chunk format. Non-SSE providers (e.g.
6
+ * Anthropic's older streaming) handle their own parsing.
7
+ */
8
+ export async function fetchJson(url, init = {}) {
9
+ const res = await fetch(url, {
10
+ ...init,
11
+ headers: { "content-type": "application/json", accept: "application/json", ...(init.headers ?? {}) },
12
+ });
13
+ if (!res.ok) {
14
+ const body = await res.text().catch(() => "");
15
+ throw new Error(`${res.status} ${res.statusText}: ${body.slice(0, 500)}`);
16
+ }
17
+ return (await res.json());
18
+ }
19
+ /** Async iterator over SSE events from a streaming HTTP response. */
20
+ export async function* streamSSE(res) {
21
+ if (!res.ok) {
22
+ const body = await res.text().catch(() => "");
23
+ throw new Error(`${res.status} ${res.statusText}: ${body.slice(0, 500)}`);
24
+ }
25
+ if (!res.body)
26
+ throw new Error("Empty response body");
27
+ const reader = res.body.getReader();
28
+ const decoder = new TextDecoder("utf-8");
29
+ let buffer = "";
30
+ while (true) {
31
+ const { value, done } = await reader.read();
32
+ if (done)
33
+ break;
34
+ // Normalize CR/LF so both "\n\n" and "\r\n\r\n" frame separators
35
+ // are detected, including a CR/LF split across two chunks.
36
+ buffer = (buffer + decoder.decode(value, { stream: true })).replace(/\r\n?/g, "\n");
37
+ let sep = buffer.indexOf("\n\n");
38
+ while (sep !== -1) {
39
+ const chunk = buffer.slice(0, sep);
40
+ buffer = buffer.slice(sep + 2);
41
+ const ev = parseSseChunk(chunk);
42
+ if (ev)
43
+ yield ev;
44
+ sep = buffer.indexOf("\n\n");
45
+ }
46
+ }
47
+ // Flush trailing event without final \n\n (some servers do this).
48
+ const trailing = buffer.trim();
49
+ if (trailing) {
50
+ const ev = parseSseChunk(trailing);
51
+ if (ev)
52
+ yield ev;
53
+ }
54
+ }
55
+ function parseSseChunk(chunk) {
56
+ let event;
57
+ const dataLines = [];
58
+ for (const lineRaw of chunk.split("\n")) {
59
+ const line = lineRaw.replace(/\r$/, "");
60
+ if (!line || line.startsWith(":"))
61
+ continue;
62
+ if (line.startsWith("event:"))
63
+ event = line.slice(6).trim();
64
+ else if (line.startsWith("data:"))
65
+ dataLines.push(line.slice(5).trimStart());
66
+ }
67
+ if (dataLines.length === 0)
68
+ return undefined;
69
+ return { event, data: dataLines.join("\n") };
70
+ }
@@ -0,0 +1 @@
1
+ export declare function renderMarkdown(md: any): any;
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Markdown → ANSI rendering for terminal output.
3
+ *
4
+ * We use marked + marked-terminal because it gives us proper code-fence
5
+ * syntax highlighting (via cli-highlight) without us hand-rolling a
6
+ * markdown parser. The result is a fully-formatted ANSI string we can
7
+ * print directly inside an Ink <Text>.
8
+ */
9
+ import { marked } from "marked";
10
+ import { markedTerminal } from "marked-terminal";
11
+ let configured = false;
12
+ function configure() {
13
+ if (configured)
14
+ return;
15
+ marked.use(markedTerminal({
16
+ reflowText: false,
17
+ tab: 2,
18
+ }));
19
+ configured = true;
20
+ }
21
+ export function renderMarkdown(md) {
22
+ configure();
23
+ try {
24
+ const out = marked.parse(md);
25
+ // marked-terminal sometimes appends a stray trailing newline; trim only
26
+ // trailing whitespace so we don't lose meaningful blank lines.
27
+ return out.replace(/\s+$/, "");
28
+ }
29
+ catch {
30
+ return md;
31
+ }
32
+ }
@@ -0,0 +1,5 @@
1
+ export interface PackageMeta {
2
+ name: string;
3
+ version: string;
4
+ }
5
+ export declare function readPackageMeta(): PackageMeta;
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Read this package's package.json (name + version) from any layout.
3
+ *
4
+ * The same source runs three ways, so `import.meta.url` points at different
5
+ * depths; try every plausible relative path and fall back gracefully.
6
+ *
7
+ * - tsx / dev: src/lib/pkg.ts -> ../../package.json
8
+ * - tsc output: dist/lib/pkg.js -> ../../package.json
9
+ * - esbuild bundle: dist/cli.js -> ../package.json
10
+ */
11
+ import { readFileSync } from "node:fs";
12
+ const FALLBACK = { name: "@datename/fastcode-termux", version: "0.0.0" };
13
+ const CANDIDATES = ["../package.json", "../../package.json", "../../../package.json"];
14
+ export function readPackageMeta() {
15
+ for (const rel of CANDIDATES) {
16
+ try {
17
+ const meta = JSON.parse(readFileSync(new URL(rel, import.meta.url), "utf8"));
18
+ if (meta && typeof meta.version === "string") {
19
+ return { name: meta.name ?? FALLBACK.name, version: meta.version };
20
+ }
21
+ }
22
+ catch {
23
+ /* try the next candidate */
24
+ }
25
+ }
26
+ return FALLBACK;
27
+ }
@@ -0,0 +1,7 @@
1
+ export declare function detectPlatform(): "darwin" | "linux" | "win32" | "termux" | "other";
2
+ export declare const isTermux: () => boolean;
3
+ export declare const isWindows: () => boolean;
4
+ export declare const isMac: () => boolean;
5
+ /** Human-readable label for the status bar. */
6
+ export declare function platformLabel(): NodeJS.Platform | "Termux" | "Linux" | "macOS" | "Windows";
7
+ export declare function safeHome(): string;
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Cross-platform detection and small helpers.
3
+ *
4
+ * FastCode targets Termux, Linux, macOS, and Windows. Most of our
5
+ * environment-aware behavior keys off these flags rather than scattering
6
+ * `process.platform === "..."` checks across the codebase.
7
+ */
8
+ import { platform, homedir } from "node:os";
9
+ export function detectPlatform() {
10
+ // Termux exposes PREFIX=/data/data/com.termux/files/usr. The extra check
11
+ // makes us robust to users who manually export PREFIX for other reasons.
12
+ const prefix = process.env["PREFIX"];
13
+ // Termux exports TERMUX_VERSION; PREFIX=/data/data/com.termux/files/usr is a
14
+ // strong secondary signal. Neither requires the directory to exist yet.
15
+ if (process.platform === "android" ||
16
+ Boolean(process.env["TERMUX_VERSION"]) ||
17
+ (prefix && prefix.includes("com.termux"))) {
18
+ return "termux";
19
+ }
20
+ switch (process.platform) {
21
+ case "linux":
22
+ case "darwin":
23
+ case "win32":
24
+ return process.platform;
25
+ default:
26
+ return "other";
27
+ }
28
+ }
29
+ export const isTermux = () => detectPlatform() === "termux";
30
+ export const isWindows = () => process.platform === "win32";
31
+ export const isMac = () => process.platform === "darwin";
32
+ /** Human-readable label for the status bar. */
33
+ export function platformLabel() {
34
+ const p = detectPlatform();
35
+ switch (p) {
36
+ case "termux":
37
+ return "Termux";
38
+ case "linux":
39
+ return "Linux";
40
+ case "darwin":
41
+ return "macOS";
42
+ case "win32":
43
+ return "Windows";
44
+ default:
45
+ return platform();
46
+ }
47
+ }
48
+ export function safeHome() {
49
+ // os.homedir() can return undefined on weird CI containers; fall back
50
+ // to $HOME / %USERPROFILE% / cwd in that order.
51
+ const fromOs = homedir();
52
+ if (fromOs)
53
+ return fromOs;
54
+ return (process.env["HOME"] ??
55
+ process.env["USERPROFILE"] ??
56
+ process.cwd());
57
+ }
@@ -0,0 +1,6 @@
1
+ export declare function createStartupTranscript(config: any): {
2
+ id: number;
3
+ role: string;
4
+ content: string;
5
+ markdown: boolean;
6
+ }[];