sneakoscope 0.7.42 → 0.7.43

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 CHANGED
@@ -42,7 +42,7 @@ sks selftest --mock
42
42
 
43
43
  | Area | What it does |
44
44
  | --- | --- |
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. |
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 explicit full-access high-reasoning profile. |
46
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
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`. |
@@ -204,7 +204,7 @@ sks --mad
204
204
  sks --mad --yes
205
205
  ```
206
206
 
207
- This creates/uses the `sks-mad-high` Codex profile for a one-shot full-access, high-reasoning tmux session with `approval_policy = "on-request"` and `approvals_reviewer = "auto_review"`, then attaches to the session in an interactive terminal. It is scoped to that explicit command and does not change normal SKS/DB safety defaults. Repeat launches reuse the same named SKS MAD tmux session.
207
+ This creates/uses the `sks-mad-high` Codex profile for a one-shot full-access, high-reasoning tmux session with `sandbox_mode = "danger-full-access"` and `approval_policy = "never"`, then launches Codex with `--sandbox danger-full-access --ask-for-approval never` and attaches to the session in an interactive terminal. It is scoped to that explicit command and does not change normal SKS/DB safety defaults. Repeat launches reuse the same named SKS MAD tmux session.
208
208
 
209
209
  MAD does not disable the pipeline contract: stages, executors, reviewers, and auto-review policy still must not invent unrequested fallback implementation code. If the requested path cannot be implemented, SKS should block with evidence rather than add substitute behavior.
210
210
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "sneakoscope",
3
3
  "displayName": "ㅅㅋㅅ",
4
- "version": "0.7.42",
4
+ "version": "0.7.43",
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",
package/src/cli/main.mjs CHANGED
@@ -1005,11 +1005,11 @@ async function madHighCommand(args = []) {
1005
1005
  return;
1006
1006
  }
1007
1007
  const profile = await enableMadHighProfile();
1008
- console.log(`SKS MAD auto-review profile ready: ${madHighProfileName()}`);
1009
- console.log('Scope: explicit tmux launch only; full access uses Codex auto_review approvals when approval prompts are raised.');
1008
+ console.log(`SKS MAD full-access profile ready: ${madHighProfileName()}`);
1009
+ console.log('Scope: explicit tmux launch only; Codex opens with danger-full-access sandbox and approval_policy=never.');
1010
1010
  const workspace = readOption(cleanArgs, '--workspace', readOption(cleanArgs, '--session', `sks-mad-${defaultTmuxSessionName(process.cwd())}`));
1011
1011
  return launchTmuxUi([...cleanArgs, '--workspace', workspace], {
1012
- codexArgs: ['--profile', profile.profile_name],
1012
+ codexArgs: profile.launch_args,
1013
1013
  autoInstallTmux: !flag(args, '--no-auto-install-tmux'),
1014
1014
  conciseBlockers: true
1015
1015
  });
@@ -1927,6 +1927,7 @@ async function selftest() {
1927
1927
  if (postinstallConflictPrompt.code !== 0 || !String(postinstallConflictPrompt.stdout || '').includes('Goal: completely remove the conflicting Codex harnesses')) throw new Error('selftest failed: interactive postinstall prompt did not print cleanup prompt');
1928
1928
  const postinstallSetupTmp = tmpdir();
1929
1929
  await writeJsonAtomic(path.join(postinstallSetupTmp, 'package.json'), { name: 'postinstall-setup-smoke', version: '0.0.0' });
1930
+ await ensureDir(path.join(postinstallSetupTmp, '.git'));
1930
1931
  const postinstallSetup = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'postinstall'], { cwd: postinstallSetupTmp, env: { INIT_CWD: postinstallSetupTmp, HOME: path.join(postinstallSetupTmp, 'home'), 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 });
1931
1932
  if (postinstallSetup.code !== 0) throw new Error(`selftest failed: postinstall setup exited ${postinstallSetup.code}: ${postinstallSetup.stderr}`);
1932
1933
  if (await exists(path.join(postinstallSetupTmp, '.agents', 'skills', 'agent-team', 'SKILL.md'))) throw new Error('selftest failed: postinstall installed deprecated agent-team fallback skill');
@@ -1964,6 +1965,7 @@ async function selftest() {
1964
1965
  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');
1965
1966
  if (!(await exists(path.join(postinstallNoMarkerGlobalRoot, '.sneakoscope', 'manifest.json')))) throw new Error('selftest failed: no-marker postinstall did not bootstrap global runtime root');
1966
1967
  if (await exists(path.join(postinstallNoMarkerCwd, '.sneakoscope'))) throw new Error('selftest failed: no-marker postinstall polluted install cwd');
1968
+ if (await exists(path.join(postinstallNoMarkerGlobalRoot, '.gitignore'))) throw new Error('selftest failed: global runtime bootstrap without project git wrote shared .gitignore');
1967
1969
  const bootstrapJsonTmp = tmpdir();
1968
1970
  await writeJsonAtomic(path.join(bootstrapJsonTmp, 'package.json'), { name: 'bootstrap-json-smoke', version: '0.0.0' });
1969
1971
  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 });
