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.
- package/README.md +42 -193
- package/package.json +3 -2
- package/skills/README.md +101 -0
- package/skills/claude/team.md +46 -0
- package/skills/codex/SKILL.md +46 -0
- package/src/cli.ts +19 -5
- package/src/commands/config.ts +2 -44
- package/src/commands/help.ts +1 -2
- package/src/commands/install-skill.ts +148 -0
- package/src/commands/talk.test.ts +458 -63
- package/src/commands/talk.ts +158 -69
- package/src/config.test.ts +0 -1
- package/src/config.ts +0 -1
- package/src/identity.ts +89 -0
- package/src/types.ts +2 -2
- package/src/version.ts +1 -1
- package/src/pm/commands.test.ts +0 -1462
- package/src/pm/commands.ts +0 -1011
- package/src/pm/manager.test.ts +0 -377
- package/src/pm/manager.ts +0 -146
- package/src/pm/permissions.test.ts +0 -444
- package/src/pm/permissions.ts +0 -293
- package/src/pm/storage/adapter.ts +0 -57
- package/src/pm/storage/fs.test.ts +0 -512
- package/src/pm/storage/fs.ts +0 -290
- package/src/pm/storage/github.ts +0 -842
- package/src/pm/types.ts +0 -91
package/src/commands/config.ts
CHANGED
|
@@ -15,13 +15,11 @@ import {
|
|
|
15
15
|
|
|
16
16
|
type EnumConfigKey = 'mode' | 'preambleMode';
|
|
17
17
|
type NumericConfigKey = 'preambleEvery';
|
|
18
|
-
type
|
|
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
|
|
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);
|
package/src/commands/help.ts
CHANGED
|
@@ -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('
|
|
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
|
+
}
|