@codexa/cli 9.0.34 → 9.0.36
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/commands/migrate.ts +137 -0
- package/db/connection.ts +134 -111
- package/package.json +1 -1
- package/workflow.ts +10 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { createClient, type Client } from "@libsql/client";
|
|
2
|
+
import { initSchema } from "../db/schema";
|
|
3
|
+
import { getDb } from "../db/connection";
|
|
4
|
+
import { existsSync } from "fs";
|
|
5
|
+
import { join } from "path";
|
|
6
|
+
|
|
7
|
+
const TABLES_IN_ORDER = [
|
|
8
|
+
"specs",
|
|
9
|
+
"project",
|
|
10
|
+
"standards",
|
|
11
|
+
"product_context",
|
|
12
|
+
"lib_contexts",
|
|
13
|
+
"agent_lib_mappings",
|
|
14
|
+
"implementation_patterns",
|
|
15
|
+
"architectural_analyses",
|
|
16
|
+
"project_utilities",
|
|
17
|
+
"schema_migrations",
|
|
18
|
+
"context",
|
|
19
|
+
"tasks",
|
|
20
|
+
"decisions",
|
|
21
|
+
"artifacts",
|
|
22
|
+
"review",
|
|
23
|
+
"snapshots",
|
|
24
|
+
"knowledge",
|
|
25
|
+
"gate_bypasses",
|
|
26
|
+
"product_goals",
|
|
27
|
+
"product_features",
|
|
28
|
+
"knowledge_graph",
|
|
29
|
+
"reasoning_log",
|
|
30
|
+
"agent_performance",
|
|
31
|
+
"knowledge_acknowledgments",
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
interface MigrateOptions {
|
|
35
|
+
dbPath?: string;
|
|
36
|
+
dryRun?: boolean;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function migrateToTurso(options: MigrateOptions = {}): Promise<void> {
|
|
40
|
+
const localPath = options.dbPath || join(process.cwd(), ".codexa", "db", "workflow.db");
|
|
41
|
+
|
|
42
|
+
if (!existsSync(localPath)) {
|
|
43
|
+
console.error(`\nArquivo nao encontrado: ${localPath}`);
|
|
44
|
+
console.error("Use --db-path para especificar o caminho do workflow.db\n");
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
console.log(`\n${"═".repeat(50)}`);
|
|
49
|
+
console.log("MIGRACAO: SQLite local → Turso");
|
|
50
|
+
console.log(`${"═".repeat(50)}\n`);
|
|
51
|
+
console.log(`Origem: ${localPath}`);
|
|
52
|
+
|
|
53
|
+
// 1. Abrir DB local via @libsql/client file: mode
|
|
54
|
+
const localClient = createClient({ url: `file:${localPath}` });
|
|
55
|
+
|
|
56
|
+
// 2. Garantir que o schema remoto esta atualizado
|
|
57
|
+
console.log("Destino: inicializando schema no Turso...");
|
|
58
|
+
await initSchema();
|
|
59
|
+
const remoteClient = getDb();
|
|
60
|
+
|
|
61
|
+
const remoteUrl = process.env.CODEXA_DB_URL || "(config.json)";
|
|
62
|
+
console.log(`Destino: ${remoteUrl}\n`);
|
|
63
|
+
|
|
64
|
+
if (options.dryRun) {
|
|
65
|
+
console.log("[DRY RUN] Nenhum dado sera escrito.\n");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// 3. Para cada tabela, ler colunas do schema remoto
|
|
69
|
+
let totalRows = 0;
|
|
70
|
+
let totalTables = 0;
|
|
71
|
+
|
|
72
|
+
for (const table of TABLES_IN_ORDER) {
|
|
73
|
+
// Verificar se a tabela existe no local
|
|
74
|
+
const localTableCheck = await localClient.execute({
|
|
75
|
+
sql: "SELECT name FROM sqlite_master WHERE type='table' AND name=?",
|
|
76
|
+
args: [table],
|
|
77
|
+
});
|
|
78
|
+
if (localTableCheck.rows.length === 0) continue;
|
|
79
|
+
|
|
80
|
+
// Pegar colunas do schema remoto (as que importam)
|
|
81
|
+
const remoteColumns = await remoteClient.execute(`PRAGMA table_info(${table})`);
|
|
82
|
+
const columnNames = remoteColumns.rows.map((r: any) => r.name as string);
|
|
83
|
+
|
|
84
|
+
if (columnNames.length === 0) continue;
|
|
85
|
+
|
|
86
|
+
// Pegar colunas do local para saber quais existem nos dois
|
|
87
|
+
const localColumns = await localClient.execute(`PRAGMA table_info(${table})`);
|
|
88
|
+
const localColumnNames = new Set(localColumns.rows.map((r: any) => r.name as string));
|
|
89
|
+
|
|
90
|
+
// Usar apenas colunas que existem em ambos
|
|
91
|
+
const commonColumns = columnNames.filter((c) => localColumnNames.has(c));
|
|
92
|
+
if (commonColumns.length === 0) continue;
|
|
93
|
+
|
|
94
|
+
// Ler dados do local (apenas colunas comuns)
|
|
95
|
+
const selectSql = `SELECT ${commonColumns.map((c) => `"${c}"`).join(", ")} FROM "${table}"`;
|
|
96
|
+
const localData = await localClient.execute(selectSql);
|
|
97
|
+
|
|
98
|
+
if (localData.rows.length === 0) continue;
|
|
99
|
+
|
|
100
|
+
const count = localData.rows.length;
|
|
101
|
+
totalRows += count;
|
|
102
|
+
totalTables++;
|
|
103
|
+
|
|
104
|
+
if (options.dryRun) {
|
|
105
|
+
console.log(` ${table}: ${count} registros`);
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Inserir no remoto em batches (max 20 statements por batch)
|
|
110
|
+
const placeholders = commonColumns.map(() => "?").join(", ");
|
|
111
|
+
const insertSql = `INSERT OR IGNORE INTO "${table}" (${commonColumns.map((c) => `"${c}"`).join(", ")}) VALUES (${placeholders})`;
|
|
112
|
+
|
|
113
|
+
const BATCH_SIZE = 20;
|
|
114
|
+
for (let i = 0; i < localData.rows.length; i += BATCH_SIZE) {
|
|
115
|
+
const batch = localData.rows.slice(i, i + BATCH_SIZE);
|
|
116
|
+
await remoteClient.batch(
|
|
117
|
+
batch.map((row: any) => ({
|
|
118
|
+
sql: insertSql,
|
|
119
|
+
args: commonColumns.map((col) => row[col] ?? null),
|
|
120
|
+
})),
|
|
121
|
+
"write"
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
console.log(` ${table}: ${count} registros migrados`);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
localClient.close();
|
|
129
|
+
|
|
130
|
+
console.log(`\n${"─".repeat(50)}`);
|
|
131
|
+
if (options.dryRun) {
|
|
132
|
+
console.log(`[DRY RUN] ${totalRows} registros em ${totalTables} tabelas seriam migrados.`);
|
|
133
|
+
} else {
|
|
134
|
+
console.log(`Migracao concluida: ${totalRows} registros em ${totalTables} tabelas.`);
|
|
135
|
+
}
|
|
136
|
+
console.log();
|
|
137
|
+
}
|
package/db/connection.ts
CHANGED
|
@@ -10,31 +10,70 @@ const TURSO_API = "https://api.turso.tech/v1";
|
|
|
10
10
|
const CONFIG_PATH = () => join(process.cwd(), ".codexa", "config.json");
|
|
11
11
|
|
|
12
12
|
// ═══════════════════════════════════════════════════════════════
|
|
13
|
-
//
|
|
13
|
+
// Config: database URL + database token (cached locally)
|
|
14
|
+
//
|
|
15
|
+
// Tokens no Turso:
|
|
16
|
+
// TURSO_API_TOKEN → Platform API (criar/gerenciar DBs)
|
|
17
|
+
// TURSO_AUTH_TOKEN → Conexao ao DB (libSQL protocol)
|
|
18
|
+
//
|
|
19
|
+
// O auto-provisioning usa TURSO_API_TOKEN para criar o DB e
|
|
20
|
+
// mintar um database token, que e salvo no config.json junto
|
|
21
|
+
// com a URL. Depois disso, so TURSO_AUTH_TOKEN e necessario
|
|
22
|
+
// (ou o token do config).
|
|
14
23
|
// ═══════════════════════════════════════════════════════════════
|
|
15
24
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
25
|
+
interface CodexaConfig {
|
|
26
|
+
database?: string;
|
|
27
|
+
token?: string;
|
|
28
|
+
}
|
|
19
29
|
|
|
20
|
-
|
|
30
|
+
function loadConfig(): CodexaConfig {
|
|
21
31
|
const configPath = CONFIG_PATH();
|
|
22
32
|
if (existsSync(configPath)) {
|
|
23
33
|
try {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
} catch { /* config invalido, continuar */ }
|
|
34
|
+
return JSON.parse(readFileSync(configPath, "utf-8"));
|
|
35
|
+
} catch { /* config invalido */ }
|
|
27
36
|
}
|
|
37
|
+
return {};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function saveConfig(config: CodexaConfig): void {
|
|
41
|
+
try {
|
|
42
|
+
const configPath = CONFIG_PATH();
|
|
43
|
+
const dir = dirname(configPath);
|
|
44
|
+
if (!existsSync(dir)) {
|
|
45
|
+
mkdirSync(dir, { recursive: true });
|
|
46
|
+
}
|
|
47
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
48
|
+
} catch { /* falha ao salvar cache, nao e critico */ }
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ═══════════════════════════════════════════════════════════════
|
|
52
|
+
// Resolucao: URL e token para conexao
|
|
53
|
+
// ═══════════════════════════════════════════════════════════════
|
|
54
|
+
|
|
55
|
+
function resolveDbUrl(): string {
|
|
56
|
+
if (process.env.CODEXA_DB_URL) return process.env.CODEXA_DB_URL;
|
|
57
|
+
|
|
58
|
+
const config = loadConfig();
|
|
59
|
+
if (config.database) return config.database;
|
|
28
60
|
|
|
29
|
-
// 3. Precisa provisionar — retorna placeholder que sera resolvido em ensureDatabase()
|
|
30
|
-
// Nao temos a URL real sem chamar a API (por causa da regiao no hostname)
|
|
31
61
|
throw new NeedsProvisioningError();
|
|
32
62
|
}
|
|
33
63
|
|
|
64
|
+
function resolveDbToken(): string | undefined {
|
|
65
|
+
// 1. Env var explicita (sempre tem prioridade)
|
|
66
|
+
if (process.env.TURSO_AUTH_TOKEN) return process.env.TURSO_AUTH_TOKEN;
|
|
67
|
+
|
|
68
|
+
// 2. Token do config (mintado durante provisioning)
|
|
69
|
+
const config = loadConfig();
|
|
70
|
+
if (config.token) return config.token;
|
|
71
|
+
|
|
72
|
+
return undefined;
|
|
73
|
+
}
|
|
74
|
+
|
|
34
75
|
class NeedsProvisioningError extends Error {
|
|
35
|
-
constructor() {
|
|
36
|
-
super("DB_NEEDS_PROVISIONING");
|
|
37
|
-
}
|
|
76
|
+
constructor() { super("DB_NEEDS_PROVISIONING"); }
|
|
38
77
|
}
|
|
39
78
|
|
|
40
79
|
function deriveDbNameFromGit(): string {
|
|
@@ -50,84 +89,57 @@ function deriveDbNameFromGit(): string {
|
|
|
50
89
|
return dirName.toLowerCase().replace(/[^a-z0-9-]/g, "-");
|
|
51
90
|
}
|
|
52
91
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const dir = dirname(configPath);
|
|
57
|
-
if (!existsSync(dir)) {
|
|
58
|
-
mkdirSync(dir, { recursive: true });
|
|
59
|
-
}
|
|
60
|
-
writeFileSync(configPath, JSON.stringify({ database: url }, null, 2) + "\n");
|
|
61
|
-
} catch { /* falha ao salvar cache, nao e critico */ }
|
|
62
|
-
}
|
|
92
|
+
// ═══════════════════════════════════════════════════════════════
|
|
93
|
+
// Turso Platform API helpers
|
|
94
|
+
// ═══════════════════════════════════════════════════════════════
|
|
63
95
|
|
|
64
|
-
function
|
|
65
|
-
const
|
|
66
|
-
if (!
|
|
96
|
+
function apiToken(): string {
|
|
97
|
+
const token = process.env.TURSO_API_TOKEN || process.env.TURSO_AUTH_TOKEN;
|
|
98
|
+
if (!token) {
|
|
67
99
|
throw new Error(
|
|
68
|
-
"
|
|
69
|
-
"
|
|
70
|
-
" export
|
|
100
|
+
"Token Turso nao configurado.\n\n" +
|
|
101
|
+
"Para auto-provisioning:\n" +
|
|
102
|
+
" export TURSO_API_TOKEN=<platform-api-token>\n" +
|
|
103
|
+
" export TURSO_ORG=sua-org\n\n" +
|
|
104
|
+
"Gerar API token: turso auth api-tokens mint codexa\n" +
|
|
105
|
+
"Ou usar sessao CLI: export TURSO_API_TOKEN=$(turso auth token)"
|
|
71
106
|
);
|
|
72
107
|
}
|
|
108
|
+
return token;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function apiHeaders(): Record<string, string> {
|
|
73
112
|
return {
|
|
74
|
-
"Authorization": `Bearer ${
|
|
113
|
+
"Authorization": `Bearer ${apiToken()}`,
|
|
75
114
|
"Content-Type": "application/json",
|
|
76
115
|
};
|
|
77
116
|
}
|
|
78
117
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
async function fetchDatabaseUrl(org: string, dbName: string): Promise<string | null> {
|
|
86
|
-
try {
|
|
87
|
-
const response = await fetch(
|
|
88
|
-
`${TURSO_API}/organizations/${org}/databases/${dbName}`,
|
|
89
|
-
{ headers: tursoHeaders() }
|
|
90
|
-
);
|
|
91
|
-
|
|
92
|
-
if (response.status === 404) return null;
|
|
93
|
-
|
|
94
|
-
if (response.status === 401 || response.status === 403) {
|
|
95
|
-
const body = await response.text();
|
|
96
|
-
throw new Error(
|
|
97
|
-
`Autenticacao Turso falhou (${response.status}).\n` +
|
|
98
|
-
` ${body}\n\n` +
|
|
99
|
-
"Verifique:\n" +
|
|
100
|
-
" 1. TURSO_AUTH_TOKEN esta correto e nao expirado\n" +
|
|
101
|
-
" 2. TURSO_ORG corresponde a sua organizacao no Turso\n\n" +
|
|
102
|
-
" Gerar novo token: turso auth token"
|
|
103
|
-
);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
if (!response.ok) return null;
|
|
118
|
+
async function apiFetchDatabase(org: string, dbName: string): Promise<string | null> {
|
|
119
|
+
const response = await fetch(
|
|
120
|
+
`${TURSO_API}/organizations/${org}/databases/${dbName}`,
|
|
121
|
+
{ headers: apiHeaders() }
|
|
122
|
+
);
|
|
107
123
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
return null;
|
|
112
|
-
} catch (e: any) {
|
|
113
|
-
if (e.message?.includes("Autenticacao Turso")) throw e;
|
|
114
|
-
return null;
|
|
124
|
+
if (response.status === 404) return null;
|
|
125
|
+
if (response.status === 401 || response.status === 403) {
|
|
126
|
+
throwAuthError();
|
|
115
127
|
}
|
|
116
|
-
|
|
128
|
+
if (!response.ok) return null;
|
|
117
129
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
130
|
+
const data = await response.json() as any;
|
|
131
|
+
const hostname = data.database?.Hostname;
|
|
132
|
+
return hostname ? `libsql://${hostname}` : null;
|
|
133
|
+
}
|
|
122
134
|
|
|
123
|
-
async function
|
|
135
|
+
async function apiCreateDatabase(org: string, dbName: string): Promise<string> {
|
|
124
136
|
const group = process.env.TURSO_GROUP || "default";
|
|
125
137
|
|
|
126
138
|
const response = await fetch(
|
|
127
139
|
`${TURSO_API}/organizations/${org}/databases`,
|
|
128
140
|
{
|
|
129
141
|
method: "POST",
|
|
130
|
-
headers:
|
|
142
|
+
headers: apiHeaders(),
|
|
131
143
|
body: JSON.stringify({ name: dbName, group }),
|
|
132
144
|
}
|
|
133
145
|
);
|
|
@@ -135,49 +147,56 @@ async function createDatabase(org: string, dbName: string): Promise<string> {
|
|
|
135
147
|
if (response.ok) {
|
|
136
148
|
console.log(`[codexa] Banco criado no Turso: ${dbName}`);
|
|
137
149
|
await new Promise((r) => setTimeout(r, 2000));
|
|
138
|
-
|
|
139
|
-
// Buscar hostname real (a resposta do POST pode nao ter o formato completo)
|
|
140
|
-
const url = await fetchDatabaseUrl(org, dbName);
|
|
141
|
-
if (url) return url;
|
|
142
|
-
|
|
143
|
-
// Fallback: construir URL sem regiao (pode funcionar em algumas configs)
|
|
144
|
-
return `libsql://${dbName}-${org}.turso.io`;
|
|
150
|
+
return (await apiFetchDatabase(org, dbName)) || `libsql://${dbName}-${org}.turso.io`;
|
|
145
151
|
}
|
|
146
152
|
|
|
147
153
|
if (response.status === 409) {
|
|
148
|
-
|
|
149
|
-
const url = await fetchDatabaseUrl(org, dbName);
|
|
150
|
-
if (url) return url;
|
|
151
|
-
return `libsql://${dbName}-${org}.turso.io`;
|
|
154
|
+
return (await apiFetchDatabase(org, dbName)) || `libsql://${dbName}-${org}.turso.io`;
|
|
152
155
|
}
|
|
153
156
|
|
|
154
157
|
if (response.status === 401 || response.status === 403) {
|
|
155
|
-
|
|
156
|
-
throw new Error(
|
|
157
|
-
`Autenticacao Turso falhou (${response.status}).\n` +
|
|
158
|
-
` ${body}\n\n` +
|
|
159
|
-
"Verifique:\n" +
|
|
160
|
-
" 1. TURSO_AUTH_TOKEN esta correto e nao expirado\n" +
|
|
161
|
-
" 2. TURSO_ORG corresponde a sua organizacao no Turso\n\n" +
|
|
162
|
-
" Gerar novo token: turso auth token"
|
|
163
|
-
);
|
|
158
|
+
throwAuthError();
|
|
164
159
|
}
|
|
165
160
|
|
|
166
161
|
const body = await response.text();
|
|
162
|
+
throw new Error(`Falha ao criar banco (${response.status}): ${body}`);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async function apiMintDbToken(org: string, dbName: string): Promise<string> {
|
|
166
|
+
const response = await fetch(
|
|
167
|
+
`${TURSO_API}/organizations/${org}/databases/${dbName}/auth/tokens`,
|
|
168
|
+
{ method: "POST", headers: apiHeaders() }
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
if (!response.ok) {
|
|
172
|
+
const body = await response.text();
|
|
173
|
+
throw new Error(`Falha ao criar database token (${response.status}): ${body}`);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const data = await response.json() as any;
|
|
177
|
+
return data.jwt;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function throwAuthError(): never {
|
|
167
181
|
throw new Error(
|
|
168
|
-
|
|
169
|
-
|
|
182
|
+
"Turso Platform API rejeitou o token.\n\n" +
|
|
183
|
+
"Para auto-provisioning, use um Platform API token:\n" +
|
|
184
|
+
" turso auth api-tokens mint codexa\n" +
|
|
185
|
+
" export TURSO_API_TOKEN=<token-gerado>\n\n" +
|
|
186
|
+
"Ou se o banco ja existe, aponte direto:\n" +
|
|
187
|
+
" export CODEXA_DB_URL=libsql://seu-db.region.turso.io\n" +
|
|
188
|
+
" export TURSO_AUTH_TOKEN=<database-token>"
|
|
170
189
|
);
|
|
171
190
|
}
|
|
172
191
|
|
|
173
192
|
// ═══════════════════════════════════════════════════════════════
|
|
174
|
-
// Provisioning:
|
|
193
|
+
// Provisioning: cria DB + minta token + salva config
|
|
175
194
|
// ═══════════════════════════════════════════════════════════════
|
|
176
195
|
|
|
177
196
|
export async function ensureDatabase(): Promise<void> {
|
|
178
197
|
if (dbProvisioned) return;
|
|
179
198
|
|
|
180
|
-
// Se ja tem URL resolvida (env ou config),
|
|
199
|
+
// Se ja tem URL resolvida (env ou config), nada a fazer
|
|
181
200
|
try {
|
|
182
201
|
resolveDbUrl();
|
|
183
202
|
dbProvisioned = true;
|
|
@@ -186,36 +205,40 @@ export async function ensureDatabase(): Promise<void> {
|
|
|
186
205
|
if (!(e instanceof NeedsProvisioningError)) throw e;
|
|
187
206
|
}
|
|
188
207
|
|
|
189
|
-
// Precisa provisionar via API
|
|
208
|
+
// Precisa provisionar via Platform API
|
|
190
209
|
const org = process.env.TURSO_ORG;
|
|
191
210
|
if (!org) {
|
|
192
211
|
throw new Error(
|
|
193
212
|
"Banco de dados nao configurado.\n\n" +
|
|
194
|
-
"Opcao 1 —
|
|
213
|
+
"Opcao 1 — Auto-provisioning:\n" +
|
|
195
214
|
" export TURSO_ORG=sua-org\n" +
|
|
196
|
-
" export
|
|
215
|
+
" export TURSO_API_TOKEN=<platform-api-token>\n" +
|
|
197
216
|
" codexa init\n\n" +
|
|
198
|
-
"Opcao 2 —
|
|
199
|
-
" export CODEXA_DB_URL=libsql://seu-db.turso.io\n" +
|
|
200
|
-
" export TURSO_AUTH_TOKEN
|
|
217
|
+
"Opcao 2 — Apontar para banco existente:\n" +
|
|
218
|
+
" export CODEXA_DB_URL=libsql://seu-db.region.turso.io\n" +
|
|
219
|
+
" export TURSO_AUTH_TOKEN=<database-token>"
|
|
201
220
|
);
|
|
202
221
|
}
|
|
203
222
|
|
|
204
223
|
const dbName = deriveDbNameFromGit();
|
|
205
224
|
|
|
206
|
-
// 1.
|
|
207
|
-
let url = await
|
|
208
|
-
|
|
209
|
-
// 2. Se nao existe, criar
|
|
225
|
+
// 1. Buscar ou criar o DB
|
|
226
|
+
let url = await apiFetchDatabase(org, dbName);
|
|
210
227
|
if (!url) {
|
|
211
|
-
url = await
|
|
228
|
+
url = await apiCreateDatabase(org, dbName);
|
|
212
229
|
}
|
|
213
230
|
|
|
214
|
-
//
|
|
215
|
-
|
|
231
|
+
// 2. Mintar um database token para conexao
|
|
232
|
+
console.log(`[codexa] Gerando token de acesso...`);
|
|
233
|
+
const dbToken = await apiMintDbToken(org, dbName);
|
|
234
|
+
|
|
235
|
+
// 3. Salvar tudo no config (URL + token)
|
|
236
|
+
saveConfig({ database: url, token: dbToken });
|
|
237
|
+
console.log(`[codexa] Configuracao salva em .codexa/config.json`);
|
|
238
|
+
|
|
216
239
|
dbProvisioned = true;
|
|
217
240
|
|
|
218
|
-
// 4. Resetar client para usar a nova URL
|
|
241
|
+
// 4. Resetar client para usar a nova URL/token
|
|
219
242
|
if (client) {
|
|
220
243
|
client.close();
|
|
221
244
|
client = null;
|
|
@@ -229,7 +252,7 @@ export async function ensureDatabase(): Promise<void> {
|
|
|
229
252
|
export function getDb(): Client {
|
|
230
253
|
if (!client) {
|
|
231
254
|
const url = resolveDbUrl();
|
|
232
|
-
const authToken =
|
|
255
|
+
const authToken = resolveDbToken();
|
|
233
256
|
|
|
234
257
|
client = createClient({
|
|
235
258
|
url,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codexa/cli",
|
|
3
|
-
"version": "9.0.
|
|
3
|
+
"version": "9.0.36",
|
|
4
4
|
"description": "Orchestrated workflow system for Claude Code - manages feature development through parallel subagents with structured phases, gates, and quality enforcement.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/workflow.ts
CHANGED
|
@@ -41,6 +41,7 @@ import {
|
|
|
41
41
|
architectCancel,
|
|
42
42
|
} from "./commands/architect";
|
|
43
43
|
import { teamSuggest } from "./commands/team";
|
|
44
|
+
import { migrateToTurso } from "./commands/migrate";
|
|
44
45
|
import { initSchema } from "./db/schema";
|
|
45
46
|
import { closeDb, dbGet, dbRun, ensureDatabase, getResolvedDbUrl } from "./db/connection";
|
|
46
47
|
import { execSync } from "child_process";
|
|
@@ -574,6 +575,15 @@ program
|
|
|
574
575
|
console.log(`\nBanco de dados inicializado: ${url}\n`);
|
|
575
576
|
});
|
|
576
577
|
|
|
578
|
+
program
|
|
579
|
+
.command("migrate")
|
|
580
|
+
.description("Migra dados do SQLite local (.codexa/db/workflow.db) para o Turso")
|
|
581
|
+
.option("--db-path <path>", "Caminho do workflow.db local (padrao: .codexa/db/workflow.db)")
|
|
582
|
+
.option("--dry-run", "Simula a migracao sem escrever dados")
|
|
583
|
+
.action(async (options) => {
|
|
584
|
+
await migrateToTurso({ dbPath: options.dbPath, dryRun: options.dryRun });
|
|
585
|
+
});
|
|
586
|
+
|
|
577
587
|
// ═══════════════════════════════════════════════════════════════
|
|
578
588
|
// DISCOVER
|
|
579
589
|
// ═══════════════════════════════════════════════════════════════
|