archondev 0.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +84 -51
- package/dist/auth-2QIFQZTL.js +12 -0
- package/dist/bug-DXLBBW3U.js +10 -0
- package/dist/{chunk-R6IMTNKV.js → chunk-2CFO5GVH.js} +0 -35
- package/dist/chunk-A7QU6JC6.js +119 -0
- package/dist/chunk-CAYCSBNX.js +202 -0
- package/dist/chunk-EDP55FCI.js +485 -0
- package/dist/chunk-I4ZVNLNO.js +4648 -0
- package/dist/chunk-IMZN36GC.js +159 -0
- package/dist/chunk-JBKFAD4M.js +650 -0
- package/dist/chunk-MOZHC2GX.js +351 -0
- package/dist/chunk-PK3OQVBG.js +91 -0
- package/dist/chunk-QGM4M3NI.js +37 -0
- package/dist/chunk-SMR7JQK6.js +399 -0
- package/dist/chunk-UDBFDXJI.js +696 -0
- package/dist/chunk-UG2ZZ7CM.js +737 -0
- package/dist/chunk-VKM3HAHW.js +832 -0
- package/dist/chunk-WCCBJSNI.js +62 -0
- package/dist/code-review-FSTYDHNG.js +16 -0
- package/dist/execute-LYID2ODD.js +13 -0
- package/dist/index.js +1250 -7206
- package/dist/keys-EL3FUM5O.js +15 -0
- package/dist/list-VXMVEIL5.js +13 -0
- package/dist/{parser-D6PBQUJH.js → parser-M4DI7A24.js} +2 -1
- package/dist/plan-7VSFESVD.js +16 -0
- package/dist/preferences-PL2ON5VY.js +17 -0
- package/dist/review-3R6QXAXQ.js +27 -0
- package/package.json +21 -1
package/README.md
CHANGED
|
@@ -1,75 +1,108 @@
|
|
|
1
1
|
# ArchonDev
|
|
2
2
|
|
|
3
|
-
**
|
|
3
|
+
**AI Development Governance — Stop Babysitting Your AI Agent**
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Two Ways to Use ArchonDev
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
### Option 1: Full CLI (Recommended)
|
|
8
|
+
The complete AI development system. It manages your entire development process so you can focus on the big picture — not constantly correcting your AI.
|
|
8
9
|
|
|
9
10
|
```bash
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
# macOS/Linux:
|
|
12
|
+
npm install -g archondev && archon
|
|
12
13
|
|
|
13
|
-
|
|
14
|
+
# Windows PowerShell:
|
|
15
|
+
npm install -g archondev; archon
|
|
16
|
+
```
|
|
14
17
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
+
**What you get:**
|
|
19
|
+
- AI that reads and respects your architecture before writing code
|
|
20
|
+
- Adversarial planning (Architect proposes, Sentinel critiques)
|
|
21
|
+
- Automatic quality gates before changes are applied
|
|
22
|
+
- Learning persistence — mistakes are remembered and avoided
|
|
23
|
+
- Bug reporting with root cause analysis
|
|
24
|
+
- AI-powered code review for any codebase
|
|
25
|
+
- Multi-provider key support with adversarial features
|
|
18
26
|
|
|
19
|
-
|
|
20
|
-
|
|
27
|
+
### Option 2: Lite Package
|
|
28
|
+
Copy governance files into any project. Works with your existing AI tools (Cursor, Claude Code, Windsurf, VS Code + Copilot).
|
|
21
29
|
|
|
22
|
-
|
|
23
|
-
archon execute ATOM-001
|
|
24
|
-
```
|
|
30
|
+
[Download Lite Packages →](https://archondev.io/download)
|
|
25
31
|
|
|
26
|
-
|
|
32
|
+
**What you get:**
|
|
33
|
+
- ARCHITECTURE.md template with best practices
|
|
34
|
+
- IDE-specific rule files (.cursorrules, CLAUDE.md, etc.)
|
|
35
|
+
- Progress tracking templates
|
|
36
|
+
- Works with any AI coding assistant
|
|
27
37
|
|
|
28
|
-
|
|
29
|
-
2. **Adversarial Planning** - An Architect agent creates plans, a Sentinel agent critiques them
|
|
30
|
-
3. **Governed Execution** - Changes are validated against your architecture before being applied
|
|
31
|
-
4. **Learning Persistence** - Insights are captured in `progress.txt` for future sessions
|
|
38
|
+
---
|
|
32
39
|
|
|
33
40
|
## Commands
|
|
34
41
|
|
|
35
42
|
| Command | Description |
|
|
36
43
|
|---------|-------------|
|
|
37
|
-
| `archon
|
|
38
|
-
| `archon
|
|
39
|
-
| `archon
|
|
40
|
-
| `archon
|
|
41
|
-
| `archon
|
|
42
|
-
| `archon
|
|
43
|
-
| `archon
|
|
44
|
-
| `archon
|
|
45
|
-
| `archon
|
|
46
|
-
| `archon
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
|
51
|
-
|
|
52
|
-
|
|
|
53
|
-
|
|
|
54
|
-
|
|
|
55
|
-
|
|
56
|
-
##
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
44
|
+
| `archon` | Interactive mode — just run and follow prompts |
|
|
45
|
+
| `archon init` | Initialize in your project |
|
|
46
|
+
| `archon plan <description>` | Create a work item with AI planning |
|
|
47
|
+
| `archon execute <atom-id>` | Execute with quality gates |
|
|
48
|
+
| `archon list` | List all work items |
|
|
49
|
+
| `archon show <atom-id>` | Show details |
|
|
50
|
+
| `archon watch` | Live TUI dashboard with status and credits |
|
|
51
|
+
| `archon bug report <title>` | Bug report with root cause analysis |
|
|
52
|
+
| `archon review init` | Initialize AI-powered code review |
|
|
53
|
+
| `archon review analyze` | Scan project and populate review tasks |
|
|
54
|
+
| `archon review run` | Run AI review on pending tasks |
|
|
55
|
+
| `archon credits` | View credit balance |
|
|
56
|
+
| `archon credits add` | Purchase credits |
|
|
57
|
+
| `archon credits budget` | Set monthly budget and alerts |
|
|
58
|
+
| `archon keys add <provider>` | Add your own API key — BYOK (Bring Your Own Key) |
|
|
59
|
+
| `archon keys list` | Show configured API keys by provider |
|
|
60
|
+
| `archon preferences` | View/set model preferences |
|
|
61
|
+
| `archon models` | List available AI models |
|
|
62
|
+
|
|
63
|
+
## Pricing
|
|
64
|
+
|
|
65
|
+
| Tier | Cost | What You Get |
|
|
66
|
+
|------|------|--------------|
|
|
67
|
+
| **Free** | $0 | 10,000 atoms/month, fast models (Haiku, Flash, GPT-4 Mini) |
|
|
68
|
+
| **Credits** | Pay as you go | All models, 10% service fee, deposit any amount |
|
|
69
|
+
| **BYOK** (Bring Your Own Key) | $0 | Use your own API keys, unlimited usage |
|
|
70
|
+
|
|
71
|
+
No subscriptions. No commitments. Start free.
|
|
64
72
|
|
|
65
|
-
##
|
|
73
|
+
## How It Works
|
|
66
74
|
|
|
67
|
-
|
|
75
|
+
1. **Define Your Rules** — Create ARCHITECTURE.md with boundaries and invariants
|
|
76
|
+
2. **AI Reads Rules First** — Every session starts by understanding your architecture
|
|
77
|
+
3. **Changes Are Validated** — Quality gates check code before it's applied
|
|
78
|
+
4. **Learnings Persist** — Insights saved for future sessions
|
|
68
79
|
|
|
69
|
-
##
|
|
80
|
+
## Working with Existing Projects
|
|
81
|
+
|
|
82
|
+
Have a project created by another AI agent? ArchonDev can review it first, then govern future changes.
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
# Step 1: Review existing code
|
|
86
|
+
cd your-existing-project
|
|
87
|
+
archon review init # Create local review database
|
|
88
|
+
archon review analyze # Scan project structure
|
|
89
|
+
archon review run --all # AI reviews all features
|
|
90
|
+
archon review export > review-report.md
|
|
91
|
+
|
|
92
|
+
# Step 2: Set up governance
|
|
93
|
+
archon init --analyze # Creates ARCHITECTURE.md
|
|
94
|
+
|
|
95
|
+
# Step 3: Fix issues with governed workflow
|
|
96
|
+
archon plan "fix critical issues from review"
|
|
97
|
+
archon execute <atom-id>
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
The CLI detects existing projects and suggests this workflow automatically.
|
|
101
|
+
|
|
102
|
+
## Documentation
|
|
70
103
|
|
|
71
|
-
|
|
104
|
+
[archondev.io](https://archondev.io)
|
|
72
105
|
|
|
73
106
|
---
|
|
74
107
|
|
|
75
|
-
*
|
|
108
|
+
*ArchonDev by Jumping Ahead Corp.*
|
|
@@ -1,35 +1,3 @@
|
|
|
1
|
-
var __create = Object.create;
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
-
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
8
|
-
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
9
|
-
}) : x)(function(x) {
|
|
10
|
-
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
11
|
-
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
12
|
-
});
|
|
13
|
-
var __commonJS = (cb, mod) => function __require2() {
|
|
14
|
-
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
15
|
-
};
|
|
16
|
-
var __copyProps = (to, from, except, desc) => {
|
|
17
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
18
|
-
for (let key of __getOwnPropNames(from))
|
|
19
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
20
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
21
|
-
}
|
|
22
|
-
return to;
|
|
23
|
-
};
|
|
24
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
25
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
26
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
27
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
28
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
29
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
30
|
-
mod
|
|
31
|
-
));
|
|
32
|
-
|
|
33
1
|
// src/core/parser/parser.ts
|
|
34
2
|
import { readFile } from "fs/promises";
|
|
35
3
|
import { existsSync } from "fs";
|
|
@@ -382,8 +350,5 @@ var ArchitectureParser = class {
|
|
|
382
350
|
};
|
|
383
351
|
|
|
384
352
|
export {
|
|
385
|
-
__require,
|
|
386
|
-
__commonJS,
|
|
387
|
-
__toESM,
|
|
388
353
|
ArchitectureParser
|
|
389
354
|
};
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
// src/utils/index.ts
|
|
2
|
+
function sleep(ms) {
|
|
3
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
4
|
+
}
|
|
5
|
+
function generateId(prefix) {
|
|
6
|
+
const timestamp = Date.now().toString(36);
|
|
7
|
+
const random = Math.random().toString(36).substring(2, 8);
|
|
8
|
+
const id = `${timestamp}-${random}`;
|
|
9
|
+
return prefix ? `${prefix}-${id}` : id;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// src/agents/clients/anthropic.ts
|
|
13
|
+
import Anthropic from "@anthropic-ai/sdk";
|
|
14
|
+
var MAX_RETRIES = 3;
|
|
15
|
+
var RETRY_DELAY_MS = 1e3;
|
|
16
|
+
var MODEL_PRICING = {
|
|
17
|
+
"claude-opus-4-5-20250514": { input: 15, output: 75 },
|
|
18
|
+
"claude-sonnet-4-20250514": { input: 3, output: 15 },
|
|
19
|
+
"claude-haiku-3-5-20241022": { input: 0.25, output: 1.25 },
|
|
20
|
+
// Fallback defaults
|
|
21
|
+
"claude-opus": { input: 15, output: 75 },
|
|
22
|
+
"claude-sonnet": { input: 3, output: 15 },
|
|
23
|
+
"claude-haiku": { input: 0.25, output: 1.25 }
|
|
24
|
+
};
|
|
25
|
+
var AnthropicClient = class {
|
|
26
|
+
client;
|
|
27
|
+
config;
|
|
28
|
+
constructor(config, apiKey) {
|
|
29
|
+
const key = apiKey ?? process.env["ANTHROPIC_API_KEY"];
|
|
30
|
+
if (!key) {
|
|
31
|
+
throw new Error("ANTHROPIC_API_KEY is required");
|
|
32
|
+
}
|
|
33
|
+
this.client = new Anthropic({ apiKey: key });
|
|
34
|
+
this.config = config;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Send a message and get a response
|
|
38
|
+
*/
|
|
39
|
+
async chat(systemPrompt, userMessage, options) {
|
|
40
|
+
let lastError = null;
|
|
41
|
+
for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
|
|
42
|
+
try {
|
|
43
|
+
const response = await this.client.messages.create({
|
|
44
|
+
model: this.config.model,
|
|
45
|
+
max_tokens: options?.maxTokens ?? this.config.maxTokens ?? 4096,
|
|
46
|
+
temperature: options?.temperature ?? this.config.temperature ?? 0.7,
|
|
47
|
+
system: systemPrompt,
|
|
48
|
+
messages: [{ role: "user", content: userMessage }]
|
|
49
|
+
});
|
|
50
|
+
const textContent = response.content.find((c) => c.type === "text");
|
|
51
|
+
const text = textContent && "text" in textContent ? textContent.text : "";
|
|
52
|
+
const usage = this.calculateUsage(
|
|
53
|
+
response.usage.input_tokens,
|
|
54
|
+
response.usage.output_tokens
|
|
55
|
+
);
|
|
56
|
+
return {
|
|
57
|
+
content: text,
|
|
58
|
+
usage,
|
|
59
|
+
model: response.model,
|
|
60
|
+
stopReason: response.stop_reason ?? "end_turn"
|
|
61
|
+
};
|
|
62
|
+
} catch (error) {
|
|
63
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
64
|
+
if (lastError.message.includes("401") || lastError.message.includes("authentication")) {
|
|
65
|
+
throw lastError;
|
|
66
|
+
}
|
|
67
|
+
if (attempt < MAX_RETRIES - 1) {
|
|
68
|
+
await sleep(RETRY_DELAY_MS * Math.pow(2, attempt));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
throw lastError ?? new Error("Unknown error");
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Calculate token usage and costs
|
|
76
|
+
*/
|
|
77
|
+
calculateUsage(inputTokens, outputTokens) {
|
|
78
|
+
const pricing = this.getPricing();
|
|
79
|
+
const baseCost = inputTokens / 1e6 * pricing.input + outputTokens / 1e6 * pricing.output;
|
|
80
|
+
return {
|
|
81
|
+
inputTokens,
|
|
82
|
+
outputTokens,
|
|
83
|
+
totalTokens: inputTokens + outputTokens,
|
|
84
|
+
baseCost,
|
|
85
|
+
markedUpCost: baseCost * 1.1
|
|
86
|
+
// 10% markup for PRO tier
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
getPricing() {
|
|
90
|
+
const pricing = MODEL_PRICING[this.config.model];
|
|
91
|
+
if (pricing) return pricing;
|
|
92
|
+
for (const [key, value] of Object.entries(MODEL_PRICING)) {
|
|
93
|
+
if (this.config.model.includes(key.replace("claude-", ""))) {
|
|
94
|
+
return value;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return { input: 3, output: 15 };
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
function getDefaultModel(role) {
|
|
101
|
+
switch (role) {
|
|
102
|
+
case "architect":
|
|
103
|
+
return "claude-sonnet-4-20250514";
|
|
104
|
+
case "sentinel":
|
|
105
|
+
return "claude-sonnet-4-20250514";
|
|
106
|
+
case "executor":
|
|
107
|
+
return "claude-sonnet-4-20250514";
|
|
108
|
+
case "reviewer":
|
|
109
|
+
return "claude-sonnet-4-20250514";
|
|
110
|
+
default:
|
|
111
|
+
return "claude-sonnet-4-20250514";
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export {
|
|
116
|
+
generateId,
|
|
117
|
+
AnthropicClient,
|
|
118
|
+
getDefaultModel
|
|
119
|
+
};
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import {
|
|
2
|
+
clearConfig,
|
|
3
|
+
loadConfig,
|
|
4
|
+
saveConfig
|
|
5
|
+
} from "./chunk-WCCBJSNI.js";
|
|
6
|
+
|
|
7
|
+
// src/cli/auth.ts
|
|
8
|
+
import { createClient } from "@supabase/supabase-js";
|
|
9
|
+
import { createServer } from "http";
|
|
10
|
+
import { URL } from "url";
|
|
11
|
+
import open from "open";
|
|
12
|
+
import chalk from "chalk";
|
|
13
|
+
import ora from "ora";
|
|
14
|
+
|
|
15
|
+
// src/cli/constants.ts
|
|
16
|
+
var SUPABASE_URL = "https://yjdkcepktrbabmzhcmrt.supabase.co";
|
|
17
|
+
var SUPABASE_ANON_KEY = "sb_publishable_XSGLVPfLZx-HA2uL6xsGCQ_KjAx2TIa";
|
|
18
|
+
var API_URL = "https://archondev-api.fly.dev";
|
|
19
|
+
|
|
20
|
+
// src/cli/auth.ts
|
|
21
|
+
var CALLBACK_PORT = 54321;
|
|
22
|
+
var CALLBACK_URL = `http://localhost:${CALLBACK_PORT}/callback`;
|
|
23
|
+
function getAuthClient() {
|
|
24
|
+
return createClient(SUPABASE_URL, SUPABASE_ANON_KEY, {
|
|
25
|
+
auth: {
|
|
26
|
+
flowType: "pkce"
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
async function login(provider = "github") {
|
|
31
|
+
const spinner = ora("Starting authentication...").start();
|
|
32
|
+
try {
|
|
33
|
+
const supabase = getAuthClient();
|
|
34
|
+
const { data, error } = await supabase.auth.signInWithOAuth({
|
|
35
|
+
provider,
|
|
36
|
+
options: {
|
|
37
|
+
redirectTo: CALLBACK_URL,
|
|
38
|
+
skipBrowserRedirect: true
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
if (error) {
|
|
42
|
+
spinner.fail(chalk.red(`Authentication failed: ${error.message}`));
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (!data.url) {
|
|
46
|
+
spinner.fail(chalk.red("Failed to get authentication URL"));
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const authResult = await startCallbackServer(data.url, spinner);
|
|
50
|
+
if (authResult) {
|
|
51
|
+
const { data: sessionData, error: sessionError } = await supabase.auth.exchangeCodeForSession(
|
|
52
|
+
authResult.code
|
|
53
|
+
);
|
|
54
|
+
if (sessionError) {
|
|
55
|
+
spinner.fail(chalk.red(`Failed to complete authentication: ${sessionError.message}`));
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (sessionData.session) {
|
|
59
|
+
let userTier = "FREE";
|
|
60
|
+
try {
|
|
61
|
+
const { data: profileData } = await supabase.from("user_profiles").select("tier").eq("auth_id", sessionData.user.id).single();
|
|
62
|
+
const profile = profileData;
|
|
63
|
+
if (profile?.tier) {
|
|
64
|
+
userTier = profile.tier;
|
|
65
|
+
}
|
|
66
|
+
} catch {
|
|
67
|
+
}
|
|
68
|
+
await saveConfig({
|
|
69
|
+
accessToken: sessionData.session.access_token,
|
|
70
|
+
refreshToken: sessionData.session.refresh_token,
|
|
71
|
+
userId: sessionData.user.id,
|
|
72
|
+
email: sessionData.user.email ?? "",
|
|
73
|
+
tier: userTier,
|
|
74
|
+
expiresAt: new Date(sessionData.session.expires_at ?? Date.now() + 36e5).toISOString()
|
|
75
|
+
});
|
|
76
|
+
spinner.succeed(chalk.green(`Logged in as ${sessionData.user.email}`));
|
|
77
|
+
console.log(chalk.dim(`Tier: ${userTier}`));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
} catch (error) {
|
|
81
|
+
spinner.fail(chalk.red(`Authentication error: ${error instanceof Error ? error.message : "Unknown error"}`));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function startCallbackServer(authUrl, spinner) {
|
|
85
|
+
return new Promise((resolve) => {
|
|
86
|
+
const server = createServer((req, res) => {
|
|
87
|
+
const url = new URL(req.url ?? "", `http://localhost:${CALLBACK_PORT}`);
|
|
88
|
+
if (url.pathname === "/callback") {
|
|
89
|
+
const code = url.searchParams.get("code");
|
|
90
|
+
if (code) {
|
|
91
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
92
|
+
res.end(`
|
|
93
|
+
<!DOCTYPE html>
|
|
94
|
+
<html>
|
|
95
|
+
<head><title>ArchonDev - Authentication Successful</title></head>
|
|
96
|
+
<body style="font-family: system-ui; text-align: center; padding: 50px;">
|
|
97
|
+
<h1>\u2705 Authentication Successful!</h1>
|
|
98
|
+
<p>You can close this window and return to the terminal.</p>
|
|
99
|
+
</body>
|
|
100
|
+
</html>
|
|
101
|
+
`);
|
|
102
|
+
server.close();
|
|
103
|
+
resolve({ code });
|
|
104
|
+
} else {
|
|
105
|
+
const error = url.searchParams.get("error_description") ?? "Authentication failed";
|
|
106
|
+
res.writeHead(400, { "Content-Type": "text/html" });
|
|
107
|
+
res.end(`
|
|
108
|
+
<!DOCTYPE html>
|
|
109
|
+
<html>
|
|
110
|
+
<head><title>ArchonDev - Authentication Failed</title></head>
|
|
111
|
+
<body style="font-family: system-ui; text-align: center; padding: 50px;">
|
|
112
|
+
<h1>\u274C Authentication Failed</h1>
|
|
113
|
+
<p>${error}</p>
|
|
114
|
+
</body>
|
|
115
|
+
</html>
|
|
116
|
+
`);
|
|
117
|
+
server.close();
|
|
118
|
+
resolve(null);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
server.listen(CALLBACK_PORT, async () => {
|
|
123
|
+
spinner.text = "Opening browser for authentication...";
|
|
124
|
+
try {
|
|
125
|
+
await open(authUrl);
|
|
126
|
+
spinner.text = "Waiting for authentication in browser...";
|
|
127
|
+
} catch {
|
|
128
|
+
spinner.text = `Please open this URL in your browser:
|
|
129
|
+
${authUrl}`;
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
setTimeout(() => {
|
|
133
|
+
server.close();
|
|
134
|
+
spinner.fail(chalk.red("Authentication timed out"));
|
|
135
|
+
resolve(null);
|
|
136
|
+
}, 5 * 60 * 1e3);
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
async function logout() {
|
|
140
|
+
const spinner = ora("Logging out...").start();
|
|
141
|
+
try {
|
|
142
|
+
const config = await loadConfig();
|
|
143
|
+
if (config.accessToken) {
|
|
144
|
+
try {
|
|
145
|
+
const supabase = getAuthClient();
|
|
146
|
+
await supabase.auth.signOut();
|
|
147
|
+
} catch {
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
await clearConfig();
|
|
151
|
+
spinner.succeed(chalk.green("Logged out successfully"));
|
|
152
|
+
} catch (error) {
|
|
153
|
+
spinner.fail(chalk.red(`Logout failed: ${error instanceof Error ? error.message : "Unknown error"}`));
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
async function status() {
|
|
157
|
+
const config = await loadConfig();
|
|
158
|
+
if (!config.accessToken) {
|
|
159
|
+
console.log(chalk.yellow("Not logged in"));
|
|
160
|
+
console.log(chalk.dim("Run `archon login` to authenticate"));
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
if (config.expiresAt) {
|
|
164
|
+
const expiresAt = new Date(config.expiresAt);
|
|
165
|
+
if (expiresAt < /* @__PURE__ */ new Date()) {
|
|
166
|
+
console.log(chalk.yellow("Session expired"));
|
|
167
|
+
console.log(chalk.dim("Run `archon login` to re-authenticate"));
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
console.log(chalk.green("\u2713 Authenticated"));
|
|
172
|
+
console.log();
|
|
173
|
+
console.log(` ${chalk.dim("Email:")} ${config.email}`);
|
|
174
|
+
console.log(` ${chalk.dim("Tier:")} ${formatTier(config.tier ?? "FREE")}`);
|
|
175
|
+
console.log(` ${chalk.dim("User:")} ${config.userId}`);
|
|
176
|
+
if (config.expiresAt) {
|
|
177
|
+
const expiresAt = new Date(config.expiresAt);
|
|
178
|
+
const hoursLeft = Math.round((expiresAt.getTime() - Date.now()) / (1e3 * 60 * 60));
|
|
179
|
+
console.log(` ${chalk.dim("Session expires in:")} ${hoursLeft} hours`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
function formatTier(tier) {
|
|
183
|
+
switch (tier) {
|
|
184
|
+
case "FREE":
|
|
185
|
+
return chalk.gray("FREE");
|
|
186
|
+
case "CREDITS":
|
|
187
|
+
return chalk.green("CREDITS (Pay-as-you-go)");
|
|
188
|
+
case "BYOK":
|
|
189
|
+
return chalk.blue("BYOK (Bring Your Own Key)");
|
|
190
|
+
default:
|
|
191
|
+
return tier;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export {
|
|
196
|
+
SUPABASE_URL,
|
|
197
|
+
SUPABASE_ANON_KEY,
|
|
198
|
+
API_URL,
|
|
199
|
+
login,
|
|
200
|
+
logout,
|
|
201
|
+
status
|
|
202
|
+
};
|