titan-agent 2026.4.2 → 2026.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -4629,180 +4629,255 @@ init_config();
4629
4629
  init_constants();
4630
4630
  init_helpers();
4631
4631
  init_memory();
4632
- import { select, input, confirm, password } from "@inquirer/prompts";
4632
+ import { select, input, confirm, password, checkbox } from "@inquirer/prompts";
4633
4633
  import chalk3 from "chalk";
4634
- async function runOnboard(installDaemon) {
4635
- console.log(chalk3.cyan("\n\u{1F680} Welcome to TITAN Setup!\n"));
4636
- console.log(chalk3.gray("This wizard will help you configure your personal AI assistant.\n"));
4634
+ async function fetchOllamaModels(baseUrl) {
4635
+ try {
4636
+ const res = await fetch(`${baseUrl}/api/tags`, { signal: AbortSignal.timeout(3e3) });
4637
+ if (!res.ok) return [];
4638
+ const json = await res.json();
4639
+ return (json.models || []).map((m) => m.name).filter(Boolean);
4640
+ } catch {
4641
+ return [];
4642
+ }
4643
+ }
4644
+ async function runOnboard(_installDaemon) {
4645
+ console.log(chalk3.cyan("\n\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
4646
+ console.log(chalk3.cyan("\u2551 \u26A1 Welcome to TITAN Setup Wizard! \u2551"));
4647
+ console.log(chalk3.cyan("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D\n"));
4648
+ console.log(chalk3.gray("This wizard will configure your personal AI assistant.\n"));
4649
+ console.log(chalk3.gray("Press Ctrl+C at any time to cancel.\n"));
4637
4650
  const config = getDefaultConfig();
4638
- console.log(chalk3.yellow("\u2500\u2500\u2500 Step 1: AI Provider \u2500\u2500\u2500\n"));
4651
+ console.log(chalk3.yellow("\u2500\u2500\u2500 Step 1 of 7: AI Provider \u2500\u2500\u2500\n"));
4639
4652
  const provider = await select({
4640
- message: "Which AI provider would you like to use?",
4653
+ message: "Which AI provider would you like to use as your primary?",
4641
4654
  choices: [
4642
- { name: "Anthropic (Claude) \u2014 Recommended", value: "anthropic" },
4643
- { name: "OpenAI (GPT-4)", value: "openai" },
4644
- { name: "Google (Gemini)", value: "google" },
4645
- { name: "Ollama (Local models)", value: "ollama" }
4655
+ { name: "\u{1F7E3} Anthropic (Claude) \u2014 Best reasoning, recommended", value: "anthropic" },
4656
+ { name: "\u{1F7E2} OpenAI (GPT-4o) \u2014 Great all-rounder", value: "openai" },
4657
+ { name: "\u{1F535} Google (Gemini) \u2014 Fast & multimodal", value: "google" },
4658
+ { name: "\u{1F7E0} Ollama (Local) \u2014 Free, private, runs on your machine", value: "ollama" }
4646
4659
  ]
4647
4660
  });
4661
+ console.log(chalk3.yellow("\n\u2500\u2500\u2500 Step 2 of 7: Model \u2500\u2500\u2500\n"));
4648
4662
  if (provider === "ollama") {
4649
4663
  const ollamaUrl = await input({
4650
4664
  message: "Ollama base URL:",
4651
4665
  default: "http://localhost:11434"
4652
4666
  });
4653
4667
  config.providers.ollama.baseUrl = ollamaUrl;
4654
- config.agent.model = "ollama/llama3.1";
4668
+ console.log(chalk3.gray(`
4669
+ \u{1F50D} Detecting models at ${ollamaUrl}...`));
4670
+ const installedModels = await fetchOllamaModels(ollamaUrl);
4671
+ if (installedModels.length > 0) {
4672
+ console.log(chalk3.green(` \u2705 Found ${installedModels.length} installed model(s)
4673
+ `));
4674
+ const chosen = await select({
4675
+ message: "Select a model to use:",
4676
+ choices: installedModels.map((m) => ({ name: m, value: `ollama/${m}` }))
4677
+ });
4678
+ config.agent.model = chosen;
4679
+ } else {
4680
+ console.log(chalk3.yellow(" \u26A0\uFE0F No models detected (Ollama may not be running, or no models pulled yet)."));
4681
+ console.log(chalk3.gray(" Run: ollama pull llama3.1 to install a model\n"));
4682
+ const modelName = await input({
4683
+ message: "Enter the Ollama model name to use:",
4684
+ default: "llama3.1"
4685
+ });
4686
+ config.agent.model = `ollama/${modelName}`;
4687
+ }
4655
4688
  } else {
4656
4689
  const apiKey = await password({
4657
- message: `Enter your ${provider} API key:`
4690
+ message: `Enter your ${provider} API key:`,
4691
+ mask: "*"
4692
+ });
4693
+ const modelChoices = {
4694
+ anthropic: [
4695
+ { name: "claude-sonnet-4-20250514 (Latest, recommended)", value: "anthropic/claude-sonnet-4-20250514" },
4696
+ { name: "claude-opus-4-0 (Most capable, slower)", value: "anthropic/claude-opus-4-0" },
4697
+ { name: "claude-3-5-haiku-20241022 (Fastest, cheapest)", value: "anthropic/claude-3-5-haiku-20241022" }
4698
+ ],
4699
+ openai: [
4700
+ { name: "gpt-4o (Recommended)", value: "openai/gpt-4o" },
4701
+ { name: "gpt-4o-mini (Fast & cheap)", value: "openai/gpt-4o-mini" },
4702
+ { name: "o3 (Best reasoning)", value: "openai/o3" },
4703
+ { name: "o4-mini (Fast reasoning)", value: "openai/o4-mini" }
4704
+ ],
4705
+ google: [
4706
+ { name: "gemini-2.5-flash (Recommended)", value: "google/gemini-2.5-flash" },
4707
+ { name: "gemini-2.5-pro (Most capable)", value: "google/gemini-2.5-pro" },
4708
+ { name: "gemini-2.0-flash (Fast)", value: "google/gemini-2.0-flash" }
4709
+ ]
4710
+ };
4711
+ const models = modelChoices[provider] || [];
4712
+ const selectedModel = await select({
4713
+ message: "Which model would you like to use?",
4714
+ choices: [...models, { name: "\u270F\uFE0F Enter manually", value: "__manual__" }]
4658
4715
  });
4716
+ if (selectedModel === "__manual__") {
4717
+ config.agent.model = await input({ message: "Enter model identifier:" });
4718
+ } else {
4719
+ config.agent.model = selectedModel;
4720
+ }
4659
4721
  if (provider === "anthropic") {
4660
4722
  config.providers.anthropic.apiKey = apiKey;
4661
- config.agent.model = "anthropic/claude-sonnet-4-20250514";
4662
4723
  } else if (provider === "openai") {
4663
4724
  config.providers.openai.apiKey = apiKey;
4664
- config.agent.model = "openai/gpt-4o";
4665
4725
  } else if (provider === "google") {
4666
4726
  config.providers.google.apiKey = apiKey;
4667
- config.agent.model = "google/gemini-2.5-flash";
4668
4727
  }
4669
- }
4670
- console.log(chalk3.yellow("\n\u2500\u2500\u2500 Step 2: Model \u2500\u2500\u2500\n"));
4671
- const useDefault = await confirm({
4672
- message: `Use default model (${config.agent.model})?`,
4673
- default: true
4674
- });
4675
- if (!useDefault) {
4676
- const model = await input({
4677
- message: "Enter model identifier (e.g., anthropic/claude-opus-4-0):",
4678
- default: config.agent.model
4728
+ const addFallback = await confirm({
4729
+ message: "Add a second provider as fallback (for failover if the primary is unavailable)?",
4730
+ default: false
4679
4731
  });
4680
- config.agent.model = model;
4681
- }
4682
- console.log(chalk3.yellow("\n\u2500\u2500\u2500 Step 3: Channels \u2500\u2500\u2500\n"));
4683
- const enableChannels = await confirm({
4684
- message: "Would you like to set up messaging channels (Discord, Telegram, Slack)?",
4685
- default: false
4686
- });
4687
- if (enableChannels) {
4688
- const selectedChannels = await select({
4689
- message: "Which channel to configure?",
4690
- choices: [
4691
- { name: "Discord", value: "discord" },
4692
- { name: "Telegram", value: "telegram" },
4693
- { name: "Slack", value: "slack" },
4694
- { name: "Skip for now", value: "skip" }
4695
- ]
4696
- });
4697
- if (selectedChannels !== "skip") {
4698
- const token = await password({
4699
- message: `Enter your ${selectedChannels} bot token:`
4732
+ if (addFallback) {
4733
+ const fallbackProviders = ["anthropic", "openai", "google", "ollama"].filter((p) => p !== provider);
4734
+ const fallback = await select({
4735
+ message: "Select fallback provider:",
4736
+ choices: fallbackProviders.map((p) => ({ name: p.charAt(0).toUpperCase() + p.slice(1), value: p }))
4700
4737
  });
4701
- if (selectedChannels === "discord") {
4702
- config.channels.discord.enabled = true;
4703
- config.channels.discord.token = token;
4704
- } else if (selectedChannels === "telegram") {
4705
- config.channels.telegram.enabled = true;
4706
- config.channels.telegram.token = token;
4707
- } else if (selectedChannels === "slack") {
4708
- config.channels.slack.enabled = true;
4709
- config.channels.slack.token = token;
4738
+ if (fallback === "ollama") {
4739
+ const ollamaUrl = await input({ message: "Ollama base URL:", default: "http://localhost:11434" });
4740
+ config.providers.ollama.baseUrl = ollamaUrl;
4741
+ } else {
4742
+ const fallbackKey = await password({ message: `Enter your ${fallback} API key:`, mask: "*" });
4743
+ if (fallback === "anthropic") config.providers.anthropic.apiKey = fallbackKey;
4744
+ else if (fallback === "openai") config.providers.openai.apiKey = fallbackKey;
4745
+ else if (fallback === "google") config.providers.google.apiKey = fallbackKey;
4710
4746
  }
4711
4747
  }
4712
4748
  }
4713
- console.log(chalk3.yellow("\n\u2500\u2500\u2500 Step 4: Security \u2500\u2500\u2500\n"));
4714
- const sandboxMode = await select({
4715
- message: "Sandbox mode for non-main sessions:",
4716
- choices: [
4717
- { name: "Host (full access, single-user)", value: "host" },
4718
- { name: "Docker (isolated containers)", value: "docker" },
4719
- { name: "None (no restrictions)", value: "none" }
4720
- ]
4721
- });
4722
- config.security.sandboxMode = sandboxMode;
4723
- console.log(chalk3.yellow("\n\u2500\u2500\u2500 Step 5: Autonomy \u2500\u2500\u2500\n"));
4724
- console.log(chalk3.gray(" This controls how much freedom TITAN has to act on its own.\n"));
4749
+ console.log(chalk3.yellow("\n\u2500\u2500\u2500 Step 3 of 7: Autonomy \u2500\u2500\u2500\n"));
4750
+ console.log(chalk3.gray(" Controls how independently TITAN acts.\n"));
4725
4751
  const autonomyMode = await select({
4726
4752
  message: "How much autonomy should TITAN have?",
4727
4753
  choices: [
4728
4754
  {
4729
- name: "\u{1F7E1} Supervised (Recommended) \u2014 Safe ops run freely, dangerous ops ask you first",
4755
+ name: "\u{1F7E1} Supervised (Recommended) \u2014 safe ops run freely, dangerous ops ask first",
4730
4756
  value: "supervised"
4731
4757
  },
4732
4758
  {
4733
- name: "\u{1F7E2} Autonomous \u2014 Full auto, TITAN acts without asking. Best for power users.",
4759
+ name: "\u{1F7E2} Autonomous \u2014 full auto, acts without asking. Best for power users.",
4734
4760
  value: "autonomous"
4735
4761
  },
4736
4762
  {
4737
- name: "\u{1F534} Locked \u2014 Every action requires your approval. Maximum control.",
4763
+ name: "\u{1F534} Locked \u2014 every single action requires your approval.",
4738
4764
  value: "locked"
4739
4765
  }
4740
4766
  ]
4741
4767
  });
4742
- config.autonomy = { mode: autonomyMode };
4743
- console.log(chalk3.yellow("\n\u2500\u2500\u2500 Step 6: Gateway \u2500\u2500\u2500\n"));
4744
- const gatewayPort = await input({
4745
- message: "Gateway port:",
4746
- default: "18789"
4768
+ config.autonomy.mode = autonomyMode;
4769
+ console.log(chalk3.yellow("\n\u2500\u2500\u2500 Step 4 of 7: Security \u2500\u2500\u2500\n"));
4770
+ const sandboxMode = await select({
4771
+ message: "Sandbox mode for shell commands:",
4772
+ choices: [
4773
+ { name: "\u{1F5A5}\uFE0F Host (Full access \u2014 single user machines)", value: "host" },
4774
+ { name: "\u{1F433} Docker (Isolated containers \u2014 recommended for shared machines)", value: "docker" },
4775
+ { name: "\u{1F6AB} None (No restrictions \u2014 not recommended)", value: "none" }
4776
+ ]
4777
+ });
4778
+ config.security.sandboxMode = sandboxMode;
4779
+ const enableShield = await confirm({
4780
+ message: "Enable Prompt Injection Shield? (blocks attempts to hijack TITAN via chat messages)",
4781
+ default: true
4782
+ });
4783
+ config.security.shield.enabled = enableShield;
4784
+ if (enableShield) {
4785
+ const shieldMode = await select({
4786
+ message: "Shield strictness:",
4787
+ choices: [
4788
+ { name: "Strict (recommended) \u2014 blocks suspicious payloads aggressively", value: "strict" },
4789
+ { name: "Standard \u2014 blocks only obvious injection attempts", value: "standard" }
4790
+ ]
4791
+ });
4792
+ config.security.shield.mode = shieldMode;
4793
+ }
4794
+ console.log(chalk3.yellow("\n\u2500\u2500\u2500 Step 5 of 7: Messaging Channels \u2500\u2500\u2500\n"));
4795
+ console.log(chalk3.gray(" Connect TITAN to Discord, Telegram, Slack, etc. (all optional)\n"));
4796
+ const channelChoices = await checkbox({
4797
+ message: "Which channels would you like to configure? (space to select, enter to continue)",
4798
+ choices: [
4799
+ { name: "\u{1F3AE} Discord", value: "discord" },
4800
+ { name: "\u2708\uFE0F Telegram", value: "telegram" },
4801
+ { name: "\u{1F4BC} Slack", value: "slack" },
4802
+ { name: "\u23ED\uFE0F Skip \u2014 configure later with `titan config`", value: "skip" }
4803
+ ]
4804
+ });
4805
+ if (!channelChoices.includes("skip")) {
4806
+ for (const channel of channelChoices) {
4807
+ const token = await password({ message: ` ${channel} bot token:`, mask: "*" });
4808
+ if (channel === "discord") {
4809
+ config.channels.discord.enabled = true;
4810
+ config.channels.discord.token = token;
4811
+ } else if (channel === "telegram") {
4812
+ config.channels.telegram.enabled = true;
4813
+ config.channels.telegram.token = token;
4814
+ } else if (channel === "slack") {
4815
+ config.channels.slack.enabled = true;
4816
+ config.channels.slack.token = token;
4817
+ }
4818
+ }
4819
+ }
4820
+ console.log(chalk3.yellow("\n\u2500\u2500\u2500 Step 6 of 7: Gateway \u2500\u2500\u2500\n"));
4821
+ console.log(chalk3.gray(" Mission Control is served at http://127.0.0.1:<port>\n"));
4822
+ const useDefaultPort = await confirm({
4823
+ message: "Use default gateway port (18789)?",
4824
+ default: true
4747
4825
  });
4748
- config.gateway.port = parseInt(gatewayPort, 10);
4826
+ if (!useDefaultPort) {
4827
+ const port = await input({ message: "Enter gateway port:", default: "18789" });
4828
+ config.gateway.port = parseInt(port, 10);
4829
+ }
4830
+ const enableGatewayAuth = await confirm({
4831
+ message: "Enable gateway authentication? (recommended if accessible from other devices)",
4832
+ default: false
4833
+ });
4834
+ if (enableGatewayAuth) {
4835
+ const authMode = await select({
4836
+ message: "Authentication mode:",
4837
+ choices: [
4838
+ { name: "Token (API key in request header)", value: "token" },
4839
+ { name: "Password (browser prompt)", value: "password" }
4840
+ ]
4841
+ });
4842
+ config.gateway.auth.mode = authMode;
4843
+ if (authMode === "token") {
4844
+ config.gateway.auth.token = await password({ message: "Set a gateway token:", mask: "*" });
4845
+ } else {
4846
+ config.gateway.auth.password = await password({ message: "Set a gateway password:", mask: "*" });
4847
+ }
4848
+ }
4849
+ console.log(chalk3.yellow("\n\u2500\u2500\u2500 Step 7 of 7: Logging \u2500\u2500\u2500\n"));
4850
+ const logLevel = await select({
4851
+ message: "Log level:",
4852
+ choices: [
4853
+ { name: "info (recommended)", value: "info" },
4854
+ { name: "debug (verbose \u2014 for troubleshooting)", value: "debug" },
4855
+ { name: "warn (quiet \u2014 warnings and errors only)", value: "warn" },
4856
+ { name: "silent (no logs)", value: "silent" }
4857
+ ]
4858
+ });
4859
+ config.logging.level = logLevel;
4749
4860
  console.log(chalk3.yellow("\n\u2500\u2500\u2500 Setting up workspace \u2500\u2500\u2500\n"));
4750
4861
  ensureDir(TITAN_HOME);
4751
4862
  ensureDir(TITAN_WORKSPACE);
4752
4863
  ensureDir(TITAN_SKILLS_DIR);
4753
4864
  initMemory();
4754
4865
  saveConfig(config);
4755
- if (installDaemon) {
4756
- console.log(chalk3.yellow("\n\u2500\u2500\u2500 Installing daemon \u2500\u2500\u2500\n"));
4757
- await installDaemonService();
4758
- }
4759
4866
  const modeEmoji = autonomyMode === "autonomous" ? "\u{1F7E2}" : autonomyMode === "locked" ? "\u{1F534}" : "\u{1F7E1}";
4760
- console.log(chalk3.green("\n\u2705 TITAN setup complete!\n"));
4761
- console.log(chalk3.white(" Configuration:"));
4762
- console.log(chalk3.gray(` Model: ${config.agent.model}`));
4763
- console.log(chalk3.gray(` Autonomy: ${modeEmoji} ${autonomyMode}`));
4764
- console.log(chalk3.gray(` Sandbox: ${config.security.sandboxMode}`));
4765
- console.log(chalk3.white("\n Quick start:"));
4766
- console.log(chalk3.gray(" $ titan gateway # Start Mission Control"));
4767
- console.log(chalk3.gray(' $ titan agent -m "Hello" # Send a message'));
4768
- console.log(chalk3.gray(" $ titan doctor # Check everything is OK"));
4769
- console.log(chalk3.gray(`
4770
- Config: ${TITAN_CONFIG_PATH}`));
4771
- console.log(chalk3.gray(` Dashboard: http://127.0.0.1:${config.gateway.port}
4772
- `));
4773
- }
4774
- async function installDaemonService() {
4775
- const platform = process.platform;
4776
- if (platform === "linux") {
4777
- console.log(chalk3.gray("Creating systemd user service..."));
4778
- const serviceContent = `[Unit]
4779
- Description=TITAN Gateway
4780
- After=network.target
4781
-
4782
- [Service]
4783
- Type=simple
4784
- ExecStart=${process.execPath} ${process.argv[1]} gateway
4785
- Restart=on-failure
4786
- RestartSec=10
4787
-
4788
- [Install]
4789
- WantedBy=default.target
4790
- `;
4791
- const { writeFileSync: writeFileSync9, mkdirSync: mkdirSync4, existsSync: existsSync14 } = await import("fs");
4792
- const { join: join8 } = await import("path");
4793
- const { homedir: homedir2 } = await import("os");
4794
- const serviceDir = join8(homedir2(), ".config", "systemd", "user");
4795
- if (!existsSync14(serviceDir)) mkdirSync4(serviceDir, { recursive: true });
4796
- writeFileSync9(join8(serviceDir, "titan.service"), serviceContent);
4797
- console.log(chalk3.green(" Service file installed. Enable with:"));
4798
- console.log(chalk3.gray(" $ systemctl --user enable titan"));
4799
- console.log(chalk3.gray(" $ systemctl --user start titan"));
4800
- } else if (platform === "darwin") {
4801
- console.log(chalk3.gray("Creating launchd plist..."));
4802
- console.log(chalk3.yellow(" macOS daemon installation - create a LaunchAgent plist manually."));
4803
- } else {
4804
- console.log(chalk3.yellow(" Daemon installation not supported on this platform."));
4805
- }
4867
+ console.log(chalk3.green("\n\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
4868
+ console.log(chalk3.green("\u2551 \u2705 TITAN is ready! \u2551"));
4869
+ console.log(chalk3.green("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D\n"));
4870
+ console.log(chalk3.white(" Your configuration:"));
4871
+ console.log(chalk3.gray(` Model: ${config.agent.model}`));
4872
+ console.log(chalk3.gray(` Autonomy: ${modeEmoji} ${autonomyMode}`));
4873
+ console.log(chalk3.gray(` Sandbox: ${config.security.sandboxMode}`));
4874
+ console.log(chalk3.gray(` Logs: ${config.logging.level}`));
4875
+ console.log(chalk3.gray(` Config: ${TITAN_CONFIG_PATH}`));
4876
+ console.log(chalk3.white("\n Next steps:"));
4877
+ console.log(chalk3.cyan(" titan gateway ") + chalk3.gray(`\u2192 Open Mission Control at http://127.0.0.1:${config.gateway.port}`));
4878
+ console.log(chalk3.cyan(' titan agent -m "Hello" ') + chalk3.gray("\u2192 Send a direct message"));
4879
+ console.log(chalk3.cyan(" titan doctor ") + chalk3.gray("\u2192 Diagnose configuration & connectivity"));
4880
+ console.log();
4806
4881
  }
4807
4882
 
4808
4883
  // src/security/pairing.ts