@covibes/zeroshot 1.5.0 → 2.0.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/CHANGELOG.md CHANGED
@@ -1,3 +1,23 @@
1
+ # [2.0.0](https://github.com/covibes/zeroshot/compare/v1.5.0...v2.0.0) (2025-12-29)
2
+
3
+
4
+ ### Code Refactoring
5
+
6
+ * **cli:** simplify flag hierarchy with cascading --ship → --pr → --isolation ([#18](https://github.com/covibes/zeroshot/issues/18)) ([5718ead](https://github.com/covibes/zeroshot/commit/5718ead37f1771a5dfa68dd9b4f55f73e1f6b9d7)), closes [#17](https://github.com/covibes/zeroshot/issues/17)
7
+
8
+
9
+ ### BREAKING CHANGES
10
+
11
+ * **cli:** `auto` command removed, use `run --ship` instead
12
+
13
+ - Remove `auto` command (use `run --ship` for full automation)
14
+ - Add `--ship` flag: isolation + PR + auto-merge
15
+ - `--pr` now auto-enables `--isolation`
16
+ - Rename `clear` → `purge` for clarity
17
+ - Update help text with cascading flag examples
18
+ - Add `agents` command stubs
19
+ - Add `--json` output support to list/status
20
+
1
21
  # [1.5.0](https://github.com/covibes/zeroshot/compare/v1.4.0...v1.5.0) (2025-12-28)
2
22
 
3
23
 
package/README.md CHANGED
@@ -67,19 +67,33 @@ gh auth login
67
67
  ## Commands
68
68
 
69
69
  ```bash
70
- zeroshot 123 # Run on GitHub issue
71
- zeroshot "Add dark mode" # Run from description
70
+ zeroshot run 123 # Run on GitHub issue
71
+ zeroshot run "Add dark mode" # Run from description
72
72
 
73
- # Full automation (isolated container + PR + merge)
74
- zeroshot auto 123
75
- zeroshot auto 123 -d # Background
73
+ # Automation levels (cascading: --ship --pr --isolation)
74
+ zeroshot run 123 --isolation # Docker isolation, no PR
75
+ zeroshot run 123 --pr # Isolation + PR (human reviews)
76
+ zeroshot run 123 --ship # Isolation + PR + auto-merge (full automation)
77
+
78
+ # Background mode
79
+ zeroshot run 123 -d # Detached/daemon
80
+ zeroshot run 123 --ship -d # Full automation, background
76
81
 
77
82
  # Control
78
- zeroshot list # See all running
83
+ zeroshot list # See all running (--json for scripting)
84
+ zeroshot status <id> # Cluster status (--json for scripting)
79
85
  zeroshot logs <id> -f # Follow output
80
86
  zeroshot resume <id> # Continue after crash
81
87
  zeroshot kill <id> # Stop
82
88
  zeroshot watch # TUI dashboard
89
+
90
+ # Agent library
91
+ zeroshot agents list # View available agents
92
+ zeroshot agents show <name> # Agent details
93
+
94
+ # Maintenance
95
+ zeroshot clean # Remove old records
96
+ zeroshot purge # NUCLEAR: kill all + delete all
83
97
  ```
84
98
 
85
99
  ---
package/cli/index.js CHANGED
@@ -47,6 +47,8 @@ const {
47
47
  DEFAULT_SETTINGS,
48
48
  } = require('../lib/settings');
49
49
  const { requirePreflight } = require('../src/preflight');
50
+ const { checkFirstRun } = require('./lib/first-run');
51
+ const { checkForUpdates } = require('./lib/update-checker');
50
52
  const { StatusFooter } = require('../src/status-footer');
51
53
 
52
54
  const program = new Command();
@@ -375,11 +377,12 @@ program
375
377
  .name('zeroshot')
376
378
  .description('Multi-agent orchestration and task management for Claude')
377
379
  .version(require('../package.json').version)
380
+ .option('-q, --quiet', 'Suppress prompts (first-run wizard, update checks)')
378
381
  .addHelpText(
379
382
  'after',
380
383
  `
381
384
  Examples:
382
- ${chalk.cyan('zeroshot auto 123')} Full automation: isolated + auto-merge PR
385
+ ${chalk.cyan('zeroshot run 123 --ship')} Full automation: isolated + auto-merge PR
383
386
  ${chalk.cyan('zeroshot run 123')} Run cluster and attach to first agent
384
387
  ${chalk.cyan('zeroshot run 123 -d')} Run cluster in background (detached)
385
388
  ${chalk.cyan('zeroshot run "Implement feature X"')} Run cluster on plain text task
@@ -395,19 +398,20 @@ Examples:
395
398
  ${chalk.cyan('zeroshot status <id>')} Detailed status of task or cluster
396
399
  ${chalk.cyan('zeroshot finish <id>')} Convert cluster to completion task (creates and merges PR)
397
400
  ${chalk.cyan('zeroshot kill <id>')} Kill a running task or cluster
398
- ${chalk.cyan('zeroshot clear')} Kill all processes and delete all data (with confirmation)
399
- ${chalk.cyan('zeroshot clear -y')} Clear everything without confirmation
401
+ ${chalk.cyan('zeroshot purge')} Kill all processes and delete all data (with confirmation)
402
+ ${chalk.cyan('zeroshot purge -y')} Purge everything without confirmation
400
403
  ${chalk.cyan('zeroshot settings')} Show/manage zeroshot settings (default model, config, etc.)
401
404
  ${chalk.cyan('zeroshot settings set <key> <val>')} Set a setting (e.g., defaultModel haiku)
402
405
  ${chalk.cyan('zeroshot config list')} List available cluster configs
403
406
  ${chalk.cyan('zeroshot config show <name>')} Visualize a cluster config (agents, triggers, flow)
404
407
  ${chalk.cyan('zeroshot export <id>')} Export cluster conversation to file
405
408
 
406
- Cluster vs Task:
407
- ${chalk.yellow('zeroshot auto')} Full automation (isolated + auto-merge PR)
408
- ${chalk.yellow('zeroshot run')} Multi-agent cluster (auto-attaches, Ctrl+B d to detach)
409
- ${chalk.yellow('zeroshot run -d')} Multi-agent cluster (background/detached)
410
- ${chalk.yellow('zeroshot task run')} Single-agent background task (simpler, faster)
409
+ Automation levels (cascading: --ship → --pr → --isolation):
410
+ ${chalk.yellow('zeroshot run 123')} Local run, no isolation
411
+ ${chalk.yellow('zeroshot run 123 --isolation')} Docker isolation, no PR
412
+ ${chalk.yellow('zeroshot run 123 --pr')} Isolation + PR (human reviews)
413
+ ${chalk.yellow('zeroshot run 123 --ship')} Isolation + PR + auto-merge (full automation)
414
+ ${chalk.yellow('zeroshot task run')} → Single-agent background task (simpler, faster)
411
415
 
412
416
  Shell completion:
413
417
  ${chalk.dim('zeroshot --completion >> ~/.bashrc && source ~/.bashrc')}
@@ -429,8 +433,8 @@ program
429
433
  '--strict-schema',
430
434
  'Enforce JSON schema via CLI (no live streaming). Default: live streaming with local validation'
431
435
  )
432
- .option('--pr', 'Create PR and merge on successful completion (requires --isolation)')
433
- .option('--full', 'Shorthand for --isolation --pr (full automation)')
436
+ .option('--pr', 'Create PR for human review (auto-enables --isolation)')
437
+ .option('--ship', 'Full automation: isolation + PR + auto-merge')
434
438
  .option('--workers <n>', 'Max sub-agents for worker to spawn in parallel', parseInt)
435
439
  .option('-d, --detach', 'Run in background (default: attach to first agent)')
436
440
  .addHelpText(
@@ -445,10 +449,15 @@ Input formats:
445
449
  )
446
450
  .action(async (inputArg, options) => {
447
451
  try {
448
- // Expand --full to --isolation + --pr
449
- if (options.full) {
450
- options.isolation = true;
452
+ // Cascading flag implications: --ship --pr --isolation
453
+ // --ship = full automation (isolation + PR + auto-merge)
454
+ if (options.ship) {
451
455
  options.pr = true;
456
+ options.isolation = true;
457
+ }
458
+ // --pr = PR for human review (auto-enables isolation)
459
+ if (options.pr) {
460
+ options.isolation = true;
452
461
  }
453
462
 
454
463
  // Auto-detect input type
@@ -476,18 +485,12 @@ Input formats:
476
485
  // This gives users clear, actionable error messages upfront
477
486
  const preflightOptions = {
478
487
  requireGh: !!input.issue, // gh CLI required when fetching GitHub issues
479
- requireDocker: options.isolation || options.full, // Docker required for isolation mode
488
+ requireDocker: options.isolation, // Docker required for isolation mode
480
489
  quiet: process.env.CREW_DAEMON === '1', // Suppress success in daemon mode
481
490
  };
482
491
  requirePreflight(preflightOptions);
483
492
 
484
493
  // === CLUSTER MODE ===
485
- // Validate --pr requires --isolation
486
- if (options.pr && !options.isolation) {
487
- console.error(chalk.red('Error: --pr requires --isolation flag for safety'));
488
- console.error(chalk.dim(' Usage: zeroshot run 123 --isolation --pr'));
489
- process.exit(1);
490
- }
491
494
 
492
495
  const { generateName } = require('../src/name-generator');
493
496
 
@@ -811,68 +814,6 @@ Input formats:
811
814
  }
812
815
  });
813
816
 
814
- // Auto command - full automation (isolation + PR)
815
- program
816
- .command('auto <input>')
817
- .description('Full automation: isolated + auto-merge PR (shorthand for run --isolation --pr)')
818
- .option('--config <file>', 'Path to cluster config JSON (default: conductor-bootstrap)')
819
- .option('-m, --model <model>', 'Model for all agents: opus, sonnet, haiku (default: from config)')
820
- .option(
821
- '--isolation-image <image>',
822
- 'Docker image for isolation (default: zeroshot-cluster-base)'
823
- )
824
- .option(
825
- '--strict-schema',
826
- 'Enforce JSON schema via CLI (no live streaming). Default: live streaming with local validation'
827
- )
828
- .option('--workers <n>', 'Max sub-agents for worker to spawn in parallel', parseInt)
829
- .option('-d, --detach', 'Run in background (default: attach to first agent)')
830
- .addHelpText(
831
- 'after',
832
- `
833
- Input formats:
834
- 123 GitHub issue number (uses current repo)
835
- org/repo#123 GitHub issue with explicit repo
836
- https://github.com/.../issues/1 Full GitHub issue URL
837
- "Implement feature X" Plain text task description
838
-
839
- Examples:
840
- ${chalk.cyan('zeroshot auto 123')} Auto-resolve issue (isolated + PR)
841
- ${chalk.cyan('zeroshot auto 123 -d')} Same, but detached/background
842
- `
843
- )
844
- .action((inputArg, options) => {
845
- // Auto command is shorthand for: zeroshot run <input> --isolation --pr [options]
846
- // Re-invoke CLI with the correct flags to avoid Commander.js internal API issues
847
- const { spawn } = require('child_process');
848
-
849
- const args = ['run', inputArg, '--isolation', '--pr'];
850
-
851
- // Forward other options
852
- if (options.config) args.push('--config', options.config);
853
- if (options.model) args.push('--model', options.model);
854
- if (options.isolationImage) args.push('--isolation-image', options.isolationImage);
855
- if (options.strictSchema) args.push('--strict-schema');
856
- if (options.workers) args.push('--workers', String(options.workers));
857
- if (options.detach) args.push('--detach');
858
-
859
- // Spawn zeroshot run with inherited stdio
860
- const proc = spawn(process.execPath, [process.argv[1], ...args], {
861
- stdio: 'inherit',
862
- cwd: process.cwd(),
863
- env: process.env,
864
- });
865
-
866
- proc.on('close', (code) => {
867
- process.exit(code || 0);
868
- });
869
-
870
- proc.on('error', (err) => {
871
- console.error(chalk.red(`Error: ${err.message}`));
872
- process.exit(1);
873
- });
874
- });
875
-
876
817
  // === TASK COMMANDS ===
877
818
  // Task run - single-agent background task
878
819
  const taskCmd = program.command('task').description('Single-agent task management');
@@ -956,48 +897,78 @@ program
956
897
  .description('List all tasks and clusters')
957
898
  .option('-s, --status <status>', 'Filter tasks by status (running, completed, failed)')
958
899
  .option('-n, --limit <n>', 'Limit number of results', parseInt)
900
+ .option('--json', 'Output as JSON')
959
901
  .action(async (options) => {
960
902
  try {
961
903
  // Get clusters
962
904
  const clusters = getOrchestrator().listClusters();
905
+ const orchestrator = getOrchestrator();
906
+
907
+ // Enrich clusters with token data
908
+ const enrichedClusters = clusters.map((cluster) => {
909
+ let totalTokens = 0;
910
+ let totalCostUsd = 0;
911
+ try {
912
+ const clusterObj = orchestrator.getCluster(cluster.id);
913
+ if (clusterObj?.messageBus) {
914
+ const tokensByRole = clusterObj.messageBus.getTokensByRole(cluster.id);
915
+ if (tokensByRole?._total?.count > 0) {
916
+ const total = tokensByRole._total;
917
+ totalTokens = (total.inputTokens || 0) + (total.outputTokens || 0);
918
+ totalCostUsd = total.totalCostUsd || 0;
919
+ }
920
+ }
921
+ } catch {
922
+ /* Token tracking not available */
923
+ }
924
+ return {
925
+ ...cluster,
926
+ totalTokens,
927
+ totalCostUsd,
928
+ };
929
+ });
963
930
 
964
931
  // Get tasks (dynamic import)
965
- const { listTasks } = await import('../task-lib/commands/list.js');
932
+ const { listTasks, getTasksData } = await import('../task-lib/commands/list.js');
966
933
 
967
- // Capture task output (listTasks prints directly, we need to capture)
968
- // For now, let's list them separately
934
+ // JSON output mode
935
+ if (options.json) {
936
+ // Get tasks data if available
937
+ let tasks = [];
938
+ try {
939
+ if (typeof getTasksData === 'function') {
940
+ tasks = await getTasksData(options);
941
+ }
942
+ } catch {
943
+ /* Tasks not available */
944
+ }
945
+
946
+ console.log(
947
+ JSON.stringify(
948
+ {
949
+ clusters: enrichedClusters,
950
+ tasks,
951
+ },
952
+ null,
953
+ 2
954
+ )
955
+ );
956
+ return;
957
+ }
969
958
 
959
+ // Human-readable output (default)
970
960
  // Print clusters
971
- if (clusters.length > 0) {
961
+ if (enrichedClusters.length > 0) {
972
962
  console.log(chalk.bold('\n=== Clusters ==='));
973
963
  console.log(
974
964
  `${'ID'.padEnd(25)} ${'State'.padEnd(12)} ${'Agents'.padEnd(8)} ${'Tokens'.padEnd(12)} ${'Cost'.padEnd(8)} Created`
975
965
  );
976
966
  console.log('-'.repeat(100));
977
967
 
978
- const orchestrator = getOrchestrator();
979
- for (const cluster of clusters) {
968
+ for (const cluster of enrichedClusters) {
980
969
  const created = new Date(cluster.createdAt).toLocaleString();
981
-
982
- // Get token usage
983
- let tokenDisplay = '-';
984
- let costDisplay = '-';
985
- try {
986
- const clusterObj = orchestrator.getCluster(cluster.id);
987
- if (clusterObj?.messageBus) {
988
- const tokensByRole = clusterObj.messageBus.getTokensByRole(cluster.id);
989
- if (tokensByRole?._total?.count > 0) {
990
- const total = tokensByRole._total;
991
- const totalTokens = (total.inputTokens || 0) + (total.outputTokens || 0);
992
- tokenDisplay = totalTokens.toLocaleString();
993
- if (total.totalCostUsd > 0) {
994
- costDisplay = '$' + total.totalCostUsd.toFixed(3);
995
- }
996
- }
997
- }
998
- } catch {
999
- /* Token tracking not available */
1000
- }
970
+ const tokenDisplay = cluster.totalTokens > 0 ? cluster.totalTokens.toLocaleString() : '-';
971
+ const costDisplay = cluster.totalCostUsd > 0 ? '$' + cluster.totalCostUsd.toFixed(3) : '-';
1001
972
 
1002
973
  // Highlight zombie clusters in red
1003
974
  const stateDisplay =
@@ -1029,14 +1000,19 @@ program
1029
1000
  program
1030
1001
  .command('status <id>')
1031
1002
  .description('Get detailed status of a task or cluster')
1032
- .action(async (id) => {
1003
+ .option('--json', 'Output as JSON')
1004
+ .action(async (id, options) => {
1033
1005
  try {
1034
1006
  const { detectIdType } = require('../lib/id-detector');
1035
1007
  const type = detectIdType(id);
1036
1008
 
1037
1009
  if (!type) {
1038
- console.error(`ID not found: ${id}`);
1039
- console.error('Not found in tasks or clusters');
1010
+ if (options.json) {
1011
+ console.log(JSON.stringify({ error: 'ID not found', id }, null, 2));
1012
+ } else {
1013
+ console.error(`ID not found: ${id}`);
1014
+ console.error('Not found in tasks or clusters');
1015
+ }
1040
1016
  process.exit(1);
1041
1017
  }
1042
1018
 
@@ -1044,6 +1020,35 @@ program
1044
1020
  // Show cluster status
1045
1021
  const status = getOrchestrator().getStatus(id);
1046
1022
 
1023
+ // Get token usage
1024
+ let tokensByRole = null;
1025
+ try {
1026
+ const cluster = getOrchestrator().getCluster(id);
1027
+ if (cluster?.messageBus) {
1028
+ tokensByRole = cluster.messageBus.getTokensByRole(id);
1029
+ }
1030
+ } catch {
1031
+ /* Token tracking not available */
1032
+ }
1033
+
1034
+ // JSON output mode
1035
+ if (options.json) {
1036
+ console.log(
1037
+ JSON.stringify(
1038
+ {
1039
+ type: 'cluster',
1040
+ ...status,
1041
+ createdAtISO: new Date(status.createdAt).toISOString(),
1042
+ tokensByRole,
1043
+ },
1044
+ null,
1045
+ 2
1046
+ )
1047
+ );
1048
+ return;
1049
+ }
1050
+
1051
+ // Human-readable output
1047
1052
  console.log(`\nCluster: ${status.id}`);
1048
1053
  if (status.isZombie) {
1049
1054
  console.log(
@@ -1066,20 +1071,14 @@ program
1066
1071
  console.log(`Messages: ${status.messageCount}`);
1067
1072
 
1068
1073
  // Show token usage if available
1069
- try {
1070
- const cluster = getOrchestrator().getCluster(id);
1071
- if (cluster?.messageBus) {
1072
- const tokensByRole = cluster.messageBus.getTokensByRole(id);
1073
- const tokenLines = formatTokenUsage(tokensByRole);
1074
- if (tokenLines) {
1075
- console.log('');
1076
- for (const line of tokenLines) {
1077
- console.log(line);
1078
- }
1074
+ if (tokensByRole) {
1075
+ const tokenLines = formatTokenUsage(tokensByRole);
1076
+ if (tokenLines) {
1077
+ console.log('');
1078
+ for (const line of tokenLines) {
1079
+ console.log(line);
1079
1080
  }
1080
1081
  }
1081
- } catch {
1082
- /* Token tracking not available */
1083
1082
  }
1084
1083
 
1085
1084
  console.log(`\nAgents:`);
@@ -1104,11 +1103,30 @@ program
1104
1103
  console.log('');
1105
1104
  } else {
1106
1105
  // Show task status
1107
- const { showStatus } = await import('../task-lib/commands/status.js');
1106
+ const { showStatus, getStatusData } = await import('../task-lib/commands/status.js');
1107
+
1108
+ if (options.json) {
1109
+ // Try to get JSON data if available
1110
+ let taskData = null;
1111
+ try {
1112
+ if (typeof getStatusData === 'function') {
1113
+ taskData = await getStatusData(id);
1114
+ }
1115
+ } catch {
1116
+ /* Not available */
1117
+ }
1118
+ console.log(JSON.stringify({ type: 'task', id, ...taskData }, null, 2));
1119
+ return;
1120
+ }
1121
+
1108
1122
  await showStatus(id);
1109
1123
  }
1110
1124
  } catch (error) {
1111
- console.error('Error getting status:', error.message);
1125
+ if (options.json) {
1126
+ console.log(JSON.stringify({ error: error.message }, null, 2));
1127
+ } else {
1128
+ console.error('Error getting status:', error.message);
1129
+ }
1112
1130
  process.exit(1);
1113
1131
  }
1114
1132
  });
@@ -2460,10 +2478,10 @@ program
2460
2478
  }
2461
2479
  });
2462
2480
 
2463
- // Clear all runs (clusters + tasks)
2481
+ // Purge all runs (clusters + tasks) - NUCLEAR option
2464
2482
  program
2465
- .command('clear')
2466
- .description('Kill all running processes and delete all data')
2483
+ .command('purge')
2484
+ .description('NUCLEAR: Kill all running processes and delete all data')
2467
2485
  .option('-y, --yes', 'Skip confirmation')
2468
2486
  .action(async (options) => {
2469
2487
  try {
@@ -2604,7 +2622,7 @@ program
2604
2622
  await cleanTasks({ all: true });
2605
2623
  }
2606
2624
 
2607
- console.log(chalk.bold.green('\nAll runs killed and cleared.'));
2625
+ console.log(chalk.bold.green('\nAll runs purged.'));
2608
2626
  } catch (error) {
2609
2627
  console.error('Error clearing runs:', error.message);
2610
2628
  process.exit(1);
@@ -3005,6 +3023,210 @@ configCmd
3005
3023
  }
3006
3024
  });
3007
3025
 
3026
+ // Agent library commands
3027
+ const agentsCmd = program.command('agents').description('View available agent definitions');
3028
+
3029
+ agentsCmd
3030
+ .command('list')
3031
+ .alias('ls')
3032
+ .description('List available agent definitions')
3033
+ .option('--verbose', 'Show full agent details')
3034
+ .option('--json', 'Output as JSON')
3035
+ .action((options) => {
3036
+ try {
3037
+ const agentsDir = path.join(PACKAGE_ROOT, 'src', 'agents');
3038
+
3039
+ // Check if agents directory exists
3040
+ if (!fs.existsSync(agentsDir)) {
3041
+ if (options.json) {
3042
+ console.log(JSON.stringify({ agents: [], error: null }, null, 2));
3043
+ } else {
3044
+ console.log(chalk.dim('No agents directory found.'));
3045
+ }
3046
+ return;
3047
+ }
3048
+
3049
+ const files = fs.readdirSync(agentsDir).filter((f) => f.endsWith('.json'));
3050
+
3051
+ if (files.length === 0) {
3052
+ if (options.json) {
3053
+ console.log(JSON.stringify({ agents: [], error: null }, null, 2));
3054
+ } else {
3055
+ console.log(chalk.dim('No agent definitions found in src/agents/'));
3056
+ }
3057
+ return;
3058
+ }
3059
+
3060
+ // Parse all agent files
3061
+ const agents = [];
3062
+ for (const file of files) {
3063
+ try {
3064
+ const agentPath = path.join(agentsDir, file);
3065
+ const agent = JSON.parse(fs.readFileSync(agentPath, 'utf8'));
3066
+ agents.push({
3067
+ file: file.replace('.json', ''),
3068
+ id: agent.id || file.replace('.json', ''),
3069
+ role: agent.role || 'unspecified',
3070
+ model: agent.model || 'default',
3071
+ triggers: agent.triggers?.length || 0,
3072
+ prompt: agent.prompt || null,
3073
+ output: agent.output || null,
3074
+ });
3075
+ } catch (err) {
3076
+ // Skip invalid JSON files
3077
+ console.error(chalk.yellow(`Warning: Could not parse ${file}: ${err.message}`));
3078
+ }
3079
+ }
3080
+
3081
+ // JSON output
3082
+ if (options.json) {
3083
+ console.log(JSON.stringify({ agents, error: null }, null, 2));
3084
+ return;
3085
+ }
3086
+
3087
+ // Human-readable output
3088
+ console.log(chalk.bold('\nAvailable agent definitions:\n'));
3089
+
3090
+ for (const agent of agents) {
3091
+ console.log(
3092
+ ` ${chalk.cyan(agent.id.padEnd(25))} ${chalk.dim('role:')} ${agent.role.padEnd(20)} ${chalk.dim('model:')} ${agent.model}`
3093
+ );
3094
+
3095
+ if (options.verbose) {
3096
+ console.log(chalk.dim(` Triggers: ${agent.triggers}`));
3097
+ if (agent.output) {
3098
+ console.log(chalk.dim(` Output topic: ${agent.output.topic || 'none'}`));
3099
+ }
3100
+ if (agent.prompt) {
3101
+ const promptPreview = agent.prompt.substring(0, 100).replace(/\n/g, ' ');
3102
+ console.log(chalk.dim(` Prompt: ${promptPreview}...`));
3103
+ }
3104
+ console.log('');
3105
+ }
3106
+ }
3107
+
3108
+ if (!options.verbose) {
3109
+ console.log('');
3110
+ console.log(chalk.dim(' Use --verbose for full details'));
3111
+ }
3112
+ console.log('');
3113
+ } catch (error) {
3114
+ if (options.json) {
3115
+ console.log(JSON.stringify({ agents: [], error: error.message }, null, 2));
3116
+ } else {
3117
+ console.error(chalk.red(`Error listing agents: ${error.message}`));
3118
+ }
3119
+ process.exit(1);
3120
+ }
3121
+ });
3122
+
3123
+ agentsCmd
3124
+ .command('show <name>')
3125
+ .description('Show detailed agent definition')
3126
+ .option('--json', 'Output as JSON')
3127
+ .action((name, options) => {
3128
+ try {
3129
+ const agentsDir = path.join(PACKAGE_ROOT, 'src', 'agents');
3130
+
3131
+ // Support both with and without .json extension
3132
+ const agentName = name.endsWith('.json') ? name : `${name}.json`;
3133
+ const agentPath = path.join(agentsDir, agentName);
3134
+
3135
+ if (!fs.existsSync(agentPath)) {
3136
+ // Try with -agent.json suffix
3137
+ const altPath = path.join(agentsDir, `${name}-agent.json`);
3138
+ if (fs.existsSync(altPath)) {
3139
+ const agent = JSON.parse(fs.readFileSync(altPath, 'utf8'));
3140
+ outputAgent(agent, options);
3141
+ return;
3142
+ }
3143
+
3144
+ if (options.json) {
3145
+ console.log(JSON.stringify({ error: `Agent not found: ${name}` }, null, 2));
3146
+ } else {
3147
+ console.error(chalk.red(`Agent not found: ${name}`));
3148
+ console.log(chalk.dim('\nAvailable agents:'));
3149
+ const files = fs.readdirSync(agentsDir).filter((f) => f.endsWith('.json'));
3150
+ files.forEach((f) => console.log(chalk.dim(` - ${f.replace('.json', '')}`)));
3151
+ }
3152
+ process.exit(1);
3153
+ }
3154
+
3155
+ const agent = JSON.parse(fs.readFileSync(agentPath, 'utf8'));
3156
+ outputAgent(agent, options);
3157
+ } catch (error) {
3158
+ if (options.json) {
3159
+ console.log(JSON.stringify({ error: error.message }, null, 2));
3160
+ } else {
3161
+ console.error(chalk.red(`Error: ${error.message}`));
3162
+ }
3163
+ process.exit(1);
3164
+ }
3165
+ });
3166
+
3167
+ function outputAgent(agent, options) {
3168
+ if (options.json) {
3169
+ console.log(JSON.stringify(agent, null, 2));
3170
+ return;
3171
+ }
3172
+
3173
+ // Human-readable output
3174
+ console.log('');
3175
+ console.log(chalk.bold.cyan('═'.repeat(80)));
3176
+ console.log(chalk.bold.cyan(` Agent: ${agent.id}`));
3177
+ console.log(chalk.bold.cyan('═'.repeat(80)));
3178
+ console.log('');
3179
+
3180
+ // Basic info
3181
+ console.log(chalk.bold('Configuration:'));
3182
+ console.log(` ${chalk.dim('ID:')} ${agent.id}`);
3183
+ console.log(` ${chalk.dim('Role:')} ${agent.role || 'unspecified'}`);
3184
+ console.log(` ${chalk.dim('Model:')} ${agent.model || 'default'}`);
3185
+ console.log('');
3186
+
3187
+ // Triggers
3188
+ if (agent.triggers && agent.triggers.length > 0) {
3189
+ console.log(chalk.bold('Triggers:'));
3190
+ for (const trigger of agent.triggers) {
3191
+ console.log(` ${chalk.yellow('•')} Topic: ${chalk.cyan(trigger.topic)}`);
3192
+ if (trigger.action) {
3193
+ console.log(` Action: ${trigger.action}`);
3194
+ }
3195
+ if (trigger.logic?.script) {
3196
+ const scriptPreview = trigger.logic.script.substring(0, 80).replace(/\n/g, ' ');
3197
+ console.log(chalk.dim(` Logic: ${scriptPreview}...`));
3198
+ }
3199
+ }
3200
+ console.log('');
3201
+ }
3202
+
3203
+ // Output
3204
+ if (agent.output) {
3205
+ console.log(chalk.bold('Output:'));
3206
+ console.log(` ${chalk.dim('Topic:')} ${agent.output.topic || 'none'}`);
3207
+ if (agent.output.publishAfter) {
3208
+ console.log(` ${chalk.dim('Publish after:')} ${agent.output.publishAfter}`);
3209
+ }
3210
+ console.log('');
3211
+ }
3212
+
3213
+ // Prompt
3214
+ if (agent.prompt) {
3215
+ console.log(chalk.bold('Prompt:'));
3216
+ console.log(chalk.dim('─'.repeat(76)));
3217
+ // Show first 500 chars of prompt
3218
+ const promptLines = agent.prompt.substring(0, 500).split('\n');
3219
+ for (const line of promptLines) {
3220
+ console.log(` ${line}`);
3221
+ }
3222
+ if (agent.prompt.length > 500) {
3223
+ console.log(chalk.dim(` ... (${agent.prompt.length - 500} more characters)`));
3224
+ }
3225
+ console.log(chalk.dim('─'.repeat(76)));
3226
+ console.log('');
3227
+ }
3228
+ }
3229
+
3008
3230
  // Helper function to keep the process alive for follow mode
3009
3231
  function keepProcessAlive(cleanupFn) {
3010
3232
  // Prevent Node.js from exiting by keeping the event loop active
@@ -4094,23 +4316,39 @@ function printMessage(msg, showClusterId = false, watchMode = false, isActive =
4094
4316
  formatGenericMessage(msg, prefix, timestamp, safePrint);
4095
4317
  }
4096
4318
 
4097
- // Default command handling: if first arg doesn't match a known command, treat it as 'run'
4098
- // This allows `zeroshot "task"` to work the same as `zeroshot run "task"`
4099
- const args = process.argv.slice(2);
4100
- if (args.length > 0) {
4101
- const firstArg = args[0];
4102
-
4103
- // Skip if it's a flag/option (starts with -)
4104
- // Skip if it's --help or --version (these are handled by commander)
4105
- if (!firstArg.startsWith('-')) {
4106
- // Get all registered command names
4107
- const commandNames = program.commands.map((cmd) => cmd.name());
4108
-
4109
- // If first arg is not a known command, prepend 'run'
4110
- if (!commandNames.includes(firstArg)) {
4111
- process.argv.splice(2, 0, 'run');
4319
+ // Main async entry point
4320
+ async function main() {
4321
+ // First-run setup wizard (blocks on first use only)
4322
+ const isQuiet = process.argv.includes('-q') || process.argv.includes('--quiet');
4323
+ await checkFirstRun({ quiet: isQuiet });
4324
+
4325
+ // Check for updates (non-blocking if offline)
4326
+ await checkForUpdates({ quiet: isQuiet });
4327
+
4328
+ // Default command handling: if first arg doesn't match a known command, treat it as 'run'
4329
+ // This allows `zeroshot "task"` to work the same as `zeroshot run "task"`
4330
+ const args = process.argv.slice(2);
4331
+ if (args.length > 0) {
4332
+ const firstArg = args[0];
4333
+
4334
+ // Skip if it's a flag/option (starts with -)
4335
+ // Skip if it's --help or --version (these are handled by commander)
4336
+ if (!firstArg.startsWith('-')) {
4337
+ // Get all registered command names
4338
+ const commandNames = program.commands.map((cmd) => cmd.name());
4339
+
4340
+ // If first arg is not a known command, prepend 'run'
4341
+ if (!commandNames.includes(firstArg)) {
4342
+ process.argv.splice(2, 0, 'run');
4343
+ }
4112
4344
  }
4113
4345
  }
4346
+
4347
+ program.parse();
4114
4348
  }
4115
4349
 
4116
- program.parse();
4350
+ // Run main
4351
+ main().catch((err) => {
4352
+ console.error('Fatal error:', err.message);
4353
+ process.exit(1);
4354
+ });
@@ -0,0 +1,174 @@
1
+ /**
2
+ * First-Run Setup Wizard
3
+ *
4
+ * Interactive setup on first use:
5
+ * - Welcome banner
6
+ * - Default model selection (sonnet/opus/haiku)
7
+ * - Auto-update preference
8
+ * - Marks setup as complete
9
+ */
10
+
11
+ const readline = require('readline');
12
+ const { loadSettings, saveSettings } = require('../../lib/settings');
13
+
14
+ /**
15
+ * Print welcome banner
16
+ */
17
+ function printWelcome() {
18
+ console.log(`
19
+ ╔═══════════════════════════════════════════════════════════════╗
20
+ ║ ║
21
+ ║ Welcome to Zeroshot! ║
22
+ ║ Multi-agent orchestration for Claude ║
23
+ ║ ║
24
+ ║ Let's configure a few settings to get started. ║
25
+ ║ ║
26
+ ╚═══════════════════════════════════════════════════════════════╝
27
+ `);
28
+ }
29
+
30
+ /**
31
+ * Create readline interface
32
+ * @returns {readline.Interface}
33
+ */
34
+ function createReadline() {
35
+ return readline.createInterface({
36
+ input: process.stdin,
37
+ output: process.stdout,
38
+ });
39
+ }
40
+
41
+ /**
42
+ * Prompt for model selection
43
+ * @param {readline.Interface} rl
44
+ * @returns {Promise<string>}
45
+ */
46
+ function promptModel(rl) {
47
+ return new Promise((resolve) => {
48
+ console.log('Which Claude model should agents use by default?\n');
49
+ console.log(' 1) sonnet - Fast & capable (recommended)');
50
+ console.log(' 2) opus - Most capable, slower');
51
+ console.log(' 3) haiku - Fastest, for simple tasks\n');
52
+
53
+ rl.question('Enter 1, 2, or 3 [1]: ', (answer) => {
54
+ const choice = answer.trim() || '1';
55
+ switch (choice) {
56
+ case '2':
57
+ resolve('opus');
58
+ break;
59
+ case '3':
60
+ resolve('haiku');
61
+ break;
62
+ default:
63
+ resolve('sonnet');
64
+ }
65
+ });
66
+ });
67
+ }
68
+
69
+ /**
70
+ * Prompt for auto-update preference
71
+ * @param {readline.Interface} rl
72
+ * @returns {Promise<boolean>}
73
+ */
74
+ function promptAutoUpdate(rl) {
75
+ return new Promise((resolve) => {
76
+ console.log('\nWould you like zeroshot to check for updates automatically?');
77
+ console.log('(Checks npm registry every 24 hours)\n');
78
+
79
+ rl.question('Enable auto-update checks? [Y/n]: ', (answer) => {
80
+ const normalized = answer.trim().toLowerCase();
81
+ // Default to yes if empty or starts with 'y'
82
+ resolve(normalized === '' || normalized === 'y' || normalized === 'yes');
83
+ });
84
+ });
85
+ }
86
+
87
+ /**
88
+ * Print completion message
89
+ * @param {object} settings - Saved settings
90
+ */
91
+ function printComplete(settings) {
92
+ console.log(`
93
+ ╔═══════════════════════════════════════════════════════════════╗
94
+ ║ Setup complete! ║
95
+ ╚═══════════════════════════════════════════════════════════════╝
96
+
97
+ Your settings:
98
+ • Default model: ${settings.defaultModel}
99
+ • Auto-updates: ${settings.autoCheckUpdates ? 'enabled' : 'disabled'}
100
+
101
+ Change anytime with: zeroshot settings set <key> <value>
102
+
103
+ Get started:
104
+ zeroshot run "Fix the bug in auth.js"
105
+ zeroshot run 123 (GitHub issue number)
106
+ zeroshot --help
107
+
108
+ `);
109
+ }
110
+
111
+ /**
112
+ * Check if first-run setup is needed
113
+ * @param {object} settings - Current settings
114
+ * @returns {boolean}
115
+ */
116
+ function detectFirstRun(settings) {
117
+ return !settings.firstRunComplete;
118
+ }
119
+
120
+ /**
121
+ * Main entry point - run first-time setup if needed
122
+ * @param {object} options
123
+ * @param {boolean} options.quiet - Skip interactive prompts
124
+ * @returns {Promise<boolean>} True if setup was run
125
+ */
126
+ async function checkFirstRun(options = {}) {
127
+ const settings = loadSettings();
128
+
129
+ // Already completed setup
130
+ if (!detectFirstRun(settings)) {
131
+ return false;
132
+ }
133
+
134
+ // Quiet mode - use defaults, mark complete
135
+ if (options.quiet) {
136
+ settings.firstRunComplete = true;
137
+ saveSettings(settings);
138
+ return true;
139
+ }
140
+
141
+ // Interactive setup
142
+ printWelcome();
143
+
144
+ const rl = createReadline();
145
+
146
+ try {
147
+ // Model selection
148
+ const model = await promptModel(rl);
149
+ settings.defaultModel = model;
150
+
151
+ // Auto-update preference
152
+ const autoUpdate = await promptAutoUpdate(rl);
153
+ settings.autoCheckUpdates = autoUpdate;
154
+
155
+ // Mark complete
156
+ settings.firstRunComplete = true;
157
+ saveSettings(settings);
158
+
159
+ // Print completion
160
+ printComplete(settings);
161
+
162
+ return true;
163
+ } finally {
164
+ rl.close();
165
+ }
166
+ }
167
+
168
+ module.exports = {
169
+ checkFirstRun,
170
+ // Exported for testing
171
+ detectFirstRun,
172
+ printWelcome,
173
+ printComplete,
174
+ };
@@ -0,0 +1,234 @@
1
+ /**
2
+ * Update Checker - Checks npm registry for newer versions
3
+ *
4
+ * Features:
5
+ * - 24-hour check interval (avoids registry spam)
6
+ * - 5-second timeout (non-blocking if offline)
7
+ * - Interactive prompt for manual update
8
+ * - Respects quiet mode (no prompts in CI/scripts)
9
+ */
10
+
11
+ const https = require('https');
12
+ const { spawn } = require('child_process');
13
+ const readline = require('readline');
14
+ const { loadSettings, saveSettings } = require('../../lib/settings');
15
+
16
+ // 24 hours in milliseconds
17
+ const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
18
+
19
+ // Timeout for npm registry fetch (5 seconds)
20
+ const FETCH_TIMEOUT_MS = 5000;
21
+
22
+ // npm registry URL
23
+ const REGISTRY_URL = 'https://registry.npmjs.org/@covibes/zeroshot/latest';
24
+
25
+ /**
26
+ * Get current package version
27
+ * @returns {string}
28
+ */
29
+ function getCurrentVersion() {
30
+ const pkg = require('../../package.json');
31
+ return pkg.version;
32
+ }
33
+
34
+ /**
35
+ * Compare semver versions
36
+ * @param {string} current - Current version (e.g., "1.5.0")
37
+ * @param {string} latest - Latest version (e.g., "1.6.0")
38
+ * @returns {boolean} True if latest > current
39
+ */
40
+ function isNewerVersion(current, latest) {
41
+ const currentParts = current.split('.').map(Number);
42
+ const latestParts = latest.split('.').map(Number);
43
+
44
+ for (let i = 0; i < 3; i++) {
45
+ const c = currentParts[i] || 0;
46
+ const l = latestParts[i] || 0;
47
+ if (l > c) return true;
48
+ if (l < c) return false;
49
+ }
50
+ return false;
51
+ }
52
+
53
+ /**
54
+ * Fetch latest version from npm registry
55
+ * @returns {Promise<string|null>} Latest version or null on failure
56
+ */
57
+ function fetchLatestVersion() {
58
+ return new Promise((resolve) => {
59
+ const req = https.get(REGISTRY_URL, { timeout: FETCH_TIMEOUT_MS }, (res) => {
60
+ if (res.statusCode !== 200) {
61
+ resolve(null);
62
+ return;
63
+ }
64
+
65
+ let data = '';
66
+ res.on('data', (chunk) => {
67
+ data += chunk;
68
+ });
69
+
70
+ res.on('end', () => {
71
+ try {
72
+ const json = JSON.parse(data);
73
+ resolve(json.version || null);
74
+ } catch {
75
+ resolve(null);
76
+ }
77
+ });
78
+ });
79
+
80
+ req.on('error', () => {
81
+ resolve(null);
82
+ });
83
+
84
+ req.on('timeout', () => {
85
+ req.destroy();
86
+ resolve(null);
87
+ });
88
+
89
+ // Additional safety timeout
90
+ setTimeout(() => {
91
+ req.destroy();
92
+ resolve(null);
93
+ }, FETCH_TIMEOUT_MS + 1000);
94
+ });
95
+ }
96
+
97
+ /**
98
+ * Prompt user for update confirmation
99
+ * @param {string} currentVersion
100
+ * @param {string} latestVersion
101
+ * @returns {Promise<boolean>} True if user wants to update
102
+ */
103
+ function promptForUpdate(currentVersion, latestVersion) {
104
+ return new Promise((resolve) => {
105
+ const rl = readline.createInterface({
106
+ input: process.stdin,
107
+ output: process.stdout,
108
+ });
109
+
110
+ console.log(`\n📦 Update available: ${currentVersion} → ${latestVersion}`);
111
+ rl.question(' Install now? [y/N] ', (answer) => {
112
+ rl.close();
113
+ resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
114
+ });
115
+ });
116
+ }
117
+
118
+ /**
119
+ * Run npm install to update the package
120
+ * @returns {Promise<boolean>} True if update succeeded
121
+ */
122
+ function runUpdate() {
123
+ return new Promise((resolve) => {
124
+ console.log('\n📥 Installing update...');
125
+
126
+ const proc = spawn('npm', ['install', '-g', '@covibes/zeroshot@latest'], {
127
+ stdio: 'inherit',
128
+ shell: true,
129
+ });
130
+
131
+ proc.on('close', (code) => {
132
+ if (code === 0) {
133
+ console.log('✅ Update installed successfully!');
134
+ console.log(' Restart zeroshot to use the new version.\n');
135
+ resolve(true);
136
+ } else {
137
+ console.log('❌ Update failed. Try manually:');
138
+ console.log(' npm install -g @covibes/zeroshot@latest\n');
139
+ resolve(false);
140
+ }
141
+ });
142
+
143
+ proc.on('error', () => {
144
+ console.log('❌ Update failed. Try manually:');
145
+ console.log(' npm install -g @covibes/zeroshot@latest\n');
146
+ resolve(false);
147
+ });
148
+ });
149
+ }
150
+
151
+ /**
152
+ * Check if update check should run
153
+ * @param {object} settings - Current settings
154
+ * @returns {boolean}
155
+ */
156
+ function shouldCheckForUpdates(settings) {
157
+ // Disabled by user
158
+ if (!settings.autoCheckUpdates) {
159
+ return false;
160
+ }
161
+
162
+ // Never checked before
163
+ if (!settings.lastUpdateCheckAt) {
164
+ return true;
165
+ }
166
+
167
+ // Check if 24 hours have passed
168
+ const elapsed = Date.now() - settings.lastUpdateCheckAt;
169
+ return elapsed >= CHECK_INTERVAL_MS;
170
+ }
171
+
172
+ /**
173
+ * Main entry point - check for updates
174
+ * @param {object} options
175
+ * @param {boolean} options.quiet - Skip interactive prompts
176
+ * @returns {Promise<void>}
177
+ */
178
+ async function checkForUpdates(options = {}) {
179
+ const settings = loadSettings();
180
+
181
+ // Skip check if not due
182
+ if (!shouldCheckForUpdates(settings)) {
183
+ return;
184
+ }
185
+
186
+ const currentVersion = getCurrentVersion();
187
+ const latestVersion = await fetchLatestVersion();
188
+
189
+ // Update last check timestamp regardless of result
190
+ settings.lastUpdateCheckAt = Date.now();
191
+ saveSettings(settings);
192
+
193
+ // Network failure - silently skip
194
+ if (!latestVersion) {
195
+ return;
196
+ }
197
+
198
+ // No update available
199
+ if (!isNewerVersion(currentVersion, latestVersion)) {
200
+ return;
201
+ }
202
+
203
+ // Already notified about this version
204
+ if (settings.lastSeenVersion === latestVersion) {
205
+ return;
206
+ }
207
+
208
+ // Update lastSeenVersion so we don't nag about the same version
209
+ settings.lastSeenVersion = latestVersion;
210
+ saveSettings(settings);
211
+
212
+ // Quiet mode - just inform, no prompt
213
+ if (options.quiet) {
214
+ console.log(`📦 Update available: ${currentVersion} → ${latestVersion}`);
215
+ console.log(' Run: npm install -g @covibes/zeroshot@latest\n');
216
+ return;
217
+ }
218
+
219
+ // Interactive mode - prompt for update
220
+ const wantsUpdate = await promptForUpdate(currentVersion, latestVersion);
221
+ if (wantsUpdate) {
222
+ await runUpdate();
223
+ }
224
+ }
225
+
226
+ module.exports = {
227
+ checkForUpdates,
228
+ // Exported for testing
229
+ getCurrentVersion,
230
+ isNewerVersion,
231
+ fetchLatestVersion,
232
+ shouldCheckForUpdates,
233
+ CHECK_INTERVAL_MS,
234
+ };
package/lib/settings.js CHANGED
@@ -7,8 +7,14 @@ const fs = require('fs');
7
7
  const path = require('path');
8
8
  const os = require('os');
9
9
 
10
- // Settings file path
11
- const SETTINGS_FILE = path.join(os.homedir(), '.zeroshot', 'settings.json');
10
+ /**
11
+ * Get settings file path (dynamically reads env var for testing)
12
+ * Using a getter ensures tests can override the path at runtime
13
+ * @returns {string}
14
+ */
15
+ function getSettingsFile() {
16
+ return process.env.ZEROSHOT_SETTINGS_FILE || path.join(os.homedir(), '.zeroshot', 'settings.json');
17
+ }
12
18
 
13
19
  // Default settings
14
20
  const DEFAULT_SETTINGS = {
@@ -17,17 +23,24 @@ const DEFAULT_SETTINGS = {
17
23
  defaultIsolation: false,
18
24
  strictSchema: true, // true = reliable json output (default), false = live streaming (may crash - see bold-meadow-11)
19
25
  logLevel: 'normal',
26
+ // Auto-update settings
27
+ autoCheckUpdates: true, // Check npm registry for newer versions
28
+ lastUpdateCheckAt: null, // Unix timestamp of last check (null = never checked)
29
+ lastSeenVersion: null, // Don't re-prompt for same version
30
+ // First-run wizard
31
+ firstRunComplete: false, // Has user completed first-run setup?
20
32
  };
21
33
 
22
34
  /**
23
35
  * Load settings from disk, merging with defaults
24
36
  */
25
37
  function loadSettings() {
26
- if (!fs.existsSync(SETTINGS_FILE)) {
38
+ const settingsFile = getSettingsFile();
39
+ if (!fs.existsSync(settingsFile)) {
27
40
  return { ...DEFAULT_SETTINGS };
28
41
  }
29
42
  try {
30
- const data = fs.readFileSync(SETTINGS_FILE, 'utf8');
43
+ const data = fs.readFileSync(settingsFile, 'utf8');
31
44
  return { ...DEFAULT_SETTINGS, ...JSON.parse(data) };
32
45
  } catch {
33
46
  console.error('Warning: Could not load settings, using defaults');
@@ -39,11 +52,12 @@ function loadSettings() {
39
52
  * Save settings to disk
40
53
  */
41
54
  function saveSettings(settings) {
42
- const dir = path.dirname(SETTINGS_FILE);
55
+ const settingsFile = getSettingsFile();
56
+ const dir = path.dirname(settingsFile);
43
57
  if (!fs.existsSync(dir)) {
44
58
  fs.mkdirSync(dir, { recursive: true });
45
59
  }
46
- fs.writeFileSync(SETTINGS_FILE, JSON.stringify(settings, null, 2), 'utf8');
60
+ fs.writeFileSync(settingsFile, JSON.stringify(settings, null, 2), 'utf8');
47
61
  }
48
62
 
49
63
  /**
@@ -93,5 +107,9 @@ module.exports = {
93
107
  validateSetting,
94
108
  coerceValue,
95
109
  DEFAULT_SETTINGS,
96
- SETTINGS_FILE,
110
+ getSettingsFile,
111
+ // Backward compatibility: SETTINGS_FILE as getter (reads env var dynamically)
112
+ get SETTINGS_FILE() {
113
+ return getSettingsFile();
114
+ },
97
115
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@covibes/zeroshot",
3
- "version": "1.5.0",
3
+ "version": "2.0.0",
4
4
  "description": "Multi-agent orchestration engine for Claude - cluster coordinator and CLI",
5
5
  "main": "src/orchestrator.js",
6
6
  "bin": {