twinclaw 1.1.8 → 1.2.0

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/core/cli.js CHANGED
@@ -164,15 +164,34 @@ export const handleVersionCli = async (argv) => {
164
164
  };
165
165
  /**
166
166
  * Handle the `start` command.
167
- * Starts the gateway.
167
+ * Starts the gateway in a new terminal window.
168
168
  */
169
169
  export const handleStartCli = async (argv, isChatMode = false) => {
170
- // We return 0 to tell main() to continue with the startup flow
171
- // If we wanted to start JUST the gateway and exit, we'd do something else
172
- // But here, 'start' means 'run the app'.
173
170
  if (isChatMode) {
174
171
  global.TWINCLAW_CHAT_MODE = true;
175
172
  }
173
+ if (process.platform === 'win32') {
174
+ const { spawn } = await import('node:child_process');
175
+ console.log('[TwinClaw] Starting gateway in new window...');
176
+ const entryScript = process.execPath;
177
+ const appPath = process.argv[1] || process.argv[0];
178
+ const args = [
179
+ '/c',
180
+ 'start',
181
+ '""',
182
+ 'cmd.exe',
183
+ '/k',
184
+ `"${entryScript}" chat`
185
+ ];
186
+ spawn('cmd.exe', args, {
187
+ detached: true,
188
+ stdio: 'ignore',
189
+ windowsHide: false,
190
+ });
191
+ console.log('[TwinClaw] Gateway started in new terminal window.');
192
+ console.log('[TwinClaw] You can now use this terminal for other commands.');
193
+ return 0;
194
+ }
176
195
  return 0;
177
196
  };
