sneakoscope 0.7.11 → 0.7.13
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 +27 -27
- package/package.json +2 -2
- package/src/cli/install-helpers.mjs +12 -13
- package/src/cli/main.mjs +87 -100
- package/src/cli/maintenance-commands.mjs +31 -31
- package/src/core/artifact-schemas.mjs +6 -6
- package/src/core/codex-app.mjs +1 -1
- package/src/core/evaluation.mjs +3 -3
- package/src/core/fsx.mjs +1 -1
- package/src/core/init.mjs +6 -6
- package/src/core/pipeline.mjs +2 -2
- package/src/core/ppt.mjs +100 -19
- package/src/core/questions.mjs +1 -1
- package/src/core/routes.mjs +9 -9
- package/src/core/team-live.mjs +8 -8
- package/src/core/tmux-ui.mjs +447 -0
- package/src/core/warp-ui.mjs +0 -557
package/src/core/warp-ui.mjs
DELETED
|
@@ -1,557 +0,0 @@
|
|
|
1
|
-
import path from 'node:path';
|
|
2
|
-
import os from 'node:os';
|
|
3
|
-
import fsp from 'node:fs/promises';
|
|
4
|
-
import { spawnSync } from 'node:child_process';
|
|
5
|
-
import { exists, nowIso, packageRoot, readJson, runProcess, sha256, sksRoot, which, writeJsonAtomic } from './fsx.mjs';
|
|
6
|
-
import { getCodexInfo } from './codex-adapter.mjs';
|
|
7
|
-
import { codexAppIntegrationStatus, formatCodexAppStatus } from './codex-app.mjs';
|
|
8
|
-
|
|
9
|
-
export const SKS_WARP_LOGO = [
|
|
10
|
-
' _____ __ __ _____',
|
|
11
|
-
' / ___// //_// ___/',
|
|
12
|
-
' \\__ \\/ ,< \\__ \\ ㅅㅋㅅ',
|
|
13
|
-
' ___/ / /| | ___/ /',
|
|
14
|
-
'/____/_/ |_|/____/',
|
|
15
|
-
'Sneakoscope Codex Warp'
|
|
16
|
-
].join('\n');
|
|
17
|
-
|
|
18
|
-
export function sanitizeWarpWorkspaceName(input) {
|
|
19
|
-
const base = String(input || 'sks').trim().replace(/[^A-Za-z0-9_.-]+/g, '-').replace(/^-+|-+$/g, '');
|
|
20
|
-
return (base || 'sks').slice(0, 80);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function defaultWarpWorkspaceName(root) {
|
|
24
|
-
const base = sanitizeWarpWorkspaceName(path.basename(root || process.cwd()) || 'project');
|
|
25
|
-
const hash = sha256(path.resolve(root || process.cwd())).slice(0, 8);
|
|
26
|
-
return sanitizeWarpWorkspaceName(`sks-${base}-${hash}`);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export function shellEscape(value) {
|
|
30
|
-
return `'${String(value).replace(/'/g, `'\\''`)}'`;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export function platformWarpInstallHint() {
|
|
34
|
-
if (process.platform === 'darwin') return 'Install Warp from https://www.warp.dev/download or run: brew install --cask warp';
|
|
35
|
-
return 'Install Warp from https://www.warp.dev/download, then run: sks warp check';
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export function warpLaunchConfigurationsDir() {
|
|
39
|
-
if (process.env.SKS_WARP_LAUNCH_CONFIG_DIR) return path.resolve(process.env.SKS_WARP_LAUNCH_CONFIG_DIR);
|
|
40
|
-
if (process.platform === 'win32') return path.join(process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming'), 'warp', 'Warp', 'data', 'launch_configurations');
|
|
41
|
-
if (process.platform === 'linux') return path.join(process.env.XDG_DATA_HOME || path.join(os.homedir(), '.local', 'share'), 'warp-terminal', 'launch_configurations');
|
|
42
|
-
return path.join(os.homedir(), '.warp', 'launch_configurations');
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export function warpStatePath(root = process.cwd()) {
|
|
46
|
-
return path.join(path.resolve(root || process.cwd()), '.sneakoscope', 'state', 'warp-launches.json');
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export function warpTeamStatePath(root = process.cwd()) {
|
|
50
|
-
return path.join(path.resolve(root || process.cwd()), '.sneakoscope', 'state', 'warp-team-launches.json');
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export function warpLaunchConfigFilename(plan = {}) {
|
|
54
|
-
const workspace = sanitizeWarpWorkspaceName(plan.workspace || defaultWarpWorkspaceName(plan.root));
|
|
55
|
-
return `${workspace}.yaml`;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export function warpLaunchConfigPath(plan = {}) {
|
|
59
|
-
return path.join(warpLaunchConfigurationsDir(), warpLaunchConfigFilename(plan));
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export function warpLaunchUri(configPathOrFilename) {
|
|
63
|
-
const filename = path.basename(String(configPathOrFilename || ''));
|
|
64
|
-
return `warp://launch/${encodeURIComponent(filename)}`;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export function isWarpShellSession(env = process.env) {
|
|
68
|
-
if (truthyEnv(env.WARP_IS_LOCAL_SHELL_SESSION)) return true;
|
|
69
|
-
if (truthyEnv(env.WARP_SESSION_ID)) return true;
|
|
70
|
-
return String(env.TERM_PROGRAM || '') === 'WarpTerminal';
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
export function warpOpenLaunchDecision(opts = {}) {
|
|
74
|
-
const args = Array.isArray(opts.args) ? opts.args : [];
|
|
75
|
-
const env = opts.env || process.env;
|
|
76
|
-
if (opts.forceOpen === true || opts.open === true || args.includes('--open') || args.includes('--force-open') || truthyEnv(env.SKS_WARP_FORCE_OPEN) || truthyEnv(env.SKS_WARP_OPEN)) {
|
|
77
|
-
return { open: true, reason: 'forced' };
|
|
78
|
-
}
|
|
79
|
-
if (opts.skipOpen === true || opts.noOpen === true || opts.open === false || args.includes('--no-open') || truthyEnv(env.SKS_WARP_SKIP_OPEN) || truthyEnv(env.SKS_WARP_NO_OPEN)) {
|
|
80
|
-
return { open: false, reason: 'opening disabled by option/env' };
|
|
81
|
-
}
|
|
82
|
-
if (isWarpShellSession(env)) {
|
|
83
|
-
return { open: false, current_session: true, reason: 'already inside Warp shell session' };
|
|
84
|
-
}
|
|
85
|
-
return { open: true, reason: 'default' };
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function truthyEnv(value) {
|
|
89
|
-
return value !== undefined && value !== null && !/^(?:0|false|no|off)$/i.test(String(value).trim());
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
export async function findWarpApp() {
|
|
93
|
-
const env = process.env.SKS_WARP_APP || process.env.WARP_APP;
|
|
94
|
-
if (env && await exists(env)) return env;
|
|
95
|
-
if (process.platform !== 'darwin') return null;
|
|
96
|
-
for (const candidate of [
|
|
97
|
-
'/Applications/Warp.app',
|
|
98
|
-
path.join(os.homedir(), 'Applications', 'Warp.app')
|
|
99
|
-
]) {
|
|
100
|
-
if (await exists(candidate)) return candidate;
|
|
101
|
-
}
|
|
102
|
-
return null;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
export async function findWarpCli() {
|
|
106
|
-
return await which('warp').catch(() => null) || await which('oz').catch(() => null);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
export async function warpReadiness(opts = {}) {
|
|
110
|
-
const app = opts.app ?? await findWarpApp();
|
|
111
|
-
const cli = opts.cli ?? await findWarpCli();
|
|
112
|
-
const launch_config_dir = warpLaunchConfigurationsDir();
|
|
113
|
-
const appOk = process.platform === 'darwin' ? Boolean(app) : Boolean(cli || app);
|
|
114
|
-
return {
|
|
115
|
-
ok: appOk,
|
|
116
|
-
app: app || null,
|
|
117
|
-
cli: cli || null,
|
|
118
|
-
version: app ? 'Warp.app' : cli ? path.basename(cli) : null,
|
|
119
|
-
launch_config_dir,
|
|
120
|
-
uri_scheme: 'warp://launch/<launch_configuration_name>',
|
|
121
|
-
error: appOk ? null : 'Warp app not found'
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
export function warpStatusKind(warp = {}) {
|
|
126
|
-
return warp.ok ? 'ok' : 'missing';
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
export function codexLaunchCommand(root, codexBin, codexArgs = []) {
|
|
130
|
-
const extraArgs = Array.isArray(codexArgs) ? codexArgs : [];
|
|
131
|
-
return [
|
|
132
|
-
'clear',
|
|
133
|
-
`printf '%s\\n' ${shellEscape(SKS_WARP_LOGO)}`,
|
|
134
|
-
`printf '\\nProject: %s\\n' ${shellEscape(root)}`,
|
|
135
|
-
'printf \'Runtime: Warp Launch Configuration for Codex CLI\\n\'',
|
|
136
|
-
'printf \'Prompt: use canonical $ commands, for example $Team or $QA-LOOP\\n\\n\'',
|
|
137
|
-
'sleep 1',
|
|
138
|
-
`exec ${[shellEscape(codexBin), ...extraArgs.map(shellEscape), '--cd', shellEscape(root)].join(' ')}`
|
|
139
|
-
].join('; ');
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
function terminalTitleCommand(title = '') {
|
|
143
|
-
return `printf '\\033]0;%s\\007' ${shellEscape(String(title || '').slice(0, 80))}`;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
function ansiColorCode(color = '') {
|
|
147
|
-
return {
|
|
148
|
-
blue: '34',
|
|
149
|
-
cyan: '36',
|
|
150
|
-
yellow: '33',
|
|
151
|
-
green: '32',
|
|
152
|
-
red: '31',
|
|
153
|
-
magenta: '35'
|
|
154
|
-
}[String(color || '').toLowerCase()] || '37';
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
function colorizedLaneBannerCommand(lines = [], color = '') {
|
|
158
|
-
const code = ansiColorCode(color);
|
|
159
|
-
const text = lines.join('\n');
|
|
160
|
-
return `printf '\\033[1;${code}m%s\\033[0m\\n' ${shellEscape(text)}`;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
export const WARP_TEAM_LANE_STYLES = Object.freeze({
|
|
164
|
-
overview: Object.freeze({ role: 'overview', label: 'overview', color_name: 'Blue', color: 'blue', icon: 'layout-dashboard' }),
|
|
165
|
-
scout: Object.freeze({ role: 'scout', label: 'scout', color_name: 'Cyan', color: 'cyan', icon: 'search' }),
|
|
166
|
-
planning: Object.freeze({ role: 'planning', label: 'plan', color_name: 'Yellow', color: 'yellow', icon: 'messages-square' }),
|
|
167
|
-
execution: Object.freeze({ role: 'execution', label: 'exec', color_name: 'Green', color: 'green', icon: 'hammer' }),
|
|
168
|
-
review: Object.freeze({ role: 'review', label: 'review', color_name: 'Red', color: 'red', icon: 'shield-check' }),
|
|
169
|
-
safety: Object.freeze({ role: 'safety', label: 'safety', color_name: 'Magenta', color: 'magenta', icon: 'database' })
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
export function teamLaneStyle(agentId = '') {
|
|
173
|
-
const id = String(agentId || '').toLowerCase();
|
|
174
|
-
if (!id || id === 'mission_overview' || id === 'overview') return WARP_TEAM_LANE_STYLES.overview;
|
|
175
|
-
if (/analysis|scout/.test(id)) return WARP_TEAM_LANE_STYLES.scout;
|
|
176
|
-
if (/debate|consensus|planner|user/.test(id)) return WARP_TEAM_LANE_STYLES.planning;
|
|
177
|
-
if (/db|safety/.test(id)) return WARP_TEAM_LANE_STYLES.safety;
|
|
178
|
-
if (/review|qa|validation/.test(id)) return WARP_TEAM_LANE_STYLES.review;
|
|
179
|
-
if (/executor|implementation|worker|developer/.test(id)) return WARP_TEAM_LANE_STYLES.execution;
|
|
180
|
-
return WARP_TEAM_LANE_STYLES.planning;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
function teamLaneTitle(agentId = '') {
|
|
184
|
-
const style = teamLaneStyle(agentId);
|
|
185
|
-
return `${style.label}: ${String(agentId || 'mission_overview')}`.slice(0, 80);
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
export function teamAgentCommand(root, missionId, agentId, phase) {
|
|
189
|
-
const style = teamLaneStyle(agentId);
|
|
190
|
-
const title = teamLaneTitle(agentId);
|
|
191
|
-
return [
|
|
192
|
-
terminalTitleCommand(title),
|
|
193
|
-
'clear',
|
|
194
|
-
colorizedLaneBannerCommand([...SKS_WARP_LOGO.split('\n'), '', `Team mission: ${missionId}`, `Agent: ${agentId}`, `Lane: ${style.label} (${style.color_name})`, `Phase: ${phase}`, 'Messages: sks team message ... --to ' + agentId, 'Cleanup: sks team cleanup-warp ' + missionId], style.color),
|
|
195
|
-
`cd ${shellEscape(root)}`,
|
|
196
|
-
`node ${shellEscape(path.join(packageRoot(), 'bin', 'sks.mjs'))} team lane ${shellEscape(missionId)} --agent ${shellEscape(agentId)} --phase ${shellEscape(phase)} --follow --lines 12`
|
|
197
|
-
].join('; ');
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
export function teamOverviewCommand(root, missionId) {
|
|
201
|
-
const style = teamLaneStyle('mission_overview');
|
|
202
|
-
const title = teamLaneTitle('mission_overview');
|
|
203
|
-
return [
|
|
204
|
-
terminalTitleCommand(title),
|
|
205
|
-
'clear',
|
|
206
|
-
colorizedLaneBannerCommand([...SKS_WARP_LOGO.split('\n'), '', `Team mission: ${missionId}`, 'View: live orchestration overview', `Lane: ${style.label} (${style.color_name})`, 'Messages: sks team message ... --to <agent|all>', 'Cleanup: sks team cleanup-warp ' + missionId], style.color),
|
|
207
|
-
`cd ${shellEscape(root)}`,
|
|
208
|
-
`node ${shellEscape(path.join(packageRoot(), 'bin', 'sks.mjs'))} team watch ${shellEscape(missionId)} --follow --lines 18`
|
|
209
|
-
].join('; ');
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
export async function buildWarpLaunchPlan(opts = {}) {
|
|
213
|
-
const root = path.resolve(opts.root || await sksRoot());
|
|
214
|
-
const workspace = sanitizeWarpWorkspaceName(opts.workspace || opts.session || defaultWarpWorkspaceName(root));
|
|
215
|
-
const sksBin = opts.sksBin || path.join(packageRoot(), 'bin', 'sks.mjs');
|
|
216
|
-
const codex = opts.codex || await getCodexInfo().catch(() => ({}));
|
|
217
|
-
const warp = opts.warp || await warpReadiness();
|
|
218
|
-
const app = opts.app || await codexAppIntegrationStatus({ codex });
|
|
219
|
-
const codexArgs = Array.isArray(opts.codexArgs) ? opts.codexArgs : [];
|
|
220
|
-
const config_path = warpLaunchConfigPath({ root, workspace });
|
|
221
|
-
return {
|
|
222
|
-
root,
|
|
223
|
-
workspace,
|
|
224
|
-
sksBin,
|
|
225
|
-
codex,
|
|
226
|
-
warp,
|
|
227
|
-
app,
|
|
228
|
-
codexArgs,
|
|
229
|
-
config_path,
|
|
230
|
-
launch_uri: warpLaunchUri(config_path),
|
|
231
|
-
ready: Boolean(warp.ok && codex.bin),
|
|
232
|
-
warnings: app.ok ? [] : app.guidance || [],
|
|
233
|
-
blockers: [
|
|
234
|
-
...(!warp.ok ? [`Warp missing. ${platformWarpInstallHint()}`] : []),
|
|
235
|
-
...(!codex.bin ? ['Codex CLI missing. Install: npm i -g @openai/codex, or set SKS_CODEX_BIN.'] : [])
|
|
236
|
-
]
|
|
237
|
-
};
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
export function formatWarpBanner(status = null) {
|
|
241
|
-
const lines = [
|
|
242
|
-
SKS_WARP_LOGO,
|
|
243
|
-
'',
|
|
244
|
-
'ㅅㅋㅅ Warp runtime',
|
|
245
|
-
'',
|
|
246
|
-
'Canonical prompt commands:',
|
|
247
|
-
' $DFix $Answer $SKS $Team $QA-LOOP $PPT $Goal $Research $AutoResearch $DB $GX $Wiki $Help',
|
|
248
|
-
'',
|
|
249
|
-
'CLI-first runtime:',
|
|
250
|
-
' sks warp open explicitly open a Warp Codex CLI launch configuration',
|
|
251
|
-
' sks --mad open one-shot MAD full-access auto-review launch configuration',
|
|
252
|
-
' sks team "task" prepare Team mission and Warp split-pane live view',
|
|
253
|
-
'',
|
|
254
|
-
'Useful terminal commands:',
|
|
255
|
-
' sks commands',
|
|
256
|
-
' sks dollar-commands',
|
|
257
|
-
' sks codex-app check',
|
|
258
|
-
' sks doctor --fix'
|
|
259
|
-
];
|
|
260
|
-
if (status) lines.push('', formatCodexAppStatus(status));
|
|
261
|
-
return lines.join('\n');
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
export function buildWarpLaunchConfigYaml(plan = {}, panes = []) {
|
|
265
|
-
const title = String(plan.title || plan.workspace || defaultWarpWorkspaceName(plan.root)).slice(0, 80);
|
|
266
|
-
const normalizedPanes = panes.length ? panes : [{ cwd: plan.root, command: plan.command || codexLaunchCommand(plan.root, plan.codex?.bin || 'codex', plan.codexArgs), focused: true }];
|
|
267
|
-
const layout = paneLayoutYaml(normalizedPanes, 14);
|
|
268
|
-
return [
|
|
269
|
-
'# Warp Launch Configuration',
|
|
270
|
-
'# Generated by Sneakoscope Codex. Warp loads this from its launch_configurations directory.',
|
|
271
|
-
'---',
|
|
272
|
-
`name: ${yamlQuote(title)}`,
|
|
273
|
-
'active_window_index: 0',
|
|
274
|
-
'windows:',
|
|
275
|
-
' - active_tab_index: 0',
|
|
276
|
-
' tabs:',
|
|
277
|
-
` - title: ${yamlQuote(title)}`,
|
|
278
|
-
` color: ${yamlQuote(plan.color || 'blue')}`,
|
|
279
|
-
' layout:',
|
|
280
|
-
layout
|
|
281
|
-
].join('\n') + '\n';
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
function paneLayoutYaml(panes = [], indent = 0) {
|
|
285
|
-
if (panes.length <= 1) return leafPaneYaml(panes[0] || {}, indent);
|
|
286
|
-
const [first, ...rest] = panes;
|
|
287
|
-
const lines = [
|
|
288
|
-
`${space(indent)}split_direction: vertical`,
|
|
289
|
-
`${space(indent)}panes:`,
|
|
290
|
-
...leafPaneYaml(first, indent + 2, true),
|
|
291
|
-
...(rest.length === 1
|
|
292
|
-
? leafPaneYaml(rest[0], indent + 2, true)
|
|
293
|
-
: [
|
|
294
|
-
`${space(indent + 2)}- split_direction: horizontal`,
|
|
295
|
-
`${space(indent + 4)}panes:`,
|
|
296
|
-
...rest.flatMap((pane) => leafPaneYaml(pane, indent + 6, true))
|
|
297
|
-
])
|
|
298
|
-
];
|
|
299
|
-
return lines.join('\n');
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
function leafPaneYaml(pane = {}, indent = 0, listItem = false) {
|
|
303
|
-
const prefix = listItem ? `${space(indent)}- ` : space(indent);
|
|
304
|
-
const cont = space(indent + (listItem ? 2 : 0));
|
|
305
|
-
const lines = [
|
|
306
|
-
`${prefix}cwd: ${yamlQuote(path.resolve(pane.cwd || pane.directory || process.cwd()))}`,
|
|
307
|
-
`${cont}commands:`,
|
|
308
|
-
`${cont} - exec: ${yamlQuote(pane.command || 'pwd')}`
|
|
309
|
-
];
|
|
310
|
-
if (pane.focused || pane.is_focused) lines.push(`${cont}is_focused: true`);
|
|
311
|
-
return listItem ? lines : lines.join('\n');
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
function yamlQuote(value) {
|
|
315
|
-
return JSON.stringify(String(value ?? ''));
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
function space(n) {
|
|
319
|
-
return ' '.repeat(n);
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
export async function writeWarpLaunchConfig(plan = {}, panes = []) {
|
|
323
|
-
const configPath = warpLaunchConfigPath(plan);
|
|
324
|
-
await fsp.mkdir(path.dirname(configPath), { recursive: true });
|
|
325
|
-
const yaml = buildWarpLaunchConfigYaml(plan, panes);
|
|
326
|
-
await fsp.writeFile(configPath, yaml, 'utf8');
|
|
327
|
-
const record = {
|
|
328
|
-
schema_version: 1,
|
|
329
|
-
workspace: plan.workspace,
|
|
330
|
-
root: path.resolve(plan.root || process.cwd()),
|
|
331
|
-
config_path: configPath,
|
|
332
|
-
launch_uri: warpLaunchUri(configPath),
|
|
333
|
-
updated_at: nowIso()
|
|
334
|
-
};
|
|
335
|
-
const statePath = warpStatePath(plan.root);
|
|
336
|
-
const state = await readJson(statePath, {}).catch(() => ({}));
|
|
337
|
-
await writeJsonAtomic(statePath, {
|
|
338
|
-
schema_version: 1,
|
|
339
|
-
updated_at: record.updated_at,
|
|
340
|
-
launches: { ...(state.launches && typeof state.launches === 'object' ? state.launches : {}), [record.workspace]: record }
|
|
341
|
-
}).catch(() => null);
|
|
342
|
-
return { config_path: configPath, yaml, record };
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
export async function openWarpLaunchConfig(configPath, opts = {}) {
|
|
346
|
-
const uri = warpLaunchUri(configPath);
|
|
347
|
-
const decision = warpOpenLaunchDecision(opts);
|
|
348
|
-
if (!decision.open) return { ok: false, skipped: true, reason: decision.reason, uri, stdout: '', stderr: '' };
|
|
349
|
-
if (process.platform === 'darwin') {
|
|
350
|
-
const run = await runProcess('open', [uri], { timeoutMs: 5000, maxOutputBytes: 16 * 1024 }).catch((err) => ({ code: 1, stderr: err.message, stdout: '' }));
|
|
351
|
-
return { ok: run.code === 0, skipped: false, reason: decision.reason, uri, stdout: run.stdout || '', stderr: run.stderr || '' };
|
|
352
|
-
}
|
|
353
|
-
const opener = await which('xdg-open').catch(() => null);
|
|
354
|
-
if (opener) {
|
|
355
|
-
const run = await runProcess(opener, [uri], { timeoutMs: 5000, maxOutputBytes: 16 * 1024 }).catch((err) => ({ code: 1, stderr: err.message, stdout: '' }));
|
|
356
|
-
return { ok: run.code === 0, skipped: false, reason: decision.reason, uri, stdout: run.stdout || '', stderr: run.stderr || '' };
|
|
357
|
-
}
|
|
358
|
-
return { ok: false, skipped: false, reason: decision.reason, uri, stdout: '', stderr: 'No platform URI opener found' };
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
export async function launchWarpUi(args = [], opts = {}) {
|
|
362
|
-
const rootArg = readOption(args, '--root', opts.root);
|
|
363
|
-
const workspaceArg = readOption(args, '--workspace', readOption(args, '--session', opts.workspace || opts.session));
|
|
364
|
-
const plan = await buildWarpLaunchPlan({ ...opts, root: rootArg, workspace: workspaceArg });
|
|
365
|
-
if (args.includes('--json')) return { plan };
|
|
366
|
-
if (!plan.ready && !args.includes('--status-only')) {
|
|
367
|
-
printWarpLaunchBlocked(plan, { concise: opts.conciseBlockers });
|
|
368
|
-
process.exitCode = 1;
|
|
369
|
-
return { plan };
|
|
370
|
-
}
|
|
371
|
-
if (args.includes('--status-only')) return { plan };
|
|
372
|
-
const command = codexLaunchCommand(plan.root, plan.codex.bin, plan.codexArgs);
|
|
373
|
-
const written = await writeWarpLaunchConfig({ ...plan, command }, [{ cwd: plan.root, command, focused: true }]);
|
|
374
|
-
const decision = warpOpenLaunchDecision({ ...opts, args });
|
|
375
|
-
const opened = decision.current_session
|
|
376
|
-
? runWarpCommandInCurrentSession(command, { cwd: plan.root, dryRun: opts.dryRunCurrentSession || opts.dryRun })
|
|
377
|
-
: await openWarpLaunchConfig(written.config_path, { ...opts, args });
|
|
378
|
-
if (!args.includes('--quiet')) {
|
|
379
|
-
console.log(`SKS Warp launch configuration: ${written.config_path}`);
|
|
380
|
-
console.log(`Warp URI: ${written.record.launch_uri}`);
|
|
381
|
-
if (opened.current_session) console.log(`Warp: current session (${opened.reason || 'already inside Warp shell session'})`);
|
|
382
|
-
else if (opened.ok) console.log('Warp: opened');
|
|
383
|
-
else if (opened.skipped) console.log(`Warp: skipped (${opened.reason || 'opening disabled'})`);
|
|
384
|
-
else if (!opened.skipped) console.log(`Warp: not opened (${opened.stderr || 'URI opener failed'})`);
|
|
385
|
-
}
|
|
386
|
-
return { plan, created: true, config_path: written.config_path, launch_uri: written.record.launch_uri, opened };
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
function runWarpCommandInCurrentSession(command, opts = {}) {
|
|
390
|
-
if (opts.dryRun) return { ok: true, current_session: true, skipped: false, reason: 'dry run current Warp session', command };
|
|
391
|
-
const shell = process.env.SHELL || '/bin/sh';
|
|
392
|
-
const run = spawnSync(shell, ['-lc', command], {
|
|
393
|
-
cwd: opts.cwd || process.cwd(),
|
|
394
|
-
stdio: 'inherit',
|
|
395
|
-
env: process.env
|
|
396
|
-
});
|
|
397
|
-
return {
|
|
398
|
-
ok: run.status === 0,
|
|
399
|
-
current_session: true,
|
|
400
|
-
skipped: false,
|
|
401
|
-
reason: 'ran in current Warp shell session',
|
|
402
|
-
code: run.status,
|
|
403
|
-
signal: run.signal || null,
|
|
404
|
-
stderr: run.error?.message || ''
|
|
405
|
-
};
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
function printWarpLaunchBlocked(plan, opts = {}) {
|
|
409
|
-
if (opts.concise) {
|
|
410
|
-
console.error('SKS Warp launch blocked.');
|
|
411
|
-
if (!plan.warp.ok) console.error(`- Warp missing: ${platformWarpInstallHint()}`);
|
|
412
|
-
if (!plan.codex.bin) console.error('- Codex CLI missing. Install: npm i -g @openai/codex@latest, or set SKS_CODEX_BIN.');
|
|
413
|
-
return;
|
|
414
|
-
}
|
|
415
|
-
console.log(formatWarpBanner(plan.app));
|
|
416
|
-
console.log('\nLaunch blocked:\n');
|
|
417
|
-
for (const blocker of Array.from(new Set(plan.blockers))) console.log(`- ${blocker}`);
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
export async function launchWarpTeamView({ root, missionId, plan = {}, promptFile = null, json = false } = {}) {
|
|
421
|
-
const launch = await buildWarpLaunchPlan({ root, workspace: `sks-team-${missionId}` });
|
|
422
|
-
const agents = [
|
|
423
|
-
...(plan.roster?.analysis_team || []),
|
|
424
|
-
...(plan.roster?.debate_team || []),
|
|
425
|
-
...(plan.roster?.development_team || []),
|
|
426
|
-
...(plan.roster?.validation_team || [])
|
|
427
|
-
];
|
|
428
|
-
const uniqueAgents = [];
|
|
429
|
-
const seen = new Set();
|
|
430
|
-
for (const agent of agents) {
|
|
431
|
-
const id = agent.id || String(agent);
|
|
432
|
-
if (seen.has(id)) continue;
|
|
433
|
-
seen.add(id);
|
|
434
|
-
uniqueAgents.push(id);
|
|
435
|
-
}
|
|
436
|
-
const commands = uniqueAgents.slice(0, Math.max(1, plan.agent_session_count || 3)).map((agentId, index) => ({
|
|
437
|
-
agent: agentId,
|
|
438
|
-
command: teamAgentCommand(launch.root, missionId, agentId, index === 0 ? 'analysis' : 'team', promptFile),
|
|
439
|
-
style: teamLaneStyle(agentId),
|
|
440
|
-
title: teamLaneTitle(agentId)
|
|
441
|
-
}));
|
|
442
|
-
const overview = { agent: 'mission_overview', role: 'overview', command: teamOverviewCommand(launch.root, missionId), style: teamLaneStyle('mission_overview'), title: teamLaneTitle('mission_overview') };
|
|
443
|
-
const lanes = [overview, ...commands.map((entry) => ({ ...entry, role: entry.style.role }))];
|
|
444
|
-
const result = {
|
|
445
|
-
ready: launch.ready,
|
|
446
|
-
warp: launch.warp,
|
|
447
|
-
workspace: launch.workspace,
|
|
448
|
-
overview,
|
|
449
|
-
agents: commands,
|
|
450
|
-
lanes,
|
|
451
|
-
cleanup_policy: 'mark-complete; Warp live panes remain user controlled',
|
|
452
|
-
blockers: launch.blockers,
|
|
453
|
-
config_path: launch.config_path,
|
|
454
|
-
launch_uri: launch.launch_uri
|
|
455
|
-
};
|
|
456
|
-
if (json || !launch.ready) return result;
|
|
457
|
-
const panes = lanes.map((lane, index) => ({ cwd: launch.root, command: lane.command, focused: index === 0 }));
|
|
458
|
-
const written = await writeWarpLaunchConfig({ ...launch, color: overview.style.color }, panes);
|
|
459
|
-
const opened = await openWarpLaunchConfig(written.config_path);
|
|
460
|
-
result.created = true;
|
|
461
|
-
result.opened = opened;
|
|
462
|
-
result.config_path = written.config_path;
|
|
463
|
-
result.launch_uri = written.record.launch_uri;
|
|
464
|
-
result.opened_lane_count = lanes.length;
|
|
465
|
-
result.all_lanes_opened = Boolean(opened.ok);
|
|
466
|
-
result.ready = Boolean(result.ready && written.config_path);
|
|
467
|
-
await writeWarpTeamRecord(launch.root, {
|
|
468
|
-
mission_id: missionId,
|
|
469
|
-
workspace: launch.workspace,
|
|
470
|
-
config_path: written.config_path,
|
|
471
|
-
launch_uri: written.record.launch_uri,
|
|
472
|
-
cleanup_policy: result.cleanup_policy,
|
|
473
|
-
lanes: lanes.map((entry) => ({
|
|
474
|
-
agent: entry.agent,
|
|
475
|
-
role: entry.style?.role || teamLaneStyle(entry.agent).role,
|
|
476
|
-
style: entry.style || teamLaneStyle(entry.agent),
|
|
477
|
-
title: entry.title || teamLaneTitle(entry.agent)
|
|
478
|
-
}))
|
|
479
|
-
}).catch(() => null);
|
|
480
|
-
return result;
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
async function writeWarpTeamRecord(root, record = {}) {
|
|
484
|
-
if (!record.mission_id || !record.config_path) return null;
|
|
485
|
-
const statePath = warpTeamStatePath(root);
|
|
486
|
-
const state = await readJson(statePath, {}).catch(() => ({}));
|
|
487
|
-
const now = nowIso();
|
|
488
|
-
const nextRecord = { ...record, schema_version: 1, root: path.resolve(root || process.cwd()), updated_at: now };
|
|
489
|
-
const missions = state.missions && typeof state.missions === 'object' ? state.missions : {};
|
|
490
|
-
await writeJsonAtomic(statePath, {
|
|
491
|
-
schema_version: 1,
|
|
492
|
-
updated_at: now,
|
|
493
|
-
missions: { ...missions, [record.mission_id]: nextRecord }
|
|
494
|
-
});
|
|
495
|
-
return nextRecord;
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
async function readWarpTeamRecord(root, missionId) {
|
|
499
|
-
const state = await readJson(warpTeamStatePath(root), {}).catch(() => ({}));
|
|
500
|
-
const missions = state.missions && typeof state.missions === 'object' ? state.missions : {};
|
|
501
|
-
if (missionId && missionId !== 'latest') return missions[missionId] || null;
|
|
502
|
-
const records = Object.values(missions).filter((entry) => entry && typeof entry === 'object');
|
|
503
|
-
records.sort((a, b) => String(b.updated_at || '').localeCompare(String(a.updated_at || '')));
|
|
504
|
-
return records[0] || null;
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
export async function cleanupWarpTeamView({ root, missionId = 'latest', closeWorkspace = false } = {}) {
|
|
508
|
-
const resolvedRoot = path.resolve(root || await sksRoot());
|
|
509
|
-
const record = await readWarpTeamRecord(resolvedRoot, missionId);
|
|
510
|
-
if (!record?.config_path) return { ok: false, skipped: true, reason: 'no recorded Warp Team launch configuration', mission_id: missionId };
|
|
511
|
-
let removed_config = false;
|
|
512
|
-
if (closeWorkspace || closeWorkspace === true) {
|
|
513
|
-
await fsp.rm(record.config_path, { force: true }).catch(() => null);
|
|
514
|
-
removed_config = true;
|
|
515
|
-
}
|
|
516
|
-
await writeWarpTeamRecord(resolvedRoot, { ...record, cleanup_completed_at: nowIso(), removed_config }).catch(() => null);
|
|
517
|
-
return {
|
|
518
|
-
ok: true,
|
|
519
|
-
mission_id: record.mission_id,
|
|
520
|
-
config_path: record.config_path,
|
|
521
|
-
launch_uri: record.launch_uri,
|
|
522
|
-
close_workspace: false,
|
|
523
|
-
removed_config,
|
|
524
|
-
requested_close_surfaces: 0,
|
|
525
|
-
closed_surfaces: 0,
|
|
526
|
-
reason: 'Warp public URI/Launch Configuration surface does not expose live pane close/select controls; cleanup marks the SKS launch record complete.'
|
|
527
|
-
};
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
export async function runWarpStatus(args = [], opts = {}) {
|
|
531
|
-
const once = args.includes('--once') || !args.includes('--watch');
|
|
532
|
-
do {
|
|
533
|
-
const app = await codexAppIntegrationStatus();
|
|
534
|
-
console.clear();
|
|
535
|
-
console.log(formatWarpBanner(app));
|
|
536
|
-
if (once) return app;
|
|
537
|
-
await new Promise((resolve) => setTimeout(resolve, 5000));
|
|
538
|
-
} while (true);
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
export function buildWarpOpenArgs(plan = {}) {
|
|
542
|
-
return ['open', warpLaunchUri(warpLaunchConfigPath(plan))];
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
export function runWarpLaunchConfigSyntaxCheck(yaml = '') {
|
|
546
|
-
const text = String(yaml || '');
|
|
547
|
-
return {
|
|
548
|
-
ok: /^---\n/m.test(text) && /\nwindows:\n/.test(text) && /\n\s+commands:\n\s+- exec:/.test(text),
|
|
549
|
-
has_split_panes: /\nsplit_direction:\s*(vertical|horizontal)/.test(text),
|
|
550
|
-
has_cwd: /\ncwd:\s*"/.test(text)
|
|
551
|
-
};
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
function readOption(args, name, fallback = null) {
|
|
555
|
-
const i = args.indexOf(name);
|
|
556
|
-
return i >= 0 && args[i + 1] ? args[i + 1] : fallback;
|
|
557
|
-
}
|