aiwcli 0.13.8 → 0.15.1

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.
Files changed (47) hide show
  1. package/README.md +11 -1
  2. package/dist/commands/launch.d.ts +8 -0
  3. package/dist/commands/launch.js +96 -5
  4. package/dist/templates/_shared/.claude/skills/codex/SKILL.md +42 -0
  5. package/dist/templates/_shared/.claude/skills/codex/prompt.md +30 -0
  6. package/dist/templates/_shared/lib-ts/agent-exec/backends/headless.ts +33 -0
  7. package/dist/templates/_shared/lib-ts/agent-exec/backends/index.ts +6 -0
  8. package/dist/templates/_shared/lib-ts/agent-exec/backends/tmux.ts +145 -0
  9. package/dist/templates/_shared/lib-ts/agent-exec/base-agent.ts +229 -0
  10. package/dist/templates/_shared/lib-ts/agent-exec/execution-backend.ts +50 -0
  11. package/dist/templates/_shared/lib-ts/agent-exec/index.ts +6 -0
  12. package/dist/templates/_shared/lib-ts/agent-exec/structured-output.ts +166 -0
  13. package/dist/templates/_shared/lib-ts/base/cli-args.ts +287 -0
  14. package/dist/templates/_shared/lib-ts/base/inference.ts +53 -47
  15. package/dist/templates/_shared/lib-ts/base/models.ts +16 -0
  16. package/dist/templates/_shared/lib-ts/base/preflight.ts +98 -0
  17. package/dist/templates/_shared/lib-ts/base/state-io.ts +1 -1
  18. package/dist/templates/_shared/lib-ts/base/subprocess-utils.ts +4 -3
  19. package/dist/templates/_shared/lib-ts/base/tmux-driver.ts +381 -0
  20. package/dist/templates/_shared/lib-ts/base/utils.ts +8 -0
  21. package/dist/templates/_shared/lib-ts/context/context-formatter.ts +35 -11
  22. package/dist/templates/_shared/lib-ts/context/context-store.ts +3 -0
  23. package/dist/templates/_shared/lib-ts/types.ts +17 -0
  24. package/dist/templates/_shared/scripts/status_line.ts +93 -47
  25. package/dist/templates/_shared/skills/prompt-codex/CLAUDE.md +71 -0
  26. package/dist/templates/_shared/skills/prompt-codex/scripts/launch-codex.ts +387 -0
  27. package/dist/templates/_shared/skills/prompt-codex/scripts/watch-codex.ts +257 -0
  28. package/dist/templates/cc-native/.claude/settings.json +121 -1
  29. package/dist/templates/cc-native/_cc-native/CLAUDE.md +73 -0
  30. package/dist/templates/cc-native/_cc-native/lib-ts/CLAUDE.md +70 -0
  31. package/dist/templates/cc-native/_cc-native/lib-ts/cli-output-parser.ts +9 -133
  32. package/dist/templates/cc-native/_cc-native/lib-ts/settings.ts +120 -43
  33. package/dist/templates/cc-native/_cc-native/lib-ts/state.ts +1 -0
  34. package/dist/templates/cc-native/_cc-native/lib-ts/types.ts +66 -12
  35. package/dist/templates/cc-native/_cc-native/plan-review/lib/agent-selection.ts +5 -4
  36. package/dist/templates/cc-native/_cc-native/plan-review/lib/orchestrator.ts +4 -4
  37. package/dist/templates/cc-native/_cc-native/plan-review/lib/preflight.ts +14 -80
  38. package/dist/templates/cc-native/_cc-native/plan-review/lib/review-pipeline.ts +16 -13
  39. package/dist/templates/cc-native/_cc-native/plan-review/lib/reviewers/agent.ts +19 -7
  40. package/dist/templates/cc-native/_cc-native/plan-review/lib/reviewers/base/base-agent.ts +4 -215
  41. package/dist/templates/cc-native/_cc-native/plan-review/lib/reviewers/index.ts +1 -1
  42. package/dist/templates/cc-native/_cc-native/plan-review/lib/reviewers/providers/claude-agent.ts +9 -39
  43. package/dist/templates/cc-native/_cc-native/plan-review/lib/reviewers/providers/codex-agent.ts +19 -22
  44. package/dist/templates/cc-native/_cc-native/plan-review/lib/reviewers/providers/gemini-agent.ts +2 -1
  45. package/dist/templates/cc-native/_cc-native/plan-review/lib/reviewers/providers/orchestrator-claude-agent.ts +65 -36
  46. package/oclif.manifest.json +21 -3
  47. package/package.json +1 -1
