@datename/fastcode-termux 0.0.0-stage → 0.1.6-beta
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/LICENSE +21 -0
- package/README.md +418 -2
- package/bin/fastcode.mjs +59 -0
- package/dist/agent/approval.d.ts +50 -0
- package/dist/agent/approval.js +7 -0
- package/dist/agent/compaction.d.ts +39 -0
- package/dist/agent/compaction.js +227 -0
- package/dist/agent/context.d.ts +29 -0
- package/dist/agent/context.js +62 -0
- package/dist/agent/guards.d.ts +43 -0
- package/dist/agent/guards.js +269 -0
- package/dist/agent/ignore.d.ts +11 -0
- package/dist/agent/ignore.js +129 -0
- package/dist/agent/limits.d.ts +24 -0
- package/dist/agent/limits.js +71 -0
- package/dist/agent/process-tree.d.ts +2 -0
- package/dist/agent/process-tree.js +31 -0
- package/dist/agent/protocol-mode.d.ts +6 -0
- package/dist/agent/protocol-mode.js +51 -0
- package/dist/agent/protocol.d.ts +4 -0
- package/dist/agent/protocol.js +129 -0
- package/dist/agent/registry.d.ts +17 -0
- package/dist/agent/registry.js +90 -0
- package/dist/agent/runner.d.ts +106 -0
- package/dist/agent/runner.js +352 -0
- package/dist/agent/shell-session.d.ts +37 -0
- package/dist/agent/shell-session.js +400 -0
- package/dist/agent/stream.d.ts +29 -0
- package/dist/agent/stream.js +103 -0
- package/dist/agent/tool-schemas.d.ts +2 -0
- package/dist/agent/tool-schemas.js +63 -0
- package/dist/agent/tools/append_file.d.ts +2 -0
- package/dist/agent/tools/append_file.js +26 -0
- package/dist/agent/tools/apply_patch.d.ts +10 -0
- package/dist/agent/tools/apply_patch.js +52 -0
- package/dist/agent/tools/copy_file.d.ts +2 -0
- package/dist/agent/tools/copy_file.js +30 -0
- package/dist/agent/tools/delete_file.d.ts +2 -0
- package/dist/agent/tools/delete_file.js +24 -0
- package/dist/agent/tools/grep.d.ts +2 -0
- package/dist/agent/tools/grep.js +23 -0
- package/dist/agent/tools/index.d.ts +2 -0
- package/dist/agent/tools/index.js +32 -0
- package/dist/agent/tools/list_files.d.ts +2 -0
- package/dist/agent/tools/list_files.js +17 -0
- package/dist/agent/tools/make_dir.d.ts +2 -0
- package/dist/agent/tools/make_dir.js +24 -0
- package/dist/agent/tools/move_path.d.ts +2 -0
- package/dist/agent/tools/move_path.js +47 -0
- package/dist/agent/tools/paths.d.ts +7 -0
- package/dist/agent/tools/paths.js +84 -0
- package/dist/agent/tools/pwd.d.ts +2 -0
- package/dist/agent/tools/pwd.js +10 -0
- package/dist/agent/tools/read_file.d.ts +2 -0
- package/dist/agent/tools/read_file.js +21 -0
- package/dist/agent/tools/read_many.d.ts +2 -0
- package/dist/agent/tools/read_many.js +25 -0
- package/dist/agent/tools/readers.d.ts +6 -0
- package/dist/agent/tools/readers.js +216 -0
- package/dist/agent/tools/replace_in_file.d.ts +2 -0
- package/dist/agent/tools/replace_in_file.js +30 -0
- package/dist/agent/tools/shell.d.ts +2 -0
- package/dist/agent/tools/shell.js +48 -0
- package/dist/agent/tools/stat_path.d.ts +2 -0
- package/dist/agent/tools/stat_path.js +31 -0
- package/dist/agent/tools/types.d.ts +39 -0
- package/dist/agent/tools/types.js +1 -0
- package/dist/agent/tools/validate.d.ts +6 -0
- package/dist/agent/tools/validate.js +29 -0
- package/dist/agent/tools/write_file.d.ts +2 -0
- package/dist/agent/tools/write_file.js +26 -0
- package/dist/agent/tools.d.ts +3 -0
- package/dist/agent/tools.js +2 -0
- package/dist/app.d.ts +4 -0
- package/dist/app.js +708 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +569 -0
- package/dist/commands/about.d.ts +8 -0
- package/dist/commands/about.js +16 -0
- package/dist/commands/agent.d.ts +11 -0
- package/dist/commands/agent.js +18 -0
- package/dist/commands/attach.d.ts +9 -0
- package/dist/commands/attach.js +15 -0
- package/dist/commands/bg.d.ts +7 -0
- package/dist/commands/bg.js +9 -0
- package/dist/commands/cancel.d.ts +10 -0
- package/dist/commands/cancel.js +10 -0
- package/dist/commands/clear.d.ts +11 -0
- package/dist/commands/clear.js +9 -0
- package/dist/commands/compact.d.ts +10 -0
- package/dist/commands/compact.js +22 -0
- package/dist/commands/config.d.ts +7 -0
- package/dist/commands/config.js +24 -0
- package/dist/commands/context.d.ts +7 -0
- package/dist/commands/context.js +24 -0
- package/dist/commands/cost.d.ts +9 -0
- package/dist/commands/cost.js +13 -0
- package/dist/commands/cwd.d.ts +7 -0
- package/dist/commands/cwd.js +7 -0
- package/dist/commands/edit.d.ts +10 -0
- package/dist/commands/edit.js +36 -0
- package/dist/commands/effort.d.ts +25 -0
- package/dist/commands/effort.js +48 -0
- package/dist/commands/exit.d.ts +9 -0
- package/dist/commands/exit.js +8 -0
- package/dist/commands/expand.d.ts +10 -0
- package/dist/commands/expand.js +8 -0
- package/dist/commands/help.d.ts +6 -0
- package/dist/commands/help.js +18 -0
- package/dist/commands/index.d.ts +9 -0
- package/dist/commands/index.js +80 -0
- package/dist/commands/jobs.d.ts +7 -0
- package/dist/commands/jobs.js +16 -0
- package/dist/commands/kill.d.ts +7 -0
- package/dist/commands/kill.js +11 -0
- package/dist/commands/login.d.ts +18 -0
- package/dist/commands/login.js +63 -0
- package/dist/commands/logout.d.ts +9 -0
- package/dist/commands/logout.js +45 -0
- package/dist/commands/model.d.ts +24 -0
- package/dist/commands/model.js +50 -0
- package/dist/commands/plugin.d.ts +11 -0
- package/dist/commands/plugin.js +74 -0
- package/dist/commands/provider.d.ts +24 -0
- package/dist/commands/provider.js +39 -0
- package/dist/commands/reset.d.ts +9 -0
- package/dist/commands/reset.js +13 -0
- package/dist/commands/skills.d.ts +11 -0
- package/dist/commands/skills.js +106 -0
- package/dist/commands/steer.d.ts +11 -0
- package/dist/commands/steer.js +13 -0
- package/dist/commands/system.d.ts +11 -0
- package/dist/commands/system.js +20 -0
- package/dist/commands/theme.d.ts +11 -0
- package/dist/commands/theme.js +22 -0
- package/dist/commands/tools.d.ts +7 -0
- package/dist/commands/tools.js +29 -0
- package/dist/commands/whoami.d.ts +9 -0
- package/dist/commands/whoami.js +52 -0
- package/dist/config/paths.d.ts +4 -0
- package/dist/config/paths.js +59 -0
- package/dist/config/store.d.ts +89 -0
- package/dist/config/store.js +260 -0
- package/dist/config/themes.d.ts +73 -0
- package/dist/config/themes.js +77 -0
- package/dist/config/trust.d.ts +22 -0
- package/dist/config/trust.js +153 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +31 -0
- package/dist/lib/diagnostics.d.ts +66 -0
- package/dist/lib/diagnostics.js +195 -0
- package/dist/lib/http.d.ts +23 -0
- package/dist/lib/http.js +128 -0
- package/dist/lib/markdown.d.ts +1 -0
- package/dist/lib/markdown.js +32 -0
- package/dist/lib/model-meta.d.ts +19 -0
- package/dist/lib/model-meta.js +113 -0
- package/dist/lib/native-tools.d.ts +12 -0
- package/dist/lib/native-tools.js +21 -0
- package/dist/lib/net.d.ts +33 -0
- package/dist/lib/net.js +168 -0
- package/dist/lib/pkg.d.ts +5 -0
- package/dist/lib/pkg.js +27 -0
- package/dist/lib/platform.d.ts +7 -0
- package/dist/lib/platform.js +57 -0
- package/dist/lib/startup.d.ts +6 -0
- package/dist/lib/startup.js +25 -0
- package/dist/lib/uiMode.d.ts +2 -0
- package/dist/lib/uiMode.js +7 -0
- package/dist/patch/apply.d.ts +15 -0
- package/dist/patch/apply.js +172 -0
- package/dist/patch/parse.d.ts +29 -0
- package/dist/patch/parse.js +112 -0
- package/dist/patch/plan.d.ts +49 -0
- package/dist/patch/plan.js +273 -0
- package/dist/plugins/manager.d.ts +23 -0
- package/dist/plugins/manager.js +101 -0
- package/dist/providers/anthropic.d.ts +22 -0
- package/dist/providers/anthropic.js +119 -0
- package/dist/providers/custom.d.ts +33 -0
- package/dist/providers/custom.js +25 -0
- package/dist/providers/deepseek.d.ts +34 -0
- package/dist/providers/deepseek.js +16 -0
- package/dist/providers/google.d.ts +33 -0
- package/dist/providers/google.js +19 -0
- package/dist/providers/groq.d.ts +34 -0
- package/dist/providers/groq.js +21 -0
- package/dist/providers/index.d.ts +10 -0
- package/dist/providers/index.js +67 -0
- package/dist/providers/mistral.d.ts +34 -0
- package/dist/providers/mistral.js +20 -0
- package/dist/providers/moonshot.d.ts +34 -0
- package/dist/providers/moonshot.js +23 -0
- package/dist/providers/ollama.d.ts +33 -0
- package/dist/providers/ollama.js +22 -0
- package/dist/providers/openai.d.ts +34 -0
- package/dist/providers/openai.js +26 -0
- package/dist/providers/openaiCompatible.d.ts +26 -0
- package/dist/providers/openaiCompatible.js +158 -0
- package/dist/providers/openrouter.d.ts +34 -0
- package/dist/providers/openrouter.js +27 -0
- package/dist/providers/qwen.d.ts +34 -0
- package/dist/providers/qwen.js +24 -0
- package/dist/providers/xai.d.ts +34 -0
- package/dist/providers/xai.js +19 -0
- package/dist/providers/zhipu.d.ts +34 -0
- package/dist/providers/zhipu.js +24 -0
- package/dist/session/log.d.ts +52 -0
- package/dist/session/log.js +331 -0
- package/dist/session/projection.d.ts +43 -0
- package/dist/session/projection.js +257 -0
- package/dist/skills/manager.d.ts +18 -0
- package/dist/skills/manager.js +201 -0
- package/dist/skills/parser.d.ts +18 -0
- package/dist/skills/parser.js +65 -0
- package/dist/types.d.ts +205 -0
- package/dist/types.js +1 -0
- package/dist/ui/Banner.d.ts +5 -0
- package/dist/ui/Banner.js +65 -0
- package/dist/ui/Message.d.ts +1 -0
- package/dist/ui/Message.js +24 -0
- package/dist/ui/Picker.d.ts +8 -0
- package/dist/ui/Picker.js +24 -0
- package/dist/ui/Prompt.d.ts +9 -0
- package/dist/ui/Prompt.js +38 -0
- package/dist/ui/Spinner.d.ts +5 -0
- package/dist/ui/Spinner.js +39 -0
- package/dist/ui/StatusBar.d.ts +11 -0
- package/dist/ui/StatusBar.js +20 -0
- package/dist/ui/TrustGate.d.ts +5 -0
- package/dist/ui/TrustGate.js +26 -0
- package/package.json +96 -4
- package/src/agent/approval.ts +47 -0
- package/src/agent/compaction.ts +276 -0
- package/src/agent/context.ts +97 -0
- package/src/agent/guards.ts +270 -0
- package/src/agent/ignore.ts +147 -0
- package/src/agent/limits.ts +71 -0
- package/src/agent/process-tree.ts +32 -0
- package/src/agent/protocol-mode.ts +59 -0
- package/src/agent/protocol.ts +129 -0
- package/src/agent/registry.ts +98 -0
- package/src/agent/runner.ts +443 -0
- package/src/agent/shell-session.ts +449 -0
- package/src/agent/stream.ts +143 -0
- package/src/agent/tool-schemas.ts +67 -0
- package/src/agent/tools/append_file.ts +28 -0
- package/src/agent/tools/apply_patch.ts +54 -0
- package/src/agent/tools/copy_file.ts +32 -0
- package/src/agent/tools/delete_file.ts +26 -0
- package/src/agent/tools/grep.ts +25 -0
- package/src/agent/tools/index.ts +34 -0
- package/src/agent/tools/list_files.ts +19 -0
- package/src/agent/tools/make_dir.ts +26 -0
- package/src/agent/tools/move_path.ts +50 -0
- package/src/agent/tools/paths.ts +92 -0
- package/src/agent/tools/pwd.ts +12 -0
- package/src/agent/tools/read_file.ts +23 -0
- package/src/agent/tools/read_many.ts +27 -0
- package/src/agent/tools/readers.ts +228 -0
- package/src/agent/tools/replace_in_file.ts +32 -0
- package/src/agent/tools/shell.ts +53 -0
- package/src/agent/tools/stat_path.ts +33 -0
- package/src/agent/tools/types.ts +42 -0
- package/src/agent/tools/validate.ts +35 -0
- package/src/agent/tools/write_file.ts +28 -0
- package/src/agent/tools.ts +3 -0
- package/src/app.ts +708 -0
- package/src/cli.ts +335 -0
- package/src/commands/about.ts +16 -0
- package/src/commands/agent.ts +18 -0
- package/src/commands/attach.ts +16 -0
- package/src/commands/bg.ts +9 -0
- package/src/commands/cancel.ts +10 -0
- package/src/commands/clear.ts +9 -0
- package/src/commands/compact.ts +23 -0
- package/src/commands/config.ts +24 -0
- package/src/commands/context.ts +25 -0
- package/src/commands/cost.ts +13 -0
- package/src/commands/cwd.ts +7 -0
- package/src/commands/edit.ts +37 -0
- package/src/commands/effort.ts +49 -0
- package/src/commands/exit.ts +8 -0
- package/src/commands/expand.ts +8 -0
- package/src/commands/help.ts +18 -0
- package/src/commands/index.ts +80 -0
- package/src/commands/jobs.ts +17 -0
- package/src/commands/kill.ts +12 -0
- package/src/commands/login.ts +63 -0
- package/src/commands/logout.ts +45 -0
- package/src/commands/model.ts +50 -0
- package/src/commands/plugin.ts +74 -0
- package/src/commands/provider.ts +39 -0
- package/src/commands/reset.ts +13 -0
- package/src/commands/skills.ts +106 -0
- package/src/commands/steer.ts +13 -0
- package/src/commands/system.ts +20 -0
- package/src/commands/theme.ts +22 -0
- package/src/commands/tools.ts +29 -0
- package/src/commands/whoami.ts +52 -0
- package/src/config/paths.ts +59 -0
- package/src/config/store.ts +293 -0
- package/src/config/themes.ts +77 -0
- package/src/config/trust.ts +183 -0
- package/src/index.ts +31 -0
- package/src/lib/diagnostics.ts +195 -0
- package/src/lib/http.ts +132 -0
- package/src/lib/markdown.ts +32 -0
- package/src/lib/model-meta.ts +138 -0
- package/src/lib/native-tools.ts +32 -0
- package/src/lib/net.ts +204 -0
- package/src/lib/pkg.ts +35 -0
- package/src/lib/platform.ts +57 -0
- package/src/lib/startup.ts +25 -0
- package/src/lib/uiMode.ts +7 -0
- package/src/patch/apply.ts +194 -0
- package/src/patch/parse.ts +131 -0
- package/src/patch/plan.ts +317 -0
- package/src/plugins/manager.ts +101 -0
- package/src/providers/anthropic.ts +119 -0
- package/src/providers/custom.ts +25 -0
- package/src/providers/deepseek.ts +16 -0
- package/src/providers/google.ts +19 -0
- package/src/providers/groq.ts +21 -0
- package/src/providers/index.ts +68 -0
- package/src/providers/mistral.ts +20 -0
- package/src/providers/moonshot.ts +23 -0
- package/src/providers/ollama.ts +22 -0
- package/src/providers/openai.ts +26 -0
- package/src/providers/openaiCompatible.ts +158 -0
- package/src/providers/openrouter.ts +27 -0
- package/src/providers/qwen.ts +24 -0
- package/src/providers/xai.ts +19 -0
- package/src/providers/zhipu.ts +24 -0
- package/src/session/log.ts +374 -0
- package/src/session/projection.ts +308 -0
- package/src/skills/manager.ts +201 -0
- package/src/skills/parser.ts +65 -0
- package/src/types.ts +205 -0
- package/src/ui/Banner.ts +68 -0
- package/src/ui/Message.ts +24 -0
- package/src/ui/Picker.ts +24 -0
- package/src/ui/Prompt.ts +38 -0
- package/src/ui/Spinner.ts +39 -0
- package/src/ui/StatusBar.ts +20 -0
- package/src/ui/TrustGate.ts +26 -0
- package/tsconfig.json +22 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Filesystem layout for FastCode user data.
|
|
3
|
+
*
|
|
4
|
+
* ~/.fastcode/
|
|
5
|
+
* config.json global preferences (provider, model, theme, …)
|
|
6
|
+
* auth.json per-provider API keys (mode 0600)
|
|
7
|
+
* skills/ installed SKILL.md folders
|
|
8
|
+
* plugins/ installed plugin folders
|
|
9
|
+
* cache/ transient downloads & registry caches
|
|
10
|
+
* sessions/ conversation transcripts (future)
|
|
11
|
+
* logs/ debug logs (future)
|
|
12
|
+
*
|
|
13
|
+
* On Windows we keep using `~/.fastcode` rather than %APPDATA% to match the
|
|
14
|
+
* dotfile convention most TUIs use; users can override via $FASTCODE_HOME.
|
|
15
|
+
*/
|
|
16
|
+
import { join } from "node:path";
|
|
17
|
+
import { mkdirSync, existsSync, chmodSync } from "node:fs";
|
|
18
|
+
import { safeHome, isWindows } from "../lib/platform.js";
|
|
19
|
+
let cached;
|
|
20
|
+
export function getPaths() {
|
|
21
|
+
if (cached)
|
|
22
|
+
return cached;
|
|
23
|
+
const root = process.env["FASTCODE_HOME"] ?? join(safeHome(), ".fastcode");
|
|
24
|
+
const paths = {
|
|
25
|
+
root,
|
|
26
|
+
config: join(root, "config.json"),
|
|
27
|
+
auth: join(root, "auth.json"),
|
|
28
|
+
trust: join(root, "trusted.json"),
|
|
29
|
+
skills: join(root, "skills"),
|
|
30
|
+
plugins: join(root, "plugins"),
|
|
31
|
+
cache: join(root, "cache"),
|
|
32
|
+
sessions: join(root, "sessions"),
|
|
33
|
+
logs: join(root, "logs"),
|
|
34
|
+
};
|
|
35
|
+
ensureDir(paths.root);
|
|
36
|
+
ensureDir(paths.skills);
|
|
37
|
+
ensureDir(paths.plugins);
|
|
38
|
+
ensureDir(paths.cache);
|
|
39
|
+
ensureDir(paths.sessions);
|
|
40
|
+
ensureDir(paths.logs);
|
|
41
|
+
cached = paths;
|
|
42
|
+
return paths;
|
|
43
|
+
}
|
|
44
|
+
export function ensureDir(dir) {
|
|
45
|
+
if (!existsSync(dir))
|
|
46
|
+
mkdirSync(dir, { recursive: true });
|
|
47
|
+
}
|
|
48
|
+
/** Tighten permissions on a file storing secrets where the OS supports it. */
|
|
49
|
+
export function lockdown(file) {
|
|
50
|
+
if (isWindows())
|
|
51
|
+
return;
|
|
52
|
+
try {
|
|
53
|
+
chmodSync(file, 0o600);
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
// best effort; some filesystems (FAT, Termux on internal storage) don't
|
|
57
|
+
// honor chmod and that's fine.
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persistent config & auth store.
|
|
3
|
+
*
|
|
4
|
+
* We deliberately keep the format human-readable JSON so users on Termux can
|
|
5
|
+
* edit it from `nano ~/.fastcode/config.json` if needed. Atomic writes prevent
|
|
6
|
+
* partial files on Ctrl+C.
|
|
7
|
+
*
|
|
8
|
+
* API keys are scoped to a provider, not to a model. auth.json stores one
|
|
9
|
+
* credential per provider id, so switching between models of the same provider
|
|
10
|
+
* reuses the same key. The old "provider::model" (v2) and provider-only (v1)
|
|
11
|
+
* formats are read and migrated to the provider-scoped v3 layout on first load.
|
|
12
|
+
*/
|
|
13
|
+
import { readFileSync, writeFileSync, existsSync, renameSync, copyFileSync } from "node:fs";
|
|
14
|
+
import { join, dirname } from "node:path";
|
|
15
|
+
import { getPaths, lockdown, ensureDir } from "./paths.js";
|
|
16
|
+
import { DEFAULT_THEME } from "./themes.js";
|
|
17
|
+
export const DEFAULT_CONFIG = {
|
|
18
|
+
providerId: "anthropic",
|
|
19
|
+
model: "claude-sonnet-5.5",
|
|
20
|
+
theme: DEFAULT_THEME,
|
|
21
|
+
agentMode: true,
|
|
22
|
+
systemPrompt: "You are FastCode, a fast, friendly coding assistant in a terminal UI. " +
|
|
23
|
+
"Prefer concise responses. Use markdown when helpful (especially fenced " +
|
|
24
|
+
"code blocks). When the user asks for code changes, output complete files " +
|
|
25
|
+
"or unified diffs the user can apply.",
|
|
26
|
+
enabledSkills: [],
|
|
27
|
+
enabledPlugins: [],
|
|
28
|
+
skillsRegistry: "https://skillshub.wtf",
|
|
29
|
+
lifetimeCostUsd: 0,
|
|
30
|
+
/**
|
|
31
|
+
* Extra directories the file tools may read and write outside the project
|
|
32
|
+
* root. Relative entries are resolved against the project root; `~/` is
|
|
33
|
+
* expanded against the user's home directory.
|
|
34
|
+
*/
|
|
35
|
+
sharedDirs: [],
|
|
36
|
+
/** Maximum agent tool turns before the loop asks the user to continue. */
|
|
37
|
+
maxAgentTurns: 24,
|
|
38
|
+
/** Abort a stream that receives no data for this long (ms). */
|
|
39
|
+
requestIdleTimeoutMs: 60_000,
|
|
40
|
+
/** Approximate character budget before history compaction kicks in. */
|
|
41
|
+
contextBudgetChars: 400_000,
|
|
42
|
+
/** Thinking depth: auto = send nothing, provider default. */
|
|
43
|
+
reasoningEffort: "auto",
|
|
44
|
+
/** Cached per-provider/model capabilities (native vs pseudo tool calling). */
|
|
45
|
+
capabilities: {
|
|
46
|
+
toolCalling: {},
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
export interface StoredCredential {
|
|
50
|
+
provider: string;
|
|
51
|
+
/** Model the key was originally saved for (display only; keys are provider-wide). */
|
|
52
|
+
model?: string;
|
|
53
|
+
key: string;
|
|
54
|
+
addedAt: string;
|
|
55
|
+
}
|
|
56
|
+
export interface AuthStore {
|
|
57
|
+
version: number;
|
|
58
|
+
/** Provider-scoped credentials, keyed by provider id. */
|
|
59
|
+
keys: Record<string, StoredCredential>;
|
|
60
|
+
/** Old provider-only keys that could not be migrated (kept for diagnostics). */
|
|
61
|
+
legacy: Record<string, string>;
|
|
62
|
+
}
|
|
63
|
+
export const AUTH_VERSION = 3;
|
|
64
|
+
export type KeyLookup = {
|
|
65
|
+
status: "ok";
|
|
66
|
+
key: string;
|
|
67
|
+
source: "file" | "env";
|
|
68
|
+
envVar?: string;
|
|
69
|
+
} | {
|
|
70
|
+
status: "unbound";
|
|
71
|
+
provider: string;
|
|
72
|
+
legacyKey: string;
|
|
73
|
+
} | {
|
|
74
|
+
status: "missing";
|
|
75
|
+
};
|
|
76
|
+
export function loadConfig() {
|
|
77
|
+
const { config } = getPaths();
|
|
78
|
+
if (!existsSync(config))
|
|
79
|
+
return { ...DEFAULT_CONFIG };
|
|
80
|
+
try {
|
|
81
|
+
const raw = JSON.parse(readFileSync(config, "utf8"));
|
|
82
|
+
return { ...DEFAULT_CONFIG, ...raw };
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return { ...DEFAULT_CONFIG };
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
export function saveConfig(cfg) {
|
|
89
|
+
const { config } = getPaths();
|
|
90
|
+
atomicWrite(config, JSON.stringify(cfg, null, 2));
|
|
91
|
+
}
|
|
92
|
+
/** Composite id used by the legacy v2 format; kept for API compatibility. */
|
|
93
|
+
export function scopeId(providerId, model) {
|
|
94
|
+
return `${providerId}::${model}`;
|
|
95
|
+
}
|
|
96
|
+
/** Pick the most recently saved credential when several legacy scopes map to one provider. */
|
|
97
|
+
function keepNewest(keys, credential) {
|
|
98
|
+
const existing = keys[credential.provider];
|
|
99
|
+
if (!existing) {
|
|
100
|
+
keys[credential.provider] = credential;
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const previous = Date.parse(existing.addedAt || "") || 0;
|
|
104
|
+
const next = Date.parse(credential.addedAt || "") || 0;
|
|
105
|
+
if (next > previous)
|
|
106
|
+
keys[credential.provider] = credential;
|
|
107
|
+
}
|
|
108
|
+
let migrationChecked = false;
|
|
109
|
+
export function loadAuth(): AuthStore {
|
|
110
|
+
const { auth } = getPaths();
|
|
111
|
+
const empty: AuthStore = { version: AUTH_VERSION, keys: {}, legacy: {} };
|
|
112
|
+
if (!existsSync(auth))
|
|
113
|
+
return empty;
|
|
114
|
+
let raw: any;
|
|
115
|
+
try {
|
|
116
|
+
raw = JSON.parse(readFileSync(auth, "utf8"));
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
return empty;
|
|
120
|
+
}
|
|
121
|
+
if (!raw || typeof raw !== "object")
|
|
122
|
+
return empty;
|
|
123
|
+
const keys: Record<string, StoredCredential> = {};
|
|
124
|
+
const legacy: Record<string, string> = {};
|
|
125
|
+
let migrated = false;
|
|
126
|
+
const source = raw.keys && typeof raw.keys === "object" ? raw.keys : {};
|
|
127
|
+
for (const [id, value] of Object.entries<any>(source)) {
|
|
128
|
+
if (value && typeof value === "object" && typeof value.key === "string" && value.key.trim()) {
|
|
129
|
+
const [idProvider, idModel] = id.split("::");
|
|
130
|
+
const provider = typeof value.provider === "string" && value.provider ? value.provider : idProvider || id;
|
|
131
|
+
const model = typeof value.model === "string" ? value.model : idModel || "";
|
|
132
|
+
if (id.includes("::") || raw.version !== AUTH_VERSION)
|
|
133
|
+
migrated = true;
|
|
134
|
+
keepNewest(keys, {
|
|
135
|
+
provider,
|
|
136
|
+
model,
|
|
137
|
+
key: value.key,
|
|
138
|
+
addedAt: typeof value.addedAt === "string" ? value.addedAt : "",
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
else if (typeof value === "string" && value.trim()) {
|
|
142
|
+
// v1: keys were plain provider -> key strings.
|
|
143
|
+
keepNewest(keys, { provider: id, key: value, addedAt: "" });
|
|
144
|
+
migrated = true;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
if (raw.legacy && typeof raw.legacy === "object") {
|
|
148
|
+
for (const [provider, value] of Object.entries<any>(raw.legacy)) {
|
|
149
|
+
if (typeof value === "string" && value.trim()) {
|
|
150
|
+
// Provider-only keys are exactly what v3 wants: migrate instead
|
|
151
|
+
// of leaving them unusable.
|
|
152
|
+
keepNewest(keys, { provider, key: value, addedAt: "" });
|
|
153
|
+
migrated = true;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
if (migrated && !migrationChecked) {
|
|
158
|
+
try {
|
|
159
|
+
// Keep the pre-migration file so a user with several old model
|
|
160
|
+
// keys can recover a credential we had to collapse.
|
|
161
|
+
const backup = auth + ".v2.bak";
|
|
162
|
+
if (!existsSync(backup))
|
|
163
|
+
copyFileSync(auth, backup);
|
|
164
|
+
writeAuth({ version: AUTH_VERSION, keys, legacy });
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
// Read-only home (or locked file): keep the migrated view in memory.
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
migrationChecked = true;
|
|
171
|
+
return { version: AUTH_VERSION, keys, legacy };
|
|
172
|
+
}
|
|
173
|
+
function writeAuth(store: AuthStore) {
|
|
174
|
+
const { auth } = getPaths();
|
|
175
|
+
atomicWrite(auth, JSON.stringify({ version: AUTH_VERSION, keys: store.keys }, null, 2));
|
|
176
|
+
lockdown(auth);
|
|
177
|
+
}
|
|
178
|
+
export function saveAuth(store: AuthStore) {
|
|
179
|
+
writeAuth(store);
|
|
180
|
+
}
|
|
181
|
+
export function getCredential(providerId, _model?): StoredCredential | undefined {
|
|
182
|
+
return loadAuth().keys[providerId];
|
|
183
|
+
}
|
|
184
|
+
export function listCredentials(): StoredCredential[] {
|
|
185
|
+
return Object.values(loadAuth().keys);
|
|
186
|
+
}
|
|
187
|
+
export function listLegacy(): Array<{
|
|
188
|
+
provider: string;
|
|
189
|
+
key: string;
|
|
190
|
+
}> {
|
|
191
|
+
return Object.entries<any>(loadAuth().legacy).map(([provider, key]) => ({ provider, key }));
|
|
192
|
+
}
|
|
193
|
+
export function getLegacy(providerId): string | undefined {
|
|
194
|
+
return loadAuth().legacy[providerId];
|
|
195
|
+
}
|
|
196
|
+
/** Save/replace the provider-wide credential (drops any legacy key). */
|
|
197
|
+
export function setCredential(providerId, model, key): StoredCredential {
|
|
198
|
+
const store = loadAuth();
|
|
199
|
+
const credential: StoredCredential = {
|
|
200
|
+
provider: providerId,
|
|
201
|
+
model,
|
|
202
|
+
key: key.trim(),
|
|
203
|
+
addedAt: new Date().toISOString(),
|
|
204
|
+
};
|
|
205
|
+
store.keys[providerId] = credential;
|
|
206
|
+
delete store.legacy[providerId];
|
|
207
|
+
saveAuth(store);
|
|
208
|
+
return credential;
|
|
209
|
+
}
|
|
210
|
+
export function removeCredential(providerId, _model?): boolean {
|
|
211
|
+
const store = loadAuth();
|
|
212
|
+
if (!store.keys[providerId])
|
|
213
|
+
return false;
|
|
214
|
+
delete store.keys[providerId];
|
|
215
|
+
saveAuth(store);
|
|
216
|
+
return true;
|
|
217
|
+
}
|
|
218
|
+
/** Remove the provider-wide credential plus any legacy leftovers. */
|
|
219
|
+
export function removeCredentialsForProvider(providerId): number {
|
|
220
|
+
const store = loadAuth();
|
|
221
|
+
let removed = 0;
|
|
222
|
+
if (store.keys[providerId]) {
|
|
223
|
+
delete store.keys[providerId];
|
|
224
|
+
removed += 1;
|
|
225
|
+
}
|
|
226
|
+
if (store.legacy[providerId]) {
|
|
227
|
+
delete store.legacy[providerId];
|
|
228
|
+
removed += 1;
|
|
229
|
+
}
|
|
230
|
+
if (removed > 0)
|
|
231
|
+
saveAuth(store);
|
|
232
|
+
return removed;
|
|
233
|
+
}
|
|
234
|
+
/** Turn a key into a safe terminal echo: `sk-****abcd`. */
|
|
235
|
+
export function maskKey(key) {
|
|
236
|
+
const value = (key ?? "").trim();
|
|
237
|
+
if (!value)
|
|
238
|
+
return "";
|
|
239
|
+
if (value.length <= 8)
|
|
240
|
+
return "*".repeat(value.length);
|
|
241
|
+
return value.slice(0, 3) + "****" + value.slice(-4);
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Resolve the key to use for a provider:
|
|
245
|
+
* 1. the provider-wide credential in auth.json,
|
|
246
|
+
* 2. the provider's environment variable (provider-scoped, any model), and
|
|
247
|
+
* 3. a legacy provider-only key that somehow escaped migration.
|
|
248
|
+
*/
|
|
249
|
+
export function resolveApiKey(providerId, _model, envVars): KeyLookup {
|
|
250
|
+
const store = loadAuth();
|
|
251
|
+
const credential = store.keys[providerId];
|
|
252
|
+
if (credential && credential.key)
|
|
253
|
+
return { status: "ok", key: credential.key, source: "file" };
|
|
254
|
+
for (const envVar of envVars ?? []) {
|
|
255
|
+
const value = process.env[envVar];
|
|
256
|
+
if (value && value.trim())
|
|
257
|
+
return { status: "ok", key: value.trim(), source: "env", envVar };
|
|
258
|
+
}
|
|
259
|
+
if (store.legacy[providerId])
|
|
260
|
+
return { status: "unbound", provider: providerId, legacyKey: store.legacy[providerId] };
|
|
261
|
+
return { status: "missing" };
|
|
262
|
+
}
|
|
263
|
+
/** Best-effort key for a provider regardless of model (used to list models). */
|
|
264
|
+
export function resolveProviderKey(providerId, envVars): {
|
|
265
|
+
key?: string;
|
|
266
|
+
source?: "file" | "env";
|
|
267
|
+
envVar?: string;
|
|
268
|
+
} {
|
|
269
|
+
const store = loadAuth();
|
|
270
|
+
const credential = store.keys[providerId];
|
|
271
|
+
if (credential && credential.key)
|
|
272
|
+
return { key: credential.key, source: "file" };
|
|
273
|
+
for (const envVar of envVars ?? []) {
|
|
274
|
+
const value = process.env[envVar];
|
|
275
|
+
if (value && value.trim())
|
|
276
|
+
return { key: value.trim(), source: "env", envVar };
|
|
277
|
+
}
|
|
278
|
+
return {};
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Write-then-rename so a Ctrl+C mid-write never leaves a corrupt file.
|
|
282
|
+
*
|
|
283
|
+
* The temp file MUST live in the same directory as the target so the rename
|
|
284
|
+
* is a same-filesystem operation. On many Linux distros `/tmp` is a tmpfs
|
|
285
|
+
* mount, which would make `renameSync` throw EXDEV across filesystems.
|
|
286
|
+
*/
|
|
287
|
+
function atomicWrite(target, contents) {
|
|
288
|
+
const dir = dirname(target);
|
|
289
|
+
ensureDir(dir);
|
|
290
|
+
const tmp = join(dir, `.fastcode-${Date.now()}-${Math.random().toString(36).slice(2)}.tmp`);
|
|
291
|
+
writeFileSync(tmp, contents, { encoding: "utf8" });
|
|
292
|
+
renameSync(tmp, target);
|
|
293
|
+
}
|
|
@@ -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,183 @@
|
|
|
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, statSync, writeFileSync } from "node:fs";
|
|
11
|
+
import { resolve } from "node:path";
|
|
12
|
+
import { ensureDir, getPaths } from "./paths.js";
|
|
13
|
+
|
|
14
|
+
const TRUTHY = ["1", "true", "yes", "on"];
|
|
15
|
+
|
|
16
|
+
export interface TrustPathEntry {
|
|
17
|
+
grantedAt: string;
|
|
18
|
+
lastUsed: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface TrustToolEntry extends TrustPathEntry {
|
|
22
|
+
catalogHash: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface TrustStore {
|
|
26
|
+
version: 1;
|
|
27
|
+
tools: Record<string, TrustToolEntry>;
|
|
28
|
+
paths: Record<string, TrustPathEntry>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
let cache: { file: string; key: string; state: TrustStore } | undefined;
|
|
32
|
+
|
|
33
|
+
export function normalizeProjectPath(dir: string): string {
|
|
34
|
+
const absolute = resolve(dir);
|
|
35
|
+
try {
|
|
36
|
+
return realpathSync(absolute);
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return absolute;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function emptyStore(): TrustStore {
|
|
44
|
+
return { version: 1, tools: {}, paths: {} };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function normalizePaths(raw: any): Record<string, TrustPathEntry> {
|
|
48
|
+
const paths: Record<string, TrustPathEntry> = {};
|
|
49
|
+
if (!raw || typeof raw !== "object")
|
|
50
|
+
return paths;
|
|
51
|
+
for (const [key, value] of Object.entries<any>(raw)) {
|
|
52
|
+
if (!key || !value || typeof value !== "object")
|
|
53
|
+
continue;
|
|
54
|
+
paths[key] = {
|
|
55
|
+
grantedAt: typeof value.grantedAt === "string" ? value.grantedAt : "",
|
|
56
|
+
lastUsed: typeof value.lastUsed === "string" ? value.lastUsed : "",
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
return paths;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function normalizeTools(raw: any): Record<string, TrustToolEntry> {
|
|
63
|
+
const tools: Record<string, TrustToolEntry> = {};
|
|
64
|
+
if (!raw || typeof raw !== "object")
|
|
65
|
+
return tools;
|
|
66
|
+
for (const [key, value] of Object.entries<any>(raw)) {
|
|
67
|
+
if (!key || !value || typeof value !== "object" || typeof value.catalogHash !== "string")
|
|
68
|
+
continue;
|
|
69
|
+
tools[key] = {
|
|
70
|
+
grantedAt: typeof value.grantedAt === "string" ? value.grantedAt : "",
|
|
71
|
+
lastUsed: typeof value.lastUsed === "string" ? value.lastUsed : "",
|
|
72
|
+
catalogHash: value.catalogHash,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
return tools;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function migrateLegacy(raw: any): TrustStore | undefined {
|
|
79
|
+
if (!raw || typeof raw !== "object" || !Array.isArray(raw.trusted))
|
|
80
|
+
return undefined;
|
|
81
|
+
const store = emptyStore();
|
|
82
|
+
for (const entry of raw.trusted) {
|
|
83
|
+
if (!entry || typeof entry.path !== "string" || !entry.path)
|
|
84
|
+
continue;
|
|
85
|
+
const at = typeof entry.addedAt === "string" ? entry.addedAt : new Date().toISOString();
|
|
86
|
+
store.paths[entry.path] = { grantedAt: at, lastUsed: at };
|
|
87
|
+
}
|
|
88
|
+
return store;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function readStore(file: string): TrustStore {
|
|
92
|
+
if (!existsSync(file))
|
|
93
|
+
return emptyStore();
|
|
94
|
+
try {
|
|
95
|
+
const raw = JSON.parse(readFileSync(file, "utf8"));
|
|
96
|
+
if (raw && raw.version === 1) {
|
|
97
|
+
return {
|
|
98
|
+
version: 1,
|
|
99
|
+
tools: normalizeTools(raw.tools),
|
|
100
|
+
paths: normalizePaths(raw.paths),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
const migrated = migrateLegacy(raw);
|
|
104
|
+
if (!migrated)
|
|
105
|
+
return emptyStore();
|
|
106
|
+
try {
|
|
107
|
+
writeFileSync(file, JSON.stringify(migrated, null, 2), "utf8");
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
// A read-only home still gets the migrated view in memory.
|
|
111
|
+
}
|
|
112
|
+
return migrated;
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
return emptyStore();
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function loadTrust(): TrustStore {
|
|
120
|
+
const { trust } = getPaths();
|
|
121
|
+
let key = "missing";
|
|
122
|
+
try {
|
|
123
|
+
const info = statSync(trust);
|
|
124
|
+
key = `${info.mtimeMs}:${info.size}`;
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
// Missing file: cache the empty store until it appears.
|
|
128
|
+
}
|
|
129
|
+
if (cache && cache.file === trust && cache.key === key)
|
|
130
|
+
return cache.state;
|
|
131
|
+
const state = readStore(trust);
|
|
132
|
+
cache = { file: trust, key, state };
|
|
133
|
+
return state;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function saveTrust(state: TrustStore): void {
|
|
137
|
+
const paths = getPaths();
|
|
138
|
+
ensureDir(paths.root);
|
|
139
|
+
writeFileSync(paths.trust, JSON.stringify(state, null, 2), "utf8");
|
|
140
|
+
try {
|
|
141
|
+
const info = statSync(paths.trust);
|
|
142
|
+
cache = { file: paths.trust, key: `${info.mtimeMs}:${info.size}`, state };
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
cache = undefined;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function trustAllEnabled(): boolean {
|
|
150
|
+
return TRUTHY.includes((process.env["FASTCODE_TRUST_ALL"] ?? "").trim().toLowerCase());
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function isTrusted(dir: string): boolean {
|
|
154
|
+
if (trustAllEnabled())
|
|
155
|
+
return true;
|
|
156
|
+
const target = normalizeProjectPath(dir);
|
|
157
|
+
return Boolean(loadTrust().paths[target]);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function trustProject(dir: string): string {
|
|
161
|
+
const target = normalizeProjectPath(dir);
|
|
162
|
+
const state = loadTrust();
|
|
163
|
+
if (!state.paths[target]) {
|
|
164
|
+
const at = new Date().toISOString();
|
|
165
|
+
state.paths[target] = { grantedAt: at, lastUsed: at };
|
|
166
|
+
saveTrust(state);
|
|
167
|
+
}
|
|
168
|
+
return target;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export function untrustProject(dir: string): boolean {
|
|
172
|
+
const target = normalizeProjectPath(dir);
|
|
173
|
+
const state = loadTrust();
|
|
174
|
+
if (!state.paths[target])
|
|
175
|
+
return false;
|
|
176
|
+
delete state.paths[target];
|
|
177
|
+
saveTrust(state);
|
|
178
|
+
return true;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export function listTrusted(): Array<{ path: string; addedAt: string }> {
|
|
182
|
+
return Object.entries(loadTrust().paths).map(([path, entry]) => ({ path, addedAt: entry.grantedAt }));
|
|
183
|
+
}
|
package/src/index.ts
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
|
+
});
|