@leejungkiin/awkit 1.7.4 → 1.7.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.
package/bin/awk.js CHANGED
@@ -1945,6 +1945,22 @@ function cmdStatus() {
1945
1945
  }
1946
1946
 
1947
1947
  log('');
1948
+
1949
+ // ── GitNexus Info ──────────────────────────────────────────────────────
1950
+ const cwd = process.cwd();
1951
+ const gitnexusDir = path.join(cwd, '.gitnexus');
1952
+ if (fs.existsSync(gitnexusDir)) {
1953
+ const registry = readGitnexusRegistry();
1954
+ const match = registry.find(r => r.path === cwd);
1955
+ if (match) {
1956
+ const stats = match.stats || {};
1957
+ log(`${C.bold}GitNexus Info:${C.reset}`);
1958
+ log(` Active repo: ${C.green}${match.name}${C.reset} (${stats.nodes || 0} nodes, ${stats.edges || 0} relations)`);
1959
+ log(` Tip: Run 'awkit index' to refresh index or 'awkit status gn' for details.`);
1960
+ log('');
1961
+ }
1962
+ }
1963
+
1948
1964
  log(`${C.gray}Tip: 'awkit sync' = harvest (pull live→repo) + install (push repo→live)${C.reset}`);
1949
1965
  log('');
1950
1966
  }
@@ -2026,6 +2042,63 @@ function cmdBuild() {
2026
2042
  }
2027
2043
  }
2028
2044
 