178
197
  /**
@@ -45,7 +45,12 @@ const WIZARD_SECTIONS = [
45
45
  {
46
46
  id: 'models',
47
47
  label: 'STEP 3: AI Models',
48
- fields: ['OPENROUTER_API_KEY', 'MODAL_API_KEY', 'GEMINI_API_KEY', 'GROQ_API_KEY'],
48
+ fields: ['OPENROUTER_API_KEY', 'MODAL_API_KEY', 'GEMINI_API_KEY', 'GROQ_API_KEY', 'GITHUB_TOKEN'],
49
+ },
50
+ {
51
+ id: 'model_selection',
52
+ label: 'STEP 4: Select Models',
53
+ fields: ['SELECT_MODELS'],
49
54
  },
50
55
  ];
51
56
  const EMBEDDING_PROVIDERS = new Set(['openai', 'ollama']);
@@ -767,6 +772,17 @@ async function collectInteractiveUpdates(existing, logger, prompter) {
767
772
  candidate = applyUpdates(candidate, { [field.key]: nextValue });
768
773
  }
769
774
  }
775
+ if (section.id === 'model_selection') {
776
+ // Special logic to ensure model selection was actually handled if skipped in loop
777
+ if (!updates['SELECT_MODELS']) {
778
+ const field = ONBOARD_FIELDS.find(f => f.key === 'SELECT_MODELS');
779
+ const nextValue = await promptFieldValue(field, '', prompter, logger);
780
+ if (nextValue) {
781
+ updates['SELECT_MODELS'] = nextValue;
782
+ candidate = applyUpdates(candidate, { 'SELECT_MODELS': nextValue });
783
+ }
784
+ }
785
+ }
770
786
  if (section.id === 'models' && !hasAnyModelKey(candidate)) {
771
787
  logger.warn('\nAt least one model API key is required (OpenRouter, Modal, Gemini).');
772
788
  // Force retry this section
@@ -871,9 +887,9 @@ function printWarnings(warnings, logger) {
871
887
  }
872
888
  function printNextActions(logger) {
873
889
  logger.log('\nNext actions:');
874
- logger.log(' 1. node src/index.ts doctor');
875
- logger.log(' 2. node src/index.ts channels login whatsapp');
876
- logger.log(' 3. node src/index.ts pairing approve <channel> <CODE>');
890
+ logger.log(' 1. twinclaw doctor');
891
+ logger.log(' 2. twinclaw channels login whatsapp');
892
+ logger.log(' 3. twinclaw pairing approve <channel> <CODE>');
877
893
  }
878
894
  export function parseOnboardArgs(args) {
879
895
  const parsed = {
@@ -0,0 +1,78 @@
1
+ import { getPersonaStateService } from '../services/persona-state.js';
2
+ import { formatHelp } from './help-formatter.js';
3
+ /**
4
+ * Handle 'persona' command.
5
+ * Allows viewing and updating the agent's soul, identity, and user context.
6
+ */
7
+ export const handlePersonaCli = async (argv) => {
8
+ const service = getPersonaStateService();
9
+ const subcommand = argv[1];
10
+ if (!subcommand || subcommand === 'show' || subcommand === 'view') {
11
+ const state = await service.getState();
12
+ console.log(', TwinClaw, Persona, State, ');, console.log('──────────────────────────────────────────────────'));
13
+ console.log(`Revision: ${state.revision}`);
14
+ console.log(`Updated: ${state.updatedAt}`);
15
+ console.log(', -- - SOUL-- - ');, console.log(state.soul || '(empty)'));
16
+ console.log(', -- - IDENTITY-- - ');, console.log(state.identity || '(empty)'));
17
+ console.log(', -- - USER, CONTEXT-- - ');, console.log(state.user || '(empty)'));
18
+ return 0;
19
+ }
20
+ if (subcommand === 'edit' || subcommand === 'update') {
21
+ const key = argv[2];
22
+ if (!['soul', 'identity', 'user'].includes(key)) {
23
+ console.error('Invalid document key. Use: soul, identity, or user.');
24
+ return 1;
25
+ }
26
+ const newContent = argv.slice(3).join(' ');
27
+ if (!newContent) {
28
+ console.error(`Please provide the new content for ${key}.`);
29
+ console.log(`Usage: twinclaw persona edit ${key} "your content here"`);
30
+ return 1;
31
+ }
32
+ try {
33
+ const currentState = await service.getState();
34
+ const result = await service.updateState({
35
+ expectedRevision: currentState.revision,
36
+ [key]: newContent,
37
+ // Keep other fields as is
38
+ soul: key === 'soul' ? newContent : currentState.soul,
39
+ identity: key === 'identity' ? newContent : currentState.identity,
40
+ user: key === 'user' ? newContent : currentState.user,
41
+ });
42
+ console.log(`✅ Successfully updated ${key}.`);
43
+ console.log(`New revision: ${result.state.revision}`);
44
+ return 0;
45
+ }
46
+ catch (err) {
47
+ console.error(`❌ Failed to update persona: ${err instanceof Error ? err.message : String(err)}`);
48
+ return 1;
49
+ }
50
+ }
51
+ if (subcommand === 'help' || argv.includes('--help') || argv.includes('-h')) {
52
+ console.log(formatHelp({
53
+ command: 'persona',
54
+ description: "View or modify the agent's core persona (soul, identity, user context).",
55
+ usage: 'twinclaw persona [subcommand] [args]',
56
+ sections: [
57
+ {
58
+ title: 'Subcommands',
59
+ content: {
60
+ 'show, view': 'Display current persona state (default)',
61
+ 'edit, update': 'Update a specific part of the persona'
62
+ }
63
+ },
64
+ {
65
+ title: 'Update Usage',
66
+ content: {
67
+ 'twinclaw persona edit soul "text"': 'Update the agent soul',
68
+ 'twinclaw persona edit identity "text"': 'Update agent identity',
69
+ 'twinclaw persona edit user "text"': 'Update user context'
70
+ }
71
+ }
72
+ ]
73
+ }));
74
+ return 0;
75
+ }
76
+ console.error(`Unknown persona subcommand: ${subcommand}`);
77
+ return 1;
78
+ };
@@ -980,8 +980,23 @@ export class ModelRouter {
980
980
  this.metrics.lastError = scrubSensitiveText(message);
981
981
  this.metrics.lastFailureAt = new Date(this.nowFn()).toISOString();
982
982
  }
983
+ getApiKeyEnvName(provider) {
984
+ const providerKeyMap = {
985
+ 'openai': 'OPENAI_API_KEY',
986
+ 'anthropic': 'ANTHROPIC_API_KEY',
987
+ 'google': 'GEMINI_API_KEY',
988
+ 'openrouter': 'OPENROUTER_API_KEY',
989
+ 'groq': 'GROQ_API_KEY',
990
+ 'modal': 'MODAL_API_KEY',
991
+ 'copilot': 'GITHUB_TOKEN',
992
+ 'github': 'GITHUB_TOKEN',
993
+ 'ollama': 'OLLAMA_API_KEY',
994
+ };
995
+ return providerKeyMap[provider.toLowerCase()] || 'MODAL_API_KEY';
996
+ }
983
997
  loadModelsFromConfig() {
984
998
  const configModels = [];
999
+ // Check if PRIMARY_MODEL is set - use config.models.primaryModel if available
985
1000
  const primaryModelId = getConfigValue('PRIMARY_MODEL');
986
1001
  const modalApiKey = getConfigValue('MODAL_API_KEY');
987
1002
  const openRouterApiKey = getConfigValue('OPENROUTER_API_KEY');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "twinclaw",
3
- "version": "1.1.8",
3
+ "version": "1.2.0",
4
4
  "description": "Eagle-eyed agentic AI gateway with multi-modal hooks and proactive memory.",
5
5
  "main": "dist/index.js",
6
6
  "bin": {