@@ -1987,9 +1989,9 @@ async function selftest() {
1987
1989
  const madProfilePath = path.join(tmp, 'mad-codex-config.toml');
1988
1990
  const madProfile = await enableMadHighProfile({ configPath: madProfilePath });
1989
1991
  const madProfileText = await safeReadText(madProfilePath);
1990
- if (madProfile.profile_name !== 'sks-mad-high' || !madProfileText.includes('sandbox_mode = "danger-full-access"') || !madProfileText.includes('approval_policy = "on-request"') || !madProfileText.includes('approvals_reviewer = "auto_review"') || !madProfileText.includes('model_reasoning_effort = "high"') || !madProfileText.includes('unrequested fallback implementation code')) throw new Error('selftest failed: MAD high profile is not full-access auto-review high with fallback-code guard');
1992
+ if (madProfile.profile_name !== 'sks-mad-high' || !madProfileText.includes('sandbox_mode = "danger-full-access"') || !madProfileText.includes('approval_policy = "never"') || !madProfileText.includes('approvals_reviewer = "auto_review"') || !madProfile.launch_args.includes('--sandbox') || !madProfile.launch_args.includes('danger-full-access') || !madProfile.launch_args.includes('--ask-for-approval') || !madProfile.launch_args.includes('never') || !madProfileText.includes('model_reasoning_effort = "high"') || !madProfileText.includes('unrequested fallback implementation code')) throw new Error('selftest failed: MAD high profile is not Codex full-access high with fallback-code guard');
1991
1993
  if (!isMadHighLaunch(['--mad', '--high']) || isMadHighLaunch(['db', '--mad'])) throw new Error('selftest failed: MAD high launch flag parsing is not top-level only');
1992
- const workspacePlan = { session: 'sks-mad-selftest', root: tmp, codexArgs: ['--profile', 'sks-mad-high'] };
1994
+ const workspacePlan = { session: 'sks-mad-selftest', root: tmp, codexArgs: madProfile.launch_args };
1993
1995
  const tmuxSyntax = runTmuxLaunchPlanSyntaxCheck(workspacePlan);
1994
1996
  if (!tmuxSyntax.ok || !tmuxSyntax.command.includes('tmux attach-session -t sks-mad-selftest')) throw new Error('selftest failed: MAD tmux attach plan is not stable by session name');
1995
1997
  const tmuxOpenArgs = buildTmuxOpenArgs(workspacePlan);
@@ -2626,6 +2628,7 @@ async function selftest() {
2626
2628
  if (!codexConfigText.includes('multi_agent = true')) throw new Error('selftest failed: multi_agent not enabled');
2627
2629
  if (!hasContext7ConfigText(codexConfigText)) throw new Error('selftest failed: Context7 MCP not configured');
2628
2630
  if (!codexConfigText.includes('[profiles.sks-task-low]') || !codexConfigText.includes('[profiles.sks-task-medium]') || !codexConfigText.includes('[profiles.sks-logic-high]') || !codexConfigText.includes('[profiles.sks-fast-high]') || !codexConfigText.includes('[profiles.sks-research-xhigh]') || !codexConfigText.includes('[profiles.sks-mad-high]')) throw new Error('selftest failed: GPT-5.5 reasoning profiles not configured');
2631
+ if (!/\[profiles\.sks-mad-high\][\s\S]*?approval_policy = "never"[\s\S]*?sandbox_mode = "danger-full-access"/.test(codexConfigText)) throw new Error('selftest failed: generated sks-mad-high profile is not full access');
2629
2632
  if (!codexConfigText.includes('[agents.analysis_scout]')) throw new Error('selftest failed: analysis_scout agent not configured');
2630
2633
  if (!codexConfigText.includes('[agents.team_consensus]')) throw new Error('selftest failed: team_consensus agent not configured');
2631
2634
  const preservedConfigTmp = tmpdir();
@@ -2633,7 +2636,7 @@ async function selftest() {
2633
2636
  await writeTextAtomic(path.join(preservedConfigTmp, '.codex', 'config.toml'), '[features]\nfast_mode_ui = true\n\n[user.fast_mode]\nvisible = true\n');
2634
2637
  await initProject(preservedConfigTmp, {});
2635
2638
  const preservedConfig = await safeReadText(path.join(preservedConfigTmp, '.codex', 'config.toml'));
2636
- if (!preservedConfig.includes('fast_mode_ui = true') || !preservedConfig.includes('[user.fast_mode]') || !preservedConfig.includes('visible = true')) throw new Error('selftest failed: Codex config merge dropped user Fast mode settings');
2639
+ if (!preservedConfig.includes('fast_mode_ui = true') || !preservedConfig.includes('[user.fast_mode]') || !preservedConfig.includes('visible = true') || !preservedConfig.includes('enabled = true') || !preservedConfig.includes('default_profile = "sks-fast-high"')) throw new Error('selftest failed: Codex config merge dropped or failed to enable Fast mode settings');
2637
2640
  if (!preservedConfig.includes('codex_hooks = true') || !preservedConfig.includes('[profiles.sks-fast-high]')) throw new Error('selftest failed: Codex config merge did not add SKS managed settings');
2638
2641
  const autoReviewHome = path.join(tmp, 'auto-review-home');
2639
2642
  const autoReviewEnv = { HOME: autoReviewHome };
@@ -3034,6 +3037,7 @@ async function selftest() {
3034
3037
  if (!pptHtml.includes('<html') || pptHtml.includes('gradient')) throw new Error('selftest failed: PPT HTML artifact missing or over-designed');
3035
3038
  const pptStyleTokens = await readJson(path.join(pptMission.dir, 'ppt-style-tokens.json'));
3036
3039
  if (pptStyleTokens.design_policy?.design_ssot?.authority !== DESIGN_SYSTEM_SSOT.authority_file || !pptStyleTokens.design_policy?.source_inputs?.some((entry) => entry.url === AWESOME_DESIGN_MD_REFERENCE.url && entry.role === 'source_input_for_ssot') || !pptStyleTokens.design_policy?.anti_generic_ai_style) throw new Error('selftest failed: PPT style tokens missing fused design SSOT/source-input anti-generic policy');
3040
+ if (!pptStyleTokens.design_policy?.design_reference_selection?.primary?.id?.startsWith('awesome-design-md:') || !pptStyleTokens.design_policy?.design_reference_selection?.selected_sources?.length || !pptStyleTokens.layout?.composition || !pptStyleTokens.layout?.treatment) throw new Error('selftest failed: PPT style tokens did not select and apply a concrete awesome-design-md reference profile');
3037
3041
  if (JSON.stringify(pptStyleTokens.design_policy?.pipeline_allowlist?.required_skills || []) !== JSON.stringify(PPT_PIPELINE_SKILL_ALLOWLIST) || !pptStyleTokens.design_policy?.pipeline_allowlist?.ignore_installed_out_of_pipeline_skills || !(pptStyleTokens.design_policy?.pipeline_allowlist?.ignored_design_skills_even_if_installed || []).includes('design-artifact-expert') || !/AI-like/.test(pptStyleTokens.design_policy?.pipeline_allowlist?.anti_ai_design_goal || '')) throw new Error('selftest failed: PPT style tokens missing skill/MCP allowlist enforcement');
3038
3042
  const audienceScript = pptHtml.match(/id="ppt-audience-strategy">([^<]+)<\/script>/);
3039
3043
  if (!audienceScript) throw new Error('selftest failed: PPT HTML missing audience strategy script data');
@@ -64,7 +64,7 @@ export async function enableMadHighProfile(opts = {}) {
64
64
  let next = upsertTable(current, `profiles.${MAD_HIGH_PROFILE}`, [
65
65
  `[profiles.${MAD_HIGH_PROFILE}]`,
66
66
  'model = "gpt-5.5"',
67
- 'approval_policy = "on-request"',
67
+ 'approval_policy = "never"',
68
68
  `approvals_reviewer = "${AUTO_REVIEW_REVIEWER}"`,
69
69
  'sandbox_mode = "danger-full-access"',
70
70
  'model_reasoning_effort = "high"'
@@ -75,9 +75,9 @@ export async function enableMadHighProfile(opts = {}) {
75
75
  return {
76
76
  config_path: configPath,
77
77
  profile_name: MAD_HIGH_PROFILE,
78
- launch_args: ['--profile', MAD_HIGH_PROFILE],
78
+ launch_args: ['--profile', MAD_HIGH_PROFILE, '--sandbox', 'danger-full-access', '--ask-for-approval', 'never'],
79
79
  sandbox_mode: 'danger-full-access',
80
- approval_policy: 'on-request',
80
+ approval_policy: 'never',
81
81
  approvals_reviewer: AUTO_REVIEW_REVIEWER,
82
82
  model_reasoning_effort: 'high',
83
83
  scope: 'explicit_launch_only'
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.42';
8
+ export const PACKAGE_VERSION = '0.7.43';
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
@@ -113,8 +113,9 @@ export async function initProject(root, opts = {}) {
113
113
  '.sneakoscope/state', '.sneakoscope/missions', '.sneakoscope/db', '.sneakoscope/bus', '.sneakoscope/hproof', '.sneakoscope/db', '.sneakoscope/wiki', '.sneakoscope/skills', '.sneakoscope/memory/q0_raw', '.sneakoscope/memory/q1_evidence', '.sneakoscope/memory/q2_facts', '.sneakoscope/memory/q3_tags', '.sneakoscope/memory/q4_bits', '.sneakoscope/gx/cartridges', '.sneakoscope/model/fingerprints', '.sneakoscope/genome/candidates', '.sneakoscope/trajectories/raw', '.sneakoscope/locks', '.sneakoscope/tmp', '.sneakoscope/arenas', '.sneakoscope/reports', '.codex', '.codex/agents', '.agents/skills'
114
114
  ];
115
115
  for (const d of dirs) await ensureDir(path.join(root, d));
116
+ const sharedIgnoreWanted = !localOnly && await shouldWriteSharedGitIgnore(root, installScope);
116
117
  const localExclude = localOnly ? await ensureLocalOnlyGitExclude(root) : null;
117
- const sharedIgnore = localOnly ? null : await ensureSharedGitIgnore(root);
118
+ const sharedIgnore = sharedIgnoreWanted ? await ensureSharedGitIgnore(root) : null;
118
119
  if (localExclude?.path) created.push(`${path.relative(root, localExclude.path)} local-only excludes`);
119
120
  if (sharedIgnore?.changed) created.push(`${path.relative(root, sharedIgnore.path)} SKS generated files ignore`);
120
121
 
@@ -429,6 +430,10 @@ function mergeManagedCodexConfigToml(existingContent = '') {
429
430
  let next = String(existingContent || '').trimEnd();
430
431
  next = upsertTomlTableKey(next, 'features', 'codex_hooks = true');
431
432
  next = upsertTomlTableKey(next, 'features', 'multi_agent = true');
433
+ next = upsertTomlTableKey(next, 'features', 'fast_mode_ui = true');
434
+ next = upsertTomlTableKey(next, 'user.fast_mode', 'visible = true');
435
+ next = upsertTomlTableKey(next, 'user.fast_mode', 'enabled = true');
436
+ next = upsertTomlTableKey(next, 'user.fast_mode', 'default_profile = "sks-fast-high"');
432
437
  next = upsertTomlTableKey(next, 'agents', 'max_threads = 6');
433
438
  next = upsertTomlTableKey(next, 'agents', 'max_depth = 1');
434
439
  for (const block of managedCodexConfigBlocks()) {
@@ -452,7 +457,7 @@ function managedCodexConfigBlocks() {
452
457
  { table: 'profiles.sks-research-xhigh', text: profileConfigBlock('sks-research-xhigh', 'xhigh') },
453
458
  { table: 'profiles.sks-research', text: profileConfigBlock('sks-research', 'xhigh', { approval: 'never' }) },
454
459
  { table: 'profiles.sks-team', text: profileConfigBlock('sks-team', 'high') },
455
- { table: 'profiles.sks-mad-high', text: profileConfigBlock('sks-mad-high', 'high', { sandbox: 'danger-full-access', approvalsReviewer: 'auto_review' }) },
460
+ { table: 'profiles.sks-mad-high', text: profileConfigBlock('sks-mad-high', 'high', { approval: 'never', sandbox: 'danger-full-access', approvalsReviewer: 'auto_review' }) },
456
461
  {
457
462
  table: 'auto_review',
458
463
  text: '[auto_review]\npolicy = "Deny destructive database operations, credential exfiltration, persistent security weakening, broad file deletion, writes outside the workspace, and unrequested fallback implementation code unless explicitly authorized by the user or sealed decision contract."'
@@ -613,6 +618,13 @@ async function ensureSharedGitIgnore(root) {
613
618
  return { path: ignorePath, patterns, changed: true };
614
619
  }
615
620
 
621
+ async function shouldWriteSharedGitIgnore(root, installScope) {
622
+ if (normalizeInstallScope(installScope) === 'project') return true;
623
+ if (await exists(path.join(root, '.git'))) return true;
624
+ if (await exists(path.join(root, '.gitignore'))) return true;
625
+ return false;
626
+ }
627
+
616
628
  async function ensureLocalOnlyGitExclude(root) {
617
629
  const gitDir = await resolveGitDir(root);
618
630
  if (!gitDir) return { path: null, patterns: [] };
package/src/core/ppt.mjs CHANGED
@@ -16,6 +16,99 @@ export const PPT_CLEANUP_REPORT_ARTIFACT = 'ppt-cleanup-report.json';
16
16
  export const PPT_PARALLEL_REPORT_ARTIFACT = 'ppt-parallel-report.json';
17
17
  export const PPT_TEMP_DIR = 'ppt-tmp';
18
18
 
19
+ const PPT_DESIGN_REFERENCE_PROFILES = Object.freeze([
20
+ {
21
+ id: 'awesome-design-md:ibm',
22
+ name: 'IBM Carbon enterprise',
23
+ source_url: 'https://raw.githubusercontent.com/VoltAgent/awesome-design-md/main/design-md/ibm/DESIGN.md',
24
+ source_summary: 'enterprise Carbon-style system: white surfaces, charcoal text, IBM Blue as the single accent, flat square tiles, thin rules, no shadow',
25
+ keywords: ['enterprise', 'b2b', 'investor', 'vc', 'strategy', 'proposal', 'board', 'finance', 'risk', 'compliance', '운영', '투자', '의사결정', '리스크', '전략'],
26
+ tokens: {
27
+ bg: '#ffffff',
28
+ text: '#161616',
29
+ muted: '#525252',
30
+ primary: '#0f62fe',
31
+ accent: '#393939',
32
+ surface: '#f4f4f4',
33
+ rule: '#e0e0e0',
34
+ display_px: 64,
35
+ body_px: 28,
36
+ caption_px: 15,
37
+ line_height: 1.36,
38
+ radius_px: 2,
39
+ treatment: 'flat_thin_rules_no_shadow',
40
+ composition: 'enterprise_evidence_grid',
41
+ mono_label: 'uppercase technical labels, sparse blue accent, source-visible rows'
42
+ },
43
+ applied_rules: [
44
+ 'use white/charcoal enterprise canvas',
45
+ 'reserve IBM Blue for one decision/action accent',
46
+ 'prefer thin rules and square evidence rows over decorative cards',
47
+ 'avoid shadows, gradients, and ornamental surfaces'
48
+ ]
49
+ },
50
+ {
51
+ id: 'awesome-design-md:vercel',
52
+ name: 'Vercel developer infrastructure',
53
+ source_url: 'https://raw.githubusercontent.com/VoltAgent/awesome-design-md/main/design-md/vercel/DESIGN.md',
54
+ source_summary: 'developer-infrastructure minimalism: white canvas, near-black type, shadow-as-border, mono technical labels, functional blue/red/pink workflow accents',
55
+ keywords: ['developer', 'devtools', 'api', 'sdk', 'cloud', 'infra', 'saas', 'technical', 'codex', 'ai', 'agent', '배포', '개발자', '기술', '자동화'],
56
+ tokens: {
57
+ bg: '#ffffff',
58
+ text: '#171717',
59
+ muted: '#4d4d4d',
60
+ primary: '#0072f5',
61
+ accent: '#ff5b4f',
62
+ surface: '#fafafa',
63
+ rule: '#ebebeb',
64
+ display_px: 66,
65
+ body_px: 28,
66
+ caption_px: 14,
67
+ line_height: 1.34,
68
+ radius_px: 8,
69
+ treatment: 'shadow_as_border_minimal_depth',
70
+ composition: 'technical_pipeline_grid',
71
+ mono_label: 'mono labels, workflow accent only when it clarifies sequence'
72
+ },
73
+ applied_rules: [
74
+ 'use near-black text on a white technical canvas',
75
+ 'show structure through shadow-as-border or one-pixel rules',
76
+ 'use mono labels for sources and technical evidence',
77
+ 'keep color functional rather than decorative'
78
+ ]
79
+ },
80
+ {
81
+ id: 'awesome-design-md:linear',
82
+ name: 'Linear precision operations',
83
+ source_url: 'https://github.com/VoltAgent/awesome-design-md',
84
+ source_summary: 'ultra-minimal precise product-management system: restrained neutral surfaces, exact spacing, one controlled purple accent',
85
+ keywords: ['roadmap', 'product', 'ops', 'workflow', 'issue', 'planning', 'productivity', '운영', '워크플로우', '프로덕트', '계획'],
86
+ tokens: {
87
+ bg: '#f7f8fb',
88
+ text: '#101114',
89
+ muted: '#5f6673',
90
+ primary: '#5e6ad2',
91
+ accent: '#26a69a',
92
+ surface: '#ffffff',
93
+ rule: '#dfe3ea',
94
+ display_px: 62,
95
+ body_px: 27,
96
+ caption_px: 14,
97
+ line_height: 1.38,
98
+ radius_px: 6,
99
+ treatment: 'precise_subtle_product_grid',
100
+ composition: 'operational_decision_matrix',
101
+ mono_label: 'compact status labels, dense but quiet operations layout'
102
+ },
103
+ applied_rules: [
104
+ 'use a quiet operational canvas with dense hierarchy',
105
+ 'keep the purple accent sparse and semantic',
106
+ 'make comparison rows easy to scan',
107
+ 'avoid marketing-style hero composition'
108
+ ]
109
+ }
110
+ ]);
111
+
19
112
  export const PPT_REQUIRED_GATE_FIELDS = Object.freeze([
20
113
  'clarification_contract_sealed',
21
114
  'audience_strategy_sealed',
@@ -282,6 +375,13 @@ export function buildPptStoryboard(contract = {}, audience = buildPptAudienceStr
282
375
 
283
376
  export function buildPptStyleTokens(contract = {}) {
284
377
  const korean = /[ㄱ-ㅎ가-힣]/.test(`${contract.prompt || ''} ${JSON.stringify(contract.answers || {})}`);
378
+ const reference = selectPptDesignReference(contract);
379
+ const refTokens = reference.applied_token_profile.color;
380
+ const fontStack = korean
381
+ ? '"Pretendard Variable", Pretendard, -apple-system, BlinkMacSystemFont, "Apple SD Gothic Neo", "Noto Sans KR", sans-serif'
382
+ : reference.primary.id.endsWith(':ibm')
383
+ ? '"IBM Plex Sans", -apple-system, BlinkMacSystemFont, "SF Pro Display", Inter, Arial, sans-serif'
384
+ : '-apple-system, BlinkMacSystemFont, "SF Pro Display", Inter, "Helvetica Neue", Arial, sans-serif';
285
385
  return {
286
386
  schema_version: 1,
287
387
  created_at: nowIso(),
@@ -294,23 +394,31 @@ export function buildPptStyleTokens(contract = {}) {
294
394
  gutter_px: 24
295
395
  },
296
396
  color: {
297
- bg: '#f7f8fa',
298
- text: '#111318',
299
- muted: '#5b6270',
300
- primary: '#0b5cff',
301
- accent: '#00a88f',
302
- surface: '#ffffff',
303
- rule: '#d7dce5'
397
+ bg: refTokens.bg,
398
+ text: refTokens.text,
399
+ muted: refTokens.muted,
400
+ primary: refTokens.primary,
401
+ accent: refTokens.accent,
402
+ surface: refTokens.surface,
403
+ rule: refTokens.rule
304
404
  },
305
405
  typography: {
306
406
  language: korean ? 'ko' : 'en',
307
- font_stack: korean
308
- ? '"Pretendard Variable", Pretendard, -apple-system, BlinkMacSystemFont, "Apple SD Gothic Neo", "Noto Sans KR", sans-serif'
309
- : '-apple-system, BlinkMacSystemFont, "SF Pro Display", Inter, "Helvetica Neue", Arial, sans-serif',
310
- display_px: 76,
311
- body_px: 30,
312
- caption_px: 16,
313
- line_height: korean ? 1.42 : 1.32
407
+ font_stack: fontStack,
408
+ display_px: refTokens.display_px,
409
+ body_px: refTokens.body_px,
410
+ caption_px: refTokens.caption_px,
411
+ line_height: korean ? Math.max(1.4, refTokens.line_height) : refTokens.line_height,
412
+ letter_spacing: 0
413
+ },
414
+ layout: {
415
+ composition: refTokens.composition,
416
+ treatment: refTokens.treatment,
417
+ radius_px: refTokens.radius_px,
418
+ rule_px: 1,
419
+ source_rail: true,
420
+ evidence_grid: true,
421
+ mono_label: refTokens.mono_label
314
422
  },
315
423
  design_policy: {
316
424
  priority: 'information_first',
@@ -328,8 +436,9 @@ export function buildPptStyleTokens(contract = {}) {
328
436
  authority: DESIGN_SYSTEM_SSOT.authority_file,
329
437
  builder_prompt: DESIGN_SYSTEM_SSOT.builder_prompt,
330
438
  route_local_artifact: PPT_STYLE_TOKENS_ARTIFACT,
331
- rule: 'PPT style tokens are a route-local projection of the design SSOT; source inputs are fused here and are not independent authorities.'
439
+ rule: 'PPT style tokens are a route-local projection of the design SSOT; source inputs are selected, fused, and applied here rather than kept as independent authorities.'
332
440
  },
441
+ design_reference_selection: reference,
333
442
  source_inputs: [
334
443
  {
335
444
  id: GETDESIGN_REFERENCE.id,
@@ -343,31 +452,112 @@ export function buildPptStyleTokens(contract = {}) {
343
452
  }
344
453
  ],
345
454
  avoid: ['over-designed decoration', 'ornamental gradients', 'nested cards', 'low-contrast gray body text', 'excessive motion or effects'],
346
- detail_strategy: ['precise spacing', 'clear hierarchy', 'thin rules', 'disciplined alignment', 'subtle accent color only when it clarifies meaning'],
347
- anti_generic_ai_style: 'prevent AI-like design: select a concrete DESIGN.md or route-local visual system before styling; do not default to generic cards, gradients, vague SaaS visuals, oversized decoration, or unsupported image-like flourishes',
455
+ detail_strategy: ['precise spacing', 'clear hierarchy', 'thin rules', 'disciplined alignment', 'visible source rails', 'subtle accent color only when it clarifies meaning'],
456
+ anti_generic_ai_style: 'prevent AI-like design: select and apply a concrete awesome-design-md reference profile before styling; do not default to generic cards, gradients, vague SaaS visuals, oversized decoration, or unsupported image-like flourishes',
348
457
  image_policy: 'use images only when they improve comprehension; prefer Codex App built-in image generation via https://developers.openai.com/codex/app/features#image-generation when generated assets are needed'
349
458
  }
350
459
  };
351
460
  }
352
461
 
462
+ export function selectPptDesignReference(contract = {}) {
463
+ const text = cleanText(`${contract.prompt || ''} ${JSON.stringify(contract.answers || {})}`).toLowerCase();
464
+ const scored = PPT_DESIGN_REFERENCE_PROFILES.map((profile) => {
465
+ const score = profile.keywords.reduce((sum, keyword) => sum + (text.includes(String(keyword).toLowerCase()) ? 1 : 0), 0);
466
+ return { profile, score };
467
+ }).sort((a, b) => b.score - a.score);
468
+ const primary = scored[0]?.score > 0 ? scored[0].profile : PPT_DESIGN_REFERENCE_PROFILES[0];
469
+ const secondary = scored.find((entry) => entry.profile.id !== primary.id && entry.score > 0)?.profile || PPT_DESIGN_REFERENCE_PROFILES.find((entry) => entry.id !== primary.id);
470
+ return {
471
+ source: AWESOME_DESIGN_MD_REFERENCE.url,
472
+ selection_method: 'keyword_match_against_sealed_ppt_contract',
473
+ primary: {
474
+ id: primary.id,
475
+ name: primary.name,
476
+ source_url: primary.source_url,
477
+ source_summary: primary.source_summary,
478
+ applied_rules: primary.applied_rules
479
+ },
480
+ secondary: secondary ? {
481
+ id: secondary.id,
482
+ name: secondary.name,
483
+ source_url: secondary.source_url,
484
+ source_summary: secondary.source_summary,
485
+ applied_rules: secondary.applied_rules.slice(0, 2)
486
+ } : null,
487
+ selected_sources: [primary, secondary].filter(Boolean).map((profile) => ({
488
+ id: profile.id,
489
+ name: profile.name,
490
+ source_url: profile.source_url,
491
+ role: profile.id === primary.id ? 'primary_style_reference' : 'secondary_guardrail_reference'
492
+ })),
493
+ applied_token_profile: {
494
+ color: primary.tokens,
495
+ composition: primary.tokens.composition,
496
+ treatment: primary.tokens.treatment
497
+ },
498
+ selection_reason: scored[0]?.score > 0
499
+ ? `matched ${scored[0].score} contract keyword(s) to ${primary.name}`
500
+ : `no strong domain match; defaulted to ${primary.name} for restrained business presentation output`
501
+ };
502
+ }
503
+
353
504
  export function buildPptHtml({ contract = {}, audience, sourceLedger, storyboard, styleTokens }) {
354
505
  const title = escapeHtml(storyboard.title);
506
+ const referenceName = escapeHtml(styleTokens.design_policy?.design_reference_selection?.primary?.name || 'selected design reference');
507
+ const audienceRaw = escapeHtml(audience?.audience_profile?.raw || 'Audience context');
508
+ const stpRaw = escapeHtml(audience?.stp?.raw || 'STP context');
509
+ const decisionRaw = escapeHtml(audience?.decision_context?.raw || storyboard.thesis || '');
510
+ const surfaceRule = styleTokens.layout?.treatment === 'shadow_as_border_minimal_depth'
511
+ ? `box-shadow: 0 0 0 1px ${styleTokens.color.rule}; border: 0;`
512
+ : `border: 1px solid ${styleTokens.color.rule}; box-shadow: none;`;
355
513
  const css = `@page { size: 16in 9in; margin: 0; }
356
514
  * { box-sizing: border-box; }
357
515
  body { margin: 0; background: ${styleTokens.color.bg}; color: ${styleTokens.color.text}; font-family: ${styleTokens.typography.font_stack}; }
358
- .page { width: 100vw; min-height: 100vh; page-break-after: always; padding: 72px 96px; display: grid; align-content: center; gap: 26px; }
359
- .kicker { color: ${styleTokens.color.primary}; font-size: 18px; font-weight: 700; letter-spacing: 0; text-transform: uppercase; }
360
- h1 { margin: 0; font-size: 72px; line-height: 1.08; letter-spacing: 0; max-width: 1120px; }
361
- p { margin: 0; color: ${styleTokens.color.muted}; font-size: 28px; line-height: ${styleTokens.typography.line_height}; max-width: 920px; }
362
- .panel { border-left: 6px solid ${styleTokens.color.primary}; padding-left: 26px; }
363
- .source { font-size: 14px; color: ${styleTokens.color.muted}; align-self: end; }`;
516
+ .page { width: 100vw; min-height: 100vh; page-break-after: always; padding: 64px 88px 54px; display: grid; grid-template-rows: auto 1fr auto; gap: 34px; }
517
+ .topline { display: grid; grid-template-columns: 1fr auto; align-items: end; border-bottom: 1px solid ${styleTokens.color.rule}; padding-bottom: 14px; }
518
+ .kicker { color: ${styleTokens.color.primary}; font-size: ${styleTokens.typography.caption_px}px; font-weight: 600; letter-spacing: 0; text-transform: uppercase; }
519
+ .reference { color: ${styleTokens.color.muted}; font-size: ${styleTokens.typography.caption_px}px; letter-spacing: 0; }
520
+ .content { display: grid; grid-template-columns: minmax(0, 6fr) minmax(320px, 4fr); gap: 58px; align-items: center; }
521
+ h1 { margin: 0; font-size: ${styleTokens.typography.display_px}px; line-height: 1.08; letter-spacing: 0; max-width: 1040px; font-weight: 600; }
522
+ p { margin: 0; color: ${styleTokens.color.muted}; font-size: ${styleTokens.typography.body_px}px; line-height: ${styleTokens.typography.line_height}; max-width: 920px; }
523
+ .claim { display: grid; gap: 26px; }
524
+ .evidence { ${surfaceRule} border-radius: ${styleTokens.layout.radius_px}px; background: ${styleTokens.color.surface}; display: grid; }
525
+ .evidence-row { padding: 22px 24px; border-bottom: 1px solid ${styleTokens.color.rule}; }
526
+ .evidence-row:last-child { border-bottom: 0; }
527
+ .label { color: ${styleTokens.color.primary}; font-size: ${styleTokens.typography.caption_px}px; font-weight: 600; letter-spacing: 0; text-transform: uppercase; margin-bottom: 8px; }
528
+ .value { color: ${styleTokens.color.text}; font-size: 20px; line-height: 1.42; }
529
+ .source { display: grid; grid-template-columns: 1fr auto; gap: 24px; color: ${styleTokens.color.muted}; font-size: ${styleTokens.typography.caption_px}px; border-top: 1px solid ${styleTokens.color.rule}; padding-top: 14px; }
530
+ .accent { width: 64px; height: 3px; background: ${styleTokens.color.accent}; }`;
364
531
  const pages = storyboard.pages.map((page) => `<section class="page">
365
- <div class="kicker">${escapeHtml(page.kind)} / ${page.number}</div>
366
- <div class="panel">
367
- <h1>${escapeHtml(page.claim)}</h1>
368
- <p>${escapeHtml(page.support)}</p>
532
+ <header class="topline">
533
+ <div class="kicker">${escapeHtml(page.kind)} / ${page.number}</div>
534
+ <div class="reference">${referenceName}</div>
535
+ </header>
536
+ <main class="content">
537
+ <div class="claim">
538
+ <div class="accent"></div>
539
+ <h1>${escapeHtml(page.claim)}</h1>
540
+ <p>${escapeHtml(page.support)}</p>
541
+ </div>
542
+ <aside class="evidence" aria-label="decision evidence">
543
+ <div class="evidence-row">
544
+ <div class="label">Audience</div>
545
+ <div class="value">${audienceRaw}</div>
546
+ </div>
547
+ <div class="evidence-row">
548
+ <div class="label">STP</div>
549
+ <div class="value">${stpRaw}</div>
550
+ </div>
551
+ <div class="evidence-row">
552
+ <div class="label">Decision</div>
553
+ <div class="value">${decisionRaw}</div>
554
+ </div>
555
+ </aside>
556
+ </main>
557
+ <div class="source">
558
+ <span>Sources: ${escapeHtml((page.source_ids || []).join(', ') || 'none')}</span>
559
+ <span>${escapeHtml(styleTokens.layout?.composition || 'presentation-grid')}</span>
369
560
  </div>
370
- <div class="source">Sources: ${escapeHtml((page.source_ids || []).join(', ') || 'none')}</div>
371
561
  </section>`).join('\n');
372
562
  return `<!doctype html>
373
563
  <html lang="${styleTokens.typography.language}">
@@ -495,6 +685,9 @@ export function buildPptRenderReport({ contract = {}, audience, sourceLedger, st
495
685
  { id: 'restrained_detail', passed: styleTokens.design_policy?.visual_style === 'simple_restrained_detailed' },
496
686
  { id: 'design_ssot_declared', passed: styleTokens.design_policy?.design_ssot?.authority === DESIGN_SYSTEM_SSOT.authority_file },
497
687
  { id: 'curated_design_md_input_fused', passed: (styleTokens.design_policy?.source_inputs || []).some((entry) => entry.url === AWESOME_DESIGN_MD_REFERENCE.url && entry.role === 'source_input_for_ssot') },
688
+ { id: 'concrete_design_reference_selected', passed: Boolean(styleTokens.design_policy?.design_reference_selection?.primary?.id && styleTokens.design_policy?.design_reference_selection?.selected_sources?.length) },
689
+ { id: 'reference_rules_applied_to_tokens', passed: Boolean(styleTokens.layout?.composition && styleTokens.layout?.treatment && styleTokens.design_policy?.design_reference_selection?.applied_token_profile) },
690
+ { id: 'html_uses_reference_layout', passed: typeof html === 'string' && html.includes('decision evidence') && html.includes(styleTokens.layout?.composition || 'presentation-grid') },
498
691
  { id: 'ppt_skill_allowlist_enforced', passed: JSON.stringify(styleTokens.design_policy?.pipeline_allowlist?.required_skills || []) === JSON.stringify([...PPT_PIPELINE_SKILL_ALLOWLIST]) },
499
692
  { id: 'out_of_pipeline_design_skills_ignored', passed: styleTokens.design_policy?.pipeline_allowlist?.ignore_installed_out_of_pipeline_skills === true && (styleTokens.design_policy?.pipeline_allowlist?.ignored_design_skills_even_if_installed || []).includes('design-artifact-expert') },
500
693
  { id: 'ppt_mcp_allowlist_scoped', passed: (styleTokens.design_policy?.pipeline_allowlist?.allowed_mcp_servers || []).every((entry) => entry.mcp === 'context7' && /external_documentation/.test(entry.condition || '')) },
@@ -679,6 +679,7 @@ export function subagentExecutionPolicyText(route, prompt = '') {
679
679
  }
680
680
  return [
681
681
  'Subagent policy: REQUIRED for code-changing or execution work in this route.',
682
+ 'The selected SKS route itself authorizes route-owned worker/reviewer subagents; the user does not need to separately ask for subagents when the default Team pipeline is active.',
682
683
  'Before editing, the parent orchestrator must visibly state the SKS route, split independent write scopes, and spawn worker/reviewer subagents whenever the tools are available.',
683
684
  'Run workers in parallel only with disjoint ownership. The parent owns integration, verification, and final evidence.',
684
685
  'If subagent tools are unavailable or the work cannot be safely split, record that as explicit subagent evidence before editing.',
@@ -6,34 +6,116 @@ import { getCodexInfo } from './codex-adapter.mjs';
6
6
  import { codexAppIntegrationStatus, formatCodexAppStatus } from './codex-app.mjs';
7
7
 
8
8
  export const SKS_TMUX_LOGO = [
9
- ' _____ __ __ _____',
10
- ' / ___/ / //_/ / ___/',
11
- ' \\__ \\ / ,< \\__ \\ ',
12
- ' ___/ / / /| | ___/ / ',
13
- '/____/ /_/ |_| /____/ ',
14
- ' SNEAKOSCOPE CODEX'
9
+ ' _______ __ __ _______',
10
+ ' / _____/ / /_/ / / _____/|',
11
+ ' / /____ / __ / / /____ | |',
12
+ ' \\____ \\ / / / / \\____ \\| |',
13
+ ' ____/ / /_/ /_/ ____/ /| | | |',
14
+ '/_____/ /_/\\__/ /_____/ |_|/ /',
15
+ ' \\_____\\ \\_\\ \\_\\ \\_____\\|_| /',
16
+ ' SNEAKOSCOPE CODEX'
15
17
  ].join('\n');
16
18
 
17
19
  const SKS_TMUX_LOGO_FRAMES = [
18
20
  [
19
- ' __ __',
20
- ' / //_/ .',
21
- ' / ,< .',
22
- ' / /| | .',
23
- ' /_/ |_|',
24
- ' S K S'
21
+ ' ||',
22
+ ' || .',
23
+ ' || .:',
24
+ ' || .::',
25
+ ' || .:::',
26
+ ' ||.::::',
27
+ ' SKS'
25
28
  ].join('\n'),
26
29
  [
27
- ' _____ __ __ _____',
28
- ' / ___/ / //_/ / ___/',
29
- ' \\__ \\ / ,< \\__ \\ ',
30
- ' / / /| | / / ',
31
- ' /_/ /_/ |_| /_/ ',
32
- ' SNEAKOSCOPE'
30
+ ' //||',
31
+ ' .// || ..',
32
+ ' // || ..::',
33
+ ' // || ..::::',
34
+ ' // || ..::::',
35
+ ' // ||::::',
36
+ ' S K S'
37
+ ].join('\n'),
38
+ [
39
+ ' ______ __ ______',
40
+ ' . / ___/| . / /__ . / ___/|',
41
+ ' / / /__ | | / //_/ / / /__ | |',
42
+ ' / /\\__ \\ | | / ,< / /\\__ \\ | |',
43
+ ' / /___/ / | | / /| | / /___/ / | |',
44
+ ' /_/_____/ |/ /_/ |_| /_/_____/ |/',
45
+ ' SNEAKOSCOPE'
46
+ ].join('\n'),
47
+ [
48
+ ' _______ __ __ _______',
49
+ ' . / _____/ . / /_/ / . / _____/|',
50
+ ' / / /____ / __ / / / /____ | |',
51
+ ' / /\\____ \\ / / / / / /\\____ \\| |',
52
+ ' / /_____/ / /_/ /_/ / /_____/ /| |',
53
+ ' /_/______/ /_/\\__/ /_/______/ |_|/',
54
+ ' SNEAKOSCOPE'
55
+ ].join('\n'),
56
+ [
57
+ ' _____ __ __ _____',
58
+ ' / ___/ / /_/ / / ___/| .',
59
+ ' / /__ / __ / / /__ | | ::',
60
+ ' \\__ \\ / / / / \\__ \\| | :::',
61
+ ' ___/ / /_/ /_/ ___/ /| | | :::',
62
+ '/____/ /_/\\__/ /____/ |_|/::',
63
+ ' \\___\\ \\_\\ \\_\\ \\____\\|_|:',
64
+ ' SNEAKOSCOPE'
65
+ ].join('\n'),
66
+ [
67
+ ' _______ __ __ _______',
68
+ ' / _____/ / /_/ / / _____/|',
69
+ ' / /____ / __ / / /____ | |',
70
+ ' \\____ \\ / / / / \\____ \\| |',
71
+ ' ____/ / /_/ /_/ ____/ /| | | |',
72
+ '/_____/ /_/\\__/ /_____/ |_|/ /',
73
+ ' \\_____\\ \\_\\ \\_\\ \\_____\\|_| /',
74
+ ' SNEAKOSCOPE CODEX'
75
+ ].join('\n'),
76
+ [
77
+ ' _______ __ _______',
78
+ ' |\\_____ \\ . / /_. |\\_____ \\',
79
+ ' | |____\\ \\ / __/ | |____\\ \\',
80
+ ' | |\\____\\ \\/ / / | |\\____\\ \\',
81
+ ' | | |___/ /_/ /__ | | |___/ /',
82
+ ' \\|_|/____/\\__/__/ \\|_|/____/',
83
+ ' SNEAKOSCOPE'
84
+ ].join('\n'),
85
+ [
86
+ ' ||\\\\',
87
+ ' .. || \\\\.',
88
+ ' ..:: || \\\\',
89
+ ' ..:::: || \\\\',
90
+ ':::: || \\\\',
91
+ ' || \\\\',
92
+ ' S K S'
93
+ ].join('\n'),
94
+ [
95
+ ' ||',
96
+ ' . ||',
97
+ ' .:: ||',
98
+ ' .:::: ||',
99
+ ' .:::: ||',
100
+ ' .:::: ||',
101
+ ' SKS'
33
102
  ].join('\n'),
34
103
  SKS_TMUX_LOGO
35
104
  ];
36
105
 
106
+ const SKS_TMUX_LOGO_ANIMATION_STEPS = Object.freeze([
107
+ { frame: 0, color: '39', bold: false, delay: '0.045' },
108
+ { frame: 1, color: '39', bold: false, delay: '0.045' },
109
+ { frame: 2, color: '45', bold: false, delay: '0.05' },
110
+ { frame: 3, color: '51', bold: false, delay: '0.055' },
111
+ { frame: 4, color: '51', bold: true, delay: '0.07' },
112
+ { frame: 5, color: '51', bold: true, delay: '0.07' },
113
+ { frame: 6, color: '45', bold: false, delay: '0.05' },
114
+ { frame: 7, color: '39', bold: false, delay: '0.045' },
115
+ { frame: 8, color: '39', bold: false, delay: '0.045' },
116
+ { frame: 9, color: '51', bold: true, delay: '0.16' }
117
+ ]);
118
+
37
119
  export const DEFAULT_SKS_CODEX_MODEL = 'gpt-5.5';
38
120
  export const DEFAULT_SKS_CODEX_REASONING = 'high';
39
121
 
@@ -135,17 +217,14 @@ export function codexLaunchCommand(root, codexBin, codexArgs = []) {
135
217
 
136
218
  export function sksLogoIntroCommand() {
137
219
  const staticLogo = `clear; printf '\\033[1;38;5;51m%s\\033[0m\\n' ${shellEscape(SKS_TMUX_LOGO)}`;
138
- const animated = [
139
- 'clear',
140
- `printf '\\033[38;5;39m%s\\033[0m\\n' ${shellEscape(SKS_TMUX_LOGO_FRAMES[0])}`,
141
- 'sleep 0.07',
142
- 'clear',
143
- `printf '\\033[1;38;5;45m%s\\033[0m\\n' ${shellEscape(SKS_TMUX_LOGO_FRAMES[1])}`,
144
- 'sleep 0.08',
145
- 'clear',
146
- `printf '\\033[1;38;5;51m%s\\033[0m\\n' ${shellEscape(SKS_TMUX_LOGO_FRAMES[2])}`,
147
- 'sleep 0.15'
148
- ].join('; ');
220
+ const animated = SKS_TMUX_LOGO_ANIMATION_STEPS.flatMap((step) => {
221
+ const style = `${step.bold ? '1;' : ''}38;5;${step.color}`;
222
+ return [
223
+ 'clear',
224
+ `printf '\\033[${style}m%s\\033[0m\\n' ${shellEscape(SKS_TMUX_LOGO_FRAMES[step.frame])}`,
225
+ `sleep ${step.delay}`
226
+ ];
227
+ }).join('; ');
149
228
  return `if [ "\${SKS_TMUX_LOGO_ANIMATION:-1}" = "0" ]; then ${staticLogo}; else ${animated}; fi`;
150
229
  }
151
230