2045
+ // ─── Run: Safe project-specific run/start script execution ──────────────────
2046
+
2047
+ function cmdRun(runArgs = []) {
2048
+ log('');
2049
+ log(`${C.cyan}${C.bold}🚀 AWK Run Control${C.reset}`);
2050
+ log('');
2051
+
2052
+ const pjPath = path.join(process.cwd(), '.project-identity');
2053
+ const pj = readJsonFile(pjPath, {});
2054
+
2055
+ if (!pj.automation || !pj.automation.run) {
2056
+ warn('No run configuration found in .project-identity under "automation.run".');
2057
+ log('Skipping run...');
2058
+ return;
2059
+ }
2060
+
2061
+ const { enabled, command } = pj.automation.run;
2062
+
2063
+ if (!enabled) {
2064
+ warn('Run is disabled for this project (automation.run.enabled is false).');
2065
+ log('Skipping run...');
2066
+ return;
2067
+ }
2068
+
2069
+ if (!command) {
2070
+ err('Run command is not configured in .project-identity (automation.run.command is empty).');
2071
+ return;
2072
+ }
2073
+
2074
+ const fullCommand = runArgs.length > 0 ? `${command} ${runArgs.join(' ')}` : command;
2075
+
2076
+ info(`Running command: ${C.bold}${fullCommand}${C.reset}`);
2077
+ try {
2078
+ execSync(fullCommand, { stdio: 'inherit' });
2079
+ } catch (e) {
2080
+ log('');
2081
+ err(`Run failed: ${e.message}`);
2082
+ process.exit(e.status || 1);
2083
+ }
2084
+ }
2085
+
2086
+ function cmdTask(taskArgs = []) {
2087
+ try {
2088
+ execSync(`symphony task ${taskArgs.join(' ')}`, { stdio: 'inherit' });
2089
+ } catch (e) {
2090
+ process.exit(e.status || 1);
2091
+ }
2092
+ }
2093
+
2094
+ function cmdProject(projectArgs = []) {
2095
+ try {
2096
+ execSync(`symphony project ${projectArgs.join(' ')}`, { stdio: 'inherit' });
2097
+ } catch (e) {
2098
+ process.exit(e.status || 1);
2099
+ }
2100
+ }
2101
+
2029
2102
  async function cmdAdmin() {
2030
2103
  info('Mở Symphony Dashboard...');
2031
2104
  try {
@@ -2115,6 +2188,93 @@ async function cmdRestart() {
2115
2188
  }
2116
2189
  }
2117
2190
 
2191
+ function cmdGreet(args) {
2192
+ let name = 'Guest';
2193
+ let lang = 'en';
2194
+ let timezone = undefined;
2195
+
2196
+ for (let i = 0; i < args.length; i++) {
2197
+ const arg = args[i];
2198
+ if (arg === '--help' || arg === '-h') {
2199
+ log('');
2200
+ log(`${C.cyan}${C.bold}👋 awkit greet — Personalized greetings${C.reset}`);
2201
+ log('');
2202
+ log('Usage:');
2203
+ log(' awkit greet [options]');
2204
+ log('');
2205
+ log('Options:');
2206
+ log(' -n, --name <name> Name of the person to greet (default: "Guest")');
2207
+ log(' -l, --lang <lang> Language code (en, vi, ja) (default: "en")');
2208
+ log(' -t, --timezone <tz> Timezone string (default: system timezone)');
2209
+ log(' -h, --help Show this help message');
2210
+ log('');
2211
+ return;
2212
+ }
2213
+ if ((arg === '--name' || arg === '-n') && args[i + 1] && !args[i + 1].startsWith('-')) {
2214
+ name = args[i + 1];
2215
+ i++;
2216
+ } else if (arg.startsWith('--name=')) {
2217
+ name = arg.split('=')[1];
2218
+ } else if ((arg === '--lang' || arg === '-l') && args[i + 1] && !args[i + 1].startsWith('-')) {
2219
+ lang = args[i + 1];
2220
+ i++;
2221
+ } else if (arg.startsWith('--lang=')) {
2222
+ lang = arg.split('=')[1];
2223
+ } else if ((arg === '--timezone' || arg === '-t') && args[i + 1] && !args[i + 1].startsWith('-')) {
2224
+ timezone = args[i + 1];
2225
+ i++;
2226
+ } else if (arg.startsWith('--timezone=')) {
2227
+ timezone = arg.split('=')[1];
2228
+ }
2229
+ }
2230
+
2231
+ try {
2232
+ const GreetingEngine = require('../core/GreetingEngine');
2233
+ const result = GreetingEngine.generateGreeting({ name, lang, timezone });
2234
+ if (result.status === 'success') {
2235
+ log(result.data.message);
2236
+ } else {
2237
+ err('Failed to generate greeting');
2238
+ }
2239
+ } catch (e) {
2240
+ err('Failed to execute Greeting Engine: ' + e.message);
2241
+ }
2242
+ }
2243
+
2244
+ function cmdGoal(args) {
2245
+ if (args.includes('--help') || args.includes('-h')) {
2246
+ log('');
2247
+ log(`${C.cyan}${C.bold}🎯 awkit goal — Codex Goal Mode Orchestrator${C.reset}`);
2248
+ log('');
2249
+ log('Usage:');
2250
+ log(' awkit goal "<objective>"');
2251
+ log(' awkit goal --status');
2252
+ log(' awkit goal --pause');
2253
+ log(' awkit goal --resume');
2254
+ log(' awkit goal --cancel');
2255
+ log('');
2256
+ return;
2257
+ }
2258
+
2259
+ try {
2260
+ const scriptPath = path.join(__dirname, '..', 'scripts', 'codex-goal.js');
2261
+ const passArgs = [];
2262
+
2263
+ if (args.length > 0 && !args[0].startsWith('-')) {
2264
+ passArgs.push('--prompt', args.join(' '));
2265
+ } else {
2266
+ passArgs.push(...args);
2267
+ }
2268
+
2269
+ const result = spawnSync('node', [scriptPath, ...passArgs], { stdio: 'inherit' });
2270
+ if (result.status !== 0) {
2271
+ process.exit(result.status || 1);
2272
+ }
2273
+ } catch (e) {
2274
+ err('Failed to execute Codex Goal Mode: ' + e.message);
2275
+ }
2276
+ }
2277
+
2118
2278
  function cmdHelp() {
2119
2279
  const line = `${C.gray}${'─'.repeat(56)}${C.reset}`;
2120
2280
  log('');
@@ -2138,9 +2298,13 @@ function cmdHelp() {
2138
2298
  if (identity.automation && identity.automation.build && identity.automation.build.enabled) {
2139
2299
  bCmd = identity.automation.build.command || 'Enabled';
2140
2300
  }
2301
+ let rCmd = 'Disabled';
2302
+ if (identity.automation && identity.automation.run && identity.automation.run.enabled) {
2303
+ rCmd = identity.automation.run.command || 'Enabled';
2304
+ }
2141
2305
 
2142
2306
  log(`${C.cyan}👉 Active Project: ${icon} ${C.bold}${name}${C.reset}`);
2143
- log(` [${pType} | Build Control: ${bCmd === 'Disabled' ? C.yellow + 'Manual' : C.green + bCmd}${C.reset}]`);
2307
+ log(` [${pType} | Build Control: ${bCmd === 'Disabled' ? C.yellow + 'Manual' : C.green + bCmd} | Run Control: ${rCmd === 'Disabled' ? C.yellow + 'Manual' : C.green + rCmd}${C.reset}]`);
2144
2308
  log(` Run ${C.green}awkit identity${C.reset} for full configuration details.`);
2145
2309
  log('');
2146
2310
  } catch (_) {}
@@ -2158,6 +2322,10 @@ function cmdHelp() {
2158
2322
  log(` ${C.green}credentials list${C.reset} List stored API keys`);
2159
2323
  log(` ${C.green}credentials set${C.reset} <k> <v> Set API key (e.g., gemini_api_key, openrouter_api_key)`);
2160
2324
  log(` ${C.green}set-openrouter${C.reset} <key> Shorthand for setting OpenRouter API Key`);
2325
+ log(` ${C.green}config-global list${C.reset} List all global configurations`);
2326
+ log(` ${C.green}config-global set${C.reset} <k> <v> Set global configuration`);
2327
+ log(` ${C.green}config-global audio${C.reset} <on|off> Turn audio alerts on/off globally`);
2328
+ log(` ${C.green}config audio${C.reset} <on|off> Shorthand to turn audio alerts on/off globally`);
2161
2329
  log('');
2162
2330
 
2163
2331
  // Project Init
@@ -2169,6 +2337,7 @@ function cmdHelp() {
2169
2337
  log(` ${C.gray} CODEBASE.md, thiết lập router (AGENTS.md, CLAUDE.md)${C.reset}`);
2170
2338
  log(` ${C.green}identity${C.reset} Show project identity & active configurations (alias: config)`);
2171
2339
  log(` ${C.green}build${C.reset} Run project build based on .project-identity config`);
2340
+ log(` ${C.green}run${C.reset} [args] Run project-specific start/dev commands based on config`);
2172
2341
  log('');
2173
2342
 
2174
2343
  // Maintenance
@@ -2195,10 +2364,31 @@ function cmdHelp() {
2195
2364
  log(` ${C.green}sync${C.reset} Full sync: harvest + install (one shot)`);
2196
2365
  log('');
2197
2366
 
2367
+ // GreetingApp
2368
+ log(`${C.bold}👋 GreetingApp${C.reset}`);
2369
+ log(line);
2370
+ log(` ${C.green}greet${C.reset} [options] Generate personalized dynamic greeting`);
2371
+ log(` ${C.gray} -n, --name <name>${C.reset} User name to greet (default: Guest)`);
2372
+ log(` ${C.gray} -l, --lang <lang>${C.reset} Language code: en, vi, ja (default: en)`);
2373
+ log(` ${C.gray} -t, --timezone <tz>${C.reset} Timezone parameter`);
2374
+ log('');
2375
+
2376
+ // Goal Mode
2377
+ log(`${C.bold}🎯 Goal Mode${C.reset}`);
2378
+ log(line);
2379
+ log(` ${C.green}goal${C.reset} <objective> Pursue a high-level goal using Codex Goal Conductor`);
2380
+ log(` ${C.green}goal --status${C.reset} Check active goal status and task progress`);
2381
+ log(` ${C.green}goal --pause${C.reset} Pause active goal execution loop`);
2382
+ log(` ${C.green}goal --resume${C.reset} Resume paused goal execution`);
2383
+ log(` ${C.green}goal --cancel${C.reset} Cancel and reset active goal session`);
2384
+ log('');
2385
+
2198
2386
  // Symphony
2199
2387
  log(`${C.bold}🎶 Symphony${C.reset}`);
2200
2388
  log(line);
2201
2389
  log(` ${C.green}admin${C.reset} Khởi động Symphony Dashboard`);
2390
+ log(` ${C.green}task${C.reset} [args] Manage Symphony tasks directly`);
2391
+ log(` ${C.green}project${C.reset} [args] Manage Symphony projects directly`);
2202
2392
  log('');
2203
2393
 
2204
2394
  // Packs
@@ -2222,11 +2412,23 @@ function cmdHelp() {
2222
2412
  log('');
2223
2413
 
2224
2414
  // Pipeline
2225
- log(`${C.bold}🔗 Pipeline${C.reset}`);
2415
+ log(`${C.bold}🔗 Pipeline & Sub-Agents${C.reset}`);
2226
2416
  log(line);
2227
- log(` ${C.green}pipeline plan${C.reset} <Feature> Lập kế hoạch kiến trúc (Opus 4.6 via agy)`);
2228
- log(` ${C.green}pipeline ui${C.reset} <Feature> Thiết kế shell & GUI assets (Codex)`);
2229
- log(` ${C.green}pipeline code${C.reset} <Feature> Thực thi viết code & review (Gemini + Critic)`);
2417
+ log(` ${C.green}pipeline plan${C.reset} <Feature> Lập kế hoạch kiến trúc (Claude / Gemini fallback)`);
2418
+ log(` ${C.green}pipeline ui${C.reset} <Feature> Thiết kế shell & GUI assets (Codex / Gemini fallback)`);
2419
+ log(` ${C.green}pipeline code${C.reset} <Feature> Thực thi viết code & review (Qwen / Gemini fallback)`);
2420
+ log('');
2421
+ log(` ${C.bold}🤖 Multi-Agent Collaborative System${C.reset}`);
2422
+ log(` AWKit integrates third-party CLI agents to optimize reasoning quality & costs:`);
2423
+ log(` • ${C.cyan}Claude Code${C.reset} ➜ Architect planning & complex reasoning (Gate 2/4)`);
2424
+ log(` • ${C.cyan}Codex CLI${C.reset} ➜ UI shell design, asset generation & reviews (Gate 2.5/5)`);
2425
+ log(` • ${C.cyan}Qwen Coder${C.reset} ➜ Fast local boilerplate code execution (Gate 4)`);
2426
+ log(` • ${C.cyan}Gemini Flash${C.reset} ➜ Central coordination, active context & fallback router`);
2427
+ log('');
2428
+ log(` To query or configure active runners and alerts:`);
2429
+ log(` - Run ${C.green}awkit config list${C.reset} to show current settings`);
2430
+ log(` - Run ${C.green}awkit config runners${C.reset} to check paths & binary availability`);
2431
+ log(` - Run ${C.green}awkit config <runner> <on|off>${C.reset} to toggle specific runner`);
2230
2432
  log('');
2231
2433
 
2232
2434
  // Trello
@@ -2252,15 +2454,16 @@ function cmdHelp() {
2252
2454
  log('');
2253
2455
 
2254
2456
  // GitNexus
2255
- log(`${C.bold}🔍 GitNexus${C.reset}`);
2457
+ log(`${C.bold}🔍 GitNexus (Shortcuts: index, clean, list)${C.reset}`);
2256
2458
  log(line);
2257
- log(` ${C.green}gitnexus list${C.reset} List all indexed repositories`);
2258
- log(` ${C.green}gitnexus status${C.reset} Show current project index info`);
2259
- log(` ${C.green}gitnexus clean${C.reset} Interactive cleanup of old indexes`);
2260
- log(` ${C.green}gitnexus clean${C.reset} ${C.gray}<name...>${C.reset} Remove specific repo(s) by name`);
2459
+ log(` ${C.green}index${C.reset} Index/refresh current codebase (alias: analyze)`);
2460
+ log(` ${C.green}list${C.reset} List all indexed repositories`);
2461
+ log(` ${C.green}status gn${C.reset} Show current project index info (or 'gn status')`);
2462
+ log(` ${C.green}clean${C.reset} Interactive cleanup of old indexes`);
2463
+ log(` ${C.green}clean${C.reset} ${C.gray}<name...>${C.reset} Remove specific repo(s) by name`);
2261
2464
  log(` ${C.gray} --stale${C.reset} Auto-remove repos with missing paths`);
2262
2465
  log(` ${C.gray} --all${C.reset} Remove all except current project`);
2263
- log(` ${C.gray} Alias: gn${C.reset}`);
2466
+ log(` ${C.gray} Alias: gn / gitnexus${C.reset}`);
2264
2467
  log('');
2265
2468
 
2266
2469
  // Available packs
@@ -2615,6 +2818,16 @@ function buildProjectIdentity(projectName, projectType, cwd, date) {
2615
2818
  automation: {
2616
2819
  autoQA: true,
2617
2820
  maxSelfCorrectionLoops: 3,
2821
+ multiAgent: {
2822
+ enabled: true,
2823
+ routingMode: 'cost-optimized',
2824
+ audioAlerts: true,
2825
+ runners: {
2826
+ claude: 'claude',
2827
+ codex: 'codex',
2828
+ qwen: 'qwen'
2829
+ }
2830
+ },
2618
2831
  telegram: {
2619
2832
  enabled: true,
2620
2833
  chatId: "",
@@ -2862,6 +3075,16 @@ async function cmdInit(forceFlag = false) {
2862
3075
  currentIdentity.automation = {
2863
3076
  autoQA: true,
2864
3077
  maxSelfCorrectionLoops: 3,
3078
+ multiAgent: {
3079
+ enabled: true,
3080
+ routingMode: 'cost-optimized',
3081
+ audioAlerts: true,
3082
+ runners: {
3083
+ claude: 'claude',
3084
+ codex: 'codex',
3085
+ qwen: 'qwen'
3086
+ }
3087
+ },
2865
3088
  telegram: {
2866
3089
  enabled: true,
2867
3090
  chatId: "",
@@ -2902,6 +3125,33 @@ async function cmdInit(forceFlag = false) {
2902
3125
  currentIdentity.automation.maxSelfCorrectionLoops = 3;
2903
3126
  changed = true;
2904
3127
  }
3128
+ if (!currentIdentity.automation.multiAgent) {
3129
+ currentIdentity.automation.multiAgent = {
3130
+ enabled: true,
3131
+ routingMode: 'cost-optimized',
3132
+ audioAlerts: true,
3133
+ runners: {
3134
+ claude: 'claude',
3135
+ codex: 'codex',
3136
+ qwen: 'qwen'
3137
+ }
3138
+ };
3139
+ changed = true;
3140
+ } else {
3141
+ if (!currentIdentity.automation.multiAgent.runners) {
3142
+ currentIdentity.automation.multiAgent.runners = {
3143
+ claude: 'claude',
3144
+ codex: 'codex',
3145
+ qwen: 'qwen'
3146
+ };
3147
+ changed = true;
3148
+ } else {
3149
+ const runners = currentIdentity.automation.multiAgent.runners;
3150
+ if (!runners.claude) { runners.claude = 'claude'; changed = true; }
3151
+ if (!runners.codex) { runners.codex = 'codex'; changed = true; }
3152
+ if (!runners.qwen) { runners.qwen = 'qwen'; changed = true; }
3153
+ }
3154
+ }
2905
3155
  if (!currentIdentity.automation.obsidian) {
2906
3156
  currentIdentity.automation.obsidian = { enabled: false, path: "", autoSync: false };
2907
3157
  changed = true;
@@ -4172,9 +4422,34 @@ function cmdGitnexus(args) {
4172
4422
  break;
4173
4423
  }
4174
4424
 
4425
+ case 'index':
4426
+ case 'analyze':
4427
+ case 'build': {
4428
+ const cwd = process.cwd();
4429
+ const usePnpm = fs.existsSync(path.join(cwd, 'pnpm-lock.yaml'));
4430
+ const indexCmd = usePnpm ? 'pnpm' : 'npx';
4431
+ const indexArgs = usePnpm ? ['dlx', 'gitnexus', 'analyze'] : ['-y', 'gitnexus', 'analyze'];
4432
+
4433
+ info(`Indexing codebase with GitNexus (${usePnpm ? 'pnpm' : 'npx'})...`);
4434
+ dim(`Running: ${indexCmd} ${indexArgs.join(' ')}`);
4435
+
4436
+ const result = spawnSync(indexCmd, indexArgs, {
4437
+ cwd,
4438
+ stdio: 'inherit',
4439
+ shell: true
4440
+ });
4441
+
4442
+ if (result.status === 0) {
4443
+ ok('GitNexus index successfully refreshed! ✨');
4444
+ } else {
4445
+ err(`GitNexus indexing failed with exit code ${result.status}`);
4446
+ }
4447
+ break;
4448
+ }
4449
+
4175
4450
  default:
4176
4451
  err(`Unknown gitnexus sub-command: ${sub}`);
4177
- log(` Available: list, status, clean`);
4452
+ log(` Available: index, list, status, clean`);
4178
4453
  break;
4179
4454
  }
4180
4455
  }
@@ -4352,6 +4627,14 @@ function cmdIdentity(args = []) {
4352
4627
  } else {
4353
4628
  log(` ${C.bold}Build Control:${C.reset} ${C.yellow}Not Configured (Manual Mode)${C.reset}`);
4354
4629
  }
4630
+ if (identity.automation && identity.automation.run) {
4631
+ const r = identity.automation.run;
4632
+ const statusStr = r.enabled ? `${C.green}Enabled (Safe Auto-Run via awkit run)${C.reset}` : `${C.yellow}Disabled / Manual Only${C.reset}`;
4633
+ log(` ${C.bold}Run Control:${C.reset} ${statusStr}`);
4634
+ log(` ${C.bold}Run Command:${C.reset} ${C.cyan}${r.command || 'N/A'}${C.reset}`);
4635
+ } else {
4636
+ log(` ${C.bold}Run Control:${C.reset} ${C.yellow}Not Configured (Manual Mode)${C.reset}`);
4637
+ }
4355
4638
  log('');
4356
4639
 
4357
4640
  // Automation Gates
@@ -4488,6 +4771,335 @@ function cmdServe(args) {
4488
4771
  });
4489
4772
  }
4490
4773
 
4774
+ const GLOBAL_CONFIG_PATH = path.join(HOME, '.awkit_config.json');
4775
+
4776
+ function readGlobalConfig() {
4777
+ const defaultConf = {
4778
+ audioAlerts: true,
4779
+ runners: {
4780
+ claude: true,
4781
+ codex: true,
4782
+ qwen: true
4783
+ }
4784
+ };
4785
+ if (!fs.existsSync(GLOBAL_CONFIG_PATH)) return defaultConf;
4786
+ try {
4787
+ const parsed = JSON.parse(fs.readFileSync(GLOBAL_CONFIG_PATH, 'utf8'));
4788
+ return {
4789
+ ...defaultConf,
4790
+ ...parsed,
4791
+ runners: {
4792
+ ...defaultConf.runners,
4793
+ ...(parsed.runners || {})
4794
+ }
4795
+ };
4796
+ } catch (_) {
4797
+ return defaultConf;
4798
+ }
4799
+ }
4800
+
4801
+ function writeGlobalConfig(config) {
4802
+ try {
4803
+ fs.writeFileSync(GLOBAL_CONFIG_PATH, JSON.stringify(config, null, 2) + '\n', 'utf8');
4804
+ return true;
4805
+ } catch (e) {
4806
+ err(`Failed to write global config: ${e.message}`);
4807
+ return false;
4808
+ }
4809
+ }
4810
+
4811
+ function findClaudePath() {
4812
+ let localConfig = {};
4813
+ try {
4814
+ const p = path.join(process.cwd(), '.project-identity');
4815
+ if (fs.existsSync(p)) {
4816
+ localConfig = JSON.parse(fs.readFileSync(p, 'utf8'));
4817
+ }
4818
+ } catch (_) {}
4819
+
4820
+ if (localConfig.automation?.multiAgent?.runners?.claude) {
4821
+ const p = localConfig.automation.multiAgent.runners.claude;
4822
+ if (fs.existsSync(p)) return p;
4823
+ }
4824
+ const homeClaude = path.join(os.homedir(), '.claude', 'local', 'claude');
4825
+ if (fs.existsSync(homeClaude)) return homeClaude;
4826
+
4827
+ try {
4828
+ const shell = process.env.SHELL || '/bin/zsh';
4829
+ const shellCmd = shell.endsWith('zsh') ? 'zsh -lc "which claude"' : 'bash -lc "which claude"';
4830
+ const detected = execSync(shellCmd, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
4831
+ if (detected && fs.existsSync(detected)) return detected;
4832
+ } catch (_) {}
4833
+
4834
+ return 'claude';
4835
+ }
4836
+
4837
+ function findCodexPath() {
4838
+ let localConfig = {};
4839
+ try {
4840
+ const p = path.join(process.cwd(), '.project-identity');
4841
+ if (fs.existsSync(p)) {
4842
+ localConfig = JSON.parse(fs.readFileSync(p, 'utf8'));
4843
+ }
4844
+ } catch (_) {}
4845
+
4846
+ if (localConfig.automation?.multiAgent?.runners?.codex) {
4847
+ const p = localConfig.automation.multiAgent.runners.codex;
4848
+ if (fs.existsSync(p)) return p;
4849
+ }
4850
+ try {
4851
+ const shell = process.env.SHELL || '/bin/zsh';
4852
+ const shellCmd = shell.endsWith('zsh') ? 'zsh -lc "which codex"' : 'bash -lc "which codex"';
4853
+ const detected = execSync(shellCmd, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
4854
+ if (detected && fs.existsSync(detected)) return detected;
4855
+ } catch (_) {}
4856
+ return 'codex';
4857
+ }
4858
+
4859
+ function findQwenPath() {
4860
+ let localConfig = {};
4861
+ try {
4862
+ const p = path.join(process.cwd(), '.project-identity');
4863
+ if (fs.existsSync(p)) {
4864
+ localConfig = JSON.parse(fs.readFileSync(p, 'utf8'));
4865
+ }
4866
+ } catch (_) {}
4867
+
4868
+ if (localConfig.automation?.multiAgent?.runners?.qwen) {
4869
+ const p = localConfig.automation.multiAgent.runners.qwen;
4870
+ if (fs.existsSync(p)) return p;
4871
+ }
4872
+ try {
4873
+ const shell = process.env.SHELL || '/bin/zsh';
4874
+ const shellCmd = shell.endsWith('zsh') ? 'zsh -lc "which qwen"' : 'bash -lc "which qwen"';
4875
+ const detected = execSync(shellCmd, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
4876
+ if (detected && fs.existsSync(detected)) return detected;
4877
+ } catch (_) {}
4878
+ return 'qwen';
4879
+ }
4880
+
4881
+ function isRunnerEnabled(runnerName) {
4882
+ let localConfig = {};
4883
+ try {
4884
+ const p = path.join(process.cwd(), '.project-identity');
4885
+ if (fs.existsSync(p)) {
4886
+ localConfig = JSON.parse(fs.readFileSync(p, 'utf8'));
4887
+ }
4888
+ } catch (_) {}
4889
+
4890
+ const localVal = localConfig.automation?.multiAgent?.runnersEnabled?.[runnerName];
4891
+ if (localVal !== undefined) return localVal;
4892
+
4893
+ const globalConfig = readGlobalConfig();
4894
+ const globalVal = globalConfig.runners?.[runnerName];
4895
+ if (globalVal !== undefined) return globalVal;
4896
+
4897
+ return true;
4898
+ }
4899
+
4900
+ function isRunnerAvailable(runnerName) {
4901
+ if (!isRunnerEnabled(runnerName)) return false;
4902
+ let p;
4903
+ if (runnerName === 'claude') p = findClaudePath();
4904
+ else if (runnerName === 'codex') p = findCodexPath();
4905
+ else if (runnerName === 'qwen') p = findQwenPath();
4906
+ else return false;
4907
+
4908
+ if (fs.existsSync(p)) return true;
4909
+ try {
4910
+ execSync(`which ${p} || command -v ${p}`, { stdio: 'ignore' });
4911
+ return true;
4912
+ } catch (_) {
4913
+ return false;
4914
+ }
4915
+ }
4916
+
4917
+ function cmdGlobalConfig(args = []) {
4918
+ const sub = args[0];
4919
+ if (sub === 'audio') {
4920
+ const config = readGlobalConfig();
4921
+ const action = args[1];
4922
+ if (!action) {
4923
+ info(`Audio Alerts: ${config.audioAlerts ? 'ON' : 'OFF'}`);
4924
+ return;
4925
+ }
4926
+ if (action === 'on' || action === 'true' || action === '1') {
4927
+ config.audioAlerts = true;
4928
+ if (writeGlobalConfig(config)) {
4929
+ ok('Audio alerts turned ON globally.');
4930
+ }
4931
+ } else if (action === 'off' || action === 'false' || action === '0') {
4932
+ config.audioAlerts = false;
4933
+ if (writeGlobalConfig(config)) {
4934
+ ok('Audio alerts turned OFF globally.');
4935
+ }
4936
+ } else {
4937
+ err(`Invalid audio option: ${action}. Use 'on' or 'off'.`);
4938
+ }
4939
+ return;
4940
+ }
4941
+
4942
+ if (sub === 'claude' || sub === 'codex' || sub === 'qwen') {
4943
+ const config = readGlobalConfig();
4944
+ const action = args[1];
4945
+ if (!action) {
4946
+ info(`${sub.toUpperCase()} Runner: ${config.runners?.[sub] !== false ? 'ON' : 'OFF'}`);
4947
+ return;
4948
+ }
4949
+ if (action === 'on' || action === 'true' || action === '1') {
4950
+ config.runners = config.runners || {};
4951
+ config.runners[sub] = true;
4952
+ if (writeGlobalConfig(config)) {
4953
+ ok(`${sub.toUpperCase()} runner turned ON globally.`);
4954
+ }
4955
+ } else if (action === 'off' || action === 'false' || action === '0') {
4956
+ config.runners = config.runners || {};
4957
+ config.runners[sub] = false;
4958
+ if (writeGlobalConfig(config)) {
4959
+ ok(`${sub.toUpperCase()} runner turned OFF globally.`);
4960
+ }
4961
+ } else {
4962
+ err(`Invalid runner option: ${action}. Use 'on' or 'off'.`);
4963
+ }
4964
+ return;
4965
+ }
4966
+
4967
+ if (sub === 'runners' || sub === 'runner') {
4968
+ const line = `${C.gray}${'─'.repeat(60)}${C.reset}`;
4969
+ log('');
4970
+ log(`${C.cyan}${C.bold}🏃 RUNNER AGENTS STATUS${C.reset}`);
4971
+ log(line);
4972
+
4973
+ const runners = ['claude', 'codex', 'qwen'];
4974
+ for (const r of runners) {
4975
+ const enabled = isRunnerEnabled(r);
4976
+ const path = r === 'claude' ? findClaudePath() : (r === 'codex' ? findCodexPath() : findQwenPath());
4977
+ const available = isRunnerAvailable(r);
4978
+
4979
+ const statusStr = enabled ? `${C.green}ON${C.reset}` : `${C.red}OFF${C.reset}`;
4980
+ const availStr = available ? `${C.green}✅ Available${C.reset}` : `${C.red}❌ Unavailable${C.reset}`;
4981
+
4982
+ log(` ● ${C.bold}${r.padEnd(8)}${C.reset} Status: [${statusStr}] Path: ${path.padEnd(30)} [${availStr}]`);
4983
+ }
4984
+ log('');
4985
+ return;
4986
+ }
4987
+
4988
+ if (sub === 'models' || sub === 'model') {
4989
+ const line = `${C.gray}${'─'.repeat(60)}${C.reset}`;
4990
+ log('');
4991
+ log(`${C.cyan}${C.bold}🤖 MODEL ROUTING POLICY${C.reset}`);
4992
+ log(line);
4993
+
4994
+ let localConfig = {};
4995
+ let projectLoaded = false;
4996
+ try {
4997
+ const p = path.join(process.cwd(), '.project-identity');
4998
+ if (fs.existsSync(p)) {
4999
+ localConfig = JSON.parse(fs.readFileSync(p, 'utf8'));
5000
+ projectLoaded = true;
5001
+ }
5002
+ } catch (_) {}
5003
+
5004
+ if (projectLoaded) {
5005
+ log(` Project: ${C.green}${C.bold}${localConfig.projectName || 'Active Project'}${C.reset}`);
5006
+ const policy = localConfig.modelPolicy || {};
5007
+ log(` Routing Mode: ${C.cyan}${policy.mode || 'auto'}${C.reset}`);
5008
+ log(` Default Tier: ${C.cyan}${policy.defaultTier || 'STANDARD'}${C.reset}`);
5009
+
5010
+ if (policy.tierOverrides && Object.keys(policy.tierOverrides).length > 0) {
5011
+ log(` Tier Overrides:`);
5012
+ for (const [pattern, tier] of Object.entries(policy.tierOverrides)) {
5013
+ log(` * ${pattern.padEnd(20)} ➜ ${C.yellow}${tier}${C.reset}`);
5014
+ }
5015
+ }
5016
+ } else {
5017
+ log(` No project loaded in current directory.`);
5018
+ }
5019
+
5020
+ log(line);
5021
+ log(` ${C.bold}Default Models:${C.reset}`);
5022
+ log(` * Standard execution: ${C.green}gemini-3.5-flash${C.reset} (Fast & cost-efficient)`);
5023
+ log(` * Heavy / Spec tasks: ${C.green}gemini-3.1-pro${C.reset} (High reasoning quality)`);
5024
+ log(` * Light tasks: ${C.green}gemini-3.1-flash-lite${C.reset} (Ultra-low latency)`);
5025
+ log('');
5026
+ return;
5027
+ }
5028
+
5029
+ if (!sub || sub === 'list') {
5030
+ const config = readGlobalConfig();
5031
+ const line = `${C.gray}${'─'.repeat(60)}${C.reset}`;
5032
+ log('');
5033
+ log(`${C.cyan}${C.bold}⚙️ AWK CLI CONFIGURATION${C.reset}`);
5034
+ log(line);
5035
+
5036
+ const formatBool = (val) => val !== false ? `${C.green}ON${C.reset}` : `${C.red}OFF${C.reset}`;
5037
+
5038
+ log(` ${C.bold}audioAlerts${C.reset.padEnd(20)} ${formatBool(config.audioAlerts).padEnd(15)} Play speech and chimes on complete`);
5039
+ log(` ${C.bold}runners.claude${C.reset.padEnd(20)} ${formatBool(config.runners?.claude).padEnd(15)} Use Claude Code CLI for planning`);
5040
+ log(` ${C.bold}runners.codex${C.reset.padEnd(20)} ${formatBool(config.runners?.codex).padEnd(15)} Use Codex CLI for UI and assets`);
5041
+ log(` ${C.bold}runners.qwen${C.reset.padEnd(20)} ${formatBool(config.runners?.qwen).padEnd(15)} Use Qwen Coder CLI for execution`);
5042
+ log(line);
5043
+ log(`💡 To toggle, run: ${C.cyan}awkit config <param> <on|off>${C.reset}`);
5044
+ log(` Example: ${C.cyan}awkit config claude off${C.reset}`);
5045
+ log('');
5046
+ return;
5047
+ }
5048
+
5049
+ if (sub === 'get' && args[1]) {
5050
+ const config = readGlobalConfig();
5051
+ const key = args[1];
5052
+ let val;
5053
+ if (key.includes('.')) {
5054
+ val = key.split('.').reduce((acc, part) => acc && acc[part], config);
5055
+ } else {
5056
+ val = config[key];
5057
+ }
5058
+ if (val !== undefined) {
5059
+ console.log(val);
5060
+ } else {
5061
+ err(`Key "${key}" not found in global config.`);
5062
+ }
5063
+ return;
5064
+ }
5065
+
5066
+ if (sub === 'set' && args[1] && args[2] !== undefined) {
5067
+ const config = readGlobalConfig();
5068
+ const key = args[1];
5069
+ let val = args[2];
5070
+ if (val === 'true') val = true;
5071
+ if (val === 'false') val = false;
5072
+
5073
+ if (key.includes('.')) {
5074
+ const parts = key.split('.');
5075
+ let current = config;
5076
+ for (let i = 0; i < parts.length - 1; i++) {
5077
+ current[parts[i]] = current[parts[i]] || {};
5078
+ current = current[parts[i]];
5079
+ }
5080
+ current[parts[parts.length - 1]] = val;
5081
+ } else {
5082
+ config[key] = val;
5083
+ }
5084
+
5085
+ if (writeGlobalConfig(config)) {
5086
+ ok(`Global config updated: "${key}" = ${val}`);
5087
+ }
5088
+ return;
5089
+ }
5090
+
5091
+ err('Usage:');
5092
+ log(' awkit config list');
5093
+ log(' awkit config get <key>');
5094
+ log(' awkit config set <key> <value>');
5095
+ log(' awkit config audio <on|off>');
5096
+ log(' awkit config claude <on|off>');
5097
+ log(' awkit config codex <on|off>');
5098
+ log(' awkit config qwen <on|off>');
5099
+ log(' awkit config runners');
5100
+ log(' awkit config models');
5101
+ }
5102
+
4491
5103
  // ─── Main ────────────────────────────────────────────────────────────────────
4492
5104
 
4493
5105
  const [, , command, ...args] = process.argv;
@@ -4498,6 +5110,15 @@ const [, , command, ...args] = process.argv;
4498
5110
  case 'build':
4499
5111
  cmdBuild();
4500
5112
  break;
5113
+ case 'run':
5114
+ cmdRun(args);
5115
+ break;
5116
+ case 'task':
5117
+ cmdTask(args);
5118
+ break;
5119
+ case 'project':
5120
+ cmdProject(args);
5121
+ break;
4501
5122
  case 'init':
4502
5123
  await cmdInit(args.includes('--force'));
4503
5124
  break;
@@ -4514,7 +5135,11 @@ const [, , command, ...args] = process.argv;
4514
5135
  cmdSync();
4515
5136
  break;
4516
5137
  case 'status':
4517
- cmdStatus();
5138
+ if (args[0] === 'gn' || args[0] === 'gitnexus') {
5139
+ cmdGitnexus(['status']);
5140
+ } else {
5141
+ cmdStatus();
5142
+ }
4518
5143
  break;
4519
5144
  case 'harvest':
4520
5145
  cmdHarvest(args.includes('--dry-run'));
@@ -4562,6 +5187,16 @@ const [, , command, ...args] = process.argv;
4562
5187
  case 'creds':
4563
5188
  cmdCredentials(args);
4564
5189
  break;
5190
+ case 'index':
5191
+ case 'analyze':
5192
+ cmdGitnexus(['index', ...args]);
5193
+ break;
5194
+ case 'clean':
5195
+ cmdGitnexus(['clean', ...args]);
5196
+ break;
5197
+ case 'list':
5198
+ cmdGitnexus(['list', ...args]);
5199
+ break;
4565
5200
  case 'set-openrouter': {
4566
5201
  const key = args[0];
4567
5202
  if (!key) {
@@ -4603,11 +5238,34 @@ const [, , command, ...args] = process.argv;
4603
5238
  case 'restart':
4604
5239
  await cmdRestart();
4605
5240
  break;
5241
+ case 'greet':
5242
+ cmdGreet(args);
5243
+ break;
5244
+ case 'goal':
5245
+ cmdGoal(args);
5246
+ break;
5247
+ case 'config-global':
5248
+ cmdGlobalConfig(args);
5249
+ break;
4606
5250
  case 'identity':
4607
5251
  case 'config':
4608
- case 'project':
4609
- cmdIdentity(args);
5252
+ case 'project': {
5253
+ const isGlobalSub = command === 'config' && (
5254
+ args.length === 0 ||
5255
+ ['audio', 'claude', 'codex', 'qwen', 'runners', 'runner', 'models', 'model', 'list', 'get', 'set'].includes(args[0])
5256
+ );
5257
+ if (isGlobalSub) {
5258
+ // If there are no arguments but a project identity exists, show project identity as default
5259
+ if (args.length === 0 && fs.existsSync(path.join(process.cwd(), '.project-identity'))) {
5260
+ cmdIdentity(args);
5261
+ } else {
5262
+ cmdGlobalConfig(args);
5263
+ }
5264
+ } else {
5265
+ cmdIdentity(args);
5266
+ }
4610
5267
  break;
5268
+ }
4611
5269
  case 'help':
4612
5270
  case '--help':
4613
5271
  case '-h':