noah-code 0.1.0__tar.gz → 0.1.1__tar.gz

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 (64) hide show
  1. {noah_code-0.1.0 → noah_code-0.1.1}/.github/workflows/release.yml +13 -1
  2. {noah_code-0.1.0 → noah_code-0.1.1}/PKG-INFO +15 -6
  3. {noah_code-0.1.0 → noah_code-0.1.1}/README.md +14 -5
  4. {noah_code-0.1.0 → noah_code-0.1.1}/docs/configuration.md +32 -1
  5. {noah_code-0.1.0 → noah_code-0.1.1}/docs/development.md +4 -2
  6. noah_code-0.1.1/docs/interactive-reference.md +96 -0
  7. noah_code-0.1.1/docs/releases/v0.1.0.md +25 -0
  8. noah_code-0.1.1/docs/releases/v0.1.1.md +40 -0
  9. {noah_code-0.1.0 → noah_code-0.1.1}/pyproject.toml +1 -1
  10. {noah_code-0.1.0 → noah_code-0.1.1}/src/noah_code/__init__.py +1 -1
  11. {noah_code-0.1.0 → noah_code-0.1.1}/src/noah_code/cli.py +58 -6
  12. noah_code-0.1.1/src/noah_code/commands.py +173 -0
  13. {noah_code-0.1.0 → noah_code-0.1.1}/src/noah_code/config.py +60 -0
  14. {noah_code-0.1.0 → noah_code-0.1.1}/src/noah_code/event_bridge.py +37 -10
  15. {noah_code-0.1.0 → noah_code-0.1.1}/src/noah_code/host.py +60 -7
  16. {noah_code-0.1.0 → noah_code-0.1.1}/src/noah_code/sessions.py +62 -0
  17. noah_code-0.1.1/src/noah_code/ui/textual.css +244 -0
  18. noah_code-0.1.1/src/noah_code/ui/textual_app.py +1153 -0
  19. {noah_code-0.1.0 → noah_code-0.1.1}/tests/test_cli.py +43 -1
  20. noah_code-0.1.1/tests/test_config.py +121 -0
  21. noah_code-0.1.1/tests/test_event_bridge.py +51 -0
  22. {noah_code-0.1.0 → noah_code-0.1.1}/tests/test_host.py +117 -0
  23. {noah_code-0.1.0 → noah_code-0.1.1}/tests/test_sessions.py +56 -0
  24. noah_code-0.1.1/tests/test_textual_tui.py +312 -0
  25. {noah_code-0.1.0 → noah_code-0.1.1}/uv.lock +1 -1
  26. noah_code-0.1.0/docs/interactive-reference.md +0 -55
  27. noah_code-0.1.0/src/noah_code/commands.py +0 -70
  28. noah_code-0.1.0/src/noah_code/ui/textual.css +0 -9
  29. noah_code-0.1.0/src/noah_code/ui/textual_app.py +0 -435
  30. noah_code-0.1.0/tests/test_config.py +0 -58
  31. noah_code-0.1.0/tests/test_textual_tui.py +0 -93
  32. {noah_code-0.1.0 → noah_code-0.1.1}/.github/workflows/ci.yml +0 -0
  33. {noah_code-0.1.0 → noah_code-0.1.1}/.gitignore +0 -0
  34. {noah_code-0.1.0 → noah_code-0.1.1}/docs/extensions.md +0 -0
  35. {noah_code-0.1.0 → noah_code-0.1.1}/docs/security.md +0 -0
  36. {noah_code-0.1.0 → noah_code-0.1.1}/install.sh +0 -0
  37. {noah_code-0.1.0 → noah_code-0.1.1}/src/noah_code/__main__.py +0 -0
  38. {noah_code-0.1.0 → noah_code-0.1.1}/src/noah_code/agent.py +0 -0
  39. {noah_code-0.1.0 → noah_code-0.1.1}/src/noah_code/approvals.py +0 -0
  40. {noah_code-0.1.0 → noah_code-0.1.1}/src/noah_code/custom_commands.py +0 -0
  41. {noah_code-0.1.0 → noah_code-0.1.1}/src/noah_code/events.py +0 -0
  42. {noah_code-0.1.0 → noah_code-0.1.1}/src/noah_code/macos_sandbox.py +0 -0
  43. {noah_code-0.1.0 → noah_code-0.1.1}/src/noah_code/mcp_setup.py +0 -0
  44. {noah_code-0.1.0 → noah_code-0.1.1}/src/noah_code/permissions.py +0 -0
  45. {noah_code-0.1.0 → noah_code-0.1.1}/src/noah_code/skills_setup.py +0 -0
  46. {noah_code-0.1.0 → noah_code-0.1.1}/src/noah_code/snapshots.py +0 -0
  47. {noah_code-0.1.0 → noah_code-0.1.1}/src/noah_code/tools/__init__.py +0 -0
  48. {noah_code-0.1.0 → noah_code-0.1.1}/src/noah_code/tools/git_tools.py +0 -0
  49. {noah_code-0.1.0 → noah_code-0.1.1}/src/noah_code/tools/workspace_tools.py +0 -0
  50. {noah_code-0.1.0 → noah_code-0.1.1}/src/noah_code/ui/__init__.py +0 -0
  51. {noah_code-0.1.0 → noah_code-0.1.1}/src/noah_code/ui/console.py +0 -0
  52. {noah_code-0.1.0 → noah_code-0.1.1}/src/noah_code/ui/protocol.py +0 -0
  53. {noah_code-0.1.0 → noah_code-0.1.1}/src/noah_code/updates.py +0 -0
  54. {noah_code-0.1.0 → noah_code-0.1.1}/src/noah_code/workspace.py +0 -0
  55. {noah_code-0.1.0 → noah_code-0.1.1}/tests/test_agent_security.py +0 -0
  56. {noah_code-0.1.0 → noah_code-0.1.1}/tests/test_custom_commands.py +0 -0
  57. {noah_code-0.1.0 → noah_code-0.1.1}/tests/test_event_bridge_and_shell.py +0 -0
  58. {noah_code-0.1.0 → noah_code-0.1.1}/tests/test_installer.py +0 -0
  59. {noah_code-0.1.0 → noah_code-0.1.1}/tests/test_permissions.py +0 -0
  60. {noah_code-0.1.0 → noah_code-0.1.1}/tests/test_run_exit.py +0 -0
  61. {noah_code-0.1.0 → noah_code-0.1.1}/tests/test_snapshots.py +0 -0
  62. {noah_code-0.1.0 → noah_code-0.1.1}/tests/test_summarization.py +0 -0
  63. {noah_code-0.1.0 → noah_code-0.1.1}/tests/test_updates.py +0 -0
  64. {noah_code-0.1.0 → noah_code-0.1.1}/tests/test_workspace_tools.py +0 -0
