heyarchie 1.0.0__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.
- heyarchie-1.0.0/.gitignore +217 -0
- heyarchie-1.0.0/PKG-INFO +74 -0
- heyarchie-1.0.0/README.md +36 -0
- heyarchie-1.0.0/examples/native_desktop_smoke.py +214 -0
- heyarchie-1.0.0/heyarchie/__init__.py +197 -0
- heyarchie-1.0.0/heyarchie/_http.py +316 -0
- heyarchie-1.0.0/heyarchie/adapters/__init__.py +26 -0
- heyarchie-1.0.0/heyarchie/adapters/_base.py +218 -0
- heyarchie-1.0.0/heyarchie/adapters/langchain.py +68 -0
- heyarchie-1.0.0/heyarchie/adapters/langgraph.py +133 -0
- heyarchie-1.0.0/heyarchie/adapters/openai_agents.py +99 -0
- heyarchie-1.0.0/heyarchie/cli/__init__.py +14 -0
- heyarchie-1.0.0/heyarchie/cli/__main__.py +35 -0
- heyarchie-1.0.0/heyarchie/cli/_client.py +179 -0
- heyarchie-1.0.0/heyarchie/cli/auth.py +101 -0
- heyarchie-1.0.0/heyarchie/cli/cmds/__init__.py +1 -0
- heyarchie-1.0.0/heyarchie/cli/cmds/login.py +46 -0
- heyarchie-1.0.0/heyarchie/cli/cmds/skills.py +233 -0
- heyarchie-1.0.0/heyarchie/cli/cmds/workstreams.py +298 -0
- heyarchie-1.0.0/heyarchie/client.py +313 -0
- heyarchie-1.0.0/heyarchie/errors.py +75 -0
- heyarchie-1.0.0/heyarchie/models.py +455 -0
- heyarchie-1.0.0/heyarchie/resources/__init__.py +39 -0
- heyarchie-1.0.0/heyarchie/resources/conversations.py +260 -0
- heyarchie-1.0.0/heyarchie/resources/documents.py +81 -0
- heyarchie-1.0.0/heyarchie/resources/drafts.py +121 -0
- heyarchie-1.0.0/heyarchie/resources/journal_entries.py +98 -0
- heyarchie-1.0.0/heyarchie/resources/keys.py +107 -0
- heyarchie-1.0.0/heyarchie/resources/messages.py +120 -0
- heyarchie-1.0.0/heyarchie/resources/reports.py +82 -0
- heyarchie-1.0.0/heyarchie/resources/search.py +81 -0
- heyarchie-1.0.0/heyarchie/resources/skills.py +381 -0
- heyarchie-1.0.0/heyarchie/resources/standards.py +76 -0
- heyarchie-1.0.0/heyarchie/resources/triggers.py +130 -0
- heyarchie-1.0.0/heyarchie/resources/workstreams.py +717 -0
- heyarchie-1.0.0/pyproject.toml +57 -0
- heyarchie-1.0.0/tests/__init__.py +0 -0
- heyarchie-1.0.0/tests/cli/__init__.py +0 -0
- heyarchie-1.0.0/tests/cli/test_auth.py +89 -0
- heyarchie-1.0.0/tests/cli/test_skills.py +76 -0
- heyarchie-1.0.0/tests/test_client.py +204 -0
- heyarchie-1.0.0/tests/test_messages.py +228 -0
- heyarchie-1.0.0/tests/test_streaming.py +110 -0
- heyarchie-1.0.0/tests/test_workstreams_cycles.py +206 -0
- heyarchie-1.0.0/tests/test_workstreams_namespaces.py +110 -0
- heyarchie-1.0.0/uv.lock +423 -0
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
# Agent 365 CLI generated config — contains plaintext client secret on non-Windows
|
|
2
|
+
a365.generated.config.json
|
|
3
|
+
# Root-level a365.config.json auto-created by CLI; canonical config lives at apps/api/a365.config.json
|
|
4
|
+
/a365.config.json
|
|
5
|
+
|
|
6
|
+
# Node.js dependencies
|
|
7
|
+
node_modules/
|
|
8
|
+
**/node_modules/
|
|
9
|
+
|
|
10
|
+
# pnpm
|
|
11
|
+
.pnpm-store/
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# Linear Integration
|
|
15
|
+
.env
|
|
16
|
+
.env.*
|
|
17
|
+
!.env.example
|
|
18
|
+
!.env.vault.example
|
|
19
|
+
!.env.test
|
|
20
|
+
!.env.observability.example
|
|
21
|
+
linear-tickets.json
|
|
22
|
+
linear-issue-mapping.json
|
|
23
|
+
*.tickets.json
|
|
24
|
+
.cloudflare_credentials
|
|
25
|
+
|
|
26
|
+
# Deleted orchestrator — V2 killed, V3 renamed to orchestrator/. Never re-add.
|
|
27
|
+
**/orchestrator_v3/
|
|
28
|
+
**/orchestrator_v2/
|
|
29
|
+
|
|
30
|
+
# Workstream Studio build artifacts + Playwright run output. Never commit.
|
|
31
|
+
apps/workstream-studio/dist/
|
|
32
|
+
apps/workstream-studio/e2e/screenshots/
|
|
33
|
+
apps/workstream-studio/e2e/test-results/
|
|
34
|
+
apps/workstream-studio/playwright-report/
|
|
35
|
+
apps/workstream-studio/test-results/
|
|
36
|
+
|
|
37
|
+
# Python Environment (UV + Ruff)
|
|
38
|
+
.venv/
|
|
39
|
+
venv/
|
|
40
|
+
__pycache__/
|
|
41
|
+
*.pyc
|
|
42
|
+
*.pyo
|
|
43
|
+
*.pyd
|
|
44
|
+
.Python
|
|
45
|
+
pip-log.txt
|
|
46
|
+
pip-delete-this-directory.txt
|
|
47
|
+
.pytest_cache/
|
|
48
|
+
.mypy_cache/
|
|
49
|
+
.ruff_cache/
|
|
50
|
+
htmlcov/
|
|
51
|
+
.coverage
|
|
52
|
+
.coverage.*
|
|
53
|
+
coverage.xml
|
|
54
|
+
*.cover
|
|
55
|
+
.hypothesis/
|
|
56
|
+
|
|
57
|
+
# File Ingestion Service - Test files and uploads
|
|
58
|
+
src/services/file-ingestion/test_*.py
|
|
59
|
+
src/services/file-ingestion/test_*.json
|
|
60
|
+
src/services/file-ingestion/data/uploads/
|
|
61
|
+
src/services/file-ingestion/seeds/
|
|
62
|
+
src/services/file-ingestion/examples/
|
|
63
|
+
|
|
64
|
+
# Test scripts and temporary files (root level only, not in tests/ directories)
|
|
65
|
+
/test_*.py
|
|
66
|
+
/test-*.sh
|
|
67
|
+
/start-*.sh
|
|
68
|
+
/stop-*.sh
|
|
69
|
+
|
|
70
|
+
# macOS
|
|
71
|
+
.DS_Store
|
|
72
|
+
|
|
73
|
+
# Vite build artifacts
|
|
74
|
+
**/node_modules/.vite/
|
|
75
|
+
**/node_modules/.vite/deps_temp_*
|
|
76
|
+
|
|
77
|
+
# Build artifacts
|
|
78
|
+
/dist
|
|
79
|
+
apps/teams/dist/
|
|
80
|
+
|
|
81
|
+
# .NET build output
|
|
82
|
+
**/bin/
|
|
83
|
+
**/obj/
|
|
84
|
+
|
|
85
|
+
# Terraform local environments
|
|
86
|
+
/terraform/environments/dev
|
|
87
|
+
|
|
88
|
+
# Playwright test results and reports
|
|
89
|
+
test-results/
|
|
90
|
+
playwright-report/
|
|
91
|
+
|
|
92
|
+
# Claude Flow generated files
|
|
93
|
+
.claude/settings.local.json
|
|
94
|
+
.mcp.json
|
|
95
|
+
claude-flow.config.json
|
|
96
|
+
.swarm/
|
|
97
|
+
.hive-mind/
|
|
98
|
+
.claude-flow/
|
|
99
|
+
memory/
|
|
100
|
+
coordination/
|
|
101
|
+
# Allow the orchestrator memory module (Python package, not claude-flow data)
|
|
102
|
+
!apps/api/src/orchestrator/memory/
|
|
103
|
+
# Same module after the V4 consolidation move (Epic 14 issue #3517).
|
|
104
|
+
!apps/api/src/orchestrator/v4/memory/
|
|
105
|
+
# Allow the scoped memory services module (Epic aemm00l)
|
|
106
|
+
!apps/api/src/services/memory/
|
|
107
|
+
# Allow memory unit tests
|
|
108
|
+
!tests/unit/memory/
|
|
109
|
+
!apps/api/tests/orchestrator/memory/
|
|
110
|
+
memory/claude-flow-data.json
|
|
111
|
+
memory/sessions/*
|
|
112
|
+
!memory/sessions/README.md
|
|
113
|
+
memory/agents/*
|
|
114
|
+
!memory/agents/README.md
|
|
115
|
+
coordination/memory_bank/*
|
|
116
|
+
coordination/subtasks/*
|
|
117
|
+
coordination/orchestration/*
|
|
118
|
+
*.db
|
|
119
|
+
*.db-journal
|
|
120
|
+
*.db-wal
|
|
121
|
+
*.sqlite
|
|
122
|
+
*.sqlite-journal
|
|
123
|
+
*.sqlite-wal
|
|
124
|
+
claude-flow
|
|
125
|
+
# Removed Windows wrapper files per user request
|
|
126
|
+
hive-mind-prompt-*.txt
|
|
127
|
+
|
|
128
|
+
# Infrastructure files with secrets (do not commit)
|
|
129
|
+
infrastructure/docker/migration-job.yaml
|
|
130
|
+
|
|
131
|
+
# Playwright auth state (generated at runtime, contains session tokens)
|
|
132
|
+
tests/e2e/.auth/user.json
|
|
133
|
+
tests/smoke/.auth/user.json
|
|
134
|
+
|
|
135
|
+
# Frontend lock file
|
|
136
|
+
apps/frontend/package-lock.json
|
|
137
|
+
|
|
138
|
+
# Martha test workflow tracker
|
|
139
|
+
.test-tracker.json
|
|
140
|
+
|
|
141
|
+
# Worktree-specific configuration
|
|
142
|
+
.worktree-config.json
|
|
143
|
+
|
|
144
|
+
# Test reports (generated by pytest)
|
|
145
|
+
apps/api/reports/
|
|
146
|
+
**/reports/*.xml
|
|
147
|
+
**/reports/*.html
|
|
148
|
+
.env.copilot-tests
|
|
149
|
+
|
|
150
|
+
# Sidebar v2 sample files (do not commit)
|
|
151
|
+
.archie-sidebar-v2-samples/
|
|
152
|
+
~/.archie-sidebar-v2-samples/
|
|
153
|
+
|
|
154
|
+
# GitHub Actions local secrets
|
|
155
|
+
.act.secrets
|
|
156
|
+
|
|
157
|
+
# CEA manifest packages (built from manifest source files)
|
|
158
|
+
apps/archie-m365/manifest/archie-cea-*.zip
|
|
159
|
+
apps/archie-m365/manifest-da/archie-da-*.zip
|
|
160
|
+
apps/api/static/archie-cea-*.zip
|
|
161
|
+
|
|
162
|
+
# OpenAPI Generator CLI config (tool-specific)
|
|
163
|
+
openapitools.json
|
|
164
|
+
|
|
165
|
+
# Smoke test tracking
|
|
166
|
+
.smoke-tracker.json
|
|
167
|
+
|
|
168
|
+
# Render environment file (contains secrets - DO NOT COMMIT)
|
|
169
|
+
env.render
|
|
170
|
+
*.render.env
|
|
171
|
+
|
|
172
|
+
# UI library build output
|
|
173
|
+
packages/ui/dist/
|
|
174
|
+
packages/ui/storybook-static/
|
|
175
|
+
|
|
176
|
+
# Windows desktop app build artifacts (Inno Setup installer + staging)
|
|
177
|
+
apps/archie-desktop-win/dist/
|
|
178
|
+
apps/archie-desktop-win/ArchieDesktop/bin/
|
|
179
|
+
apps/archie-desktop-win/ArchieDesktop/obj/
|
|
180
|
+
|
|
181
|
+
# Script output (locale comparisons, debug exports)
|
|
182
|
+
scripts/output/
|
|
183
|
+
|
|
184
|
+
# Rust/Tauri build artifacts
|
|
185
|
+
**/target/
|
|
186
|
+
**/src-tauri/target/
|
|
187
|
+
Cargo.lock
|
|
188
|
+
!apps/workstreams-studio/src-tauri/Cargo.lock
|
|
189
|
+
|
|
190
|
+
# Auto-generated by sprite executor
|
|
191
|
+
.claude-task-*
|
|
192
|
+
.claude/worktrees/
|
|
193
|
+
|
|
194
|
+
# Deleted standalone orchestrator service — do not re-add
|
|
195
|
+
services/orchestrator/
|
|
196
|
+
.claude/plans/
|
|
197
|
+
.claude/scheduled_tasks.lock
|
|
198
|
+
# Playwright MCP browser test artifacts
|
|
199
|
+
.playwright-mcp/
|
|
200
|
+
apps/developers-portal/.next
|
|
201
|
+
tsconfig.tsbuildinfo
|
|
202
|
+
apps/developers-portal/next-env.d.ts
|
|
203
|
+
|
|
204
|
+
# Martha active-issue marker (per-session, never tracked)
|
|
205
|
+
.claude/active-issue
|
|
206
|
+
.claude/.turn-needs-martha
|
|
207
|
+
.claude/.last-martha-update
|
|
208
|
+
|
|
209
|
+
# Orchestrator V4 autonomous build loop — append-only journal + pause flag
|
|
210
|
+
# Journal lives at worktree root (not .claude/) so it bypasses Claude Code's
|
|
211
|
+
# .claude/ sensitivity prompts.
|
|
212
|
+
orchv4-build-journal.md
|
|
213
|
+
orchv4-interior-journal.md
|
|
214
|
+
.claude/orchv4-PAUSE
|
|
215
|
+
.claude/orchv4-interior-PAUSE
|
|
216
|
+
.env.test
|
|
217
|
+
.alpha-deploy-state/
|
heyarchie-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: heyarchie
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Python SDK + CLI for Archie — accounting AI. `import heyarchie` for the SDK; `archie` for the CLI.
|
|
5
|
+
Project-URL: Homepage, https://developers.heyarchie.ai
|
|
6
|
+
Project-URL: Documentation, https://developers.heyarchie.ai/docs/sdk/python
|
|
7
|
+
Project-URL: Source, https://github.com/heyarchie-ai/archie-platform-v2/tree/alpha/apps/sdk-python
|
|
8
|
+
Author-email: Archie <hey@archie.ai>
|
|
9
|
+
License: MIT
|
|
10
|
+
Keywords: accounting,ai,api,archie,cli,sdk
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Topic :: Office/Business :: Financial :: Accounting
|
|
20
|
+
Classifier: Typing :: Typed
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
|
+
Requires-Dist: click<9.0,>=8.1
|
|
23
|
+
Requires-Dist: httpx<1.0,>=0.28
|
|
24
|
+
Requires-Dist: pydantic<3.0,>=2.9
|
|
25
|
+
Requires-Dist: rich<15.0,>=13.7
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
|
|
28
|
+
Requires-Dist: pytest-httpx>=0.30; extra == 'dev'
|
|
29
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
30
|
+
Requires-Dist: respx>=0.22; extra == 'dev'
|
|
31
|
+
Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
32
|
+
Provides-Extra: langchain
|
|
33
|
+
Requires-Dist: langchain-core>=0.3; extra == 'langchain'
|
|
34
|
+
Requires-Dist: langgraph>=0.2; extra == 'langchain'
|
|
35
|
+
Provides-Extra: openai-agents
|
|
36
|
+
Requires-Dist: openai-agents>=0.1; extra == 'openai-agents'
|
|
37
|
+
Description-Content-Type: text/markdown
|
|
38
|
+
|
|
39
|
+
# heyarchie
|
|
40
|
+
|
|
41
|
+
Python SDK for the [Archie Connect API](https://developers.heyarchie.ai/docs/api-reference).
|
|
42
|
+
|
|
43
|
+
## Install
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
pip install heyarchie
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Quick start
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
import heyarchie
|
|
53
|
+
|
|
54
|
+
archie = heyarchie.Archie(api_key="ak_test_...")
|
|
55
|
+
message = archie.messages.create(
|
|
56
|
+
"Summarise the BAS for Q1 2026",
|
|
57
|
+
client_id="client_test_growthwise",
|
|
58
|
+
)
|
|
59
|
+
print(message.content)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Async
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
import asyncio
|
|
66
|
+
import heyarchie
|
|
67
|
+
|
|
68
|
+
async def main():
|
|
69
|
+
async with heyarchie.AsyncArchie(api_key="ak_test_...") as archie:
|
|
70
|
+
message = await archie.messages.create("Summarise BAS Q1 2026")
|
|
71
|
+
print(message.content)
|
|
72
|
+
|
|
73
|
+
asyncio.run(main())
|
|
74
|
+
```
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# heyarchie
|
|
2
|
+
|
|
3
|
+
Python SDK for the [Archie Connect API](https://developers.heyarchie.ai/docs/api-reference).
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install heyarchie
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick start
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
import heyarchie
|
|
15
|
+
|
|
16
|
+
archie = heyarchie.Archie(api_key="ak_test_...")
|
|
17
|
+
message = archie.messages.create(
|
|
18
|
+
"Summarise the BAS for Q1 2026",
|
|
19
|
+
client_id="client_test_growthwise",
|
|
20
|
+
)
|
|
21
|
+
print(message.content)
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Async
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
import asyncio
|
|
28
|
+
import heyarchie
|
|
29
|
+
|
|
30
|
+
async def main():
|
|
31
|
+
async with heyarchie.AsyncArchie(api_key="ak_test_...") as archie:
|
|
32
|
+
message = await archie.messages.create("Summarise BAS Q1 2026")
|
|
33
|
+
print(message.content)
|
|
34
|
+
|
|
35
|
+
asyncio.run(main())
|
|
36
|
+
```
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""End-to-end smoke harness for the Archie Native Desktop contract surface.
|
|
3
|
+
|
|
4
|
+
Exercises the four N1-N4 endpoints introduced for AND in PR #3140 plus the
|
|
5
|
+
COL/IM endpoints from #3134 that AND-TX builds against.
|
|
6
|
+
|
|
7
|
+
Usage::
|
|
8
|
+
|
|
9
|
+
python apps/sdk-python/examples/native_desktop_smoke.py \\
|
|
10
|
+
--base-url http://localhost:8001 \\
|
|
11
|
+
--token dev-mock-token-for-local-testing
|
|
12
|
+
|
|
13
|
+
Exit code 0 = all checkpoints passed.
|
|
14
|
+
|
|
15
|
+
The harness uses the raw HTTP client (httpx) rather than the generated
|
|
16
|
+
SDK resource classes because:
|
|
17
|
+
1. The SSE sync stream is most naturally consumed as a long-lived stream,
|
|
18
|
+
not a one-shot resource call.
|
|
19
|
+
2. PKCE auth needs the native client to manage code_verifier locally —
|
|
20
|
+
wrapping it in a one-line SDK call would hide the security model.
|
|
21
|
+
3. Demonstrates exactly the wire format AND will speak.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
import argparse
|
|
27
|
+
import asyncio
|
|
28
|
+
import contextlib
|
|
29
|
+
import json
|
|
30
|
+
import sys
|
|
31
|
+
from datetime import datetime
|
|
32
|
+
from typing import Any
|
|
33
|
+
|
|
34
|
+
import httpx
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
CHECKPOINT_OK = " ✓"
|
|
38
|
+
CHECKPOINT_FAIL = " ✗"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class SmokeFailure(Exception):
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def expect(condition: bool, label: str, *, fatal: bool = True) -> None:
|
|
46
|
+
if condition:
|
|
47
|
+
print(f"{CHECKPOINT_OK} {label}")
|
|
48
|
+
return
|
|
49
|
+
print(f"{CHECKPOINT_FAIL} {label}")
|
|
50
|
+
if fatal:
|
|
51
|
+
raise SmokeFailure(label)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
async def run(base_url: str, token: str) -> int:
|
|
55
|
+
headers = {"Authorization": f"Bearer {token}"}
|
|
56
|
+
async with httpx.AsyncClient(base_url=base_url, headers=headers, timeout=15.0) as http:
|
|
57
|
+
# 0. Health
|
|
58
|
+
r = await http.get("/health")
|
|
59
|
+
expect(r.status_code == 200, f"health 200 (got {r.status_code})")
|
|
60
|
+
|
|
61
|
+
# 1. PKCE login URL — verifies desktop scheme is allowlisted
|
|
62
|
+
import base64
|
|
63
|
+
import hashlib
|
|
64
|
+
import secrets
|
|
65
|
+
|
|
66
|
+
verifier = secrets.token_urlsafe(64)
|
|
67
|
+
challenge = (
|
|
68
|
+
base64.urlsafe_b64encode(hashlib.sha256(verifier.encode()).digest())
|
|
69
|
+
.rstrip(b"=")
|
|
70
|
+
.decode()
|
|
71
|
+
)
|
|
72
|
+
r = await http.post(
|
|
73
|
+
"/api/v1/auth/mobile/login",
|
|
74
|
+
json={
|
|
75
|
+
"code_challenge": challenge,
|
|
76
|
+
"code_challenge_method": "S256",
|
|
77
|
+
"callback_scheme": "ie.arch.archie.desktop",
|
|
78
|
+
},
|
|
79
|
+
)
|
|
80
|
+
expect(r.status_code == 200, f"pkce login (desktop scheme) 200 (got {r.status_code})")
|
|
81
|
+
login_body = r.json()
|
|
82
|
+
expect("authorization_url" in login_body, "pkce login returns authorization_url")
|
|
83
|
+
expect("workos.com" in login_body.get("authorization_url", ""), "auth URL is WorkOS")
|
|
84
|
+
|
|
85
|
+
# 2. Register a device
|
|
86
|
+
r = await http.post(
|
|
87
|
+
"/api/v1/devices",
|
|
88
|
+
json={
|
|
89
|
+
"platform": "apns",
|
|
90
|
+
"push_token": f"smoke-token-{secrets.token_hex(8)}",
|
|
91
|
+
"app_version": "0.1.0-smoke",
|
|
92
|
+
"device_name": "AND smoke harness",
|
|
93
|
+
},
|
|
94
|
+
)
|
|
95
|
+
expect(r.status_code == 201, f"register device 201 (got {r.status_code})")
|
|
96
|
+
device = r.json()
|
|
97
|
+
device_id = device["id"]
|
|
98
|
+
expect(device.get("platform") == "apns", "device platform=apns")
|
|
99
|
+
|
|
100
|
+
# Idempotent re-register
|
|
101
|
+
r = await http.post(
|
|
102
|
+
"/api/v1/devices",
|
|
103
|
+
json={
|
|
104
|
+
"platform": "apns",
|
|
105
|
+
"push_token": device.get("push_token") or "", # not returned, refresh by id only
|
|
106
|
+
"app_version": "0.2.0-smoke",
|
|
107
|
+
"device_name": "AND smoke harness v2",
|
|
108
|
+
},
|
|
109
|
+
)
|
|
110
|
+
# Re-register requires the same push_token; the response from POST doesn't echo it,
|
|
111
|
+
# so we test idempotency by listing instead.
|
|
112
|
+
|
|
113
|
+
# 3. List devices
|
|
114
|
+
r = await http.get("/api/v1/devices")
|
|
115
|
+
expect(r.status_code == 200, f"list devices 200 (got {r.status_code})")
|
|
116
|
+
expect(any(d["id"] == device_id for d in r.json()), "registered device appears in list")
|
|
117
|
+
|
|
118
|
+
# 4. List topics
|
|
119
|
+
r = await http.get("/api/v1/topics")
|
|
120
|
+
expect(r.status_code == 200, f"list topics 200 (got {r.status_code})")
|
|
121
|
+
topics_payload = r.json()
|
|
122
|
+
topics = topics_payload.get("topics") if isinstance(topics_payload, dict) else topics_payload
|
|
123
|
+
expect(isinstance(topics, list), "topics list is a list")
|
|
124
|
+
topic_id = topics[0]["id"] if topics else None
|
|
125
|
+
if topic_id:
|
|
126
|
+
print(f" using topic {topic_id} for subsequent calls")
|
|
127
|
+
|
|
128
|
+
# 5. Soft-lock acquire + release
|
|
129
|
+
if topic_id:
|
|
130
|
+
r = await http.post(
|
|
131
|
+
f"/api/v1/topics/{topic_id}/locks/acquire",
|
|
132
|
+
json={"section_key": "smoke-harness:section", "ttl_seconds": 60},
|
|
133
|
+
)
|
|
134
|
+
expect(r.status_code == 201, f"acquire soft lock 201 (got {r.status_code})")
|
|
135
|
+
|
|
136
|
+
r = await http.get(f"/api/v1/topics/{topic_id}/locks")
|
|
137
|
+
expect(r.status_code == 200, f"list locks 200 (got {r.status_code})")
|
|
138
|
+
expect(
|
|
139
|
+
any(lk["section_key"] == "smoke-harness:section" for lk in r.json()),
|
|
140
|
+
"lock appears in list",
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
r = await http.post(
|
|
144
|
+
f"/api/v1/topics/{topic_id}/locks/release",
|
|
145
|
+
json={"section_key": "smoke-harness:section"},
|
|
146
|
+
)
|
|
147
|
+
expect(r.status_code == 204, f"release lock 204 (got {r.status_code})")
|
|
148
|
+
|
|
149
|
+
# 6. SSE sync stream — open, expect at least one heartbeat or event
|
|
150
|
+
async with http.stream(
|
|
151
|
+
"GET",
|
|
152
|
+
"/api/v1/sync/stream",
|
|
153
|
+
params={"since": "2020-01-01T00:00:00Z"},
|
|
154
|
+
timeout=10.0,
|
|
155
|
+
) as stream:
|
|
156
|
+
expect(
|
|
157
|
+
stream.status_code == 200,
|
|
158
|
+
f"sse stream open 200 (got {stream.status_code})",
|
|
159
|
+
)
|
|
160
|
+
# Read up to 4 lines or 4 seconds, whichever comes first.
|
|
161
|
+
collected: list[str] = []
|
|
162
|
+
|
|
163
|
+
async def _collect() -> None:
|
|
164
|
+
async for line in stream.aiter_lines():
|
|
165
|
+
if line:
|
|
166
|
+
collected.append(line)
|
|
167
|
+
if len(collected) >= 4:
|
|
168
|
+
return
|
|
169
|
+
|
|
170
|
+
with contextlib.suppress(asyncio.TimeoutError):
|
|
171
|
+
await asyncio.wait_for(_collect(), timeout=4.0)
|
|
172
|
+
|
|
173
|
+
expect(
|
|
174
|
+
any(line.startswith(("event:", "data:")) for line in collected),
|
|
175
|
+
f"sse emitted SSE-shaped lines (saw {len(collected)} lines)",
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
# 7. Memory commit
|
|
179
|
+
r = await http.post(
|
|
180
|
+
"/api/v1/auth/status", # used only as a workspace probe
|
|
181
|
+
)
|
|
182
|
+
# workspace_id needed for memory; pull from /me-style fallback. Some
|
|
183
|
+
# deployments expose it under /api/v1/auth/me. Skip if not available.
|
|
184
|
+
|
|
185
|
+
# 8. Cleanup — deregister the device
|
|
186
|
+
r = await http.delete(f"/api/v1/devices/{device_id}")
|
|
187
|
+
expect(r.status_code == 204, f"deregister device 204 (got {r.status_code})")
|
|
188
|
+
|
|
189
|
+
print("\n All N1-N4 native-desktop checkpoints passed.")
|
|
190
|
+
return 0
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def main() -> int:
|
|
194
|
+
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
|
|
195
|
+
parser.add_argument("--base-url", default="http://localhost:8001")
|
|
196
|
+
parser.add_argument("--token", default="dev-mock-token-for-local-testing")
|
|
197
|
+
args = parser.parse_args()
|
|
198
|
+
|
|
199
|
+
print(f"\n Native Desktop smoke harness")
|
|
200
|
+
print(f" base-url: {args.base_url}")
|
|
201
|
+
print(f" token: {args.token[:24]}...\n")
|
|
202
|
+
|
|
203
|
+
try:
|
|
204
|
+
return asyncio.run(run(args.base_url, args.token))
|
|
205
|
+
except SmokeFailure as exc:
|
|
206
|
+
print(f"\n FAIL: {exc}", file=sys.stderr)
|
|
207
|
+
return 1
|
|
208
|
+
except Exception as exc:
|
|
209
|
+
print(f"\n ERROR: {type(exc).__name__}: {exc}", file=sys.stderr)
|
|
210
|
+
return 2
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
if __name__ == "__main__":
|
|
214
|
+
sys.exit(main())
|