tmux-team 2.2.0 → 3.0.0-alpha.2

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.
@@ -15,13 +15,11 @@ import {
15
15
 
16
16
  type EnumConfigKey = 'mode' | 'preambleMode';
17
17
  type NumericConfigKey = 'preambleEvery';
18
- type BoolConfigKey = 'hideOrphanTasks';
19
- type ConfigKey = EnumConfigKey | NumericConfigKey | BoolConfigKey;
18
+ type ConfigKey = EnumConfigKey | NumericConfigKey;
20
19
 
21
20
  const ENUM_KEYS: EnumConfigKey[] = ['mode', 'preambleMode'];
22
21
  const NUMERIC_KEYS: NumericConfigKey[] = ['preambleEvery'];
23
- const BOOL_KEYS: BoolConfigKey[] = ['hideOrphanTasks'];
24
- const VALID_KEYS: ConfigKey[] = [...ENUM_KEYS, ...NUMERIC_KEYS, ...BOOL_KEYS];
22
+ const VALID_KEYS: ConfigKey[] = [...ENUM_KEYS, ...NUMERIC_KEYS];
25
23
 
26
24
  const VALID_VALUES: Record<EnumConfigKey, string[]> = {
27
25
  mode: ['polling', 'wait'],
@@ -40,10 +38,6 @@ function isNumericKey(key: ConfigKey): key is NumericConfigKey {
40
38
  return NUMERIC_KEYS.includes(key as NumericConfigKey);
41
39
  }
42
40
 
43
- function isBoolKey(key: ConfigKey): key is BoolConfigKey {
44
- return BOOL_KEYS.includes(key as BoolConfigKey);
45
- }
46
-
47
41
  function isValidValue(key: EnumConfigKey, value: string): boolean {
48
42
  return VALID_VALUES[key].includes(value);
49
43
  }
@@ -62,7 +56,6 @@ function showConfig(ctx: Context): void {
62
56
  mode: ctx.config.mode,
63
57
  preambleMode: ctx.config.preambleMode,
64
58
  preambleEvery: ctx.config.defaults.preambleEvery,
65
- hideOrphanTasks: ctx.config.defaults.hideOrphanTasks,
66
59
  defaults: ctx.config.defaults,
67
60
  },
68
61
  sources: {
@@ -78,8 +71,6 @@ function showConfig(ctx: Context): void {
78
71
  : globalConfig.defaults?.preambleEvery !== undefined
79
72
  ? 'global'
80
73
  : 'default',
81
- hideOrphanTasks:
82
- globalConfig.defaults?.hideOrphanTasks !== undefined ? 'global' : 'default',
83
74
  },
84
75
  paths: {
85
76
  global: ctx.paths.globalConfig,
@@ -102,8 +93,6 @@ function showConfig(ctx: Context): void {
102
93
  : globalConfig.defaults?.preambleEvery !== undefined
103
94
  ? '(global)'
104
95
  : '(default)';
105
- const hideOrphanSource =
106
- globalConfig.defaults?.hideOrphanTasks !== undefined ? '(global)' : '(default)';
107
96
 
108
97
  ctx.ui.info('Current configuration:\n');
109
98
  ctx.ui.table(
@@ -112,7 +101,6 @@ function showConfig(ctx: Context): void {
112
101
  ['mode', ctx.config.mode, modeSource],
113
102
  ['preambleMode', ctx.config.preambleMode, preambleSource],
114
103
  ['preambleEvery', String(ctx.config.defaults.preambleEvery), preambleEverySource],
115
- ['hideOrphanTasks', String(ctx.config.defaults.hideOrphanTasks), hideOrphanSource],
116
104
  ['defaults.timeout', String(ctx.config.defaults.timeout), '(global)'],
117
105
  ['defaults.pollInterval', String(ctx.config.defaults.pollInterval), '(global)'],
118
106
  ['defaults.captureLines', String(ctx.config.defaults.captureLines), '(global)'],
@@ -154,31 +142,6 @@ function setConfig(ctx: Context, key: string, value: string, global: boolean): v
154
142
  }
155
143
  }
156
144
 
157
- if (isBoolKey(validKey)) {
158
- if (value !== 'true' && value !== 'false') {
159
- ctx.ui.error(`Invalid value for ${key}: ${value}. Use true or false.`);
160
- ctx.exit(ExitCodes.ERROR);
161
- }
162
- }
163
-
164
- if (key === 'hideOrphanTasks') {
165
- const globalConfig = loadGlobalConfig(ctx.paths);
166
- if (!globalConfig.defaults) {
167
- globalConfig.defaults = {
168
- timeout: 180,
169
- pollInterval: 1,
170
- captureLines: 100,
171
- preambleEvery: ctx.config.defaults.preambleEvery,
172
- hideOrphanTasks: value === 'true',
173
- };
174
- } else {
175
- globalConfig.defaults.hideOrphanTasks = value === 'true';
176
- }
177
- saveGlobalConfig(ctx.paths, globalConfig);
178
- ctx.ui.success(`Set ${key}=${value} in global config`);
179
- return;
180
- }
181
-
182
145
  if (global) {
183
146
  // Set in global config
184
147
  const globalConfig = loadGlobalConfig(ctx.paths);
@@ -193,7 +156,6 @@ function setConfig(ctx: Context, key: string, value: string, global: boolean): v
193
156
  pollInterval: 1,
194
157
  captureLines: 100,
195
158
  preambleEvery: parseInt(value, 10),
196
- hideOrphanTasks: ctx.config.defaults.hideOrphanTasks,
197
159
  };
198
160
  } else {
199
161
  globalConfig.defaults.preambleEvery = parseInt(value, 10);
@@ -223,10 +185,6 @@ function clearConfig(ctx: Context, key?: string): void {
223
185
  ctx.ui.error(`Invalid key: ${key}. Valid keys: ${VALID_KEYS.join(', ')}`);
224
186
  ctx.exit(ExitCodes.ERROR);
225
187
  }
226
- if (key === 'hideOrphanTasks') {
227
- ctx.ui.error(`Cannot clear global-only key: ${key}. Edit global config instead.`);
228
- ctx.exit(ExitCodes.ERROR);
229
- }
230
188
 
231
189
  // Clear specific key from local settings
232
190
  const localConfigFile = loadLocalConfigFile(ctx.paths);
@@ -42,7 +42,7 @@ ${colors.yellow('COMMANDS')}
42
42
  ${colors.green('init')} Create empty tmux-team.json
43
43
  ${colors.green('config')} [show|set|clear] View/modify settings
44
44
  ${colors.green('preamble')} [show|set|clear] Manage agent preambles
45
- ${colors.green('pm')} <subcommand> Project management (run 'pm help')
45
+ ${colors.green('install-skill')} <agent> Install skill for AI agent
46
46
  ${colors.green('completion')} Output shell completion script
47
47
  ${colors.green('help')} Show this help message
48
48
 
@@ -80,6 +80,5 @@ ${colors.yellow('CHANGE MODE')}
80
80
  tmux-team config set mode polling ${colors.dim('Enable polling mode (local)')}
81
81
  tmux-team config set preambleMode disabled ${colors.dim('Disable preambles (local)')}
82
82
  tmux-team config set preambleEvery 5 ${colors.dim('Inject preamble every 5 messages')}
83
- tmux-team config set hideOrphanTasks true ${colors.dim('Hide tasks without milestones (global)')}
84
83
  `);
85
84
  }
@@ -0,0 +1,148 @@
1
+ // ─────────────────────────────────────────────────────────────
2
+ // install-skill command - install tmux-team skills for AI agents
3
+ // ─────────────────────────────────────────────────────────────
4
+
5
+ import * as fs from 'node:fs';
6
+ import * as path from 'node:path';
7
+ import * as os from 'node:os';
8
+ import { fileURLToPath } from 'node:url';
9
+ import type { Context } from '../types.js';
10
+ import { ExitCodes } from '../context.js';
11
+
12
+ type AgentType = 'claude' | 'codex';
13
+ type Scope = 'user' | 'local';
14
+
15
+ interface SkillConfig {
16
+ sourceFile: string;
17
+ userDir: string;
18
+ localDir: string;
19
+ targetFile: string;
20
+ }
21
+
22
+ function getCodexHome(): string {
23
+ return process.env.CODEX_HOME || path.join(os.homedir(), '.codex');
24
+ }
25
+
26
+ const SKILL_CONFIGS: Record<AgentType, SkillConfig> = {
27
+ claude: {
28
+ sourceFile: 'skills/claude/team.md',
29
+ userDir: path.join(os.homedir(), '.claude', 'commands'),
30
+ localDir: '.claude/commands',
31
+ targetFile: 'team.md',
32
+ },
33
+ codex: {
34
+ sourceFile: 'skills/codex/SKILL.md',
35
+ userDir: path.join(getCodexHome(), 'skills', 'tmux-team'),
36
+ localDir: '.codex/skills/tmux-team',
37
+ targetFile: 'SKILL.md',
38
+ },
39
+ };
40
+
41
+ const SUPPORTED_AGENTS = Object.keys(SKILL_CONFIGS) as AgentType[];
42
+
43
+ function findPackageRoot(): string {
44
+ // Get current file's directory (ES modules don't have __dirname)
45
+ const currentFile = fileURLToPath(import.meta.url);
46
+ let dir = path.dirname(currentFile);
47
+
48
+ // Try to find the package root by looking for package.json
49
+ for (let i = 0; i < 5; i++) {
50
+ const pkgPath = path.join(dir, 'package.json');
51
+ if (fs.existsSync(pkgPath)) {
52
+ return dir;
53
+ }
54
+ const parent = path.dirname(dir);
55
+ if (parent === dir) break;
56
+ dir = parent;
57
+ }
58
+ // Fallback: assume we're in src/commands
59
+ return path.resolve(path.dirname(currentFile), '..', '..');
60
+ }
61
+
62
+ function exitWithError(ctx: Context, error: string, hint?: string): never {
63
+ if (ctx.flags.json) {
64
+ ctx.ui.json({ success: false, error, hint });
65
+ } else {
66
+ ctx.ui.error(error);
67
+ if (hint) ctx.ui.info(hint);
68
+ }
69
+ ctx.exit(ExitCodes.ERROR);
70
+ }
71
+
72
+ export function cmdInstallSkill(ctx: Context, agent?: string, scope: string = 'user'): void {
73
+ // Validate agent
74
+ if (!agent) {
75
+ exitWithError(
76
+ ctx,
77
+ 'Usage: tmux-team install-skill <agent> [--local|--user]',
78
+ `Supported agents: ${SUPPORTED_AGENTS.join(', ')}`
79
+ );
80
+ }
81
+
82
+ const agentLower = agent.toLowerCase() as AgentType;
83
+ if (!SUPPORTED_AGENTS.includes(agentLower)) {
84
+ exitWithError(
85
+ ctx,
86
+ `Unknown agent: ${agent}`,
87
+ `Supported agents: ${SUPPORTED_AGENTS.join(', ')}`
88
+ );
89
+ }
90
+
91
+ // Validate scope
92
+ const scopeLower = scope.toLowerCase() as Scope;
93
+ if (scopeLower !== 'user' && scopeLower !== 'local') {
94
+ exitWithError(ctx, `Invalid scope: ${scope}. Use 'user' or 'local'.`);
95
+ }
96
+
97
+ const config = SKILL_CONFIGS[agentLower];
98
+ const pkgRoot = findPackageRoot();
99
+ const sourcePath = path.join(pkgRoot, config.sourceFile);
100
+
101
+ // Check source file exists
102
+ if (!fs.existsSync(sourcePath)) {
103
+ exitWithError(
104
+ ctx,
105
+ `Skill file not found: ${sourcePath}`,
106
+ 'Make sure tmux-team is properly installed.'
107
+ );
108
+ }
109
+
110
+ // Determine target directory
111
+ const targetDir = scopeLower === 'user' ? config.userDir : path.resolve(config.localDir);
112
+ const targetPath = path.join(targetDir, config.targetFile);
113
+
114
+ // Check if already exists
115
+ if (fs.existsSync(targetPath) && !ctx.flags.force) {
116
+ exitWithError(ctx, `Skill already exists: ${targetPath}`, 'Use --force to overwrite.');
117
+ }
118
+
119
+ // Create directory if needed
120
+ if (!fs.existsSync(targetDir)) {
121
+ fs.mkdirSync(targetDir, { recursive: true });
122
+ if (ctx.flags.verbose) {
123
+ ctx.ui.info(`Created directory: ${targetDir}`);
124
+ }
125
+ }
126
+
127
+ // Copy file
128
+ fs.copyFileSync(sourcePath, targetPath);
129
+
130
+ if (ctx.flags.json) {
131
+ ctx.ui.json({
132
+ success: true,
133
+ agent: agentLower,
134
+ scope: scopeLower,
135
+ path: targetPath,
136
+ });
137
+ } else {
138
+ ctx.ui.success(`Installed ${agentLower} skill to ${targetPath}`);
139
+
140
+ // Show usage hint
141
+ if (agentLower === 'claude') {
142
+ ctx.ui.info('Usage: /team talk codex "message"');
143
+ } else if (agentLower === 'codex') {
144
+ ctx.ui.info('Enable skills: codex --enable skills');
145
+ ctx.ui.info('Usage: $tmux-team or implicit invocation');
146
+ }
147
+ }
148
+ }