sneakoscope 0.7.25 → 0.7.33
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 +68 -9
- package/package.json +3 -1
- package/src/cli/install-helpers.mjs +93 -6
- package/src/cli/main.mjs +112 -12
- package/src/cli/openclaw-command.mjs +83 -0
- package/src/core/fsx.mjs +1 -1
- package/src/core/init.mjs +17 -4
- package/src/core/openclaw.mjs +167 -0
- package/src/core/routes.mjs +3 -2
- package/src/core/tmux-ui.mjs +2 -1
package/README.md
CHANGED
|
@@ -11,11 +11,10 @@ Install globally, then run `sks` from either a project or any global shell locat
|
|
|
11
11
|
```sh
|
|
12
12
|
npm i -g sneakoscope
|
|
13
13
|
sks root
|
|
14
|
-
sks bootstrap
|
|
15
14
|
sks
|
|
16
15
|
```
|
|
17
16
|
|
|
18
|
-
`
|
|
17
|
+
`npm i -g sneakoscope` automatically refreshes the `sks` command shim, global Codex App `$` skills, and SKS bootstrap surface. When the install is run from a project, postinstall bootstraps that project. When it is run outside a repo/project marker, postinstall bootstraps the per-user global runtime root instead of writing `.sneakoscope` into a random current directory. `sks root` tells you which root SKS will use.
|
|
19
18
|
|
|
20
19
|
If you only want a one-shot run without keeping `sks` installed globally:
|
|
21
20
|
|
|
@@ -43,8 +42,9 @@ sks selftest --mock
|
|
|
43
42
|
|
|
44
43
|
| Area | What it does |
|
|
45
44
|
| --- | --- |
|
|
46
|
-
| CLI runtime | `sks tmux
|
|
45
|
+
| CLI runtime | Bare `sks` opens or reuses the default tmux Codex CLI workspace. `sks tmux open` remains the explicit form for session/workspace flags, and `sks --mad` launches the high-reasoning auto-review profile. |
|
|
47
46
|
| Codex App commands | Installs generated skills so `$Team`, `$From-Chat-IMG`, `$DFix`, `$QA-LOOP`, `$PPT`, `$Goal`, `$DB`, `$Wiki`, `$Help`, and related routes are visible in prompt workflows. |
|
|
47
|
+
| OpenClaw agents | Generates an OpenClaw skill package so OpenClaw agents can attach `sneakoscope-codex`, enable the `shell` tool, and discover/use SKS commands from the target repo root. |
|
|
48
48
|
| Pipeline plans | Writes `pipeline-plan.json` for stateful routes so the runtime lane, kept stages, skipped stages, verification commands, and no-unrequested-fallback invariant are visible with `sks pipeline plan`. |
|
|
49
49
|
| Team orchestration | Runs substantial work through score-based ambiguity handling, scouts, TriWiki refresh, debate, runtime task graphs, worker inboxes, implementation, review, cleanup, reflection, and Honest Mode; narrow work should use Proof Field evidence to skip unrelated pipeline work instead of expanding Team. |
|
|
50
50
|
| Skill dreaming | Records cheap generated-skill usage counters in JSON and only periodically scans `.agents/skills` for keep, merge, prune, and improvement candidates. Reports are recommendation-only and never delete skills automatically. |
|
|
@@ -74,9 +74,9 @@ Install tmux from [tmux.dev/download](https://www.tmux.dev/download). On macOS,
|
|
|
74
74
|
brew install tmux
|
|
75
75
|
```
|
|
76
76
|
|
|
77
|
-
`sks --mad` is stricter than the normal runtime path:
|
|
77
|
+
The default `sks` runtime checks npm for newer `sneakoscope` and `@openai/codex` versions before opening tmux and prompts to update when the terminal can answer y/n. If you approve the Codex CLI update, SKS installs `@openai/codex@latest` and opens tmux with the version visible on PATH. `sks --mad` is stricter than the normal runtime path:
|
|
78
78
|
|
|
79
|
-
- Checks npm for
|
|
79
|
+
- Checks npm for newer `sneakoscope` and `@openai/codex` versions before launch and asks whether to update when the terminal can answer y/n.
|
|
80
80
|
- Installs the latest Codex CLI with `npm i -g @openai/codex@latest` when it is missing and you approve or pass `--yes`.
|
|
81
81
|
- Requires tmux 3.x or newer before opening the session.
|
|
82
82
|
- Creates or reuses a named detached tmux session, splits panes, and prints the attach command.
|
|
@@ -90,10 +90,9 @@ Use this when you want `sks` available from any repo:
|
|
|
90
90
|
```sh
|
|
91
91
|
npm i -g sneakoscope
|
|
92
92
|
sks root
|
|
93
|
-
sks bootstrap
|
|
94
93
|
```
|
|
95
94
|
|
|
96
|
-
`sks` commands work even when no project root is present. Project-aware commands use the nearest `.sneakoscope`, `.dcodex`, or `.git` root; if none exists, SKS uses a per-user global runtime root.
|
|
95
|
+
`sks` commands work even when no project root is present. Project-aware commands use the nearest `.sneakoscope`, `.dcodex`, or `.git` root; if none exists, SKS uses a per-user global runtime root. Global npm install/upgrade automatically bootstraps the current project when a project marker is present, otherwise it bootstraps the global runtime root. Run `sks bootstrap` manually only when you intentionally want to initialize or repair the current project after install.
|
|
97
96
|
|
|
98
97
|
Project setup writes shared `.gitignore` entries for generated SKS files: `.sneakoscope/`, `.codex/`, `.agents/`, and managed `AGENTS.md`. Setup, doctor repair, and npm postinstall refreshes also compare the previous SKS generated-file manifest with the current package templates and prune stale SKS-generated legacy skills or agent files while preserving user-owned custom skills. Use `sks setup --local-only` when you want those excludes kept only in `.git/info/exclude`.
|
|
99
98
|
|
|
@@ -161,12 +160,15 @@ sks fix-path
|
|
|
161
160
|
### Open Codex CLI With tmux
|
|
162
161
|
|
|
163
162
|
```sh
|
|
163
|
+
sks
|
|
164
164
|
sks tmux open
|
|
165
165
|
sks tmux check
|
|
166
166
|
sks tmux status --once
|
|
167
167
|
```
|
|
168
168
|
|
|
169
|
-
`sks
|
|
169
|
+
Bare `sks` creates or reuses the default named tmux session for Codex CLI. Use `sks tmux open` when you need explicit `--workspace` / `--session` flags, `sks tmux check` for readiness without launching, and `sks help` for CLI help.
|
|
170
|
+
|
|
171
|
+
Before opening tmux, SKS checks the installed Codex CLI against npm `@openai/codex@latest`. If a newer version exists, it asks `Y/n`; answering `y` updates automatically with `npm i -g @openai/codex@latest` and then opens tmux with the updated Codex CLI.
|
|
170
172
|
|
|
171
173
|
### MAD tmux Launch
|
|
172
174
|
|
|
@@ -301,6 +303,61 @@ Use `sks dollar-commands` to confirm that terminal discovery and Codex App promp
|
|
|
301
303
|
|
|
302
304
|
TriWiki is intentionally sparse: `sks wiki sweep` records demote, soft-forget, archive, delete, promote-to-skill, and promote-to-rule candidates instead of injecting every old claim into future prompts. `sks harness fixture` validates the broader Harness Growth Factory contract: deliberate forgetting fixtures, skill card metadata, experiment schema, tool-error taxonomy, permission profiles, MultiAgentV2 defaults, and tmux cockpit view coverage. `sks code-structure scan` flags handwritten files above 1000/2000/3000-line thresholds so new logic can be extracted before command files become harder to maintain.
|
|
303
305
|
|
|
306
|
+
## OpenClaw Agent Usage
|
|
307
|
+
|
|
308
|
+
Sneakoscope can generate an OpenClaw skill package for agents that need to operate SKS-enabled repositories.
|
|
309
|
+
|
|
310
|
+
```sh
|
|
311
|
+
sks openclaw install
|
|
312
|
+
sks openclaw path
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
By default this writes:
|
|
316
|
+
|
|
317
|
+
```text
|
|
318
|
+
~/.openclaw/skills/sneakoscope-codex/
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
The generated skill contains `manifest.yaml`, `SKILL.md`, a skill README, and `openclaw-agent-config.example.yaml`. If you use a custom OpenClaw home, set `OPENCLAW_HOME` or pass `--dir`:
|
|
322
|
+
|
|
323
|
+
```sh
|
|
324
|
+
OPENCLAW_HOME=/opt/openclaw sks openclaw install
|
|
325
|
+
sks openclaw install --dir /opt/openclaw/skills/sneakoscope-codex
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
Attach the skill to an OpenClaw agent with the built-in `shell` tool enabled:
|
|
329
|
+
|
|
330
|
+
```yaml
|
|
331
|
+
agents:
|
|
332
|
+
coding-agent:
|
|
333
|
+
tools:
|
|
334
|
+
- shell
|
|
335
|
+
env:
|
|
336
|
+
SKS_OPENCLAW: "1"
|
|
337
|
+
skills:
|
|
338
|
+
- sneakoscope-codex
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
`SKS_OPENCLAW=1` tells SKS that commands are running from OpenClaw. In that mode, SKS auto-approves update/install prompts such as the Codex CLI update check before tmux launch, instead of waiting for a human `Y/n` response.
|
|
342
|
+
|
|
343
|
+
Then prompt the OpenClaw agent from the target repo root:
|
|
344
|
+
|
|
345
|
+
```text
|
|
346
|
+
Run sks root, inspect AGENTS.md, then use the SKS Team route to implement this fix and verify it.
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
Useful commands for OpenClaw agents:
|
|
350
|
+
|
|
351
|
+
```sh
|
|
352
|
+
SKS_OPENCLAW=1 sks root
|
|
353
|
+
SKS_OPENCLAW=1 sks commands
|
|
354
|
+
SKS_OPENCLAW=1 sks dollar-commands
|
|
355
|
+
SKS_OPENCLAW=1 sks deps check
|
|
356
|
+
SKS_OPENCLAW=1 sks proof-field scan --intent "small CLI change" --changed src/cli/main.mjs
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
If OpenClaw runs the skill inside a sandbox, grant shell execution only for the trusted local workspace. Database, Supabase, migration, and destructive filesystem work should still follow the repo's SKS safety route and require explicit write scope.
|
|
360
|
+
|
|
304
361
|
## Prompt `$` Commands
|
|
305
362
|
|
|
306
363
|
Use these inside Codex App or another agent prompt. They are prompt commands, not terminal commands.
|
|
@@ -341,9 +398,11 @@ sks selftest --mock
|
|
|
341
398
|
|
|
342
399
|
```sh
|
|
343
400
|
sks tmux check
|
|
344
|
-
sks
|
|
401
|
+
sks
|
|
345
402
|
```
|
|
346
403
|
|
|
404
|
+
`sks tmux open` is the equivalent explicit launch form when you want to pass tmux session flags.
|
|
405
|
+
|
|
347
406
|
For the high-reasoning full-access profile:
|
|
348
407
|
|
|
349
408
|
```sh
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sneakoscope",
|
|
3
3
|
"displayName": "ㅅㅋㅅ",
|
|
4
|
-
"version": "0.7.
|
|
4
|
+
"version": "0.7.33",
|
|
5
5
|
"description": "Sneakoscope Codex: database-safe Codex CLI/App harness with Team, Goal, AutoResearch, TriWiki, and Honest Mode.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"homepage": "https://github.com/mandarange/Sneakoscope-Codex#readme",
|
|
@@ -48,6 +48,8 @@
|
|
|
48
48
|
"sks",
|
|
49
49
|
"openai",
|
|
50
50
|
"openai-codex",
|
|
51
|
+
"openclaw",
|
|
52
|
+
"openclaw-skill",
|
|
51
53
|
"cli",
|
|
52
54
|
"developer-tools",
|
|
53
55
|
"ai-coding",
|
|
@@ -3,7 +3,7 @@ import os from 'node:os';
|
|
|
3
3
|
import fsp from 'node:fs/promises';
|
|
4
4
|
import readline from 'node:readline/promises';
|
|
5
5
|
import { stdin as input, stdout as output } from 'node:process';
|
|
6
|
-
import { ensureDir, exists, packageRoot, runProcess, which, writeTextAtomic } from '../core/fsx.mjs';
|
|
6
|
+
import { ensureDir, exists, globalSksRoot, packageRoot, runProcess, which, writeTextAtomic } from '../core/fsx.mjs';
|
|
7
7
|
import { getCodexInfo } from '../core/codex-adapter.mjs';
|
|
8
8
|
import { formatHarnessConflictReport, llmHarnessCleanupPrompt, scanHarnessConflicts } from '../core/harness-conflicts.mjs';
|
|
9
9
|
import { installSkills } from '../core/init.mjs';
|
|
@@ -83,15 +83,20 @@ function shouldAskPostinstallQuestion() {
|
|
|
83
83
|
export async function postinstallBootstrapDecision(root) {
|
|
84
84
|
if (process.env.SKS_POSTINSTALL_NO_BOOTSTRAP === '1') return { run: false, reason: 'SKS_POSTINSTALL_NO_BOOTSTRAP=1' };
|
|
85
85
|
if (process.env.SKS_POSTINSTALL_BOOTSTRAP === '0') return { run: false, reason: 'SKS_POSTINSTALL_BOOTSTRAP=0' };
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
return { run: true, reason: '
|
|
86
|
+
const installRoot = path.resolve(root || process.cwd());
|
|
87
|
+
const candidate = await isProjectSetupCandidate(installRoot);
|
|
88
|
+
const target = candidate ? installRoot : globalSksRoot();
|
|
89
|
+
if (process.env.SKS_POSTINSTALL_BOOTSTRAP === '1') return { run: true, target, reason: 'forced by SKS_POSTINSTALL_BOOTSTRAP=1' };
|
|
90
|
+
if (candidate) return { run: true, target, reason: 'auto-running sks setup --bootstrap --install-scope global --force' };
|
|
91
|
+
return { run: true, target, reason: 'no project marker found; auto-running global SKS runtime bootstrap' };
|
|
90
92
|
}
|
|
91
93
|
|
|
92
94
|
async function runPostinstallBootstrap(root, bootstrap) {
|
|
93
95
|
const previousCwd = process.cwd();
|
|
94
|
-
|
|
96
|
+
const decision = await postinstallBootstrapDecision(root);
|
|
97
|
+
const target = path.resolve(decision.target || root || previousCwd);
|
|
98
|
+
await ensureDir(target);
|
|
99
|
+
process.chdir(target);
|
|
95
100
|
try {
|
|
96
101
|
await bootstrap(['--from-postinstall', '--install-scope', 'global', '--force']);
|
|
97
102
|
} finally {
|
|
@@ -254,6 +259,88 @@ export async function ensureCodexCliTool({ skip = false } = {}) {
|
|
|
254
259
|
};
|
|
255
260
|
}
|
|
256
261
|
|
|
262
|
+
export async function maybePromptCodexUpdateForLaunch(args = [], opts = {}) {
|
|
263
|
+
if (hasFlag(args, '--json') || hasFlag(args, '--skip-cli-tools') || hasFlag(args, '--skip-codex-update') || process.env.SKS_SKIP_CODEX_UPDATE === '1') return { status: 'skipped' };
|
|
264
|
+
const latest = await npmPackageVersion('@openai/codex');
|
|
265
|
+
const codex = await getCodexInfo().catch(() => ({}));
|
|
266
|
+
const current = codexCliVersionNumber(codex.version);
|
|
267
|
+
const command = 'npm i -g @openai/codex@latest';
|
|
268
|
+
const label = opts.label || 'tmux launch';
|
|
269
|
+
const missing = !codex.bin;
|
|
270
|
+
const updateAvailable = Boolean(latest.version && current && compareVersions(latest.version, current) > 0);
|
|
271
|
+
if (!missing && !updateAvailable) return { status: 'current', latest: latest.version || null, current, bin: codex.bin || null, error: latest.error || null };
|
|
272
|
+
const prompt = missing
|
|
273
|
+
? `Codex CLI missing. Install @openai/codex${latest.version ? ` ${latest.version}` : '@latest'} before ${label}? [Y/n] `
|
|
274
|
+
: `Codex CLI ${current} -> ${latest.version} update before ${label}? [Y/n] `;
|
|
275
|
+
if (shouldAutoApproveInstall(args)) return installCodexLatest(command, latest.version, current);
|
|
276
|
+
if (!canAskYesNo()) {
|
|
277
|
+
const reason = missing ? 'Codex CLI missing' : `Codex CLI update available: ${current} -> ${latest.version}`;
|
|
278
|
+
console.log(`${reason}. Run: ${command}`);
|
|
279
|
+
return { status: missing ? 'missing' : 'available', latest: latest.version || null, current, command, bin: codex.bin || null };
|
|
280
|
+
}
|
|
281
|
+
const answer = (await askPostinstallQuestion(prompt)).trim();
|
|
282
|
+
const yes = answer === '' || /^(y|yes|예|네|응)$/i.test(answer);
|
|
283
|
+
if (!yes) return { status: 'skipped_by_user', latest: latest.version || null, current, command, bin: codex.bin || null };
|
|
284
|
+
return installCodexLatest(command, latest.version, current);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
export function shouldAutoApproveInstall(args = [], env = process.env) {
|
|
288
|
+
return hasFlag(args, '--yes') || hasFlag(args, '-y') || isOpenClawRuntime(env);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function canAskYesNo() {
|
|
292
|
+
return Boolean(input.isTTY && output.isTTY && process.env.CI !== 'true');
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function hasFlag(args = [], name) {
|
|
296
|
+
return args.includes(name);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function isOpenClawRuntime(env = process.env) {
|
|
300
|
+
return ['SKS_OPENCLAW', 'OPENCLAW', 'OPENCLAW_AGENT', 'OPENCLAW_RUN_ID', 'OPENCLAW_SESSION_ID']
|
|
301
|
+
.some((key) => /^(1|true|yes|y)$/i.test(String(env[key] || '').trim()));
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
async function installCodexLatest(command, latestVersion, previousVersion = null) {
|
|
305
|
+
const npm = await which('npm').catch(() => null);
|
|
306
|
+
if (!npm) return { status: 'failed', latest: latestVersion || null, previous: previousVersion || null, command, error: 'npm not found on PATH' };
|
|
307
|
+
const install = await runProcess(npm, ['i', '-g', '@openai/codex@latest'], { timeoutMs: 180000, maxOutputBytes: 128 * 1024 }).catch((err) => ({ code: 1, stdout: '', stderr: err.message }));
|
|
308
|
+
if (install.code !== 0) return { status: 'failed', latest: latestVersion || null, previous: previousVersion || null, command, error: `${install.stderr || install.stdout || command + ' failed'}`.trim() };
|
|
309
|
+
const after = await getCodexInfo().catch(() => ({}));
|
|
310
|
+
const afterVersion = codexCliVersionNumber(after.version);
|
|
311
|
+
if (!after.bin) return { status: 'updated_not_reflected', latest: latestVersion || null, previous: previousVersion || null, version: afterVersion || null, command, error: 'npm completed, but codex is not on PATH. Restart the shell or set SKS_CODEX_BIN.' };
|
|
312
|
+
if (latestVersion && afterVersion && compareVersions(afterVersion, latestVersion) < 0) {
|
|
313
|
+
return { status: 'updated_not_reflected', latest: latestVersion, previous: previousVersion || null, version: afterVersion, bin: after.bin, command, error: `npm completed, but PATH still resolves Codex CLI ${afterVersion}; expected ${latestVersion}.` };
|
|
314
|
+
}
|
|
315
|
+
console.log(`Codex CLI ready: ${previousVersion || 'missing'} -> ${after.version || after.bin}`);
|
|
316
|
+
return { status: previousVersion ? 'updated' : 'installed', latest: latestVersion || null, previous: previousVersion || null, version: afterVersion || null, raw_version: after.version || null, bin: after.bin || null, command };
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function codexCliVersionNumber(versionText = '') {
|
|
320
|
+
const match = String(versionText || '').match(/(\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?)/);
|
|
321
|
+
return match ? match[1] : null;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
async function npmPackageVersion(name) {
|
|
325
|
+
const envName = `SKS_NPM_VIEW_${String(name || '').replace(/[^A-Za-z0-9]+/g, '_').toUpperCase()}_VERSION`;
|
|
326
|
+
if (process.env[envName]) return { version: process.env[envName] };
|
|
327
|
+
const npm = await which('npm').catch(() => null);
|
|
328
|
+
if (!npm) return { error: 'npm not found' };
|
|
329
|
+
const result = await runProcess(npm, ['view', name, 'version'], { timeoutMs: 5000, maxOutputBytes: 4096 });
|
|
330
|
+
if (result.code !== 0) return { error: `${result.stderr || result.stdout || 'npm view failed'}`.trim() };
|
|
331
|
+
return { version: result.stdout.trim().split(/\s+/).pop() };
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function compareVersions(a, b) {
|
|
335
|
+
const pa = String(a || '').split(/[.-]/).map((x) => Number.parseInt(x, 10) || 0);
|
|
336
|
+
const pb = String(b || '').split(/[.-]/).map((x) => Number.parseInt(x, 10) || 0);
|
|
337
|
+
for (let i = 0; i < Math.max(pa.length, pb.length, 3); i++) {
|
|
338
|
+
if ((pa[i] || 0) > (pb[i] || 0)) return 1;
|
|
339
|
+
if ((pa[i] || 0) < (pb[i] || 0)) return -1;
|
|
340
|
+
}
|
|
341
|
+
return 0;
|
|
342
|
+
}
|
|
343
|
+
|
|
257
344
|
async function isProjectSetupCandidate(root) {
|
|
258
345
|
const markers = ['package.json', '.git', 'AGENTS.md', '.codex', '.sneakoscope'];
|
|
259
346
|
for (const marker of markers) {
|
package/src/cli/main.mjs
CHANGED
|
@@ -56,11 +56,13 @@ import { buildPromptContext } from '../core/prompt-context-builder.mjs';
|
|
|
56
56
|
import { renderTeamDashboardState, writeTeamDashboardState } from '../core/team-dashboard-renderer.mjs';
|
|
57
57
|
import { GOAL_WORKFLOW_ARTIFACT } from '../core/goal-workflow.mjs';
|
|
58
58
|
import { CODEX_APP_DOCS_URL, codexAppIntegrationStatus, formatCodexAppStatus } from '../core/codex-app.mjs';
|
|
59
|
+
import { OPENCLAW_SKILL_NAME, installOpenClawSkill } from '../core/openclaw.mjs';
|
|
59
60
|
import { buildTmuxLaunchPlan, buildTmuxOpenArgs, createTmuxSession, isTmuxShellSession, runTmuxLaunchPlanSyntaxCheck, tmuxReadiness, tmuxStatusKind, defaultTmuxSessionName, formatTmuxBanner, launchTmuxTeamView, launchTmuxUi, platformTmuxInstallHint, runTmuxStatus, sanitizeTmuxSessionName, teamLaneStyle } from '../core/tmux-ui.mjs';
|
|
60
61
|
import { autoReviewProfileName, autoReviewStatus, autoReviewSummary, enableAutoReview, disableAutoReview, enableMadHighProfile, madHighProfileName } from '../core/auto-review.mjs';
|
|
61
62
|
import { context7Command } from './context7-command.mjs';
|
|
62
|
-
import { askPostinstallQuestion, checkContext7, checkRequiredSkills, ensureCodexCliTool, ensureGlobalCodexSkillsDuringInstall, ensureProjectContext7Config, ensureRelatedCliTools, ensureSksCommandDuringInstall, globalCodexSkillsRoot, postinstall, postinstallBootstrapDecision } from './install-helpers.mjs';
|
|
63
|
+
import { askPostinstallQuestion, checkContext7, checkRequiredSkills, ensureCodexCliTool, ensureGlobalCodexSkillsDuringInstall, ensureProjectContext7Config, ensureRelatedCliTools, ensureSksCommandDuringInstall, globalCodexSkillsRoot, maybePromptCodexUpdateForLaunch, postinstall, postinstallBootstrapDecision, shouldAutoApproveInstall } from './install-helpers.mjs';
|
|
63
64
|
import { buildTeamPlan, codeStructureCommand, dbCommand, defaultBeta, defaultVGraph, evalCommand, gcCommand, goalCommand, gxCommand, harnessCommand, hproofCommand, memoryCommand, migrateWikiContextPack, parseTeamCreateArgs, perfCommand, profileCommand, projectWikiClaims, proofFieldCommand, qaLoopCommand, quickstartCommand, researchCommand, skillDreamCommand, statsCommand, team, teamWorkflowMarkdown, validateArtifactsCommand, wikiCommand, wikiVoxelRowCount, writeWikiContextPack } from './maintenance-commands.mjs';
|
|
65
|
+
import { openClawCommand } from './openclaw-command.mjs';
|
|
64
66
|
|
|
65
67
|
const flag = (args, name) => args.includes(name);
|
|
66
68
|
const promptOf = (args) => args.filter((x) => !String(x).startsWith('--')).join(' ').trim();
|
|
@@ -80,7 +82,7 @@ export async function main(args) {
|
|
|
80
82
|
if (isAutoReviewFlag(args[0])) return autoReviewCommand('start', args.slice(1));
|
|
81
83
|
const [cmd, sub, ...rest] = args;
|
|
82
84
|
const tail = sub === undefined ? [] : [sub, ...rest];
|
|
83
|
-
if (!cmd) return
|
|
85
|
+
if (!cmd) return defaultTmuxCommand();
|
|
84
86
|
if (cmd === '--help' || cmd === '-h') return help();
|
|
85
87
|
if (cmd === '--version' || cmd === '-v' || cmd === 'version') return version();
|
|
86
88
|
if (cmd === 'tmux') return !sub || String(sub).startsWith('--') ? tmuxCommand('check', tail) : tmuxCommand(sub, rest);
|
|
@@ -88,7 +90,7 @@ export async function main(args) {
|
|
|
88
90
|
if (cmd === 'dollar-commands' || cmd === 'dollars' || cmd === '$') return dollarCommands(tail);
|
|
89
91
|
if (String(cmd).toLowerCase() === 'dfix') return dfixHelp();
|
|
90
92
|
const handlers = {
|
|
91
|
-
postinstall: () => postinstall({ bootstrap }), wizard: () => wizard(tail), ui: () => wizard(tail), 'update-check': () => updateCheck(tail), help: () => help(tail), commands: () => commands(tail), usage: () => usage(tail), root: () => rootCommand(tail), quickstart: () => quickstartCommand(), 'codex-app': () => codexAppHelp(tail), bootstrap: () => bootstrap(tail), deps: () => deps(sub, rest),
|
|
93
|
+
postinstall: () => postinstall({ bootstrap }), wizard: () => wizard(tail), ui: () => wizard(tail), 'update-check': () => updateCheck(tail), help: () => help(tail), commands: () => commands(tail), usage: () => usage(tail), root: () => rootCommand(tail), quickstart: () => quickstartCommand(), 'codex-app': () => codexAppHelp(tail), openclaw: () => openClawCommand(tail), bootstrap: () => bootstrap(tail), deps: () => deps(sub, rest),
|
|
92
94
|
'qa-loop': () => qaLoopCommand(sub, rest), ppt: () => pptCommand(sub, rest), context7: () => context7Command(sub, rest), pipeline: () => pipeline(sub, rest), guard: () => guard(sub, rest), conflicts: () => conflicts(sub, rest), versioning: () => versioning(sub, rest), reasoning: () => reasoningCommand(tail), aliases: () => aliases(), setup: () => setup(tail), 'fix-path': () => fixPath(tail), doctor: () => doctor(tail), init: () => init(tail), selftest: () => selftest(tail),
|
|
93
95
|
goal: () => goalCommand(sub, rest), research: () => researchCommand(sub, rest), hook: () => emitHook(sub), profile: () => profileCommand(sub, rest), hproof: () => hproofCommand(sub, rest), 'validate-artifacts': () => validateArtifactsCommand(tail), perf: () => perfCommand(sub, rest), 'proof-field': () => proofFieldCommand(sub, rest), 'skill-dream': () => skillDreamCommand(sub, rest), 'code-structure': () => codeStructureCommand(sub, rest), memory: () => memoryCommand(sub, rest), gx: () => gxCommand(sub, rest),
|
|
94
96
|
team: () => team(tail), db: () => dbCommand(sub, rest), eval: () => evalCommand(sub, rest), harness: () => harnessCommand(sub, rest), wiki: () => wikiCommand(sub, rest), gc: () => gcCommand(tail), stats: () => statsCommand(tail)
|
|
@@ -98,6 +100,26 @@ export async function main(args) {
|
|
|
98
100
|
process.exitCode = 1;
|
|
99
101
|
}
|
|
100
102
|
|
|
103
|
+
async function defaultTmuxCommand(args = []) {
|
|
104
|
+
const update = await maybePromptSksUpdateForLaunch(args, { label: 'default tmux launch' });
|
|
105
|
+
if (update.status === 'updated') {
|
|
106
|
+
console.log(`SKS updated from ${PACKAGE_VERSION} to ${update.latest}. Rerun: sks`);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (update.status === 'failed') {
|
|
110
|
+
console.error(`SKS update failed: ${update.error}`);
|
|
111
|
+
process.exitCode = 1;
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
const codexUpdate = await maybePromptCodexUpdateForLaunch(args, { label: 'default tmux launch' });
|
|
115
|
+
if (codexUpdate.status === 'failed' || codexUpdate.status === 'updated_not_reflected') {
|
|
116
|
+
console.error(`Codex CLI update failed: ${codexUpdate.error || 'updated version was not visible on PATH'}`);
|
|
117
|
+
process.exitCode = 1;
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
return launchTmuxUi(args, { conciseBlockers: true });
|
|
121
|
+
}
|
|
122
|
+
|
|
101
123
|
function help(args = []) {
|
|
102
124
|
const topic = args[0];
|
|
103
125
|
if (topic) return usage([topic]);
|
|
@@ -105,6 +127,7 @@ function help(args = []) {
|
|
|
105
127
|
Sneakoscope Codex
|
|
106
128
|
|
|
107
129
|
Usage:
|
|
130
|
+
sks
|
|
108
131
|
sks help [topic]
|
|
109
132
|
sks version
|
|
110
133
|
sks update-check [--json]
|
|
@@ -116,6 +139,7 @@ Usage:
|
|
|
116
139
|
sks bootstrap [--install-scope global|project] [--local-only] [--json]
|
|
117
140
|
sks deps check|install [tmux|codex|context7|all] [--yes] [--json]
|
|
118
141
|
sks codex-app
|
|
142
|
+
sks openclaw install|path|print [--dir path] [--force] [--json]
|
|
119
143
|
sks --mad [--high]
|
|
120
144
|
sks auto-review status|enable|start [--high]
|
|
121
145
|
sks --Auto-review [--high]
|
|
@@ -870,6 +894,12 @@ async function tmuxCommand(sub = 'start', args = []) {
|
|
|
870
894
|
return;
|
|
871
895
|
}
|
|
872
896
|
if (['start', 'attach', 'connect', 'open'].includes(action)) {
|
|
897
|
+
const codexUpdate = await maybePromptCodexUpdateForLaunch(args, { label: 'tmux launch' });
|
|
898
|
+
if (codexUpdate.status === 'failed' || codexUpdate.status === 'updated_not_reflected') {
|
|
899
|
+
console.error(`Codex CLI update failed: ${codexUpdate.error || 'updated version was not visible on PATH'}`);
|
|
900
|
+
process.exitCode = 1;
|
|
901
|
+
return;
|
|
902
|
+
}
|
|
873
903
|
const result = await launchTmuxUi(args);
|
|
874
904
|
if (flag(args, '--json')) console.log(JSON.stringify(result, null, 2));
|
|
875
905
|
return;
|
|
@@ -884,7 +914,7 @@ async function madHighCommand(args = []) {
|
|
|
884
914
|
const profile = await enableMadHighProfile();
|
|
885
915
|
return console.log(JSON.stringify(profile, null, 2));
|
|
886
916
|
}
|
|
887
|
-
const update = await
|
|
917
|
+
const update = await maybePromptSksUpdateForLaunch(args, { label: 'MAD launch' });
|
|
888
918
|
if (update.status === 'updated') {
|
|
889
919
|
console.log(`SKS updated from ${PACKAGE_VERSION} to ${update.latest}. Rerun: sks --mad`);
|
|
890
920
|
return;
|
|
@@ -894,6 +924,12 @@ async function madHighCommand(args = []) {
|
|
|
894
924
|
process.exitCode = 1;
|
|
895
925
|
return;
|
|
896
926
|
}
|
|
927
|
+
const codexUpdate = await maybePromptCodexUpdateForLaunch(args, { label: 'MAD launch' });
|
|
928
|
+
if (codexUpdate.status === 'failed' || codexUpdate.status === 'updated_not_reflected') {
|
|
929
|
+
console.error(`Codex CLI update failed: ${codexUpdate.error || 'updated version was not visible on PATH'}`);
|
|
930
|
+
process.exitCode = 1;
|
|
931
|
+
return;
|
|
932
|
+
}
|
|
897
933
|
const deps = await ensureMadLaunchDependencies(args);
|
|
898
934
|
if (!deps.ready) {
|
|
899
935
|
console.error('SKS MAD launch blocked by missing dependencies.');
|
|
@@ -912,18 +948,19 @@ async function madHighCommand(args = []) {
|
|
|
912
948
|
});
|
|
913
949
|
}
|
|
914
950
|
|
|
915
|
-
async function
|
|
951
|
+
async function maybePromptSksUpdateForLaunch(args = [], opts = {}) {
|
|
916
952
|
if (flag(args, '--json') || flag(args, '--skip-update-check') || process.env.SKS_SKIP_UPDATE_CHECK === '1') return { status: 'skipped' };
|
|
917
953
|
const latest = await npmPackageVersion('sneakoscope');
|
|
918
954
|
const currentPackage = await effectivePackageVersion();
|
|
919
955
|
if (!latest.version || compareVersions(latest.version, currentPackage) <= 0) return { status: 'current', latest: latest.version || null, error: latest.error || null };
|
|
920
956
|
const command = 'npm i -g sneakoscope@latest';
|
|
921
|
-
if (
|
|
957
|
+
if (shouldAutoApproveInstall(args)) return installSksLatest(command, latest.version);
|
|
922
958
|
if (!canAskYesNo()) {
|
|
923
959
|
console.log(`SKS update available: ${currentPackage} -> ${latest.version}. Run: ${command}`);
|
|
924
960
|
return { status: 'available', latest: latest.version, command };
|
|
925
961
|
}
|
|
926
|
-
const
|
|
962
|
+
const label = opts.label || 'launch';
|
|
963
|
+
const answer = (await askPostinstallQuestion(`SKS ${currentPackage} -> ${latest.version} update before ${label}? [Y/n] `)).trim();
|
|
927
964
|
const yes = answer === '' || /^(y|yes|예|네|응)$/i.test(answer);
|
|
928
965
|
if (!yes) return { status: 'skipped_by_user', latest: latest.version, command };
|
|
929
966
|
return installSksLatest(command, latest.version);
|
|
@@ -1071,7 +1108,7 @@ async function installTmuxDependency(args = []) {
|
|
|
1071
1108
|
}
|
|
1072
1109
|
|
|
1073
1110
|
async function confirmInstall(question, args = []) {
|
|
1074
|
-
if (
|
|
1111
|
+
if (shouldAutoApproveInstall(args)) return true;
|
|
1075
1112
|
if (!canAskYesNo()) return false;
|
|
1076
1113
|
return /^(y|yes|예|네|응)$/i.test((await askPostinstallQuestion(`${question} [y/N] `)).trim());
|
|
1077
1114
|
}
|
|
@@ -1116,6 +1153,12 @@ async function autoReviewCommand(sub = 'status', args = []) {
|
|
|
1116
1153
|
const status = await enableAutoReview({ high });
|
|
1117
1154
|
if (flag(args, '--json')) return console.log(JSON.stringify(status, null, 2));
|
|
1118
1155
|
console.log(`SKS Auto-Review enabled: ${profile}`);
|
|
1156
|
+
const codexUpdate = await maybePromptCodexUpdateForLaunch(args, { label: 'auto-review tmux launch' });
|
|
1157
|
+
if (codexUpdate.status === 'failed' || codexUpdate.status === 'updated_not_reflected') {
|
|
1158
|
+
console.error(`Codex CLI update failed: ${codexUpdate.error || 'updated version was not visible on PATH'}`);
|
|
1159
|
+
process.exitCode = 1;
|
|
1160
|
+
return;
|
|
1161
|
+
}
|
|
1119
1162
|
const sessionArg = readOption(cleanArgs, '--session', null);
|
|
1120
1163
|
const session = sessionArg || sanitizeTmuxSessionName(`${profile}-${defaultTmuxSessionName(process.cwd())}`);
|
|
1121
1164
|
return launchTmuxUi([...cleanArgs, '--session', session], { codexArgs: ['--profile', profile] });
|
|
@@ -1176,6 +1219,7 @@ Codex App prompt commands:
|
|
|
1176
1219
|
${formatDollarCommandsCompact(' ')}
|
|
1177
1220
|
|
|
1178
1221
|
Examples:
|
|
1222
|
+
sks
|
|
1179
1223
|
sks setup
|
|
1180
1224
|
sneakoscope setup
|
|
1181
1225
|
sks commands
|
|
@@ -1191,7 +1235,8 @@ function usage(args = []) {
|
|
|
1191
1235
|
bootstrap: ['Bootstrap', '', ' sks bootstrap', ' sks setup --bootstrap', '', 'Creates project SKS files, Codex App skills/hooks/config, state/guard files, then checks Codex App, Context7, and tmux.'],
|
|
1192
1236
|
root: ['Root', '', ' sks root [--json]', '', 'Inside a project, SKS uses that project root. Outside any project marker, runtime commands use the per-user global SKS root instead of writing .sneakoscope into the current random folder.'],
|
|
1193
1237
|
deps: ['Dependencies', '', ' sks deps check [--json]', ' sks deps install [tmux|codex|context7|all] [--yes]', '', 'tmux on macOS uses Homebrew only after approval.'],
|
|
1194
|
-
tmux: ['tmux', '', ' sks tmux open', ' sks tmux check', ' sks tmux status --once', ' sks deps install tmux', '', 'tmux launch is
|
|
1238
|
+
tmux: ['tmux', '', ' sks', ' sks tmux open', ' sks tmux check', ' sks tmux status --once', ' sks deps install tmux', '', 'Running bare `sks` opens or reuses the default tmux Codex CLI session. Before launch, SKS checks npm @openai/codex@latest and prompts Y/n when the installed Codex CLI is missing or outdated. Use `sks tmux open` when you need explicit session/workspace flags, and `sks help` for CLI help.'],
|
|
1239
|
+
openclaw: ['OpenClaw', '', ' sks openclaw install', ' sks openclaw path', ' sks openclaw print SKILL.md', '', 'Installs an OpenClaw skill package under ~/.openclaw/skills/sneakoscope-codex so OpenClaw agents can attach skills: [sneakoscope-codex] with the shell tool and call local SKS commands from a project root.'],
|
|
1195
1240
|
team: ['Team', '', ' sks team "task" executor:5 reviewer:2 user:1', ' sks team watch latest', ' sks team lane latest --agent analysis_scout_1 --follow', ' sks team message latest --from analysis_scout_1 --to executor_1 --message "handoff note"', ' sks team cleanup-tmux latest', '', '$Team runs questions -> contract -> scouts -> TriWiki attention -> debate -> runtime graph/inbox -> fresh executors -> review -> cleanup -> reflection -> Honest.'],
|
|
1196
1241
|
'qa-loop': ['QA-LOOP', '', ' sks qa-loop prepare "QA this app"', ' sks qa-loop answer <MISSION_ID> answers.json', ' sks qa-loop run <MISSION_ID> --max-cycles 8', '', 'Report: YYYY-MM-DD-v<version>-qa-report.md'],
|
|
1197
1242
|
ppt: ['PPT', '', ' $PPT 투자자용 피치덱을 HTML 기반 PDF로 만들어줘', ' $PPT 우리 SaaS 소개자료 만들어줘', ' sks ppt build latest --json', ' sks ppt status latest --json', '', '$PPT asks delivery context, audience profile, STP strategy, decision context, and 3+ pain-point/solution/aha mappings before source research, design-system work, HTML/PDF export, and render QA. Independent strategy/render/file-write phases run in parallel where inputs allow and are recorded in ppt-parallel-report.json. The visual system must stay simple, restrained, and information-first; editable source HTML is kept under source-html/, PPT-only temporary build files are cleaned, and installed skills/MCPs outside the $PPT allowlist are ignored. Design uses getdesign-reference plus the built-in PPT design pipeline; imagegen and Context7 are conditional only when the sealed PPT contract needs raster assets or current external docs.'],
|
|
@@ -1653,7 +1698,7 @@ function readMaxCycles(args, fallback) {
|
|
|
1653
1698
|
|
|
1654
1699
|
function positionalArgs(args = []) {
|
|
1655
1700
|
const out = [];
|
|
1656
|
-
const valueFlags = new Set(['--format', '--iterations', '--out', '--baseline', '--candidate', '--install-scope', '--max-cycles', '--depth', '--scope', '--transport', '--query', '--topic', '--tokens', '--timeout-ms', '--sql', '--command', '--project-ref', '--agent', '--phase', '--message', '--role', '--max-anchors', '--lines']);
|
|
1701
|
+
const valueFlags = new Set(['--format', '--iterations', '--out', '--baseline', '--candidate', '--install-scope', '--max-cycles', '--depth', '--scope', '--transport', '--query', '--topic', '--tokens', '--timeout-ms', '--sql', '--command', '--project-ref', '--agent', '--phase', '--message', '--role', '--max-anchors', '--lines', '--dir']);
|
|
1657
1702
|
for (let i = 0; i < args.length; i++) {
|
|
1658
1703
|
const arg = String(args[i]);
|
|
1659
1704
|
if (valueFlags.has(arg)) {
|
|
@@ -1840,6 +1885,16 @@ async function selftest() {
|
|
|
1840
1885
|
if (oldNoBootstrap === undefined) delete process.env.SKS_POSTINSTALL_NO_BOOTSTRAP;
|
|
1841
1886
|
else process.env.SKS_POSTINSTALL_NO_BOOTSTRAP = oldNoBootstrap;
|
|
1842
1887
|
if (noBootstrapDecision.run || noBootstrapDecision.reason !== 'SKS_POSTINSTALL_NO_BOOTSTRAP=1') throw new Error('selftest failed: postinstall bootstrap opt-out decision');
|
|
1888
|
+
const postinstallNoMarkerTmp = tmpdir();
|
|
1889
|
+
const postinstallNoMarkerHome = path.join(postinstallNoMarkerTmp, 'home');
|
|
1890
|
+
const postinstallNoMarkerCwd = path.join(postinstallNoMarkerTmp, 'cwd');
|
|
1891
|
+
const postinstallNoMarkerGlobalRoot = path.join(postinstallNoMarkerTmp, 'global-root');
|
|
1892
|
+
await ensureDir(postinstallNoMarkerCwd);
|
|
1893
|
+
const postinstallNoMarker = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'postinstall'], { cwd: postinstallNoMarkerCwd, env: { INIT_CWD: postinstallNoMarkerCwd, HOME: postinstallNoMarkerHome, SKS_GLOBAL_ROOT: postinstallNoMarkerGlobalRoot, SKS_SKIP_POSTINSTALL_SHIM: '1', SKS_SKIP_POSTINSTALL_CONTEXT7: '1', SKS_SKIP_POSTINSTALL_GETDESIGN: '1', SKS_SKIP_CLI_TOOLS: '1' }, timeoutMs: 30000, maxOutputBytes: 256 * 1024 });
|
|
1894
|
+
if (postinstallNoMarker.code !== 0) throw new Error(`selftest failed: no-marker postinstall bootstrap exited ${postinstallNoMarker.code}: ${postinstallNoMarker.stderr}`);
|
|
1895
|
+
if (!String(postinstallNoMarker.stdout || '').includes('no project marker found; auto-running global SKS runtime bootstrap')) throw new Error('selftest failed: no-marker postinstall did not report global runtime bootstrap');
|
|
1896
|
+
if (!(await exists(path.join(postinstallNoMarkerGlobalRoot, '.sneakoscope', 'manifest.json')))) throw new Error('selftest failed: no-marker postinstall did not bootstrap global runtime root');
|
|
1897
|
+
if (await exists(path.join(postinstallNoMarkerCwd, '.sneakoscope'))) throw new Error('selftest failed: no-marker postinstall polluted install cwd');
|
|
1843
1898
|
const bootstrapJsonTmp = tmpdir();
|
|
1844
1899
|
await writeJsonAtomic(path.join(bootstrapJsonTmp, 'package.json'), { name: 'bootstrap-json-smoke', version: '0.0.0' });
|
|
1845
1900
|
const bootstrapJson = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'bootstrap', '--json'], { cwd: bootstrapJsonTmp, env: { HOME: path.join(bootstrapJsonTmp, 'home'), SKS_SKIP_POSTINSTALL_GLOBAL_SKILLS: '1', SKS_SKIP_CLI_TOOLS: '1' }, timeoutMs: 30000, maxOutputBytes: 256 * 1024 });
|
|
@@ -1872,6 +1927,39 @@ async function selftest() {
|
|
|
1872
1927
|
if (tmuxOpenArgs.join(' ') !== 'attach-session -t sks-mad-selftest') throw new Error('selftest failed: MAD tmux attach args are not stable by session name');
|
|
1873
1928
|
if (!isTmuxShellSession({ TMUX: '/tmp/tmux-501/default,1,0' })) throw new Error('selftest failed: tmux shell session env was not detected');
|
|
1874
1929
|
if (tmuxStatusKind({ ok: false, bin: null }) !== 'missing') throw new Error('selftest failed: missing tmux was not labeled missing');
|
|
1930
|
+
const bareDefault = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs')], {
|
|
1931
|
+
cwd: globalCwd,
|
|
1932
|
+
env: { SKS_GLOBAL_ROOT: globalRuntimeRoot, SKS_NPM_VIEW_SNEAKOSCOPE_VERSION: PACKAGE_VERSION, PATH: '' },
|
|
1933
|
+
timeoutMs: 15000,
|
|
1934
|
+
maxOutputBytes: 64 * 1024
|
|
1935
|
+
});
|
|
1936
|
+
if (bareDefault.code !== 1 || !String(bareDefault.stderr || '').includes('SKS tmux launch blocked') || String(bareDefault.stdout || '').includes('Usage:')) throw new Error('selftest failed: bare sks did not route to default tmux launch');
|
|
1937
|
+
const fakeCodexBin = path.join(tmp, 'fake-codex-bin');
|
|
1938
|
+
await ensureDir(fakeCodexBin);
|
|
1939
|
+
const fakeCodexPath = path.join(fakeCodexBin, 'codex');
|
|
1940
|
+
await writeTextAtomic(fakeCodexPath, '#!/bin/sh\necho "codex-cli 0.1.0"\n');
|
|
1941
|
+
await fsp.chmod(fakeCodexPath, 0o755);
|
|
1942
|
+
const codexUpdatePrompt = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs')], {
|
|
1943
|
+
cwd: globalCwd,
|
|
1944
|
+
env: { SKS_GLOBAL_ROOT: globalRuntimeRoot, SKS_NPM_VIEW_SNEAKOSCOPE_VERSION: PACKAGE_VERSION, SKS_NPM_VIEW__OPENAI_CODEX_VERSION: '99.0.0', PATH: fakeCodexBin },
|
|
1945
|
+
timeoutMs: 15000,
|
|
1946
|
+
maxOutputBytes: 64 * 1024
|
|
1947
|
+
});
|
|
1948
|
+
if (!String(codexUpdatePrompt.stdout || '').includes('Codex CLI update available: 0.1.0 -> 99.0.0') || String(codexUpdatePrompt.stdout || '').includes('Usage:')) throw new Error('selftest failed: bare sks did not recommend Codex CLI update before tmux launch');
|
|
1949
|
+
const openClawAutoBin = path.join(tmp, 'openclaw-auto-bin');
|
|
1950
|
+
await ensureDir(openClawAutoBin);
|
|
1951
|
+
const openClawCodexPath = path.join(openClawAutoBin, 'codex');
|
|
1952
|
+
await writeTextAtomic(openClawCodexPath, '#!/bin/sh\necho "codex-cli 0.1.0"\n');
|
|
1953
|
+
await writeTextAtomic(path.join(openClawAutoBin, 'npm'), '#!/bin/sh\nDIR="${0%/*}"\nif [ "$1" = "i" ]; then\n printf \'#!/bin/sh\\necho "codex-cli 99.0.0"\\n\' > "$DIR/codex"\n chmod +x "$DIR/codex"\n exit 0\nfi\necho "unexpected npm $*" >&2\nexit 1\n');
|
|
1954
|
+
await fsp.chmod(openClawCodexPath, 0o755);
|
|
1955
|
+
await fsp.chmod(path.join(openClawAutoBin, 'npm'), 0o755);
|
|
1956
|
+
const openClawAutoUpdate = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs')], {
|
|
1957
|
+
cwd: globalCwd,
|
|
1958
|
+
env: { SKS_GLOBAL_ROOT: globalRuntimeRoot, SKS_OPENCLAW: '1', SKS_NPM_VIEW_SNEAKOSCOPE_VERSION: PACKAGE_VERSION, SKS_NPM_VIEW__OPENAI_CODEX_VERSION: '99.0.0', PATH: openClawAutoBin },
|
|
1959
|
+
timeoutMs: 15000,
|
|
1960
|
+
maxOutputBytes: 64 * 1024
|
|
1961
|
+
});
|
|
1962
|
+
if (!String(openClawAutoUpdate.stdout || '').includes('Codex CLI ready: 0.1.0 -> codex-cli 99.0.0')) throw new Error('selftest failed: OpenClaw mode did not auto-approve Codex CLI update before tmux launch');
|
|
1875
1963
|
const guardBlocked = await checkHarnessModification(tmp, { tool_name: 'apply_patch', command: '*** Update File: .agents/skills/team/SKILL.md\n+tamper\n' });
|
|
1876
1964
|
if (guardBlocked.action !== 'block') throw new Error('selftest failed: harness guard allowed skill tampering');
|
|
1877
1965
|
const setupBlocked = await checkHarnessModification(tmp, { command: 'sks setup --force' });
|
|
@@ -2014,6 +2102,9 @@ async function selftest() {
|
|
|
2014
2102
|
if (!promptPipelineSkillExists) throw new Error('selftest failed: prompt pipeline skill not installed');
|
|
2015
2103
|
const promptPipelineText = await safeReadText(path.join(tmp, '.agents', 'skills', 'prompt-pipeline', 'SKILL.md'));
|
|
2016
2104
|
if (!promptPipelineText.includes('TriWiki context-tracking SSOT')) throw new Error('selftest failed: prompt pipeline missing TriWiki context-tracking SSOT');
|
|
2105
|
+
if (!promptPipelineText.includes('Codex App pipeline activation:') || !promptPipelineText.includes('sks hook user-prompt-submit') || !promptPipelineText.includes('hookSpecificOutput.additionalContext')) throw new Error('selftest failed: prompt pipeline missing Codex App pipeline activation fallback');
|
|
2106
|
+
const teamSkillText = await safeReadText(path.join(tmp, '.agents', 'skills', 'team', 'SKILL.md'));
|
|
2107
|
+
if (!teamSkillText.includes('Codex App pipeline activation:') || !teamSkillText.includes('sks pipeline status') || !teamSkillText.includes('mission/pipeline artifacts')) throw new Error('selftest failed: Team skill missing pipeline activation fallback');
|
|
2017
2108
|
if (!promptPipelineText.includes('before every route stage') || !promptPipelineText.includes('sks wiki refresh')) throw new Error('selftest failed: prompt pipeline missing per-stage TriWiki policy');
|
|
2018
2109
|
if (!promptPipelineText.includes('single design decision authority') || !promptPipelineText.includes('imagegen') || !promptPipelineText.includes('getdesign-reference') || !promptPipelineText.includes(AWESOME_DESIGN_MD_REFERENCE.url) || !promptPipelineText.includes('not parallel authorities')) throw new Error('selftest failed: prompt pipeline missing design SSOT/source-input routing');
|
|
2019
2110
|
if (!promptPipelineText.includes(CODEX_APP_IMAGE_GENERATION_DOC_URL)) throw new Error('selftest failed: prompt pipeline missing Codex App image generation policy');
|
|
@@ -2062,7 +2153,16 @@ async function selftest() {
|
|
|
2062
2153
|
if (!DOLLAR_DEFAULT_PIPELINE_TEXT.includes('$Team')) throw new Error('selftest failed: dollar-commands missing Team default routing guidance');
|
|
2063
2154
|
if (!DOLLAR_DEFAULT_PIPELINE_TEXT.includes('$From-Chat-IMG')) throw new Error('selftest failed: dollar-commands missing From-Chat-IMG guidance');
|
|
2064
2155
|
if (!DOLLAR_DEFAULT_PIPELINE_TEXT.includes('$MAD-SKS')) throw new Error('selftest failed: dollar-commands missing MAD-SKS scoped override guidance');
|
|
2065
|
-
if (!COMMAND_CATALOG.some((c) => c.name === 'context7') || !COMMAND_CATALOG.some((c) => c.name === 'pipeline') || !COMMAND_CATALOG.some((c) => c.name === 'qa-loop') || !COMMAND_CATALOG.some((c) => c.name === 'root')) throw new Error('selftest failed: context7/pipeline/qa-loop/root commands missing from catalog');
|
|
2156
|
+
if (!COMMAND_CATALOG.some((c) => c.name === 'context7') || !COMMAND_CATALOG.some((c) => c.name === 'pipeline') || !COMMAND_CATALOG.some((c) => c.name === 'qa-loop') || !COMMAND_CATALOG.some((c) => c.name === 'root') || !COMMAND_CATALOG.some((c) => c.name === 'openclaw')) throw new Error('selftest failed: context7/pipeline/qa-loop/root/openclaw commands missing from catalog');
|
|
2157
|
+
const openClawTmp = tmpdir();
|
|
2158
|
+
const openClawResult = await installOpenClawSkill({ targetDir: path.join(openClawTmp, 'skills', OPENCLAW_SKILL_NAME) });
|
|
2159
|
+
if (!openClawResult.ok) throw new Error(`selftest failed: OpenClaw skill install blocked: ${openClawResult.reason}`);
|
|
2160
|
+
const openClawSkillText = await safeReadText(path.join(openClawResult.target_dir, 'SKILL.md'));
|
|
2161
|
+
const openClawManifestText = await safeReadText(path.join(openClawResult.target_dir, 'manifest.yaml'));
|
|
2162
|
+
const openClawConfigText = await safeReadText(path.join(openClawResult.target_dir, 'openclaw-agent-config.example.yaml'));
|
|
2163
|
+
if (!openClawSkillText.includes('sks root') || !openClawSkillText.includes('$Team') || !openClawSkillText.includes('OpenClaw agent must have the built-in `shell` tool enabled') || !openClawSkillText.includes('SKS_OPENCLAW=1')) throw new Error('selftest failed: OpenClaw skill missing SKS agent guidance');
|
|
2164
|
+
if (!openClawManifestText.includes('generated_by: sneakoscope') || !openClawManifestText.includes(`version: ${PACKAGE_VERSION}`)) throw new Error('selftest failed: OpenClaw manifest missing generated marker or version');
|
|
2165
|
+
if (!openClawConfigText.includes(`- ${OPENCLAW_SKILL_NAME}`) || !openClawConfigText.includes('- shell') || !openClawConfigText.includes('SKS_OPENCLAW')) throw new Error('selftest failed: OpenClaw agent config example missing skill, shell tool, or OpenClaw env');
|
|
2066
2166
|
const registryDollarCommands = DOLLAR_COMMANDS.map((c) => c.command);
|
|
2067
2167
|
const manifest = await readJson(path.join(tmp, '.sneakoscope', 'manifest.json'));
|
|
2068
2168
|
const policy = await readJson(path.join(tmp, '.sneakoscope', 'policy.json'));
|
|
@@ -2138,7 +2238,7 @@ async function selftest() {
|
|
|
2138
2238
|
if (String(hookUpdateCurrentContext).includes('Update SKS now') || String(hookUpdateCurrentContext).includes('Skip update for this conversation')) throw new Error('selftest failed: hook prompted for update even though installed SKS is current');
|
|
2139
2239
|
const hookUpdateCurrentState = await readJson(path.join(hookUpdateCurrentTmp, '.sneakoscope', 'state', 'update-check.json'), {});
|
|
2140
2240
|
if (hookUpdateCurrentState.pending_offer) throw new Error('selftest failed: current installed SKS left a pending update offer');
|
|
2141
|
-
if (hookUpdateCurrentState.current !== '9.9.9' || hookUpdateCurrentState.runtime_current !== PACKAGE_VERSION || hookUpdateCurrentState.installed_current !== '9.9.9') throw new Error(
|
|
2241
|
+
if (hookUpdateCurrentState.current !== '9.9.9' || hookUpdateCurrentState.runtime_current !== PACKAGE_VERSION || hookUpdateCurrentState.installed_current !== '9.9.9') throw new Error(`selftest failed: hook did not record effective installed SKS version: ${JSON.stringify({ expected: { current: '9.9.9', runtime_current: PACKAGE_VERSION, installed_current: '9.9.9' }, actual: hookUpdateCurrentState })}`);
|
|
2142
2242
|
const hookUpdatePendingTmp = tmpdir();
|
|
2143
2243
|
await initProject(hookUpdatePendingTmp, {});
|
|
2144
2244
|
await writeJsonAtomic(path.join(hookUpdatePendingTmp, '.sneakoscope', 'state', 'update-check.json'), {
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { OPENCLAW_SKILL_NAME, buildOpenClawSkillFiles, defaultOpenClawSkillDir, installOpenClawSkill } from '../core/openclaw.mjs';
|
|
3
|
+
|
|
4
|
+
const flag = (args, name) => args.includes(name);
|
|
5
|
+
|
|
6
|
+
function readFlagValue(args, name, fallback) {
|
|
7
|
+
const i = args.indexOf(name);
|
|
8
|
+
return i >= 0 && args[i + 1] ? args[i + 1] : fallback;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function positionalArgs(args = []) {
|
|
12
|
+
const out = [];
|
|
13
|
+
const valueFlags = new Set(['--dir']);
|
|
14
|
+
for (let i = 0; i < args.length; i++) {
|
|
15
|
+
const arg = String(args[i]);
|
|
16
|
+
if (valueFlags.has(arg)) {
|
|
17
|
+
i++;
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
if (!arg.startsWith('--')) out.push(arg);
|
|
21
|
+
}
|
|
22
|
+
return out;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function openClawCommand(args = []) {
|
|
26
|
+
const action = args[0] || 'help';
|
|
27
|
+
const targetDir = readFlagValue(args, '--dir', defaultOpenClawSkillDir());
|
|
28
|
+
const resultOptions = {
|
|
29
|
+
targetDir,
|
|
30
|
+
force: flag(args, '--force'),
|
|
31
|
+
dryRun: flag(args, '--dry-run')
|
|
32
|
+
};
|
|
33
|
+
if (action === 'path') {
|
|
34
|
+
const result = { skill: OPENCLAW_SKILL_NAME, target_dir: path.resolve(targetDir) };
|
|
35
|
+
if (flag(args, '--json')) return console.log(JSON.stringify(result, null, 2));
|
|
36
|
+
console.log(result.target_dir);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
if (action === 'print') {
|
|
40
|
+
const file = positionalArgs(args.slice(1))[0] || 'SKILL.md';
|
|
41
|
+
const files = buildOpenClawSkillFiles();
|
|
42
|
+
if (!files[file]) {
|
|
43
|
+
console.error(`Unknown OpenClaw skill file: ${file}`);
|
|
44
|
+
console.error(`Files: ${Object.keys(files).join(', ')}`);
|
|
45
|
+
process.exitCode = 1;
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
console.log(files[file]);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
if (action === 'install') {
|
|
52
|
+
const result = await installOpenClawSkill(resultOptions);
|
|
53
|
+
if (flag(args, '--json')) return console.log(JSON.stringify(result, null, 2));
|
|
54
|
+
if (!result.ok) {
|
|
55
|
+
console.error(`OpenClaw skill install blocked: ${result.reason}`);
|
|
56
|
+
console.error(`Target: ${result.target_dir}`);
|
|
57
|
+
process.exitCode = 1;
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
console.log(`OpenClaw skill ${result.status}: ${result.target_dir}`);
|
|
61
|
+
console.log(`Attach it to an agent with skills: [${OPENCLAW_SKILL_NAME}] and tools: [shell].`);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
console.log(`OpenClaw
|
|
65
|
+
|
|
66
|
+
Usage:
|
|
67
|
+
sks openclaw install [--dir path] [--force] [--dry-run] [--json]
|
|
68
|
+
sks openclaw path [--dir path] [--json]
|
|
69
|
+
sks openclaw print [SKILL.md|manifest.yaml|README.md|openclaw-agent-config.example.yaml]
|
|
70
|
+
|
|
71
|
+
Default skill: ${OPENCLAW_SKILL_NAME}
|
|
72
|
+
Default path: ${defaultOpenClawSkillDir()}
|
|
73
|
+
|
|
74
|
+
After install, add this to an OpenClaw agent config:
|
|
75
|
+
|
|
76
|
+
agents:
|
|
77
|
+
coding-agent:
|
|
78
|
+
tools:
|
|
79
|
+
- shell
|
|
80
|
+
skills:
|
|
81
|
+
- ${OPENCLAW_SKILL_NAME}
|
|
82
|
+
`);
|
|
83
|
+
}
|
package/src/core/fsx.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import os from 'node:os';
|
|
|
5
5
|
import crypto from 'node:crypto';
|
|
6
6
|
import { spawn } from 'node:child_process';
|
|
7
7
|
|
|
8
|
-
export const PACKAGE_VERSION = '0.7.
|
|
8
|
+
export const PACKAGE_VERSION = '0.7.33';
|
|
9
9
|
export const DEFAULT_PROCESS_TAIL_BYTES = 256 * 1024;
|
|
10
10
|
export const DEFAULT_PROCESS_TIMEOUT_MS = 30 * 60 * 1000;
|
|
11
11
|
|
package/src/core/init.mjs
CHANGED
|
@@ -569,8 +569,8 @@ function codexAppQuickReference(scope, commandPrefix) {
|
|
|
569
569
|
`Runtime root: ${commandPrefix} root shows whether SKS is using the nearest project root or the per-user global SKS runtime root; outside any project marker, runtime commands use the global root instead of writing .sneakoscope into the current random directory.`,
|
|
570
570
|
`Context Tracking: TriWiki SSOT. Before each route phase read only the latest coordinate+voxel overlay pack at .sneakoscope/wiki/context-pack.json; coordinate-only legacy packs are invalid. Use attention.use_first for compact high-trust recall and hydrate attention.hydrate_first from source before risky/lower-trust decisions. During every stage hydrate low-trust claims from source/hash/RGBA anchors; after changes run ${commandPrefix} wiki refresh or pack; before handoff/final run ${commandPrefix} wiki validate .sneakoscope/wiki/context-pack.json.`,
|
|
571
571
|
stackCurrentDocsPolicyText(commandPrefix),
|
|
572
|
-
`Team tmux view: ${commandPrefix} team "task" prepares live watch/lane commands without opening tmux by default; add --open-tmux when you explicitly want a named tmux session with an overview watch pane plus color-coded split per-agent lanes; ${commandPrefix} team lane latest --agent analysis_scout_1 --follow shows one agent's status, assigned runtime tasks, recent agent events, direct messages, and fallback global tail; ${commandPrefix} team message latest --from analysis_scout_1 --to executor_1 --message "handoff note" mirrors bounded agent communication into transcript/lane panes; ${commandPrefix} team cleanup-tmux latest marks the SKS session record complete and asks follow panes to show a cleanup summary then stop.`,
|
|
573
|
-
`Runtime: open Codex App once, then run ${commandPrefix} bootstrap
|
|
572
|
+
`Team tmux view: ${commandPrefix} team "task" prepares live watch/lane commands without opening a Team tmux view by default; add --open-tmux when you explicitly want a named Team tmux session with an overview watch pane plus color-coded split per-agent lanes; ${commandPrefix} team lane latest --agent analysis_scout_1 --follow shows one agent's status, assigned runtime tasks, recent agent events, direct messages, and fallback global tail; ${commandPrefix} team message latest --from analysis_scout_1 --to executor_1 --message "handoff note" mirrors bounded agent communication into transcript/lane panes; ${commandPrefix} team cleanup-tmux latest marks the SKS session record complete and asks follow panes to show a cleanup summary then stop.`,
|
|
573
|
+
`Runtime: open Codex App once, then run ${commandPrefix} bootstrap and ${commandPrefix} deps check. Bare ${commandPrefix} opens or reuses the default tmux/Codex CLI session; before launch it checks npm @openai/codex@latest and prompts Y/n when the installed Codex CLI is missing or outdated. ${commandPrefix} tmux open is the explicit form for session/workspace flags.`,
|
|
574
574
|
`Guard: generated harness files are immutable outside the engine source repo; check ${commandPrefix} guard check; conflicts use ${commandPrefix} conflicts prompt with human approval.`
|
|
575
575
|
].join('\n') + '\n';
|
|
576
576
|
}
|
|
@@ -680,8 +680,11 @@ async function removeStaleGeneratedSkillsFromManifest(root, skillNames) {
|
|
|
680
680
|
function enrichSkillContent(name, content) {
|
|
681
681
|
if (!['sks', 'answer', 'wiki', 'team', 'qa-loop', 'ppt', 'computer-use', 'computer-use-fast', 'cu', 'goal', 'research', 'autoresearch', 'db', 'gx', 'reflection', 'prompt-pipeline', 'pipeline-runner', 'context7-docs', 'turbo-context-pack', 'hproof-evidence-bind'].includes(name)) return content;
|
|
682
682
|
const text = String(content || '').trimEnd();
|
|
683
|
-
|
|
684
|
-
|
|
683
|
+
const activation = pipelineActivationText(name);
|
|
684
|
+
if (text.includes('TriWiki context-tracking SSOT')) {
|
|
685
|
+
return activation && !text.includes('Codex App pipeline activation:') ? `${text}\n\n${activation}` : text;
|
|
686
|
+
}
|
|
687
|
+
return `${text}${activation ? `\n\n${activation}` : ''}
|
|
685
688
|
|
|
686
689
|
Context tracking:
|
|
687
690
|
- Ask only ambiguity that can change scope, safety, behavior, or acceptance; infer the rest from TriWiki/current code and seal answers before execution.
|
|
@@ -692,6 +695,16 @@ Context tracking:
|
|
|
692
695
|
`;
|
|
693
696
|
}
|
|
694
697
|
|
|
698
|
+
function pipelineActivationText(name) {
|
|
699
|
+
const stateful = new Set(['sks', 'team', 'qa-loop', 'ppt', 'computer-use', 'computer-use-fast', 'cu', 'goal', 'research', 'autoresearch', 'db', 'gx', 'prompt-pipeline', 'pipeline-runner']);
|
|
700
|
+
if (!stateful.has(name)) return '';
|
|
701
|
+
return `Codex App pipeline activation:
|
|
702
|
+
- If the SKS UserPromptSubmit hook already injected route context, follow that context.
|
|
703
|
+
- If no SKS hook context is visible in the current turn, immediately run \`sks hook user-prompt-submit\` from the target repo root with JSON input containing the current prompt, for example: \`printf '%s' '{"prompt":"<user prompt>","cwd":"<repo root>"}' | sks hook user-prompt-submit\`.
|
|
704
|
+
- Use the returned \`hookSpecificOutput.additionalContext\` as authoritative route context before answering or editing. If it creates a mission, continue through \`sks pipeline status\`, \`sks pipeline plan\`, and \`sks pipeline answer\` as directed.
|
|
705
|
+
- Do not treat this skill text alone as completion of the SKS route; a stateful SKS route must materialize mission/pipeline artifacts or explicitly report why the hook could not run.`;
|
|
706
|
+
}
|
|
707
|
+
|
|
695
708
|
async function writeSkillMetadata(dir, name) {
|
|
696
709
|
const effort = ['computer-use', 'computer-use-fast', 'cu'].includes(name)
|
|
697
710
|
? 'low'
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import { exists, readText, writeTextAtomic, PACKAGE_VERSION } from './fsx.mjs';
|
|
4
|
+
|
|
5
|
+
export const OPENCLAW_SKILL_NAME = 'sneakoscope-codex';
|
|
6
|
+
|
|
7
|
+
export function defaultOpenClawHome(env = process.env) {
|
|
8
|
+
return path.resolve(env.OPENCLAW_HOME || path.join(env.HOME || os.homedir(), '.openclaw'));
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function defaultOpenClawSkillDir(env = process.env) {
|
|
12
|
+
return path.join(defaultOpenClawHome(env), 'skills', OPENCLAW_SKILL_NAME);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function buildOpenClawSkillFiles(options = {}) {
|
|
16
|
+
const sksCommand = options.sksCommand || 'sks';
|
|
17
|
+
const version = options.version || PACKAGE_VERSION;
|
|
18
|
+
const skillName = options.skillName || OPENCLAW_SKILL_NAME;
|
|
19
|
+
return {
|
|
20
|
+
'manifest.yaml': openClawManifest({ skillName, version }),
|
|
21
|
+
'SKILL.md': openClawSkillMarkdown({ sksCommand, skillName, version }),
|
|
22
|
+
'README.md': openClawSkillReadme({ sksCommand, skillName, version }),
|
|
23
|
+
'openclaw-agent-config.example.yaml': openClawAgentConfigExample({ skillName })
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function installOpenClawSkill(options = {}) {
|
|
28
|
+
const targetDir = path.resolve(options.targetDir || defaultOpenClawSkillDir(options.env || process.env));
|
|
29
|
+
const files = buildOpenClawSkillFiles(options);
|
|
30
|
+
const existingManifest = path.join(targetDir, 'manifest.yaml');
|
|
31
|
+
const existing = await exists(existingManifest);
|
|
32
|
+
if (existing && !options.force) {
|
|
33
|
+
const text = await readText(existingManifest, '');
|
|
34
|
+
if (!text.includes('generated_by: sneakoscope')) {
|
|
35
|
+
return {
|
|
36
|
+
ok: false,
|
|
37
|
+
status: 'blocked_existing_skill',
|
|
38
|
+
target_dir: targetDir,
|
|
39
|
+
reason: 'Existing OpenClaw skill is not marked as generated by Sneakoscope. Re-run with --force to overwrite.'
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
if (options.dryRun) {
|
|
44
|
+
return { ok: true, status: existing ? 'would_update' : 'would_create', target_dir: targetDir, files: Object.keys(files) };
|
|
45
|
+
}
|
|
46
|
+
for (const [name, content] of Object.entries(files)) {
|
|
47
|
+
await writeTextAtomic(path.join(targetDir, name), content);
|
|
48
|
+
}
|
|
49
|
+
return { ok: true, status: existing ? 'updated' : 'created', target_dir: targetDir, files: Object.keys(files) };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function openClawManifest({ skillName, version }) {
|
|
53
|
+
return `name: ${skillName}
|
|
54
|
+
version: ${version}
|
|
55
|
+
description: Expose Sneakoscope Codex (sks) workflows to OpenClaw agents.
|
|
56
|
+
author: mandarange
|
|
57
|
+
license: MIT
|
|
58
|
+
generated_by: sneakoscope
|
|
59
|
+
homepage: https://github.com/mandarange/Sneakoscope-Codex
|
|
60
|
+
entrypoint: SKILL.md
|
|
61
|
+
permissions:
|
|
62
|
+
shell: execute
|
|
63
|
+
filesystem: read_write_project
|
|
64
|
+
tags:
|
|
65
|
+
- codex
|
|
66
|
+
- coding-agent
|
|
67
|
+
- orchestration
|
|
68
|
+
- qa
|
|
69
|
+
- safety
|
|
70
|
+
`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function openClawSkillMarkdown({ sksCommand, skillName, version }) {
|
|
74
|
+
return `---
|
|
75
|
+
name: ${skillName}
|
|
76
|
+
version: ${version}
|
|
77
|
+
description: Use Sneakoscope Codex routes, checks, and release gates from an OpenClaw agent.
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
# Sneakoscope Codex For OpenClaw
|
|
81
|
+
|
|
82
|
+
Use this skill when the user asks an OpenClaw agent to work in a codebase that uses Sneakoscope Codex, SKS, Team, QA-LOOP, TriWiki, Context7, DB safety, Honest Mode, release gates, or \`$...\` prompt routes.
|
|
83
|
+
|
|
84
|
+
## Required Tool Access
|
|
85
|
+
|
|
86
|
+
The OpenClaw agent must have the built-in \`shell\` tool enabled. Prefer running commands in the target repository root.
|
|
87
|
+
|
|
88
|
+
Set \`SKS_OPENCLAW=1\` for SKS shell commands. In OpenClaw mode, SKS treats update/install prompts as automatically approved so \`sks\` can update \`sneakoscope\` or \`@openai/codex\` before launching tmux without pausing for a human \`Y/n\` answer.
|
|
89
|
+
|
|
90
|
+
## Core Commands
|
|
91
|
+
|
|
92
|
+
- \`${sksCommand} root\` checks whether SKS is using a project root or the global runtime root.
|
|
93
|
+
- \`${sksCommand} bootstrap\` installs or repairs SKS project files, Codex App skills, hooks, and runtime state.
|
|
94
|
+
- \`${sksCommand} commands\` lists terminal commands the agent can call.
|
|
95
|
+
- \`${sksCommand} dollar-commands\` lists prompt routes such as \`$Team\`, \`$DFix\`, \`$QA-LOOP\`, \`$PPT\`, \`$Goal\`, \`$DB\`, \`$Wiki\`, and \`$Help\`.
|
|
96
|
+
- \`${sksCommand} deps check\` checks Codex, Context7, tmux, and related local readiness.
|
|
97
|
+
- \`${sksCommand} proof-field scan --intent "<task>" --changed file1,file2\` checks whether a narrow change can stay on a lightweight proof path.
|
|
98
|
+
- \`${sksCommand} release:check\` is not a terminal command; use \`npm run release:check\` inside the Sneakoscope package repository.
|
|
99
|
+
|
|
100
|
+
## Agent Operating Rules
|
|
101
|
+
|
|
102
|
+
1. Before substantive work, run \`SKS_OPENCLAW=1 ${sksCommand} root\` and inspect the repository's \`AGENTS.md\` if present.
|
|
103
|
+
2. For implementation, prefer the repository's requested SKS route. General code work normally routes to \`$Team\`; tiny design or copy edits can use \`$DFix\`; UI/browser dogfood uses \`$QA-LOOP\`; database or Supabase work uses \`$DB\`.
|
|
104
|
+
3. Do not invent fallback implementation code when the requested SKS path is blocked. Report the blocker with command output and source paths.
|
|
105
|
+
4. For database, migration, and Supabase tasks, default to read-only inspection unless the user explicitly authorizes a write/migration scope.
|
|
106
|
+
5. Before claiming completion, run the most relevant verification command and summarize what passed, what was not verified, and any remaining blocker.
|
|
107
|
+
|
|
108
|
+
## Example Prompts For An OpenClaw Agent
|
|
109
|
+
|
|
110
|
+
- "In this repo, run \`${sksCommand} root\`, inspect \`AGENTS.md\`, then use the SKS Team route to implement the bug fix and verify it."
|
|
111
|
+
- "Use SKS QA-LOOP against localhost:3000. Do not run destructive tests."
|
|
112
|
+
- "Prepare this project for Codex App use: run \`${sksCommand} bootstrap\`, \`${sksCommand} codex-app check\`, and \`${sksCommand} dollar-commands\`."
|
|
113
|
+
`;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function openClawSkillReadme({ sksCommand, skillName, version }) {
|
|
117
|
+
return `# ${skillName}
|
|
118
|
+
|
|
119
|
+
Version: ${version}
|
|
120
|
+
|
|
121
|
+
This OpenClaw skill lets an OpenClaw agent discover and use Sneakoscope Codex through the local \`${sksCommand}\` command.
|
|
122
|
+
|
|
123
|
+
OpenClaw agents should set \`SKS_OPENCLAW=1\` when running SKS commands. That mode auto-approves SKS dependency/update prompts, including the Codex CLI update preflight before tmux launch.
|
|
124
|
+
|
|
125
|
+
## Install
|
|
126
|
+
|
|
127
|
+
\`\`\`sh
|
|
128
|
+
${sksCommand} openclaw install
|
|
129
|
+
\`\`\`
|
|
130
|
+
|
|
131
|
+
Then attach the skill to an OpenClaw agent:
|
|
132
|
+
|
|
133
|
+
\`\`\`yaml
|
|
134
|
+
agents:
|
|
135
|
+
coding-agent:
|
|
136
|
+
tools:
|
|
137
|
+
- shell
|
|
138
|
+
skills:
|
|
139
|
+
- ${skillName}
|
|
140
|
+
\`\`\`
|
|
141
|
+
|
|
142
|
+
## Verify
|
|
143
|
+
|
|
144
|
+
\`\`\`sh
|
|
145
|
+
openclaw skill run ${skillName} --help
|
|
146
|
+
${sksCommand} root
|
|
147
|
+
${sksCommand} commands
|
|
148
|
+
\`\`\`
|
|
149
|
+
|
|
150
|
+
If OpenClaw runs skills in a sandbox, allow shell execution for this skill and run it from the target repository root so SKS can find \`.git\`, \`.sneakoscope\`, or \`.dcodex\`.
|
|
151
|
+
`;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function openClawAgentConfigExample({ skillName }) {
|
|
155
|
+
return `agents:
|
|
156
|
+
coding-agent:
|
|
157
|
+
model: openai/gpt-4o
|
|
158
|
+
channels:
|
|
159
|
+
- terminal
|
|
160
|
+
tools:
|
|
161
|
+
- shell
|
|
162
|
+
env:
|
|
163
|
+
SKS_OPENCLAW: "1"
|
|
164
|
+
skills:
|
|
165
|
+
- ${skillName}
|
|
166
|
+
`;
|
|
167
|
+
}
|
package/src/core/routes.mjs
CHANGED
|
@@ -7,7 +7,7 @@ export const FROM_CHAT_IMG_CHECKLIST_ARTIFACT = 'from-chat-img-checklist.md';
|
|
|
7
7
|
export const FROM_CHAT_IMG_TEMP_TRIWIKI_ARTIFACT = 'from-chat-img-temp-triwiki.json';
|
|
8
8
|
export const FROM_CHAT_IMG_QA_LOOP_ARTIFACT = 'from-chat-img-qa-loop.json';
|
|
9
9
|
export const FROM_CHAT_IMG_TEMP_TRIWIKI_SESSIONS = 5;
|
|
10
|
-
export const USAGE_TOPICS = 'install|setup|bootstrap|root|deps|tmux|auto-review|team|qa-loop|ppt|goal|research|db|codex-app|dfix|design|imagegen|dollar|context7|pipeline|reasoning|guard|conflicts|versioning|eval|harness|hproof|gx|wiki|code-structure|proof-field|skill-dream';
|
|
10
|
+
export const USAGE_TOPICS = 'install|setup|bootstrap|root|deps|tmux|auto-review|team|qa-loop|ppt|goal|research|db|codex-app|openclaw|dfix|design|imagegen|dollar|context7|pipeline|reasoning|guard|conflicts|versioning|eval|harness|hproof|gx|wiki|code-structure|proof-field|skill-dream';
|
|
11
11
|
export const CODEX_COMPUTER_USE_EVIDENCE_SOURCE = 'codex_computer_use';
|
|
12
12
|
export const CODEX_APP_IMAGE_GENERATION_DOC_URL = 'https://developers.openai.com/codex/app/features#image-generation';
|
|
13
13
|
export const CODEX_COMPUTER_USE_ONLY_POLICY = 'Pipeline UI/browser verification and visual inspection must use Codex Computer Use only. Do not use Playwright, Chrome MCP, Browser Use, Selenium, Puppeteer, or any other browser automation substitute; if Codex Computer Use is unavailable, mark the UI/browser evidence unverified instead of substituting another tool.';
|
|
@@ -456,7 +456,8 @@ export const COMMAND_CATALOG = [
|
|
|
456
456
|
{ name: 'root', usage: 'sks root [--json]', description: 'Show whether SKS is using a project root or the per-user global SKS runtime root.' },
|
|
457
457
|
{ name: 'deps', usage: 'sks deps check|install [tmux|codex|context7|all] [--yes]', description: 'Check or guided-install Node/npm PATH, Codex CLI/App, Context7, Browser Use, Computer Use, tmux, and Homebrew on macOS.' },
|
|
458
458
|
{ name: 'codex-app', usage: 'sks codex-app [check|open]', description: 'Check Codex App install and first-party MCP/plugin readiness, then show app setup files and examples.' },
|
|
459
|
-
{ name: '
|
|
459
|
+
{ name: 'openclaw', usage: 'sks openclaw install|path|print [--dir path] [--force] [--json]', description: 'Generate an OpenClaw skill package so OpenClaw agents can discover and use local SKS workflows.' },
|
|
460
|
+
{ name: 'tmux', usage: 'sks | sks tmux open|check|status [--workspace name]', description: 'Open the default SKS tmux runtime with bare sks, or use tmux subcommands for explicit launch/check/status.' },
|
|
460
461
|
{ name: 'mad', usage: 'sks --mad [--high]', description: 'Open a one-shot tmux Codex CLI workspace with the SKS MAD full-access auto-review profile.' },
|
|
461
462
|
{ name: 'auto-review', usage: 'sks auto-review status|enable|start [--high] | sks --Auto-review --high', description: 'Enable Codex automatic approval review and launch SKS tmux with the auto-review profile.' },
|
|
462
463
|
{ name: 'dollar-commands', usage: 'sks dollar-commands [--json]', description: 'List Codex App $ commands such as $DFix and $Team.' },
|
package/src/core/tmux-ui.mjs
CHANGED
|
@@ -207,7 +207,8 @@ export function formatTmuxBanner(status = null) {
|
|
|
207
207
|
' $DFix $Answer $SKS $Team $QA-LOOP $PPT $Goal $Research $AutoResearch $DB $GX $Wiki $Help',
|
|
208
208
|
'',
|
|
209
209
|
'CLI-first runtime:',
|
|
210
|
-
' sks
|
|
210
|
+
' sks open or attach the default tmux Codex CLI session',
|
|
211
|
+
' sks tmux open open or attach a tmux Codex CLI session with explicit flags',
|
|
211
212
|
' sks --mad open one-shot MAD full-access auto-review tmux session',
|
|
212
213
|
' sks team "task" prepare Team mission and tmux multi-pane live view',
|
|
213
214
|
'',
|