@@ -13,14 +13,15 @@ import { execFileSync } from "node:child_process";
13
13
  import * as fs from "node:fs";
14
14
  import { homedir } from "node:os";
15
15
  import * as path from "node:path";
16
+ import type { ContextState } from "../lib-ts/types.js";
16
17
 
17
18
  // PAI infrastructure imports — graceful fallback when libs aren't available
18
19
  let CONTEXT_BASELINE_TOKENS = 22_600;
19
- let getContextBySessionId: (id: string) => Record<string, unknown> | null =
20
+ let getContextBySessionId: (id: string, root?: string) => ContextState | null =
20
21
  () => null;
21
- let getContext: (id: string) => Record<string, unknown> | null = () => null;
22
- let loadState: (id: string) => Record<string, unknown> | null = () => null;
23
- let saveState: (id: string, state: unknown) => void = () => {};
22
+ let getContext: (id: string, root?: string) => ContextState | null = () => null;
23
+ let loadState: (id: string, root?: string) => ContextState | null = () => null;
24
+ let saveState: (id: string, state: ContextState) => void = () => {};
24
25
  let findLatestPlan: (contextId: string) => string | null = () => null;
25
26
 
26
27
  try {
@@ -59,41 +60,60 @@ const CACHE_DIR = path.join(OUTPUT_DIR, "cache");
59
60
  const STATUSLINE_CACHE = path.join(CACHE_DIR, ".statusline-cache.json");
60
61
 
61
62
  // ---------------------------------------------------------------------------
62
- // NO_COLOR support (https://no-color.org)
63
+ // Color level detection (truecolor → 256-color → basic 16 → none)
63
64
  // ---------------------------------------------------------------------------
64
- const NO_COLOR = Boolean(process.env.NO_COLOR);
65
+ type ColorLevel = 0 | 1 | 2 | 3;
66
+
67
+ function detectColorLevel(): ColorLevel {
68
+ if (process.env.NO_COLOR) return 0;
69
+ const fc = process.env.FORCE_COLOR;
70
+ if (fc !== undefined) return Math.min(3, Math.max(0, parseInt(fc, 10) || 0)) as ColorLevel;
71
+ const ct = process.env.COLORTERM ?? "";
72
+ if (ct === "truecolor" || ct === "24bit") return 3;
73
+ if ((process.env.TERM ?? "").includes("256color")) return 2;
74
+ return 1;
75
+ }
76
+
77
+ const COLOR_LEVEL = detectColorLevel();
78
+
79
+ function fg(r: number, g: number, b: number, x256: number, b16: number): string {
80
+ if (COLOR_LEVEL === 0) return "";
81
+ if (COLOR_LEVEL === 3) return `\u001B[38;2;${r};${g};${b}m`;
82
+ if (COLOR_LEVEL === 2) return `\u001B[38;5;${x256}m`;
83
+ return `\u001B[${b16}m`;
84
+ }
65
85
 
66
- const RESET = NO_COLOR ? "" : "\u001B[0m";
86
+ const RESET = COLOR_LEVEL === 0 ? "" : "\u001B[0m";
67
87
 
68
88
  // Structural
69
- const SLATE_300 = NO_COLOR ? "" : "\u001B[38;2;203;213;225m";
70
- const SLATE_400 = NO_COLOR ? "" : "\u001B[38;2;148;163;184m";
71
- const SLATE_500 = NO_COLOR ? "" : "\u001B[38;2;100;116;139m";
72
- const SLATE_600 = NO_COLOR ? "" : "\u001B[38;2;71;85;105m";
89
+ const SLATE_300 = fg(203, 213, 225, 188, 37);
90
+ const SLATE_400 = fg(148, 163, 184, 146, 37);
91
+ const SLATE_500 = fg(100, 116, 139, 103, 90);
92
+ const SLATE_600 = fg(71, 85, 105, 240, 90);
73
93
 
74
94
  // Semantic
75
- const EMERALD = NO_COLOR ? "" : "\u001B[38;2;74;222;128m";
76
- const ROSE = NO_COLOR ? "" : "\u001B[38;2;251;113;133m";
77
- const AMBER = NO_COLOR ? "" : "\u001B[38;2;251;191;36m";
95
+ const EMERALD = fg(74, 222, 128, 79, 92);
96
+ const ROSE = fg(251, 113, 133, 211, 91);
97
+ const AMBER = fg(251, 191, 36, 221, 93);
78
98
 
79
99
  // Context colors
80
- const CTX_PRIMARY = NO_COLOR ? "" : "\u001B[38;2;129;140;248m";
81
- const CTX_SECONDARY = NO_COLOR ? "" : "\u001B[38;2;165;180;252m";
82
- const CTX_ACCENT = NO_COLOR ? "" : "\u001B[38;2;139;92;246m";
83
- const CTX_BUCKET_EMPTY = NO_COLOR ? "" : "\u001B[38;2;75;82;95m";
100
+ const CTX_PRIMARY = fg(129, 140, 248, 147, 94);
101
+ const CTX_SECONDARY = fg(165, 180, 252, 153, 94);
102
+ const CTX_ACCENT = fg(139, 92, 246, 141, 35);
103
+ const CTX_BUCKET_EMPTY = fg(75, 82, 95, 239, 90);
84
104
 
85
105
  // Git colors
86
- const GIT_PRIMARY = NO_COLOR ? "" : "\u001B[38;2;56;189;248m";
87
- const GIT_VALUE = NO_COLOR ? "" : "\u001B[38;2;186;230;253m";
88
- const GIT_DIR = NO_COLOR ? "" : "\u001B[38;2;147;197;253m";
89
- const GIT_CLEAN = NO_COLOR ? "" : "\u001B[38;2;125;211;252m";
90
- const GIT_MODIFIED = NO_COLOR ? "" : "\u001B[38;2;96;165;250m";
91
- const GIT_ADDED = NO_COLOR ? "" : "\u001B[38;2;59;130;246m";
92
- const GIT_STASH = NO_COLOR ? "" : "\u001B[38;2;165;180;252m";
93
- const GIT_AGE_FRESH = NO_COLOR ? "" : "\u001B[38;2;125;211;252m";
94
- const GIT_AGE_RECENT = NO_COLOR ? "" : "\u001B[38;2;96;165;250m";
95
- const GIT_AGE_STALE = NO_COLOR ? "" : "\u001B[38;2;59;130;246m";
96
- const GIT_AGE_OLD = NO_COLOR ? "" : "\u001B[38;2;99;102;241m";
106
+ const GIT_PRIMARY = fg(56, 189, 248, 81, 96);
107
+ const GIT_VALUE = fg(186, 230, 253, 195, 96);
108
+ const GIT_DIR = fg(147, 197, 253, 153, 94);
109
+ const GIT_CLEAN = fg(125, 211, 252, 117, 96);
110
+ const GIT_MODIFIED = fg(96, 165, 250, 111, 94);
111
+ const GIT_ADDED = fg(59, 130, 246, 75, 34);
112
+ const GIT_STASH = fg(165, 180, 252, 153, 94);
113
+ const GIT_AGE_FRESH = fg(125, 211, 252, 117, 96);
114
+ const GIT_AGE_RECENT = fg(96, 165, 250, 111, 94);
115
+ const GIT_AGE_STALE = fg(59, 130, 246, 75, 34);
116
+ const GIT_AGE_OLD = fg(99, 102, 241, 105, 35);
97
117
 
98
118
  // ---------------------------------------------------------------------------
99
119
  // Display modes
@@ -127,7 +147,7 @@ function getDisplayMode(width: number): string {
127
147
  // ---------------------------------------------------------------------------
128
148
 
129
149
  function getBucketColor(pos: number, maxPos: number): string {
130
- if (NO_COLOR) return "";
150
+ if (COLOR_LEVEL === 0) return "";
131
151
  const pct = Math.floor((pos * 100) / maxPos);
132
152
 
133
153
  let b: number;
@@ -150,7 +170,17 @@ function getBucketColor(pos: number, maxPos: number): string {
150
170
  b = 60 + Math.floor(((68 - 60) * t) / 34);
151
171
  }
152
172
 
153
- return `\u001B[38;2;${r};${g};${b}m`;
173
+ if (COLOR_LEVEL === 3) return `\u001B[38;2;${r};${g};${b}m`;
174
+ if (COLOR_LEVEL === 2) {
175
+ const ri = Math.round(r / 51);
176
+ const gi = Math.round(g / 51);
177
+ const bi = Math.round(b / 51);
178
+ return `\u001B[38;5;${16 + 36 * ri + 6 * gi + bi}m`;
179
+ }
180
+ // Level 1: three-band semantic mapping
181
+ if (pct <= 33) return `\u001B[92m`; // bright green
182
+ if (pct <= 66) return `\u001B[93m`; // bright yellow
183
+ return `\u001B[91m`; // bright red
154
184
  }
155
185
 
156
186
  // ---------------------------------------------------------------------------
@@ -299,7 +329,7 @@ function renderContext(
299
329
  line =
300
330
  `${CTX_PRIMARY}\u25C9${RESET} ${CTX_ACCENT}${shortModel}${RESET} ` +
301
331
  `${SLATE_600}\u2502${RESET} ` +
302
- `${bar} ${pctColor}${contextPct}%${RESET} ${SLATE_500}(${contextK}k)${RESET}`;
332
+ `${bar} ${pctColor}${contextPct}%${RESET} ${SLATE_500}(${contextK}k/${maxK}k)${RESET}`;
303
333
 
304
334
  break;
305
335
  }
@@ -590,7 +620,7 @@ function findActivePlanFile(): string | null {
590
620
  function renderContextManager(
591
621
  mode: string,
592
622
  contextId: string,
593
- contextState: Record<string, unknown> | null,
623
+ contextState: ContextState | null,
594
624
  ): string {
595
625
  // Strip YYMMDD-HHMM- timestamp prefix from context ID for display
596
626
  let displayId = contextId.replace(/^\d{6}-\d{4}-/, "");
@@ -620,7 +650,7 @@ function renderContextManager(
620
650
  if (isPlanning) {
621
651
  const label = mode === "nano" ? "Plan" : "Planning";
622
652
  modeBadge = ` ${SLATE_600}\u2502${RESET} ${CTX_SECONDARY}Mode:${RESET} ${AMBER}${label}${RESET}`;
623
- } else if (stateMode === "has_plan") {
653
+ } else if (stateMode === "has_staged_work") {
624
654
  const label = mode === "nano" ? "Ready" : "Plan Ready";
625
655
  modeBadge = ` ${SLATE_600}\u2502${RESET} ${CTX_SECONDARY}Mode:${RESET} ${EMERALD}${label}${RESET}`;
626
656
  } else if (stateMode === "active") {
@@ -634,7 +664,7 @@ function renderContextManager(
634
664
  planFilePath = activePlanFile;
635
665
  } else if (statePlanPath) {
636
666
  planFilePath = statePlanPath;
637
- } else if (stateMode === "has_plan" || stateMode === "active") {
667
+ } else if (stateMode === "has_staged_work" || stateMode === "active") {
638
668
  try {
639
669
  planFilePath = findLatestPlan(contextId) ?? null;
640
670
  } catch {
@@ -717,7 +747,7 @@ function resolveContextId(sessionId: string): string | null {
717
747
  const context = getContextBySessionId(sessionId);
718
748
  if (context) {
719
749
  if (!cache.sessions) cache.sessions = {};
720
- const ctxId = (context as Record<string, unknown>).id as string;
750
+ const ctxId = context.id;
721
751
  cache.sessions[sessionId] = { context_id: ctxId };
722
752
  saveCache(cache);
723
753
  return ctxId;
@@ -730,9 +760,9 @@ function resolveContextId(sessionId: string): string | null {
730
760
  return null;
731
761
  }
732
762
 
733
- function loadContextState(contextId: string): Record<string, unknown> | null {
763
+ function loadContextState(contextId: string): ContextState | null {
734
764
  try {
735
- return loadState(contextId) as Record<string, unknown> | null;
765
+ return loadState(contextId);
736
766
  } catch {
737
767
  return null;
738
768
  }
@@ -743,12 +773,12 @@ function writeContextWindow(
743
773
  contextWindowData: Record<string, unknown>,
744
774
  ): void {
745
775
  try {
746
- const state = getContext(contextId) as Record<string, unknown> | null;
776
+ const state = getContext(contextId);
747
777
  if (state) {
748
- if (!state.last_session) state.last_session = {};
749
- state.last_session.context_remaining_pct =
778
+ if (!state.last_session) state.last_session = { session_id: undefined, saved_at: undefined, save_reason: undefined, transcript_path: undefined };
779
+ (state.last_session as Record<string, unknown>).context_remaining_pct =
750
780
  contextWindowData.remaining_percentage;
751
- saveState(contextId, state as unknown);
781
+ saveState(contextId, state);
752
782
  }
753
783
  } catch {
754
784
  /* ignore */
@@ -759,11 +789,28 @@ function writeContextWindow(
759
789
  // Main
760
790
  // ---------------------------------------------------------------------------
761
791
 
792
+ /** Shape of the JSON payload piped to status_line.ts via stdin */
793
+ interface StatusLineInput {
794
+ session_id?: string;
795
+ model?: { display_name?: string };
796
+ workspace?: { project_dir?: string };
797
+ context_window?: {
798
+ current_usage?: {
799
+ cache_read_input_tokens?: number;
800
+ input_tokens?: number;
801
+ cache_creation_input_tokens?: number;
802
+ output_tokens?: number;
803
+ };
804
+ context_window_size?: number;
805
+ used_percentage?: number;
806
+ };
807
+ }
808
+
762
809
  function main(): void {
763
810
  // Read JSON from stdin
764
- let inputData: Record<string, unknown>;
811
+ let inputData: StatusLineInput;
765
812
  try {
766
- inputData = JSON.parse(fs.readFileSync(0, "utf-8"));
813
+ inputData = JSON.parse(fs.readFileSync(0, "utf-8")) as StatusLineInput;
767
814
  } catch {
768
815
  inputData = {};
769
816
  }
@@ -775,8 +822,7 @@ function main(): void {
775
822
  // Extract input fields
776
823
  const sessionId = inputData.session_id ?? "";
777
824
  const modelName = inputData.model?.display_name ?? "unknown";
778
- const workspace = inputData.workspace ?? {};
779
- const currentDir: string = workspace.project_dir ?? process.cwd();
825
+ const currentDir: string = inputData.workspace?.project_dir ?? process.cwd();
780
826
  const dirName = path.basename(currentDir);
781
827
 
782
828
  // Context window data
@@ -795,7 +841,7 @@ function main(): void {
795
841
  const contextUsed = totalInput + outputTokens + CONTEXT_BASELINE_TOKENS;
796
842
 
797
843
  if (usedPct !== undefined && usedPct !== null) {
798
- contextPct = Math.floor(usedPct);
844
+ contextPct = Math.floor(usedPct as number);
799
845
  } else {
800
846
  contextPct =
801
847
  contextMax > 0 ? Math.floor((contextUsed * 100) / contextMax) : 0;
@@ -0,0 +1,71 @@
1
+ # Prompt Codex Skill
2
+
3
+ Launch Codex CLI in a tmux pane and inject a prompt into its REPL.
4
+
5
+ ## Directory Structure
6
+
7
+ ```
8
+ prompt-codex/
9
+ ├── CLAUDE.md ← This file
10
+ └── scripts/
11
+ ├── launch-codex.ts ← CLI entry point
12
+ └── watch-codex.ts ← Capture watcher and summarizer
13
+ ```
14
+
15
+ ## Script: launch-codex.ts
16
+
17
+ **Usage:**
18
+ ```bash
19
+ bun .aiwcli/_shared/skills/prompt-codex/scripts/launch-codex.ts [--model <tier|id>] [--sandbox <sandbox-mode>] [--full-auto] plan
20
+ bun .aiwcli/_shared/skills/prompt-codex/scripts/launch-codex.ts [--model <tier|id>] [--sandbox <sandbox-mode>] [--full-auto] --file <path>
21
+ bun .aiwcli/_shared/skills/prompt-codex/scripts/launch-codex.ts [--model <tier|id>] [--sandbox <sandbox-mode>] [--full-auto] <inline text...>
22
+ bun .aiwcli/_shared/skills/prompt-codex/scripts/launch-codex.ts [--model <tier|id>] [--sandbox <sandbox-mode>] [--full-auto] [--capture] <mode>
23
+ ```
24
+
25
+ **Args:**
26
+ - `plan` — discover active plan via context system, inject into Codex REPL
27
+ - `--file <path>` — inject file contents into Codex REPL
28
+ - `<text...>` — join remaining args as inline prompt, write to temp file, inject
29
+ - `--model <alias|tier|id>` — Aliases: `spark` → `gpt-5.3-codex-spark`, `codex` → `gpt-5.3-codex`, `gpt` → `gpt-5.2`. Tiers: `fast`/`standard`/`smart` (resolved via `resolveModelForProvider()`). Or any full model ID. Aliases are checked first (local `CODEX_ALIASES` constant in `launch-codex.ts`), then tiers, then pass-through. Omitted = Codex default.
30
+ - `--sandbox <mode>` — `read-only`, `workspace-write`, or `danger-full-access`. Default is `danger-full-access` for implementation handoffs.
31
+ - `--no-yolo` — Disable YOLO mode (on by default). YOLO maps to Codex CLI's `--dangerously-bypass-approvals-and-sandbox`. Use `--no-yolo` to restore normal approval prompts.
32
+ - `--capture` — Best-effort session capture. On success, prints:
33
+ - `CODEX_CAPTURE_PANE=<pane_id>`
34
+ - `CODEX_CAPTURE_SESSION_ID=<session_id>`
35
+ - `CODEX_CAPTURE_SESSION_FILE=<session_file>`
36
+ These are consumed by the skill prompt to run `watch-codex.ts` as a background task.
37
+
38
+ **Plan discovery order:**
39
+ 1. `CLAUDE_SESSION_ID` env → `getContextBySessionId()` → `findLatestPlan(contextId)`
40
+ 2. Fallback: scan `_output/contexts/*/plans/*.md` by mtime (inline, no `_cc-native` import)
41
+
42
+ **Dependencies (all from `_shared/lib-ts/`):**
43
+ - `base/tmux-driver.ts` — `launchDriverInTmuxOrFallback()`, `getTmuxAvailability()`
44
+ - `base/cli-args.ts` — `resolveCodexModel()`, `codexReplSpec()`, `buildCliInvocation()`, `isCodexSandbox()`
45
+ - `base/logger.ts` — `logDebug()`, `logWarn()` (injection diagnostics)
46
+ - `context/context-store.ts` — `getContextBySessionId()`
47
+ - `context/context-formatter.ts` — `buildExternalAgentContext()` (orientation header for Codex)
48
+ - `context/plan-manager.ts` — `findLatestPlan()`
49
+
50
+ **Design decisions:**
51
+ - Always creates a new tmux pane (no pane reuse/tracking)
52
+ - No exec fallback — REPL mode requires tmux
53
+ - `_shared` only — never imports from `_cc-native`
54
+ - Temp file cleanup after injection confirmed
55
+
56
+ ## Script: watch-codex.ts
57
+
58
+ **Usage:**
59
+ ```bash
60
+ bun .aiwcli/_shared/skills/prompt-codex/scripts/watch-codex.ts <pane_id> <session_id> <session_file>
61
+ ```
62
+
63
+ **Behavior:**
64
+ - Polls tmux until `<pane_id>` closes
65
+ - Primary: parses `<session_file>` transcript and summarizes via Spark (`inference()` + `CODEX_MODELS.spark`)
66
+ - Fallback: runs `codex exec resume <session_id>` if transcript summarization fails
67
+ - Final fallback: emits concise transcript lines directly from `<session_file>`
68
+
69
+ **Resilience policy:**
70
+ - Capture path is best-effort and never blocks Codex launch
71
+ - Watcher exits cleanly on poll/summary/parse failures with fallback text