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.
- {noah_code-0.1.0 → noah_code-0.1.1}/.github/workflows/release.yml +13 -1
- {noah_code-0.1.0 → noah_code-0.1.1}/PKG-INFO +15 -6
- {noah_code-0.1.0 → noah_code-0.1.1}/README.md +14 -5
- {noah_code-0.1.0 → noah_code-0.1.1}/docs/configuration.md +32 -1
- {noah_code-0.1.0 → noah_code-0.1.1}/docs/development.md +4 -2
- noah_code-0.1.1/docs/interactive-reference.md +96 -0
- noah_code-0.1.1/docs/releases/v0.1.0.md +25 -0
- noah_code-0.1.1/docs/releases/v0.1.1.md +40 -0
- {noah_code-0.1.0 → noah_code-0.1.1}/pyproject.toml +1 -1
- {noah_code-0.1.0 → noah_code-0.1.1}/src/noah_code/__init__.py +1 -1
- {noah_code-0.1.0 → noah_code-0.1.1}/src/noah_code/cli.py +58 -6
- noah_code-0.1.1/src/noah_code/commands.py +173 -0
- {noah_code-0.1.0 → noah_code-0.1.1}/src/noah_code/config.py +60 -0
- {noah_code-0.1.0 → noah_code-0.1.1}/src/noah_code/event_bridge.py +37 -10
- {noah_code-0.1.0 → noah_code-0.1.1}/src/noah_code/host.py +60 -7
- {noah_code-0.1.0 → noah_code-0.1.1}/src/noah_code/sessions.py +62 -0
- noah_code-0.1.1/src/noah_code/ui/textual.css +244 -0
- noah_code-0.1.1/src/noah_code/ui/textual_app.py +1153 -0
- {noah_code-0.1.0 → noah_code-0.1.1}/tests/test_cli.py +43 -1
- noah_code-0.1.1/tests/test_config.py +121 -0
- noah_code-0.1.1/tests/test_event_bridge.py +51 -0
- {noah_code-0.1.0 → noah_code-0.1.1}/tests/test_host.py +117 -0
- {noah_code-0.1.0 → noah_code-0.1.1}/tests/test_sessions.py +56 -0
- noah_code-0.1.1/tests/test_textual_tui.py +312 -0
- {noah_code-0.1.0 → noah_code-0.1.1}/uv.lock +1 -1
- noah_code-0.1.0/docs/interactive-reference.md +0 -55
- noah_code-0.1.0/src/noah_code/commands.py +0 -70
- noah_code-0.1.0/src/noah_code/ui/textual.css +0 -9
- noah_code-0.1.0/src/noah_code/ui/textual_app.py +0 -435
- noah_code-0.1.0/tests/test_config.py +0 -58
- noah_code-0.1.0/tests/test_textual_tui.py +0 -93
- {noah_code-0.1.0 → noah_code-0.1.1}/.github/workflows/ci.yml +0 -0
- {noah_code-0.1.0 → noah_code-0.1.1}/.gitignore +0 -0
- {noah_code-0.1.0 → noah_code-0.1.1}/docs/extensions.md +0 -0
- {noah_code-0.1.0 → noah_code-0.1.1}/docs/security.md +0 -0
- {noah_code-0.1.0 → noah_code-0.1.1}/install.sh +0 -0
- {noah_code-0.1.0 → noah_code-0.1.1}/src/noah_code/__main__.py +0 -0
- {noah_code-0.1.0 → noah_code-0.1.1}/src/noah_code/agent.py +0 -0
- {noah_code-0.1.0 → noah_code-0.1.1}/src/noah_code/approvals.py +0 -0
- {noah_code-0.1.0 → noah_code-0.1.1}/src/noah_code/custom_commands.py +0 -0
- {noah_code-0.1.0 → noah_code-0.1.1}/src/noah_code/events.py +0 -0
- {noah_code-0.1.0 → noah_code-0.1.1}/src/noah_code/macos_sandbox.py +0 -0
- {noah_code-0.1.0 → noah_code-0.1.1}/src/noah_code/mcp_setup.py +0 -0
- {noah_code-0.1.0 → noah_code-0.1.1}/src/noah_code/permissions.py +0 -0
- {noah_code-0.1.0 → noah_code-0.1.1}/src/noah_code/skills_setup.py +0 -0
- {noah_code-0.1.0 → noah_code-0.1.1}/src/noah_code/snapshots.py +0 -0
- {noah_code-0.1.0 → noah_code-0.1.1}/src/noah_code/tools/__init__.py +0 -0
- {noah_code-0.1.0 → noah_code-0.1.1}/src/noah_code/tools/git_tools.py +0 -0
- {noah_code-0.1.0 → noah_code-0.1.1}/src/noah_code/tools/workspace_tools.py +0 -0
- {noah_code-0.1.0 → noah_code-0.1.1}/src/noah_code/ui/__init__.py +0 -0
- {noah_code-0.1.0 → noah_code-0.1.1}/src/noah_code/ui/console.py +0 -0
- {noah_code-0.1.0 → noah_code-0.1.1}/src/noah_code/ui/protocol.py +0 -0
- {noah_code-0.1.0 → noah_code-0.1.1}/src/noah_code/updates.py +0 -0
- {noah_code-0.1.0 → noah_code-0.1.1}/src/noah_code/workspace.py +0 -0
- {noah_code-0.1.0 → noah_code-0.1.1}/tests/test_agent_security.py +0 -0
- {noah_code-0.1.0 → noah_code-0.1.1}/tests/test_custom_commands.py +0 -0
- {noah_code-0.1.0 → noah_code-0.1.1}/tests/test_event_bridge_and_shell.py +0 -0
- {noah_code-0.1.0 → noah_code-0.1.1}/tests/test_installer.py +0 -0
- {noah_code-0.1.0 → noah_code-0.1.1}/tests/test_permissions.py +0 -0
- {noah_code-0.1.0 → noah_code-0.1.1}/tests/test_run_exit.py +0 -0
- {noah_code-0.1.0 → noah_code-0.1.1}/tests/test_snapshots.py +0 -0
- {noah_code-0.1.0 → noah_code-0.1.1}/tests/test_summarization.py +0 -0
- {noah_code-0.1.0 → noah_code-0.1.1}/tests/test_updates.py +0 -0
- {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:
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
37
|
-
|
|
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
|
|
@@ -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
|
|
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(
|
|
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(), ""
|