@@ -105,6 +105,11 @@ jobs:
105
105
  id-token: write
106
106
  attestations: write
107
107
  steps:
108
+ - name: Check out tagged release notes
109
+ uses: actions/checkout@v7
110
+ with:
111
+ persist-credentials: false
112
+
108
113
  - name: Download release distributions
109
114
  uses: actions/download-artifact@v6
110
115
  with:
@@ -123,5 +128,12 @@ jobs:
123
128
  - name: Create release and upload assets
124
129
  env:
125
130
  GH_TOKEN: ${{ github.token }}
131
+ GH_REPO: ${{ github.repository }}
126
132
  RELEASE_TAG: ${{ github.ref_name }}
127
- run: gh release create "$RELEASE_TAG" dist/* --verify-tag --generate-notes --title "$RELEASE_TAG"
133
+ run: |
134
+ notes_file="docs/releases/${RELEASE_TAG}.md"
135
+ if [ -f "$notes_file" ]; then
136
+ gh release create "$RELEASE_TAG" dist/* --verify-tag --notes-file "$notes_file" --title "$RELEASE_TAG"
137
+ else
138
+ gh release create "$RELEASE_TAG" dist/* --verify-tag --generate-notes --title "$RELEASE_TAG"
139
+ fi
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: noah-code
3
- Version: 0.1.0
3
+ Version: 0.1.1
4
4
  Summary: Noah Code terminal coding agent, built on NVIDIA OO Agents (NOOA)
5
5
  Project-URL: Homepage, https://github.com/skundu42/noah-code
6
6
  Project-URL: Documentation, https://github.com/skundu42/noah-code#readme
@@ -58,10 +58,6 @@ or system package setup is required.
58
58
  ```bash
59
59
  curl -LsSf https://raw.githubusercontent.com/skundu42/noah-code/main/install.sh | sh
60
60
  ```
61
-
62
- The bootstrapper validates macOS/Linux support, installs the standalone uv package manager, and
63
- downloads an isolated Python 3.12 runtime plus Noah Code. It does not modify a system Python.
64
-
65
61
  Open a new terminal, move into a repository, and run:
66
62
 
67
63
  ```bash
@@ -80,8 +76,11 @@ and seccomp support. You also need an LLM provider account such as OpenAI, Anthr
80
76
  - Switch between implementation-focused **build** mode and read-only **plan** mode.
81
77
  - Approve actions once or for a session with ordered `allow`, `ask`, and `deny` rules.
82
78
  - Undo and redo journaled file edits across process restarts.
83
- - Work in a full-screen TUI, a classic console, or one-shot non-interactive mode.
79
+ - Work in an adaptive Atom One Dark cockpit, a classic console, or one-shot non-interactive mode.
80
+ - Type `/` for a live-filtering command and configuration reference; press Enter to send.
81
+ - Follow batched live tool output, then inspect compact execution records with `F2`.
84
82
  - Resume workspace-scoped sessions with todos and automatic context compaction.
83
+ - Switch AI models between turns, with optional cross-repository defaults.
85
84
  - Extend workflows with slash commands, opt-in skills, MCP servers, model selection, and tracing.
86
85
 
87
86
  ## Quick start
@@ -133,6 +132,15 @@ The package also installs `noah-code` and `nc` as equivalent entry points. Becau
133
132
  refers to netcat, `noah` or `noah-code` is recommended. Keep provider API keys in the environment
134
133
  or trusted NOOA user configuration, never in a repository or Noah Code session metadata.
135
134
 
135
+ Inside a session, `/model MODEL` switches the active model immediately and remembers it when that
136
+ session is resumed. It does not change other sessions. Use `/model --global MODEL` when the new
137
+ model should also become the default for future sessions in every repository.
138
+
139
+ The TUI keeps the conversation central and adds a session-and-plan rail on terminals at least
140
+ 110 columns wide. Tool output streams in a bounded activity panel and compacts after completion,
141
+ keeping long runs responsive without deleting persisted session data. Press `F2` for activity
142
+ details or `F3` for paginated conversation history.
143
+
136
144
  ## Documentation
137
145
 
138
146
  - [Interactive interface and sessions](docs/interactive-reference.md)
@@ -140,6 +148,7 @@ or trusted NOOA user configuration, never in a repository or Noah Code session m
140
148
  - [Generated-code security](docs/security.md)
141
149
  - [Custom commands, skills, MCP, and tracing](docs/extensions.md)
142
150
  - [Development, CI, and releases](docs/development.md)
151
+ - [Release notes](docs/releases/)
143
152
 
144
153
  ## Updates
145
154
 
@@ -16,10 +16,6 @@ or system package setup is required.
16
16
  ```bash
17
17
  curl -LsSf https://raw.githubusercontent.com/skundu42/noah-code/main/install.sh | sh
18
18
  ```
19
-
20
- The bootstrapper validates macOS/Linux support, installs the standalone uv package manager, and
21
- downloads an isolated Python 3.12 runtime plus Noah Code. It does not modify a system Python.
22
-
23
19
  Open a new terminal, move into a repository, and run:
24
20
 
25
21
  ```bash
@@ -38,8 +34,11 @@ and seccomp support. You also need an LLM provider account such as OpenAI, Anthr
38
34
  - Switch between implementation-focused **build** mode and read-only **plan** mode.
39
35
  - Approve actions once or for a session with ordered `allow`, `ask`, and `deny` rules.
40
36
  - Undo and redo journaled file edits across process restarts.
41
- - Work in a full-screen TUI, a classic console, or one-shot non-interactive mode.
37
+ - Work in an adaptive Atom One Dark cockpit, a classic console, or one-shot non-interactive mode.
38
+ - Type `/` for a live-filtering command and configuration reference; press Enter to send.
39
+ - Follow batched live tool output, then inspect compact execution records with `F2`.
42
40
  - Resume workspace-scoped sessions with todos and automatic context compaction.
41
+ - Switch AI models between turns, with optional cross-repository defaults.
43
42
  - Extend workflows with slash commands, opt-in skills, MCP servers, model selection, and tracing.
44
43
 
45
44
  ## Quick start
@@ -91,6 +90,15 @@ The package also installs `noah-code` and `nc` as equivalent entry points. Becau
91
90
  refers to netcat, `noah` or `noah-code` is recommended. Keep provider API keys in the environment
92
91
  or trusted NOOA user configuration, never in a repository or Noah Code session metadata.
93
92
 
93
+ Inside a session, `/model MODEL` switches the active model immediately and remembers it when that
94
+ session is resumed. It does not change other sessions. Use `/model --global MODEL` when the new
95
+ model should also become the default for future sessions in every repository.
96
+
97
+ The TUI keeps the conversation central and adds a session-and-plan rail on terminals at least
98
+ 110 columns wide. Tool output streams in a bounded activity panel and compacts after completion,
99
+ keeping long runs responsive without deleting persisted session data. Press `F2` for activity
100
+ details or `F3` for paginated conversation history.
101
+
94
102
  ## Documentation
95
103
 
96
104
  - [Interactive interface and sessions](docs/interactive-reference.md)
@@ -98,6 +106,7 @@ or trusted NOOA user configuration, never in a repository or Noah Code session m
98
106
  - [Generated-code security](docs/security.md)
99
107
  - [Custom commands, skills, MCP, and tracing](docs/extensions.md)
100
108
  - [Development, CI, and releases](docs/development.md)
109
+ - [Release notes](docs/releases/)
101
110
 
102
111
  ## Updates
103
112
 
@@ -10,6 +10,28 @@ Configuration is merged in this order, with later layers taking precedence:
10
10
  4. `NOAH_CODE_*` environment variables.
11
11
  5. CLI flags.
12
12
 
13
+ ### First-run model setup
14
+
15
+ The first interactive `noah` launch asks for an AI model before starting the agent. Noah Code
16
+ saves that choice as the top-level `model` in `~/.config/noah-code/config.toml`, making it the
17
+ default for every repository. Enter a LiteLLM model name or an alias from the NOOA model registry.
18
+
19
+ An explicit `noah --model MODEL` on the first launch is saved without prompting. Project config,
20
+ `NOAH_CODE_MODEL`, and later `--model` flags still override the user default according to the
21
+ precedence above. Non-interactive commands such as `noah run`, `doctor`, and `config show` never
22
+ open the onboarding prompt.
23
+
24
+ Inside an interactive session, switch only the current session or replace the global default:
25
+
26
+ ```text
27
+ /model openai/MODEL_NAME
28
+ /model --global anthropic/MODEL_NAME
29
+ ```
30
+
31
+ Model switches take effect between turns and are stored in the current session metadata, so a
32
+ resumed session continues with its most recently selected model. A session-only `/model MODEL`
33
+ does not change the user configuration or affect new sessions.
34
+
13
35
  Example user configuration:
14
36
 
15
37
  ```toml
@@ -21,6 +43,7 @@ cell_timeout = 120
21
43
  command_timeout = 60
22
44
 
23
45
  [ui]
46
+ theme = "atom-one-dark"
24
47
  frontend = "tui" # "tui" or "console"
25
48
  markdown = true
26
49
  stream_shell = true
@@ -60,12 +83,20 @@ A user-configured `permission_rules` array replaces the default rule array. Copy
60
83
  default you still want before adding overrides. Hard secret, destructive-shell, and plan-mode
61
84
  gates remain enforced in code.
62
85
 
63
- Inspect the resolved configuration with:
86
+ Inspect the resolved configuration from the CLI or inside an interactive session. `/config`
87
+ lists every nested path, while an optional path scopes the output. Values whose names look like
88
+ credentials are redacted.
64
89
 
65
90
  ```bash
66
91
  noah config show .
67
92
  ```
68
93
 
94
+ ```text
95
+ /config
96
+ /config summarization
97
+ /config updates.auto_install
98
+ ```
99
+
69
100
  Model and provider configuration follows NOOA conventions, including its model registry,
70
101
  environment variables, and configuration under `~/.config/nooa/`.
71
102
 
@@ -33,8 +33,10 @@ Before the first release, configure a PyPI Trusted Publisher for package `noah-c
33
33
  `skundu42`, repository `noah-code`, workflow `release.yml`, and GitHub environment `pypi`. The
34
34
  GitHub environment permits deployments only from tags matching `v*`.
35
35
 
36
- For every release, update the version in `pyproject.toml` and `src/noah_code/__init__.py`, commit
37
- and push the change, then push a matching annotated tag. For example:
36
+ For every release, update the version in `pyproject.toml`, `src/noah_code/__init__.py`, and
37
+ `uv.lock`. Add curated notes at `docs/releases/vX.Y.Z.md`; the release workflow uses that file
38
+ when it exists and otherwise falls back to generated notes. Commit and push the change, then push
39
+ a matching annotated tag. For example:
38
40
 
39
41
  ```bash
40
42
  git tag -a v0.2.0 -m "Noah Code v0.2.0"
@@ -0,0 +1,96 @@
1
+ # Interactive interface and sessions
2
+
3
+ ## TUI keybindings
4
+
5
+ | Key | Action |
6
+ |-----|--------|
7
+ | `Enter` | Send the current message |
8
+ | `Shift+Enter` | Insert a newline without sending |
9
+ | `Ctrl+P` | Open the command palette |
10
+ | `Ctrl+O` | Open the session picker |
11
+ | `Ctrl+N` | Start a new session |
12
+ | `Ctrl+C` | Cancel the active turn; press twice while idle to quit |
13
+ | `Ctrl+Q` | Quit |
14
+ | `F1` or `?` | Show help |
15
+ | `F2` | Open recent activity and full captured output |
16
+ | `F3` | Open paginated persisted conversation history |
17
+ | `End` | Return to live transcript output and clear the new-output counter |
18
+
19
+ At an approval prompt, press `1` to approve once, `2` to remember the approval for the current
20
+ session, or `3`/`Esc` to reject it.
21
+
22
+ The TUI uses Atom One Dark by default. Its conversation-first layout adapts to terminal size. At
23
+ 110 columns or wider, a context rail shows the session, active tool, and up to six open or blocked
24
+ todos. Narrow terminals retain the transcript, live activity, suggestions, and composer without
25
+ the rail. Terminals 25 rows high or shorter use compact spacing.
26
+
27
+ Type `/` in the composer to open the inline command list; the list remains visible and filters
28
+ continuously. Use `Up`/`Down` to highlight a command, `Tab` to complete it, and `Esc` to close the
29
+ list. `Enter` always sends the current command. Typing `/config` expands the list to every resolved
30
+ configuration path and its current redacted value.
31
+
32
+ Tool and shell output is batched into a live execution panel instead of forcing one full-screen
33
+ redraw for every chunk. When the tool finishes, the panel becomes a compact transcript record with
34
+ outcome, duration, and output-line count. `F2` retains the latest 100 activity records, bounded by
35
+ the configured `max_output_chars` per activity.
36
+
37
+ ## Built-in slash commands
38
+
39
+ | Command | Purpose |
40
+ |---------|---------|
41
+ | `/help` | Show interactive help |
42
+ | `/config [PATH]` | Show every resolved setting or one nested path |
43
+ | `/mode` | Show or switch between `build` and `plan` |
44
+ | `/model [MODEL]` | Show or switch the active session model |
45
+ | `/model --global MODEL` | Switch the active model and save it as the default for all repositories |
46
+ | `/session`, `/sessions`, `/new`, `/continue` | Inspect, switch, create, and resume sessions |
47
+ | `/compact` | Summarize older conversation context |
48
+ | `/todos` | Show the agent's current task list |
49
+ | `/status`, `/diff` | Inspect the repository |
50
+ | `/undo`, `/redo` | Restore or reapply journaled file edits |
51
+ | `/skills` | Inspect discovered skills |
52
+ | `/trace` | Show the active tracing destination |
53
+ | `/exit` | End the session |
54
+
55
+ Examples:
56
+
57
+ ```text
58
+ /config
59
+ /config ui
60
+ /config ui.theme
61
+ ```
62
+
63
+ Use `/mode build|plan` and `/model MODEL` for settings that support live switching. Use
64
+ `/model --global MODEL` to make that model the cross-repository default. Other settings are
65
+ resolved at startup and should be changed in the user configuration file.
66
+
67
+ `/model MODEL` takes effect before the next turn and persists in the current session, including
68
+ after resuming it. Other sessions and repositories keep their existing defaults unless the
69
+ `--global` form is used.
70
+
71
+ ## Session management
72
+
73
+ ```bash
74
+ noah sessions list .
75
+ noah sessions show SESSION_ID
76
+ noah sessions delete SESSION_ID
77
+ ```
78
+
79
+ Each session has a NOOA-backed SQLite database plus metadata for its workspace identity, model,
80
+ mode, title, remembered permission rules, todos, and edit journal. Session files are created with
81
+ private filesystem permissions and cannot accidentally be resumed against another workspace.
82
+
83
+ The latest 50 persisted user, agent, summary, error, and activity events are restored after the
84
+ TUI's first paint. `F3` loads older history in read-only pages of 50, so resuming a long session
85
+ does not delay input or load the entire database into the transcript.
86
+
87
+ Long conversations use token-budget summarization while preserving recent messages. Force
88
+ compaction with `/compact`.
89
+
90
+ File edits made through workspace tools record pre-images and post-images with content hashes:
91
+
92
+ - `/undo` refuses to overwrite a file changed concurrently by the user.
93
+ - A failed multi-file restoration is rolled back.
94
+ - `/redo` survives process restarts.
95
+ - A turn that ran a mutating shell command is marked as not fully reversible because the command
96
+ may have changed files outside the journal.
@@ -0,0 +1,25 @@
1
+ # Noah Code v0.1.0
2
+
3
+ The first public release of Noah Code: a terminal coding agent built on NVIDIA OO Agents (NOOA).
4
+
5
+ ## Highlights
6
+
7
+ - One-command installation on macOS and Linux with an isolated, managed Python runtime.
8
+ - Interactive Textual and classic console interfaces, plus one-shot `noah run` automation.
9
+ - Repository exploration, guarded file editing, streamed shell commands, and Git inspection.
10
+ - Build and read-only plan modes with ordered allow, ask, and deny permission rules.
11
+ - Workspace-scoped persistent sessions with todos, context compaction, undo, and redo.
12
+ - Support for repository instructions, custom slash commands, skills, MCP servers, tracing, and
13
+ multiple LLM providers through NOOA and LiteLLM.
14
+ - Automatic PyPI update checks and a release pipeline with cross-platform CI, trusted publishing,
15
+ checksums, and artifact attestations.
16
+
17
+ ## Install
18
+
19
+ ```bash
20
+ curl -LsSf https://raw.githubusercontent.com/skundu42/noah-code/main/install.sh | sh
21
+ ```
22
+
23
+ Then open a repository with `noah .`.
24
+
25
+ **Full changelog:** https://github.com/skundu42/noah-code/commits/v0.1.0
@@ -0,0 +1,40 @@
1
+ # Noah Code v0.1.1
2
+
3
+ This release makes the interactive experience faster, clearer, and easier to configure across
4
+ repositories.
5
+
6
+ ## Highlights
7
+
8
+ - Added first-run AI model setup with a private global default in
9
+ `~/.config/noah-code/config.toml`.
10
+ - Added `/model MODEL` for persistent session-level switching and `/model --global MODEL` for the
11
+ cross-repository default.
12
+ - Made Atom One Dark the default theme and added live slash-command and configuration discovery.
13
+ - Added an adaptive conversation-first cockpit with a session and todo rail on wide terminals.
14
+ - Grouped tool execution into a live activity ledger that compacts successful work while retaining
15
+ details through `F2`.
16
+ - Added paginated, read-only conversation history through `F3` when resuming sessions.
17
+ - Added keyboard-driven command and session pickers, safer approval cards, scroll locking, and new
18
+ output indicators.
19
+ - Batched high-volume shell events and bounded display history; the 2,000-chunk local benchmark
20
+ improved from approximately 454 ms to 123 ms.
21
+
22
+ ## Reliability
23
+
24
+ - Added structured activity correlation and isolated SQLite history reads.
25
+ - Expanded responsive-layout, event batching, history, model configuration, markup safety, and
26
+ session persistence coverage to 92 tests.
27
+ - Corrected GitHub release repository context and added curated release-note support to the release
28
+ workflow.
29
+
30
+ ## Upgrade
31
+
32
+ Existing managed installations can run:
33
+
34
+ ```bash
35
+ noah update
36
+ ```
37
+
38
+ New installations can use the one-command installer from the README.
39
+
40
+ **Full changelog:** https://github.com/skundu42/noah-code/compare/v0.1.0...v0.1.1
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "noah-code"
3
- version = "0.1.0"
3
+ version = "0.1.1"
4
4
  description = "Noah Code terminal coding agent, built on NVIDIA OO Agents (NOOA)"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12,<3.14"
@@ -1,3 +1,3 @@
1
1
  """Noah Code: terminal coding harness on NVIDIA OO Agents."""
2
2
 
3
- __version__ = "0.1.0"
3
+ __version__ = "0.1.1"
@@ -3,13 +3,20 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import asyncio
6
+ import os
6
7
  import sys
7
8
  from typing import Any, Literal
8
9
 
9
10
  import click
10
11
 
11
12
  from noah_code import __version__
12
- from noah_code.config import config_sources, load_config
13
+ from noah_code.config import (
14
+ NoahCodeConfig,
15
+ config_sources,
16
+ load_config,
17
+ save_user_default_model,
18
+ user_default_model,
19
+ )
13
20
  from noah_code.host import AgentHost
14
21
  from noah_code.sessions import SessionError, SessionStore
15
22
  from noah_code.ui.console import ConsoleUI
@@ -41,10 +48,50 @@ def _common_options(fn): # noqa: ANN001
41
48
  fn = click.option(
42
49
  "--auto", is_flag=True, help="Auto-approve ask decisions (never overrides deny)"
43
50
  )(fn)
44
- fn = click.option("--model", "model", default=None, help="Override model alias")(fn)
51
+ fn = click.option(
52
+ "--model", "model", default=None, help="Override the model for this launch"
53
+ )(fn)
45
54
  return fn
46
55
 
47
56
 
57
+ def _configure_first_run_model(model_override: str | None) -> str | None:
58
+ """Prompt once for a cross-repository default before an interactive launch."""
59
+
60
+ if user_default_model() is not None:
61
+ return model_override
62
+
63
+ if model_override is not None:
64
+ path = save_user_default_model(model_override)
65
+ click.echo(f"Saved {model_override} as the default model in {path}.", err=True)
66
+ return model_override
67
+
68
+ suggested = os.environ.get("NOAH_CODE_MODEL") or NoahCodeConfig().model
69
+ click.secho("Noah Code · first-run model setup", fg="bright_blue", bold=True, err=True)
70
+ click.echo(
71
+ "Choose the model Noah Code should use by default in every repository.",
72
+ err=True,
73
+ )
74
+ click.echo(
75
+ "Enter a LiteLLM model name or a configured NOOA model alias. "
76
+ "Repository config, environment variables, and --model can override it later.",
77
+ err=True,
78
+ )
79
+ click.echo(
80
+ "You can switch models between turns with /model MODEL, without starting a new session.",
81
+ err=True,
82
+ )
83
+
84
+ while True:
85
+ selected = click.prompt("Default model", default=suggested, show_default=True).strip()
86
+ try:
87
+ path = save_user_default_model(selected)
88
+ except ValueError as exc:
89
+ click.echo(f"Invalid model: {exc}", err=True)
90
+ continue
91
+ click.echo(f"Saved the global default in {path}.", err=True)
92
+ return selected
93
+
94
+
48
95
  @click.command("noah-code")
49
96
  @click.version_option(__version__, prog_name="noah-code")
50
97
  @click.argument("path", required=False, type=click.Path())
@@ -327,6 +374,15 @@ async def _interactive(
327
374
  use_console: bool,
328
375
  unsafe_inprocess_code_execution: bool,
329
376
  ) -> int:
377
+ try:
378
+ model = _configure_first_run_model(model)
379
+ except (OSError, ValueError) as exc:
380
+ click.echo(f"error: first-run model setup failed: {exc}", err=True)
381
+ return EXIT_CONFIG
382
+ except click.Abort:
383
+ click.echo("error: first-run model setup was cancelled", err=True)
384
+ return EXIT_CONFIG
385
+
330
386
  frontend: Literal["tui", "console"] | None = "console" if use_console else None
331
387
  prepared, code = await _prepare(
332
388
  path=path,
@@ -399,8 +455,6 @@ async def _run_once(
399
455
 
400
456
  def _launched_as_nc() -> bool:
401
457
  """Detect whether the process was launched as `nc` or `noah`."""
402
- import os
403
-
404
458
  name = os.path.basename(sys.argv[0]) if sys.argv else ""
405
459
  return name in {"nc", "nc.exe", "noah", "noah.exe"}
406
460
 
@@ -408,8 +462,6 @@ def _launched_as_nc() -> bool:
408
462
  def main(argv: list[str] | None = None) -> None:
409
463
  """Dispatch: subcommands via group; otherwise interactive with optional PATH."""
410
464
  args = list(sys.argv[1:] if argv is None else argv)
411
- import os
412
-
413
465
  base = os.path.basename(sys.argv[0]) if sys.argv else "noah-code"
414
466
  prog = base if base in {"nc", "noah", "noah-code"} else "noah-code"
415
467
  if args and args[0] in SUBCOMMANDS:
@@ -0,0 +1,173 @@
1
+ """Slash-command and host command helpers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from dataclasses import dataclass
7
+ from typing import Any
8
+
9
+ from noah_code.custom_commands import CustomCommand
10
+
11
+
12
+ @dataclass(frozen=True)
13
+ class CommandSpec:
14
+ name: str
15
+ description: str
16
+ usage: str | None = None
17
+ host_only: bool = False
18
+
19
+ @property
20
+ def invocation(self) -> str:
21
+ return f"/{self.usage or self.name}"
22
+
23
+
24
+ @dataclass(frozen=True)
25
+ class CommandSuggestion:
26
+ invocation: str
27
+ description: str
28
+
29
+
30
+ BUILTIN_COMMANDS: list[CommandSpec] = [
31
+ CommandSpec("help", "Show available commands", host_only=True),
32
+ CommandSpec("config", "Show every resolved setting or one path", "config [PATH]", True),
33
+ CommandSpec("mode", "Show or switch the active mode", "mode [build|plan]", True),
34
+ CommandSpec("model", "Show or switch the model for this session", "model [MODEL]", True),
35
+ CommandSpec("session", "Show current session", host_only=True),
36
+ CommandSpec("sessions", "List or switch sessions", "sessions [SESSION_ID]", True),
37
+ CommandSpec("new", "Start a new session", host_only=True),
38
+ CommandSpec("continue", "Resume most recent session", host_only=True),
39
+ CommandSpec("compact", "Trigger history summarization", host_only=True),
40
+ CommandSpec("todos", "Show todo list", host_only=True),
41
+ CommandSpec("status", "Show mode/model/session/context", host_only=True),
42
+ CommandSpec("diff", "Show git diff", host_only=True),
43
+ CommandSpec("undo", "Undo last WorkspaceTools turn", host_only=True),
44
+ CommandSpec("redo", "Redo last undone turn", host_only=True),
45
+ CommandSpec("skills", "Show discovered/activated skills", host_only=True),
46
+ CommandSpec("trace", "Show tracing destination", host_only=True),
47
+ CommandSpec("exit", "Exit Noah Code", host_only=True),
48
+ ]
49
+
50
+
51
+ _SECRET_CONFIG_PARTS = (
52
+ "api_key",
53
+ "authorization",
54
+ "credential",
55
+ "password",
56
+ "private_key",
57
+ "secret",
58
+ "token",
59
+ )
60
+
61
+
62
+ def _config_mapping(config: Any) -> dict[str, Any]:
63
+ if hasattr(config, "model_dump"):
64
+ value = config.model_dump(mode="json")
65
+ elif isinstance(config, dict):
66
+ value = config
67
+ else:
68
+ raise TypeError("config must be a Pydantic model or mapping")
69
+ if not isinstance(value, dict):
70
+ raise TypeError("config did not resolve to a mapping")
71
+ return value
72
+
73
+
74
+ def _redacted_config_value(path: str, value: Any) -> Any:
75
+ normalized = path.lower().replace("-", "_")
76
+ if any(part in normalized for part in _SECRET_CONFIG_PARTS):
77
+ return "***"
78
+ return value
79
+
80
+
81
+ def _flatten_config(value: Any, prefix: str = "") -> list[tuple[str, Any]]:
82
+ if isinstance(value, dict) and value:
83
+ rows: list[tuple[str, Any]] = []
84
+ for key, child in value.items():
85
+ path = f"{prefix}.{key}" if prefix else str(key)
86
+ rows.extend(_flatten_config(child, path))
87
+ return rows
88
+ return [(prefix, _redacted_config_value(prefix, value))]
89
+
90
+
91
+ def config_entries(config: Any, path: str = "") -> list[tuple[str, str]]:
92
+ """Return redacted leaf settings, optionally scoped to a dotted path."""
93
+
94
+ rows = _flatten_config(_config_mapping(config))
95
+ query = path.strip().lower().strip(".")
96
+ if query:
97
+ rows = [
98
+ (key, value)
99
+ for key, value in rows
100
+ if key.lower() == query or key.lower().startswith(f"{query}.")
101
+ ]
102
+ return [(key, json.dumps(value, ensure_ascii=False, sort_keys=True)) for key, value in rows]
103
+
104
+
105
+ def config_text(config: Any, path: str = "") -> str:
106
+ rows = config_entries(config, path)
107
+ if not rows:
108
+ raise KeyError(path)
109
+ width = max(len(key) for key, _value in rows)
110
+ title = f"Resolved configuration ({path.strip()}):" if path.strip() else "Resolved configuration:"
111
+ return "\n".join([title, "", *(f" {key:<{width}} {value}" for key, value in rows)])
112
+
113
+
114
+ def all_command_suggestions(
115
+ custom: dict[str, CustomCommand] | None = None,
116
+ ) -> list[CommandSuggestion]:
117
+ suggestions = [
118
+ CommandSuggestion(command.invocation, command.description) for command in BUILTIN_COMMANDS
119
+ ]
120
+ suggestions.append(
121
+ CommandSuggestion("/model --global MODEL", "Set the default model for every repository")
122
+ )
123
+ if custom:
124
+ suggestions.extend(
125
+ CommandSuggestion(f"/{name} [ARGS]", command.description)
126
+ for name, command in sorted(custom.items())
127
+ )
128
+ return suggestions
129
+
130
+
131
+ def config_command_suggestions(config: Any) -> list[CommandSuggestion]:
132
+ suggestions = []
133
+ for path, value in config_entries(config):
134
+ display_value = value if len(value) <= 80 else f"{value[:77]}…"
135
+ suggestions.append(CommandSuggestion(f"/config {path}", f"current: {display_value}"))
136
+ return suggestions
137
+
138
+
139
+ def help_text(custom: dict[str, CustomCommand] | None = None) -> str:
140
+ lines = ["Noah Code commands:", ""]
141
+ for cmd in BUILTIN_COMMANDS:
142
+ lines.append(f" {cmd.invocation:<28} {cmd.description}")
143
+ lines.append(f" {'/model --global MODEL':<28} Set the default model for every repository")
144
+ if custom:
145
+ lines.append("")
146
+ lines.append("Custom commands:")
147
+ for name, c in sorted(custom.items()):
148
+ lines.append(f" /{name:<12} {c.description} ({c.source})")
149
+ lines.append("")
150
+ lines.append(
151
+ "File-journal undo only covers WorkspaceTools edits, not arbitrary shell mutations."
152
+ )
153
+ return "\n".join(lines)
154
+
155
+
156
+ def all_command_names(custom: dict[str, CustomCommand] | None = None) -> list[str]:
157
+ names = [f"/{c.name}" for c in BUILTIN_COMMANDS]
158
+ if custom:
159
+ names.extend(f"/{n}" for n in sorted(custom))
160
+ return names
161
+
162
+
163
+ def parse_slash(text: str) -> tuple[str, str] | None:
164
+ stripped = text.strip()
165
+ if not stripped.startswith("/"):
166
+ return None
167
+ body = stripped[1:]
168
+ if not body:
169
+ return None
170
+ if " " in body:
171
+ name, rest = body.split(" ", 1)
172
+ return name.lower(), rest.strip()
173
+ return body.lower(), ""