tink-harness 1.2.1 → 1.2.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/.claude-plugin/plugin.json +2 -2
- package/CHANGELOG.md +103 -75
- package/README.ko.md +130 -122
- package/README.md +96 -92
- package/VERSIONING.md +2 -2
- package/bin/install.js +318 -257
- package/commands/cast.md +179 -172
- package/docs/context-change-review.ko.md +14 -0
- package/docs/context-change-review.md +14 -0
- package/docs/external-context-policy.ko.md +15 -0
- package/docs/external-context-policy.md +15 -0
- package/docs/graph-contracts-and-guards.md +61 -0
- package/docs/harness-lifecycle-signals.ko.md +23 -0
- package/docs/harness-lifecycle-signals.md +23 -0
- package/docs/hooks.md +49 -0
- package/docs/memory-decision-layers.ko.md +14 -0
- package/docs/memory-decision-layers.md +14 -0
- package/docs/memory.md +31 -0
- package/docs/phase-5-update-confidence.ko.md +99 -0
- package/docs/phase-5-update-confidence.md +97 -0
- package/docs/planned-work-units.ko.md +77 -0
- package/docs/planned-work-units.md +77 -0
- package/docs/pr/2026-06-07-phase-5-6-follow-up.ko.md +35 -0
- package/docs/pr/2026-06-07-v1.2.0-improvements.html +450 -0
- package/docs/pr/2026-06-08-planned-work-units.ko.md +27 -0
- package/docs/pr/2026-06-08-v1.2.2.ko.md +27 -0
- package/docs/repo-signals.ko.md +104 -0
- package/docs/repo-signals.md +95 -77
- package/docs/research.md +16 -0
- package/docs/tink-idea-implementation-plan.ko.md +201 -0
- package/docs/update-diagnosis.ko.md +16 -0
- package/docs/update-diagnosis.md +16 -0
- package/docs/update-troubleshooting.ko.md +113 -0
- package/docs/update-troubleshooting.md +100 -0
- package/docs/update-verification-recipe.ko.md +118 -0
- package/docs/update-verification-recipe.md +119 -0
- package/docs/verification-evidence-details.ko.md +14 -0
- package/docs/verification-evidence-details.md +14 -0
- package/docs/work-state.ko.md +94 -0
- package/docs/work-state.md +92 -0
- package/package.json +2 -4
- package/templates/claude/commands/tink/cast.md +179 -172
- package/templates/codex/skills/tink-cast/SKILL.md +14 -13
- package/templates/codex/skills/tink-core/RULES.md +163 -112
- package/templates/tink/memory/approved/README.md +5 -0
- package/templates/tink/memory/candidate/README.md +5 -0
- package/templates/tink/memory/evidence/README.md +5 -0
- package/templates/tink/memory/rejected/README.md +5 -0
- package/templates/tink/schemas/harness-lifecycle.schema.json +44 -0
- package/templates/tink/schemas/mcp-policy.schema.json +65 -0
- package/templates/tink/schemas/verification.schema.json +154 -141
package/bin/install.js
CHANGED
|
@@ -11,13 +11,20 @@ const __dirname = path.dirname(__filename);
|
|
|
11
11
|
const root = path.resolve(__dirname, '..');
|
|
12
12
|
const args = process.argv.slice(2);
|
|
13
13
|
const command = args[0] || 'install';
|
|
14
|
-
const isUpdate = command === 'update';
|
|
15
|
-
const dryRun = args.includes('--dry-run');
|
|
16
|
-
const force = args.includes('--force');
|
|
17
|
-
const yes = args.includes('--yes') || args.includes('-y');
|
|
18
|
-
const interactive = process.stdin.isTTY && process.stdout.isTTY && !yes && !dryRun;
|
|
19
|
-
const source = 'https://github.com/dotoricode/tink-harness.git';
|
|
20
|
-
const validSurfaces = new Set(['claude', 'codex']);
|
|
14
|
+
const isUpdate = command === 'update';
|
|
15
|
+
const dryRun = args.includes('--dry-run');
|
|
16
|
+
const force = args.includes('--force');
|
|
17
|
+
const yes = args.includes('--yes') || args.includes('-y');
|
|
18
|
+
const interactive = process.stdin.isTTY && process.stdout.isTTY && !yes && !dryRun;
|
|
19
|
+
const source = 'https://github.com/dotoricode/tink-harness.git';
|
|
20
|
+
const validSurfaces = new Set(['claude', 'codex']);
|
|
21
|
+
const operationLog = {
|
|
22
|
+
written: [],
|
|
23
|
+
updated: [],
|
|
24
|
+
preserved: [],
|
|
25
|
+
removedLegacy: [],
|
|
26
|
+
keptUnknown: []
|
|
27
|
+
};
|
|
21
28
|
|
|
22
29
|
const COPY = {
|
|
23
30
|
en: {
|
|
@@ -67,124 +74,124 @@ const COPY = {
|
|
|
67
74
|
}
|
|
68
75
|
};
|
|
69
76
|
|
|
70
|
-
const COMPONENTS = {
|
|
77
|
+
const COMPONENTS = {
|
|
71
78
|
en: [
|
|
72
|
-
{ value: 'commands', label: 'Claude Code commands', hint: '/tink:setup, /tink:cast, /tink:verify, /tink:list, /tink:frog, /tink:weave, /tink:update' },
|
|
79
|
+
{ value: 'commands', label: 'Claude Code commands', hint: '/tink:setup, /tink:cast, /tink:verify, /tink:list, /tink:frog, /tink:weave, /tink:update' },
|
|
73
80
|
{ value: 'skill', label: 'Tink skill', hint: 'Tink operating rules for Claude Code' },
|
|
74
81
|
{ value: 'harnesses', label: 'Built-in harnesses', hint: 'Reusable task templates' },
|
|
75
82
|
{ value: 'memory', label: 'Memory templates', hint: 'Approved mistakes/preferences/lessons files' },
|
|
76
83
|
{ value: 'hook', label: 'Hook recommendation (optional)', hint: 'Registers a safe UserPromptSubmit hook when selected. Off by default.' }
|
|
77
84
|
],
|
|
78
85
|
ko: [
|
|
79
|
-
{ value: 'commands', label: 'Claude Code 명령', hint: '/tink:setup, /tink:cast, /tink:verify, /tink:list, /tink:frog, /tink:weave, /tink:update' },
|
|
86
|
+
{ value: 'commands', label: 'Claude Code 명령', hint: '/tink:setup, /tink:cast, /tink:verify, /tink:list, /tink:frog, /tink:weave, /tink:update' },
|
|
80
87
|
{ value: 'skill', label: 'Tink skill', hint: 'Claude Code가 읽는 Tink 작업 원칙' },
|
|
81
88
|
{ value: 'harnesses', label: '기본 harness', hint: '재사용 작업 템플릿' },
|
|
82
89
|
{ value: 'memory', label: 'Memory 템플릿', hint: '승인된 실수/선호/교훈 파일' },
|
|
83
90
|
{ value: 'hook', label: 'Hook 추천 (선택)', hint: '선택하면 안전한 UserPromptSubmit hook으로 등록합니다. 기본 off.' }
|
|
84
91
|
],
|
|
85
92
|
zh: [
|
|
86
|
-
{ value: 'commands', label: 'Claude Code 命令', hint: '/tink:setup, /tink:cast, /tink:verify, /tink:list, /tink:frog, /tink:weave, /tink:update' },
|
|
93
|
+
{ value: 'commands', label: 'Claude Code 命令', hint: '/tink:setup, /tink:cast, /tink:verify, /tink:list, /tink:frog, /tink:weave, /tink:update' },
|
|
87
94
|
{ value: 'skill', label: 'Tink skill', hint: 'Claude Code 读取的 Tink 工作规则' },
|
|
88
95
|
{ value: 'harnesses', label: '内置 harness', hint: '可复用任务模板' },
|
|
89
96
|
{ value: 'memory', label: 'Memory 模板', hint: '经批准的错误/偏好/经验文件' },
|
|
90
97
|
{ value: 'hook', label: 'Hook 推荐(可选)', hint: '选择后注册安全的 UserPromptSubmit hook。默认关闭。' }
|
|
91
98
|
]
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
const SURFACE_OPTIONS = {
|
|
95
|
-
en: [
|
|
96
|
-
{ value: 'claude', label: 'Claude Code', hint: 'Install /tink:* commands, Claude skill, and optional hook support' },
|
|
97
|
-
{ value: 'codex', label: 'Codex', hint: 'Install $tink:* skills into CODEX_HOME or ~/.codex' }
|
|
98
|
-
],
|
|
99
|
-
ko: [
|
|
100
|
-
{ value: 'claude', label: 'Claude Code', hint: '/tink:* 명령, Claude skill, 선택 hook 지원 설치' },
|
|
101
|
-
{ value: 'codex', label: 'Codex', hint: '$tink:* skills를 CODEX_HOME 또는 ~/.codex에 설치' }
|
|
102
|
-
],
|
|
103
|
-
zh: [
|
|
104
|
-
{ value: 'claude', label: 'Claude Code', hint: 'Install /tink:* commands, Claude skill, and optional hook support' },
|
|
105
|
-
{ value: 'codex', label: 'Codex', hint: 'Install $tink:* skills into CODEX_HOME or ~/.codex' }
|
|
106
|
-
]
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
function argValue(name) {
|
|
110
|
-
const prefix = `${name}=`;
|
|
111
|
-
const found = args.find((arg) => arg.startsWith(prefix));
|
|
112
|
-
return found ? found.slice(prefix.length) : undefined;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function usage() {
|
|
116
|
-
console.log(`Tink installer for Claude Code and Codex\n\nUsage:\n npx tink-harness@latest [install] [--scope=repo|global] [--global] [--lang=en|ko|zh] [--yes] [--with-hook] [--dry-run] [--force]\n npx tink-harness@latest update [--scope=repo|global] [--global] [--lang=en|ko|zh] [--yes] [--dry-run] [--force]\n\nCommands:\n install Install Tink.\n update Update Tink to the latest templates. Keeps user-modified files.\n\nDefault interactive flow:\n 1. Select language\n 2. Show TINK wizard\n 3. Select Claude Code, Codex, or both\n 4. Select components\n 5. Select repo/global installation scope\n 6. Select git tracking policy for project state\n\nScopes:\n repo Install shared .tink files into the current project.\n global Install shared .tink files into your home directory.\n`);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function normalizeSurfaces(surfaces) {
|
|
120
|
-
const values = [...new Set(surfaces)];
|
|
121
|
-
if (values.some((value) => !validSurfaces.has(value))) {
|
|
122
|
-
console.error(`Invalid install surface: ${values.find((value) => !validSurfaces.has(value))}`);
|
|
123
|
-
usage();
|
|
124
|
-
process.exit(1);
|
|
125
|
-
}
|
|
126
|
-
if (values.includes('claude') && values.includes('codex')) return 'all';
|
|
127
|
-
return values[0] || 'claude';
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
function resolveDefaultSurfaces() {
|
|
131
|
-
if (argValue('--agent')) {
|
|
132
|
-
console.error('--agent is no longer supported. Run the interactive installer and select Claude Code, Codex, or both during setup.');
|
|
133
|
-
usage();
|
|
134
|
-
process.exit(1);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const envValue = process.env.TINK_INSTALL_SURFACES;
|
|
138
|
-
if (!envValue) return 'claude';
|
|
139
|
-
if (envValue.trim().toLowerCase() === 'all') return 'all';
|
|
140
|
-
return normalizeSurfaces(envValue.split(',').map((value) => value.trim().toLowerCase()).filter(Boolean));
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
function includesClaude(agent) {
|
|
144
|
-
return agent === 'claude' || agent === 'all';
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
function includesCodex(agent) {
|
|
148
|
-
return agent === 'codex' || agent === 'all';
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
function codexHome() {
|
|
152
|
-
return process.env.CODEX_HOME || path.join(os.homedir(), '.codex');
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
function componentOptionsFor(agent, language) {
|
|
156
|
-
const options = COMPONENTS[language].filter((item) => {
|
|
157
|
-
if (item.value === 'commands') return includesClaude(agent);
|
|
158
|
-
if (item.value === 'hook') return includesClaude(agent);
|
|
159
|
-
return true;
|
|
160
|
-
});
|
|
161
|
-
return options.map((item) => {
|
|
162
|
-
if (item.value !== 'skill') return item;
|
|
163
|
-
if (agent === 'codex') {
|
|
164
|
-
return {
|
|
165
|
-
value: 'skill',
|
|
166
|
-
label: language === 'ko' ? 'Codex Tink skills' : language === 'zh' ? 'Codex Tink skills' : 'Codex Tink skills',
|
|
167
|
-
hint: language === 'ko'
|
|
168
|
-
? 'Codex가 $tink로 읽는 Tink 작업 원칙'
|
|
169
|
-
: language === 'zh'
|
|
170
|
-
? 'Codex 通过 $tink 读取的 Tink 工作规则'
|
|
171
|
-
: 'Tink operating rules for Codex through $tink:*'
|
|
172
|
-
};
|
|
173
|
-
}
|
|
174
|
-
if (agent === 'all') {
|
|
175
|
-
return {
|
|
176
|
-
value: 'skill',
|
|
177
|
-
label: language === 'ko' ? 'Tink skills' : language === 'zh' ? 'Tink skills' : 'Tink skills',
|
|
178
|
-
hint: language === 'ko'
|
|
179
|
-
? 'Claude Code와 Codex가 읽는 Tink 작업 원칙'
|
|
180
|
-
: language === 'zh'
|
|
181
|
-
? 'Claude Code 和 Codex 读取的 Tink 工作规则'
|
|
182
|
-
: 'Tink operating rules for Claude Code and Codex'
|
|
183
|
-
};
|
|
184
|
-
}
|
|
185
|
-
return item;
|
|
186
|
-
});
|
|
187
|
-
}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const SURFACE_OPTIONS = {
|
|
102
|
+
en: [
|
|
103
|
+
{ value: 'claude', label: 'Claude Code', hint: 'Install /tink:* commands, Claude skill, and optional hook support' },
|
|
104
|
+
{ value: 'codex', label: 'Codex', hint: 'Install $tink:* skills into CODEX_HOME or ~/.codex' }
|
|
105
|
+
],
|
|
106
|
+
ko: [
|
|
107
|
+
{ value: 'claude', label: 'Claude Code', hint: '/tink:* 명령, Claude skill, 선택 hook 지원 설치' },
|
|
108
|
+
{ value: 'codex', label: 'Codex', hint: '$tink:* skills를 CODEX_HOME 또는 ~/.codex에 설치' }
|
|
109
|
+
],
|
|
110
|
+
zh: [
|
|
111
|
+
{ value: 'claude', label: 'Claude Code', hint: 'Install /tink:* commands, Claude skill, and optional hook support' },
|
|
112
|
+
{ value: 'codex', label: 'Codex', hint: 'Install $tink:* skills into CODEX_HOME or ~/.codex' }
|
|
113
|
+
]
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
function argValue(name) {
|
|
117
|
+
const prefix = `${name}=`;
|
|
118
|
+
const found = args.find((arg) => arg.startsWith(prefix));
|
|
119
|
+
return found ? found.slice(prefix.length) : undefined;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function usage() {
|
|
123
|
+
console.log(`Tink installer for Claude Code and Codex\n\nUsage:\n npx tink-harness@latest [install] [--scope=repo|global] [--global] [--lang=en|ko|zh] [--yes] [--with-hook] [--dry-run] [--force]\n npx tink-harness@latest update [--scope=repo|global] [--global] [--lang=en|ko|zh] [--yes] [--dry-run] [--force]\n\nCommands:\n install Install Tink.\n update Update Tink to the latest templates. Keeps user-modified files.\n\nDefault interactive flow:\n 1. Select language\n 2. Show TINK wizard\n 3. Select Claude Code, Codex, or both\n 4. Select components\n 5. Select repo/global installation scope\n 6. Select git tracking policy for project state\n\nScopes:\n repo Install shared .tink files into the current project.\n global Install shared .tink files into your home directory.\n`);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function normalizeSurfaces(surfaces) {
|
|
127
|
+
const values = [...new Set(surfaces)];
|
|
128
|
+
if (values.some((value) => !validSurfaces.has(value))) {
|
|
129
|
+
console.error(`Invalid install surface: ${values.find((value) => !validSurfaces.has(value))}`);
|
|
130
|
+
usage();
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
133
|
+
if (values.includes('claude') && values.includes('codex')) return 'all';
|
|
134
|
+
return values[0] || 'claude';
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function resolveDefaultSurfaces() {
|
|
138
|
+
if (argValue('--agent')) {
|
|
139
|
+
console.error('--agent is no longer supported. Run the interactive installer and select Claude Code, Codex, or both during setup.');
|
|
140
|
+
usage();
|
|
141
|
+
process.exit(1);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const envValue = process.env.TINK_INSTALL_SURFACES;
|
|
145
|
+
if (!envValue) return 'claude';
|
|
146
|
+
if (envValue.trim().toLowerCase() === 'all') return 'all';
|
|
147
|
+
return normalizeSurfaces(envValue.split(',').map((value) => value.trim().toLowerCase()).filter(Boolean));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function includesClaude(agent) {
|
|
151
|
+
return agent === 'claude' || agent === 'all';
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function includesCodex(agent) {
|
|
155
|
+
return agent === 'codex' || agent === 'all';
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function codexHome() {
|
|
159
|
+
return process.env.CODEX_HOME || path.join(os.homedir(), '.codex');
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function componentOptionsFor(agent, language) {
|
|
163
|
+
const options = COMPONENTS[language].filter((item) => {
|
|
164
|
+
if (item.value === 'commands') return includesClaude(agent);
|
|
165
|
+
if (item.value === 'hook') return includesClaude(agent);
|
|
166
|
+
return true;
|
|
167
|
+
});
|
|
168
|
+
return options.map((item) => {
|
|
169
|
+
if (item.value !== 'skill') return item;
|
|
170
|
+
if (agent === 'codex') {
|
|
171
|
+
return {
|
|
172
|
+
value: 'skill',
|
|
173
|
+
label: language === 'ko' ? 'Codex Tink skills' : language === 'zh' ? 'Codex Tink skills' : 'Codex Tink skills',
|
|
174
|
+
hint: language === 'ko'
|
|
175
|
+
? 'Codex가 $tink로 읽는 Tink 작업 원칙'
|
|
176
|
+
: language === 'zh'
|
|
177
|
+
? 'Codex 通过 $tink 读取的 Tink 工作规则'
|
|
178
|
+
: 'Tink operating rules for Codex through $tink:*'
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
if (agent === 'all') {
|
|
182
|
+
return {
|
|
183
|
+
value: 'skill',
|
|
184
|
+
label: language === 'ko' ? 'Tink skills' : language === 'zh' ? 'Tink skills' : 'Tink skills',
|
|
185
|
+
hint: language === 'ko'
|
|
186
|
+
? 'Claude Code와 Codex가 읽는 Tink 작업 원칙'
|
|
187
|
+
: language === 'zh'
|
|
188
|
+
? 'Claude Code 和 Codex 读取的 Tink 工作规则'
|
|
189
|
+
: 'Tink operating rules for Claude Code and Codex'
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
return item;
|
|
193
|
+
});
|
|
194
|
+
}
|
|
188
195
|
|
|
189
196
|
function colorLine(line, color) {
|
|
190
197
|
if (!process.stdout.isTTY && !interactive) return line;
|
|
@@ -235,13 +242,24 @@ function displayPath(base, filePath) {
|
|
|
235
242
|
return path.relative(base, filePath) || '.';
|
|
236
243
|
}
|
|
237
244
|
|
|
238
|
-
function
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
}
|
|
245
|
+
function recordOperation(kind, base, filePath) {
|
|
246
|
+
operationLog[kind].push(displayPath(base, filePath).replace(/\\/g, '/'));
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function shortList(items, emptyText = '- none') {
|
|
250
|
+
if (!items.length) return emptyText;
|
|
251
|
+
const shown = items.slice(0, 8).map((item) => `- ${item}`);
|
|
252
|
+
if (items.length > shown.length) shown.push(`- ...and ${items.length - shown.length} more`);
|
|
253
|
+
return shown.join('\n');
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function isAlwaysUpdatePath(src) {
|
|
257
|
+
const rel = path.relative(root, src).replace(/\\/g, '/');
|
|
258
|
+
return rel.startsWith('templates/claude/commands/') ||
|
|
259
|
+
rel.startsWith('templates/claude/skills/') ||
|
|
260
|
+
rel.startsWith('templates/codex/skills/') ||
|
|
261
|
+
rel.startsWith('templates/tink/maintenance/');
|
|
262
|
+
}
|
|
245
263
|
|
|
246
264
|
function writeFileFromTemplate(src, dest, base) {
|
|
247
265
|
const exists = fs.existsSync(dest);
|
|
@@ -254,6 +272,7 @@ function writeFileFromTemplate(src, dest, base) {
|
|
|
254
272
|
}
|
|
255
273
|
if (!isAlwaysUpdatePath(src)) {
|
|
256
274
|
log.message(`keep user-modified ${displayPath(base, dest)}`);
|
|
275
|
+
recordOperation('preserved', base, dest);
|
|
257
276
|
return;
|
|
258
277
|
}
|
|
259
278
|
// commands/skills/maintenance: always update to new version
|
|
@@ -263,6 +282,7 @@ function writeFileFromTemplate(src, dest, base) {
|
|
|
263
282
|
}
|
|
264
283
|
}
|
|
265
284
|
log.message(`${dryRun ? 'would write' : (exists ? 'update' : 'write')} ${displayPath(base, dest)}`);
|
|
285
|
+
recordOperation(exists ? 'updated' : 'written', base, dest);
|
|
266
286
|
if (!dryRun) {
|
|
267
287
|
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
268
288
|
fs.copyFileSync(src, dest);
|
|
@@ -282,7 +302,7 @@ function copyDir(src, dest, base) {
|
|
|
282
302
|
}
|
|
283
303
|
}
|
|
284
304
|
|
|
285
|
-
function copyTinkCommands(templateRoot, target) {
|
|
305
|
+
function copyTinkCommands(templateRoot, target) {
|
|
286
306
|
const commandSrc = path.join(templateRoot, 'claude/commands/tink');
|
|
287
307
|
const commandDest = path.join(target, '.claude/commands/tink');
|
|
288
308
|
const flatCommandDest = path.join(target, '.claude/commands');
|
|
@@ -294,6 +314,7 @@ function copyTinkCommands(templateRoot, target) {
|
|
|
294
314
|
const legacy = path.join(flatCommandDest, name);
|
|
295
315
|
if (fs.existsSync(legacy)) {
|
|
296
316
|
log.message(`${dryRun ? 'would remove legacy' : 'remove legacy'} ${displayPath(target, legacy)}`);
|
|
317
|
+
recordOperation('removedLegacy', target, legacy);
|
|
297
318
|
if (!dryRun) fs.rmSync(legacy, { force: true });
|
|
298
319
|
}
|
|
299
320
|
}
|
|
@@ -301,6 +322,7 @@ function copyTinkCommands(templateRoot, target) {
|
|
|
301
322
|
const legacy = path.join(commandDest, name);
|
|
302
323
|
if (fs.existsSync(legacy)) {
|
|
303
324
|
log.message(`${dryRun ? 'would remove legacy' : 'remove legacy'} ${displayPath(target, legacy)}`);
|
|
325
|
+
recordOperation('removedLegacy', target, legacy);
|
|
304
326
|
if (!dryRun) fs.rmSync(legacy, { force: true });
|
|
305
327
|
}
|
|
306
328
|
}
|
|
@@ -308,12 +330,14 @@ function copyTinkCommands(templateRoot, target) {
|
|
|
308
330
|
const legacy = path.join(flatCommandDest, name);
|
|
309
331
|
if (fs.existsSync(legacy)) {
|
|
310
332
|
log.message(`${dryRun ? 'would remove legacy Tiny' : 'remove legacy Tiny'} ${displayPath(target, legacy)}`);
|
|
333
|
+
recordOperation('removedLegacy', target, legacy);
|
|
311
334
|
if (!dryRun) fs.rmSync(legacy, { force: true });
|
|
312
335
|
}
|
|
313
336
|
}
|
|
314
337
|
for (const legacyDir of legacyDirs) {
|
|
315
338
|
if (fs.existsSync(legacyDir)) {
|
|
316
339
|
log.message(`${dryRun ? 'would remove legacy Tiny' : 'remove legacy Tiny'} ${displayPath(target, legacyDir)}`);
|
|
340
|
+
recordOperation('removedLegacy', target, legacyDir);
|
|
317
341
|
if (!dryRun) fs.rmSync(legacyDir, { recursive: true, force: true });
|
|
318
342
|
}
|
|
319
343
|
}
|
|
@@ -321,29 +345,31 @@ function copyTinkCommands(templateRoot, target) {
|
|
|
321
345
|
if (!entry.isFile() || !entry.name.endsWith('.md')) continue;
|
|
322
346
|
writeFileFromTemplate(path.join(commandSrc, entry.name), path.join(commandDest, entry.name), target);
|
|
323
347
|
}
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
function removeLegacyCodexSkill(codexTarget) {
|
|
327
|
-
const legacyDir = path.join(codexTarget, 'skills/tink');
|
|
328
|
-
const legacySkill = path.join(legacyDir, 'SKILL.md');
|
|
329
|
-
if (!fs.existsSync(legacyDir)) return;
|
|
330
|
-
|
|
331
|
-
let shouldRemove = false;
|
|
332
|
-
if (fs.existsSync(legacySkill)) {
|
|
333
|
-
const text = fs.readFileSync(legacySkill, 'utf8');
|
|
334
|
-
shouldRemove = text.includes('name: tink') && text.includes('Tink');
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
if (!shouldRemove) {
|
|
338
|
-
log.message(`keep unknown ${displayPath(codexTarget, legacyDir)}`);
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function removeLegacyCodexSkill(codexTarget) {
|
|
351
|
+
const legacyDir = path.join(codexTarget, 'skills/tink');
|
|
352
|
+
const legacySkill = path.join(legacyDir, 'SKILL.md');
|
|
353
|
+
if (!fs.existsSync(legacyDir)) return;
|
|
354
|
+
|
|
355
|
+
let shouldRemove = false;
|
|
356
|
+
if (fs.existsSync(legacySkill)) {
|
|
357
|
+
const text = fs.readFileSync(legacySkill, 'utf8');
|
|
358
|
+
shouldRemove = text.includes('name: tink') && text.includes('Tink');
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
if (!shouldRemove) {
|
|
362
|
+
log.message(`keep unknown ${displayPath(codexTarget, legacyDir)}`);
|
|
363
|
+
recordOperation('keptUnknown', codexTarget, legacyDir);
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
log.message(`${dryRun ? 'would remove legacy' : 'remove legacy'} ${displayPath(codexTarget, legacyDir)}`);
|
|
368
|
+
recordOperation('removedLegacy', codexTarget, legacyDir);
|
|
369
|
+
if (!dryRun) fs.rmSync(legacyDir, { recursive: true, force: true });
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function readJsonFile(filePath, fallback) {
|
|
347
373
|
if (!fs.existsSync(filePath)) return fallback;
|
|
348
374
|
try {
|
|
349
375
|
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
@@ -381,42 +407,42 @@ function registerClaudeHook(target, scope, base) {
|
|
|
381
407
|
writeJsonFile(settingsPath, settings, base);
|
|
382
408
|
}
|
|
383
409
|
|
|
384
|
-
function copySelected(scope, components, agent) {
|
|
385
|
-
const repoTarget = process.cwd();
|
|
386
|
-
const globalTarget = os.homedir();
|
|
387
|
-
const codexTarget = codexHome();
|
|
388
|
-
const target = scope === 'global' ? globalTarget : repoTarget;
|
|
389
|
-
const templateRoot = path.join(root, 'templates');
|
|
390
|
-
|
|
391
|
-
if (includesClaude(agent) && components.includes('commands')) {
|
|
392
|
-
copyTinkCommands(templateRoot, target);
|
|
393
|
-
}
|
|
394
|
-
if (components.includes('skill')) {
|
|
395
|
-
if (includesClaude(agent)) {
|
|
396
|
-
copyDir(path.join(templateRoot, 'claude/skills'), path.join(target, '.claude/skills'), target);
|
|
397
|
-
}
|
|
398
|
-
if (includesCodex(agent)) {
|
|
399
|
-
removeLegacyCodexSkill(codexTarget);
|
|
400
|
-
copyDir(path.join(templateRoot, 'codex/skills'), path.join(codexTarget, 'skills'), codexTarget);
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
if (components.includes('harnesses')) {
|
|
404
|
-
copyDir(path.join(templateRoot, 'tink/harnesses'), path.join(target, '.tink/harnesses'), target);
|
|
405
|
-
copyDir(path.join(templateRoot, 'tink/rules'), path.join(target, '.tink/rules'), target);
|
|
406
|
-
copyDir(path.join(templateRoot, 'tink/schemas'), path.join(target, '.tink/schemas'), target);
|
|
407
|
-
copyDir(path.join(templateRoot, 'tink/maintenance'), path.join(target, '.tink/maintenance'), target);
|
|
408
|
-
writeFileFromTemplate(path.join(templateRoot, 'tink/config.json'), path.join(target, '.tink/config.json'), target);
|
|
409
|
-
}
|
|
410
|
+
function copySelected(scope, components, agent) {
|
|
411
|
+
const repoTarget = process.cwd();
|
|
412
|
+
const globalTarget = os.homedir();
|
|
413
|
+
const codexTarget = codexHome();
|
|
414
|
+
const target = scope === 'global' ? globalTarget : repoTarget;
|
|
415
|
+
const templateRoot = path.join(root, 'templates');
|
|
416
|
+
|
|
417
|
+
if (includesClaude(agent) && components.includes('commands')) {
|
|
418
|
+
copyTinkCommands(templateRoot, target);
|
|
419
|
+
}
|
|
420
|
+
if (components.includes('skill')) {
|
|
421
|
+
if (includesClaude(agent)) {
|
|
422
|
+
copyDir(path.join(templateRoot, 'claude/skills'), path.join(target, '.claude/skills'), target);
|
|
423
|
+
}
|
|
424
|
+
if (includesCodex(agent)) {
|
|
425
|
+
removeLegacyCodexSkill(codexTarget);
|
|
426
|
+
copyDir(path.join(templateRoot, 'codex/skills'), path.join(codexTarget, 'skills'), codexTarget);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
if (components.includes('harnesses')) {
|
|
430
|
+
copyDir(path.join(templateRoot, 'tink/harnesses'), path.join(target, '.tink/harnesses'), target);
|
|
431
|
+
copyDir(path.join(templateRoot, 'tink/rules'), path.join(target, '.tink/rules'), target);
|
|
432
|
+
copyDir(path.join(templateRoot, 'tink/schemas'), path.join(target, '.tink/schemas'), target);
|
|
433
|
+
copyDir(path.join(templateRoot, 'tink/maintenance'), path.join(target, '.tink/maintenance'), target);
|
|
434
|
+
writeFileFromTemplate(path.join(templateRoot, 'tink/config.json'), path.join(target, '.tink/config.json'), target);
|
|
435
|
+
}
|
|
410
436
|
if (components.includes('memory')) {
|
|
411
437
|
copyDir(path.join(templateRoot, 'tink/memory'), path.join(target, '.tink/memory'), target);
|
|
412
438
|
}
|
|
413
|
-
if (includesClaude(agent) && components.includes('hook')) {
|
|
414
|
-
copyDir(path.join(templateRoot, 'tink/hooks'), path.join(target, '.tink/hooks'), target);
|
|
415
|
-
registerClaudeHook(target, scope, target);
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
return { repoTarget, globalTarget, codexTarget, installTarget: target };
|
|
419
|
-
}
|
|
439
|
+
if (includesClaude(agent) && components.includes('hook')) {
|
|
440
|
+
copyDir(path.join(templateRoot, 'tink/hooks'), path.join(target, '.tink/hooks'), target);
|
|
441
|
+
registerClaudeHook(target, scope, target);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
return { repoTarget, globalTarget, codexTarget, installTarget: target };
|
|
445
|
+
}
|
|
420
446
|
|
|
421
447
|
function updateGitignore(target, policy) {
|
|
422
448
|
if (policy === 'all') {
|
|
@@ -441,31 +467,64 @@ function updateGitignore(target, policy) {
|
|
|
441
467
|
}
|
|
442
468
|
}
|
|
443
469
|
|
|
444
|
-
function patchConfig(target, scope, hookScope, language) {
|
|
470
|
+
function patchConfig(target, scope, hookScope, language) {
|
|
445
471
|
const configPath = path.join(target, '.tink/config.json');
|
|
446
472
|
if (!fs.existsSync(configPath) || dryRun) return;
|
|
473
|
+
const rel = displayPath(target, configPath).replace(/\\/g, '/');
|
|
474
|
+
if (isUpdate && !force && operationLog.preserved.includes(rel)) return;
|
|
447
475
|
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
448
476
|
config.install_scope = scope;
|
|
449
477
|
config.hook_scope = hookScope;
|
|
450
478
|
config.language = language;
|
|
451
479
|
fs.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`);
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
function nextStepFor(agent) {
|
|
455
|
-
if (agent === 'codex') {
|
|
456
|
-
return 'Next: open Codex and use $tink:cast <task> to start. Use $tink:setup only to review or change settings.';
|
|
457
|
-
}
|
|
458
|
-
if (agent === 'all') {
|
|
459
|
-
return 'Next: in Claude Code run /tink:cast <task>, or in Codex use $tink:cast <task>.';
|
|
460
|
-
}
|
|
461
|
-
return 'Next: open Claude Code and run /tink:cast <task> to start. Run /tink:setup only to review or change settings.';
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
function doneLineFor(agent) {
|
|
465
|
-
if (agent === 'codex') return '\nDone. Open Codex and use $tink:cast <task> to start.';
|
|
466
|
-
if (agent === 'all') return '\nDone. Use /tink:cast <task> in Claude Code or $tink:cast <task> in Codex to start.';
|
|
467
|
-
return '\nDone. Open Claude Code and run /tink:cast <task> to start.';
|
|
468
|
-
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
function nextStepFor(agent) {
|
|
483
|
+
if (agent === 'codex') {
|
|
484
|
+
return 'Next: open Codex and use $tink:cast <task> to start. Use $tink:setup only to review or change settings.';
|
|
485
|
+
}
|
|
486
|
+
if (agent === 'all') {
|
|
487
|
+
return 'Next: in Claude Code run /tink:cast <task>, or in Codex use $tink:cast <task>.';
|
|
488
|
+
}
|
|
489
|
+
return 'Next: open Claude Code and run /tink:cast <task> to start. Run /tink:setup only to review or change settings.';
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
function doneLineFor(agent) {
|
|
493
|
+
if (agent === 'codex') return '\nDone. Open Codex and use $tink:cast <task> to start.';
|
|
494
|
+
if (agent === 'all') return '\nDone. Use /tink:cast <task> in Claude Code or $tink:cast <task> in Codex to start.';
|
|
495
|
+
return '\nDone. Open Claude Code and run /tink:cast <task> to start.';
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
function updateResultSummary(agent, targets) {
|
|
499
|
+
const locations = [
|
|
500
|
+
includesClaude(agent) ? `Claude Code commands: ${path.join(targets.installTarget, '.claude/commands/tink')}` : null,
|
|
501
|
+
includesClaude(agent) ? `Claude Code skill: ${path.join(targets.installTarget, '.claude/skills/tink')}` : null,
|
|
502
|
+
includesCodex(agent) ? `Codex skills: ${path.join(targets.codexTarget, 'skills')}` : null,
|
|
503
|
+
`Tink shared files: ${path.join(targets.installTarget, '.tink')}`
|
|
504
|
+
].filter(Boolean);
|
|
505
|
+
|
|
506
|
+
return [
|
|
507
|
+
'Update Result Summary',
|
|
508
|
+
`Surfaces: ${agent}`,
|
|
509
|
+
`Install target: ${targets.installTarget}`,
|
|
510
|
+
includesCodex(agent) ? `Codex target: ${targets.codexTarget}` : null,
|
|
511
|
+
'',
|
|
512
|
+
'Updated or added:',
|
|
513
|
+
shortList([...operationLog.updated, ...operationLog.written]),
|
|
514
|
+
'',
|
|
515
|
+
'Preserved user-modified files:',
|
|
516
|
+
shortList(operationLog.preserved),
|
|
517
|
+
'',
|
|
518
|
+
'Removed legacy paths:',
|
|
519
|
+
shortList(operationLog.removedLegacy),
|
|
520
|
+
operationLog.keptUnknown.length ? ['', 'Kept unknown legacy-looking paths:', shortList(operationLog.keptUnknown)] : null,
|
|
521
|
+
'',
|
|
522
|
+
'Installed locations:',
|
|
523
|
+
locations.map((item) => `- ${item}`).join('\n'),
|
|
524
|
+
'',
|
|
525
|
+
`Next: ${nextStepFor(agent).replace(/^Next: /, '')}`
|
|
526
|
+
].flat().filter((line) => line !== null).join('\n');
|
|
527
|
+
}
|
|
469
528
|
|
|
470
529
|
function detectLanguage() {
|
|
471
530
|
const envLang = (process.env.LANG || process.env.LANGUAGE || process.env.LC_ALL || '').toLowerCase();
|
|
@@ -474,27 +533,27 @@ function detectLanguage() {
|
|
|
474
533
|
return 'en';
|
|
475
534
|
}
|
|
476
535
|
|
|
477
|
-
async function resolveChoices() {
|
|
478
|
-
let agent = resolveDefaultSurfaces();
|
|
479
|
-
let scope = args.includes('--global') ? 'global' : (argValue('--scope') || undefined);
|
|
480
|
-
let language = argValue('--lang') || argValue('--language') || detectLanguage();
|
|
481
|
-
if (scope && !['repo', 'global'].includes(scope)) {
|
|
482
|
-
console.error(`Invalid scope: ${scope}`);
|
|
483
|
-
usage();
|
|
536
|
+
async function resolveChoices() {
|
|
537
|
+
let agent = resolveDefaultSurfaces();
|
|
538
|
+
let scope = args.includes('--global') ? 'global' : (argValue('--scope') || undefined);
|
|
539
|
+
let language = argValue('--lang') || argValue('--language') || detectLanguage();
|
|
540
|
+
if (scope && !['repo', 'global'].includes(scope)) {
|
|
541
|
+
console.error(`Invalid scope: ${scope}`);
|
|
542
|
+
usage();
|
|
484
543
|
process.exit(1);
|
|
485
|
-
}
|
|
486
|
-
if (!['en', 'ko', 'zh'].includes(language)) language = 'en';
|
|
487
|
-
|
|
488
|
-
let components = componentOptionsFor(agent, language).map((item) => item.value).filter((value) => value !== 'hook');
|
|
489
|
-
if (includesClaude(agent) && args.includes('--with-hook')) components.push('hook');
|
|
490
|
-
let gitPolicy = 'harnesses';
|
|
491
|
-
let hookScope = 'off';
|
|
492
|
-
|
|
493
|
-
if (!interactive) {
|
|
494
|
-
scope = scope || 'repo';
|
|
495
|
-
if (components.includes('hook')) hookScope = scope;
|
|
496
|
-
return { agent, scope, components, gitPolicy, hookScope, language };
|
|
497
|
-
}
|
|
544
|
+
}
|
|
545
|
+
if (!['en', 'ko', 'zh'].includes(language)) language = 'en';
|
|
546
|
+
|
|
547
|
+
let components = componentOptionsFor(agent, language).map((item) => item.value).filter((value) => value !== 'hook');
|
|
548
|
+
if (includesClaude(agent) && args.includes('--with-hook')) components.push('hook');
|
|
549
|
+
let gitPolicy = 'harnesses';
|
|
550
|
+
let hookScope = 'off';
|
|
551
|
+
|
|
552
|
+
if (!interactive) {
|
|
553
|
+
scope = scope || 'repo';
|
|
554
|
+
if (components.includes('hook')) hookScope = scope;
|
|
555
|
+
return { agent, scope, components, gitPolicy, hookScope, language };
|
|
556
|
+
}
|
|
498
557
|
|
|
499
558
|
language = handleCancel(await select({
|
|
500
559
|
message: COPY[language].language,
|
|
@@ -525,23 +584,23 @@ async function resolveChoices() {
|
|
|
525
584
|
language === 'ko' ? '항목 설명' : language === 'zh' ? '项目说明' : 'Component notes'
|
|
526
585
|
);
|
|
527
586
|
|
|
528
|
-
agent = normalizeSurfaces(handleCancel(await multiselect({
|
|
529
|
-
message: language === 'ko'
|
|
530
|
-
? '설치할 agent surface를 선택하세요 (space로 토글)'
|
|
531
|
-
: 'Select agent surfaces to install (space to toggle)',
|
|
532
|
-
options: SURFACE_OPTIONS[language],
|
|
533
|
-
initialValues: agent === 'all' ? ['claude', 'codex'] : [agent],
|
|
534
|
-
required: true
|
|
535
|
-
})));
|
|
536
|
-
components = componentOptionsFor(agent, language).map((item) => item.value).filter((value) => value !== 'hook');
|
|
537
|
-
if (includesClaude(agent) && args.includes('--with-hook')) components.push('hook');
|
|
538
|
-
|
|
539
|
-
components = handleCancel(await multiselect({
|
|
540
|
-
message: copy.components,
|
|
541
|
-
options: componentOptionsFor(agent, language),
|
|
542
|
-
initialValues: components,
|
|
543
|
-
required: true
|
|
544
|
-
}));
|
|
587
|
+
agent = normalizeSurfaces(handleCancel(await multiselect({
|
|
588
|
+
message: language === 'ko'
|
|
589
|
+
? '설치할 agent surface를 선택하세요 (space로 토글)'
|
|
590
|
+
: 'Select agent surfaces to install (space to toggle)',
|
|
591
|
+
options: SURFACE_OPTIONS[language],
|
|
592
|
+
initialValues: agent === 'all' ? ['claude', 'codex'] : [agent],
|
|
593
|
+
required: true
|
|
594
|
+
})));
|
|
595
|
+
components = componentOptionsFor(agent, language).map((item) => item.value).filter((value) => value !== 'hook');
|
|
596
|
+
if (includesClaude(agent) && args.includes('--with-hook')) components.push('hook');
|
|
597
|
+
|
|
598
|
+
components = handleCancel(await multiselect({
|
|
599
|
+
message: copy.components,
|
|
600
|
+
options: componentOptionsFor(agent, language),
|
|
601
|
+
initialValues: components,
|
|
602
|
+
required: true
|
|
603
|
+
}));
|
|
545
604
|
|
|
546
605
|
scope = handleCancel(await select({
|
|
547
606
|
message: copy.scope,
|
|
@@ -593,8 +652,8 @@ async function resolveChoices() {
|
|
|
593
652
|
}));
|
|
594
653
|
}
|
|
595
654
|
|
|
596
|
-
if (includesClaude(agent) && components.includes('hook')) {
|
|
597
|
-
hookScope = scope;
|
|
655
|
+
if (includesClaude(agent) && components.includes('hook')) {
|
|
656
|
+
hookScope = scope;
|
|
598
657
|
note(
|
|
599
658
|
language === 'ko'
|
|
600
659
|
? `Hook 추천을 ${scope} 범위의 Claude Code UserPromptSubmit에 등록합니다. 추가 범위 질문은 하지 않습니다. 작업 실행/저장 없이 일반 프롬프트에서만 Tink 사용을 추천합니다.`
|
|
@@ -605,8 +664,8 @@ async function resolveChoices() {
|
|
|
605
664
|
);
|
|
606
665
|
}
|
|
607
666
|
|
|
608
|
-
return { agent, scope, components, gitPolicy, hookScope, language };
|
|
609
|
-
}
|
|
667
|
+
return { agent, scope, components, gitPolicy, hookScope, language };
|
|
668
|
+
}
|
|
610
669
|
|
|
611
670
|
async function main() {
|
|
612
671
|
if (command === 'help' || args.includes('--help')) {
|
|
@@ -620,18 +679,18 @@ async function main() {
|
|
|
620
679
|
process.exit(1);
|
|
621
680
|
}
|
|
622
681
|
|
|
623
|
-
const { agent, scope, components, gitPolicy, hookScope, language } = await resolveChoices();
|
|
624
|
-
|
|
625
|
-
if (!interactive) {
|
|
626
|
-
console.log(
|
|
627
|
-
console.log(`Source: ${source}`);
|
|
628
|
-
console.log(`surfaces ${agent}`);
|
|
629
|
-
console.log(`language ${language}`);
|
|
630
|
-
console.log(`scope ${scope}`);
|
|
631
|
-
console.log(`components ${components.join(', ')}`);
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
const targets = copySelected(scope, components, agent);
|
|
682
|
+
const { agent, scope, components, gitPolicy, hookScope, language } = await resolveChoices();
|
|
683
|
+
|
|
684
|
+
if (!interactive) {
|
|
685
|
+
console.log(`${isUpdate ? 'Updating' : 'Installing'} Tink for ${agent === 'claude' ? 'Claude Code' : agent === 'codex' ? 'Codex' : 'Claude Code and Codex'}`);
|
|
686
|
+
console.log(`Source: ${source}`);
|
|
687
|
+
console.log(`surfaces ${agent}`);
|
|
688
|
+
console.log(`language ${language}`);
|
|
689
|
+
console.log(`scope ${scope}`);
|
|
690
|
+
console.log(`components ${components.join(', ')}`);
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
const targets = copySelected(scope, components, agent);
|
|
635
694
|
|
|
636
695
|
if (scope === 'repo' && components.some((item) => ['harnesses', 'memory', 'hook'].includes(item))) {
|
|
637
696
|
updateGitignore(targets.repoTarget, gitPolicy);
|
|
@@ -640,25 +699,27 @@ async function main() {
|
|
|
640
699
|
}
|
|
641
700
|
patchConfig(targets.installTarget, scope, hookScope, language);
|
|
642
701
|
|
|
643
|
-
const summary = [
|
|
644
|
-
`Language: ${language}`,
|
|
645
|
-
`Surfaces: ${agent}`,
|
|
646
|
-
`Scope: ${scope}`,
|
|
647
|
-
`Install target: ${targets.installTarget}`,
|
|
648
|
-
includesCodex(agent) ? `Codex target: ${targets.codexTarget}` : null,
|
|
649
|
-
`Components: ${components.join(', ')}`,
|
|
650
|
-
`Hook scope: ${hookScope}`,
|
|
651
|
-
nextStepFor(agent)
|
|
652
|
-
].filter(Boolean).join('\n');
|
|
702
|
+
const summary = [
|
|
703
|
+
`Language: ${language}`,
|
|
704
|
+
`Surfaces: ${agent}`,
|
|
705
|
+
`Scope: ${scope}`,
|
|
706
|
+
`Install target: ${targets.installTarget}`,
|
|
707
|
+
includesCodex(agent) ? `Codex target: ${targets.codexTarget}` : null,
|
|
708
|
+
`Components: ${components.join(', ')}`,
|
|
709
|
+
`Hook scope: ${hookScope}`,
|
|
710
|
+
nextStepFor(agent)
|
|
711
|
+
].filter(Boolean).join('\n');
|
|
653
712
|
|
|
654
713
|
if (interactive) {
|
|
655
714
|
note(summary, COPY[language].installed);
|
|
715
|
+
if (isUpdate) note(updateResultSummary(agent, targets), 'Update Result Summary');
|
|
656
716
|
outro(COPY[language].done);
|
|
657
|
-
} else {
|
|
658
|
-
console.log(`\n${summary}`);
|
|
659
|
-
console.log(
|
|
660
|
-
|
|
661
|
-
}
|
|
717
|
+
} else {
|
|
718
|
+
console.log(`\n${summary}`);
|
|
719
|
+
if (isUpdate) console.log(`\n${updateResultSummary(agent, targets)}`);
|
|
720
|
+
console.log(doneLineFor(agent));
|
|
721
|
+
}
|
|
722
|
+
}
|
|
662
723
|
|
|
663
724
|
main().catch((error) => {
|
|
664
725
|
console.error(error instanceof Error ? error.message : String(error));
|