@tomkapa/tayto 0.6.0 → 0.7.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/dist/{chunk-74Q55TOV.js → chunk-5V4TBQ5S.js} +38 -4
- package/dist/chunk-5V4TBQ5S.js.map +1 -0
- package/dist/index.js +29 -21
- package/dist/index.js.map +1 -1
- package/dist/{tui-WMESKCRD.js → tui-NCL4RFFD.js} +443 -181
- package/dist/tui-NCL4RFFD.js.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-74Q55TOV.js.map +0 -1
- package/dist/tui-WMESKCRD.js.map +0 -1
|
@@ -154,6 +154,38 @@ function err(error) {
|
|
|
154
154
|
return { ok: false, error };
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
+
// src/types/git-remote.ts
|
|
158
|
+
var GitRemote = class _GitRemote {
|
|
159
|
+
value;
|
|
160
|
+
constructor(normalized) {
|
|
161
|
+
this.value = normalized;
|
|
162
|
+
}
|
|
163
|
+
static parse(raw) {
|
|
164
|
+
return new _GitRemote(_GitRemote.normalize(raw));
|
|
165
|
+
}
|
|
166
|
+
static normalize(url) {
|
|
167
|
+
const trimmed = url.trim();
|
|
168
|
+
const sshMatch = trimmed.match(/^git@([^:]+):(.+?)(?:\.git)?$/);
|
|
169
|
+
if (sshMatch) {
|
|
170
|
+
const [, host, path] = sshMatch;
|
|
171
|
+
return `${host}/${path}`.toLowerCase();
|
|
172
|
+
}
|
|
173
|
+
try {
|
|
174
|
+
const parsed = new URL(trimmed);
|
|
175
|
+
const path = parsed.pathname.replace(/\.git$/, "").replace(/^\//, "");
|
|
176
|
+
return `${parsed.host}/${path}`.toLowerCase();
|
|
177
|
+
} catch {
|
|
178
|
+
return trimmed.toLowerCase();
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
equals(other) {
|
|
182
|
+
return this.value === other.value;
|
|
183
|
+
}
|
|
184
|
+
toString() {
|
|
185
|
+
return this.value;
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
|
|
157
189
|
// src/utils/git.ts
|
|
158
190
|
function detectGitRemote(cwd) {
|
|
159
191
|
try {
|
|
@@ -166,12 +198,13 @@ function detectGitRemote(cwd) {
|
|
|
166
198
|
logger.info("detectGitRemote: no git remote found (non-zero exit or error)");
|
|
167
199
|
return ok(null);
|
|
168
200
|
}
|
|
169
|
-
const
|
|
170
|
-
if (!
|
|
201
|
+
const raw = result.stdout.trim();
|
|
202
|
+
if (!raw) {
|
|
171
203
|
logger.info("detectGitRemote: empty stdout from git remote get-url");
|
|
172
204
|
return ok(null);
|
|
173
205
|
}
|
|
174
|
-
|
|
206
|
+
const remote = GitRemote.parse(raw);
|
|
207
|
+
logger.info(`detectGitRemote: found remote=${remote.value}`);
|
|
175
208
|
return ok(remote);
|
|
176
209
|
} catch (e) {
|
|
177
210
|
logger.info(`detectGitRemote: exception during git detection: ${String(e)}`);
|
|
@@ -183,6 +216,7 @@ export {
|
|
|
183
216
|
logger,
|
|
184
217
|
ok,
|
|
185
218
|
err,
|
|
219
|
+
GitRemote,
|
|
186
220
|
TaskStatus,
|
|
187
221
|
TaskType,
|
|
188
222
|
TaskLevel,
|
|
@@ -196,4 +230,4 @@ export {
|
|
|
196
230
|
isTerminalStatus,
|
|
197
231
|
detectGitRemote
|
|
198
232
|
};
|
|
199
|
-
//# sourceMappingURL=chunk-
|
|
233
|
+
//# sourceMappingURL=chunk-5V4TBQ5S.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/logging/logger.ts","../src/types/enums.ts","../src/utils/git.ts","../src/types/common.ts","../src/types/git-remote.ts"],"sourcesContent":["import { appendFileSync, readdirSync, unlinkSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { trace, type Span, SpanStatusCode } from '@opentelemetry/api';\n\nconst tracer = trace.getTracer('task');\nconst LOG_RETENTION_DAYS = 7;\n\nexport interface LogAttributes {\n [key: string]: string | number | boolean;\n}\n\ntype LogLevel = 'INFO' | 'WARN' | 'ERROR';\n\nfunction formatTimestamp(): string {\n return new Date().toISOString();\n}\n\nfunction formatAttrs(attrs?: LogAttributes): string {\n if (!attrs || Object.keys(attrs).length === 0) return '';\n return ' ' + JSON.stringify(attrs);\n}\n\nclass Logger {\n private logFilePath: string | null = null;\n\n init(logDir: string): void {\n const date = new Date().toISOString().slice(0, 10); // YYYY-MM-DD\n this.logFilePath = join(logDir, `task-${date}.log`);\n this.pruneOldLogs(logDir);\n }\n\n info(message: string, attrs?: LogAttributes): void {\n this.write('INFO', message, attrs);\n const span = trace.getActiveSpan();\n if (span) {\n span.addEvent(message, attrs);\n }\n }\n\n warn(message: string, attrs?: LogAttributes): void {\n this.write('WARN', message, attrs);\n const span = trace.getActiveSpan();\n if (span) {\n span.addEvent(`WARN: ${message}`, attrs);\n }\n }\n\n error(message: string, error?: unknown, attrs?: LogAttributes): void {\n const errorDetail = error instanceof Error ? ` | ${error.stack ?? error.message}` : '';\n this.write('ERROR', `${message}${errorDetail}`, attrs);\n const span = trace.getActiveSpan();\n if (span) {\n span.addEvent(`ERROR: ${message}`, attrs);\n if (error instanceof Error) {\n span.recordException(error);\n }\n span.setStatus({ code: SpanStatusCode.ERROR, message });\n }\n }\n\n startSpan<T>(name: string, fn: (span: Span) => T): T {\n return tracer.startActiveSpan(name, (span) => {\n try {\n const result = fn(span);\n span.end();\n return result;\n } catch (e) {\n if (e instanceof Error) {\n span.recordException(e);\n }\n span.setStatus({ code: SpanStatusCode.ERROR });\n span.end();\n throw e;\n }\n });\n }\n\n private write(level: LogLevel, message: string, attrs?: LogAttributes): void {\n if (!this.logFilePath) return;\n const line = `${formatTimestamp()} [${level}] ${message}${formatAttrs(attrs)}\\n`;\n try {\n appendFileSync(this.logFilePath, line);\n } catch {\n // Swallowing here is intentional: logging must never crash the app.\n // If the log file is unwritable, the OTel span still captures the event.\n }\n }\n\n private pruneOldLogs(logDir: string): void {\n try {\n const cutoff = Date.now() - LOG_RETENTION_DAYS * 24 * 60 * 60 * 1000;\n const files = readdirSync(logDir).filter((f) => f.startsWith('task-') && f.endsWith('.log'));\n for (const file of files) {\n const dateStr = file.slice('task-'.length, -'.log'.length);\n const fileDate = new Date(dateStr).getTime();\n if (!isNaN(fileDate) && fileDate < cutoff) {\n unlinkSync(join(logDir, file));\n }\n }\n } catch {\n // Best-effort cleanup — don't crash if pruning fails\n }\n }\n}\n\nexport const logger = new Logger();\n","export const TaskStatus = {\n Backlog: 'backlog',\n Todo: 'todo',\n InProgress: 'in-progress',\n Review: 'review',\n Done: 'done',\n Cancelled: 'cancelled',\n} as const;\nexport type TaskStatus = (typeof TaskStatus)[keyof typeof TaskStatus];\n\nexport const TaskType = {\n Epic: 'epic',\n Story: 'story',\n TechDebt: 'tech-debt',\n Bug: 'bug',\n} as const;\nexport type TaskType = (typeof TaskType)[keyof typeof TaskType];\n\n/**\n * Task level derived from type.\n * Level 1: epics (grouping/planning layer)\n * Level 2: stories, tech-debt, bugs (execution layer)\n */\nexport const TaskLevel = {\n Epic: 1,\n Work: 2,\n} as const;\nexport type TaskLevel = (typeof TaskLevel)[keyof typeof TaskLevel];\n\nconst TYPE_TO_LEVEL: Record<string, TaskLevel> = {\n [TaskType.Epic]: TaskLevel.Epic,\n [TaskType.Story]: TaskLevel.Work,\n [TaskType.TechDebt]: TaskLevel.Work,\n [TaskType.Bug]: TaskLevel.Work,\n};\n\nexport function getTaskLevel(type: string): TaskLevel {\n return TYPE_TO_LEVEL[type] ?? TaskLevel.Work;\n}\n\n/** Types that belong to the work (level 2) execution layer. */\nexport const WORK_TYPES: ReadonlySet<string> = new Set([\n TaskType.Story,\n TaskType.TechDebt,\n TaskType.Bug,\n]);\n\n/** Types stored in the database. */\nexport const DependencyType = {\n Blocks: 'blocks',\n RelatesTo: 'relates-to',\n Duplicates: 'duplicates',\n} as const;\nexport type DependencyType = (typeof DependencyType)[keyof typeof DependencyType];\n\n/**\n * UI-level dependency types — includes BlockedBy which is a reverse-Blocks\n * relationship resolved before persisting to the database.\n */\nexport const UIDependencyType = {\n ...DependencyType,\n BlockedBy: 'blocked-by',\n} as const;\nexport type UIDependencyType = (typeof UIDependencyType)[keyof typeof UIDependencyType];\n\n/** Gap between consecutive rank values, used for insertion between neighbors. */\nexport const RANK_GAP = 1000.0;\n\n/**\n * Collapse-safe midpoint between two rank values. Returns `null` when\n * IEEE 754 double precision cannot represent a strictly-between value —\n * callers use this as a signal to rebalance the rank grid and retry.\n * Returning a number unconditionally would silently collide with an\n * endpoint and corrupt the task ordering.\n */\nexport function midpoint(a: number, b: number): number | null {\n const m = (a + b) / 2;\n return m > a && m < b ? m : null;\n}\n\n/** Statuses that represent terminal/completed task states. */\nexport const TERMINAL_STATUSES: ReadonlySet<string> = new Set([\n TaskStatus.Done,\n TaskStatus.Cancelled,\n]);\n\nexport function isTerminalStatus(status: string): boolean {\n return TERMINAL_STATUSES.has(status);\n}\n","import { spawnSync } from 'node:child_process';\nimport type { Result } from '../types/common.js';\nimport { ok } from '../types/common.js';\nimport { GitRemote } from '../types/git-remote.js';\nimport { logger } from '../logging/logger.js';\n\n/**\n * Detect the git origin remote URL from the given directory.\n * Returns ok(GitRemote) if found, ok(null) if no git repo or no origin remote.\n * Never returns err() — all git failures are silent fallbacks.\n */\nexport function detectGitRemote(cwd?: string): Result<GitRemote | null> {\n try {\n const result = spawnSync('git', ['remote', 'get-url', 'origin'], {\n cwd: cwd ?? process.cwd(),\n encoding: 'utf-8',\n timeout: 5000,\n });\n\n if (result.status !== 0 || result.error) {\n logger.info('detectGitRemote: no git remote found (non-zero exit or error)');\n return ok(null);\n }\n\n const raw = result.stdout.trim();\n if (!raw) {\n logger.info('detectGitRemote: empty stdout from git remote get-url');\n return ok(null);\n }\n\n const remote = GitRemote.parse(raw);\n logger.info(`detectGitRemote: found remote=${remote.value}`);\n return ok(remote);\n } catch (e: unknown) {\n logger.info(`detectGitRemote: exception during git detection: ${String(e)}`);\n return ok(null);\n }\n}\n","import type { AppError } from '../errors/app-error.js';\n\nexport type Result<T, E = AppError> = { ok: true; value: T } | { ok: false; error: E };\n\nexport function ok<T>(value: T): Result<T, never> {\n return { ok: true, value };\n}\n\nexport function err<E>(error: E): Result<never, E> {\n return { ok: false, error };\n}\n\nexport interface CLIOutput<T> {\n ok: boolean;\n data?: T;\n error?: { code: string; message: string };\n}\n","/**\n * Value object representing a normalized git remote URL.\n * Accepts any common format (SSH or HTTPS, with or without .git suffix)\n * and stores the canonical `host/owner/repo` form.\n */\nexport class GitRemote {\n readonly value: string;\n\n private constructor(normalized: string) {\n this.value = normalized;\n }\n\n static parse(raw: string): GitRemote {\n return new GitRemote(GitRemote.normalize(raw));\n }\n\n private static normalize(url: string): string {\n const trimmed = url.trim();\n\n // SSH format: git@github.com:owner/repo.git or git@github.com:owner/repo\n const sshMatch = trimmed.match(/^git@([^:]+):(.+?)(?:\\.git)?$/);\n if (sshMatch) {\n const [, host, path] = sshMatch;\n return `${host}/${path}`.toLowerCase();\n }\n\n // HTTPS/HTTP format: https://github.com/owner/repo.git or without .git\n try {\n const parsed = new URL(trimmed);\n const path = parsed.pathname.replace(/\\.git$/, '').replace(/^\\//, '');\n return `${parsed.host}/${path}`.toLowerCase();\n } catch {\n return trimmed.toLowerCase();\n }\n }\n\n equals(other: GitRemote): boolean {\n return this.value === other.value;\n }\n\n toString(): string {\n return this.value;\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
DependencyType,
|
|
4
|
+
GitRemote,
|
|
4
5
|
RANK_GAP,
|
|
5
6
|
TERMINAL_STATUSES,
|
|
6
7
|
TaskLevel,
|
|
@@ -15,7 +16,7 @@ import {
|
|
|
15
16
|
logger,
|
|
16
17
|
midpoint,
|
|
17
18
|
ok
|
|
18
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-5V4TBQ5S.js";
|
|
19
20
|
|
|
20
21
|
// src/config/index.ts
|
|
21
22
|
import { mkdirSync } from "fs";
|
|
@@ -35,6 +36,7 @@ function loadConfig() {
|
|
|
35
36
|
logLevel: process.env["TASK_LOG_LEVEL"] ?? "info",
|
|
36
37
|
otelEndpoint: process.env["OTEL_EXPORTER_OTLP_ENDPOINT"],
|
|
37
38
|
updateCachePath: join(dataDir, "update-check.json"),
|
|
39
|
+
dismissedGitRemotesPath: join(dataDir, "dismissed-git-remotes.json"),
|
|
38
40
|
noUpdateCheck: process.env["TAYTO_NO_UPDATE_CHECK"] === "1"
|
|
39
41
|
};
|
|
40
42
|
}
|
|
@@ -160,7 +162,7 @@ function rowToProject(row) {
|
|
|
160
162
|
name: row.name,
|
|
161
163
|
description: row.description,
|
|
162
164
|
isDefault: row.is_default === 1,
|
|
163
|
-
gitRemote: row.git_remote,
|
|
165
|
+
gitRemote: row.git_remote ? GitRemote.parse(row.git_remote) : null,
|
|
164
166
|
createdAt: row.created_at,
|
|
165
167
|
updatedAt: row.updated_at
|
|
166
168
|
};
|
|
@@ -187,7 +189,7 @@ var SqliteProjectRepository = class {
|
|
|
187
189
|
input.name,
|
|
188
190
|
input.description ?? "",
|
|
189
191
|
input.isDefault ? 1 : 0,
|
|
190
|
-
input.gitRemote ?? null,
|
|
192
|
+
input.gitRemote?.value ?? null,
|
|
191
193
|
now,
|
|
192
194
|
now
|
|
193
195
|
);
|
|
@@ -202,7 +204,7 @@ var SqliteProjectRepository = class {
|
|
|
202
204
|
return err(
|
|
203
205
|
new AppError(
|
|
204
206
|
"DUPLICATE",
|
|
205
|
-
`Git remote already linked to another project: ${input.gitRemote}`,
|
|
207
|
+
`Git remote already linked to another project: ${input.gitRemote?.value}`,
|
|
206
208
|
e
|
|
207
209
|
)
|
|
208
210
|
);
|
|
@@ -239,7 +241,7 @@ var SqliteProjectRepository = class {
|
|
|
239
241
|
}
|
|
240
242
|
findByGitRemote(remote) {
|
|
241
243
|
try {
|
|
242
|
-
const row = this.db.prepare(`SELECT * FROM projects WHERE git_remote = ? AND ${NOT_DELETED}`).get(remote);
|
|
244
|
+
const row = this.db.prepare(`SELECT * FROM projects WHERE git_remote = ? AND ${NOT_DELETED}`).get(remote.value);
|
|
243
245
|
return ok(row ? rowToProject(row) : null);
|
|
244
246
|
} catch (e) {
|
|
245
247
|
return err(new AppError("DB_ERROR", "Failed to find project by git remote", e));
|
|
@@ -280,7 +282,7 @@ var SqliteProjectRepository = class {
|
|
|
280
282
|
input.name ?? existing.name,
|
|
281
283
|
input.description ?? existing.description,
|
|
282
284
|
input.isDefault !== void 0 ? input.isDefault ? 1 : 0 : existing.is_default,
|
|
283
|
-
input.gitRemote !== void 0 ? input.gitRemote : existing.git_remote,
|
|
285
|
+
input.gitRemote !== void 0 ? input.gitRemote?.value ?? null : existing.git_remote,
|
|
284
286
|
now,
|
|
285
287
|
id
|
|
286
288
|
);
|
|
@@ -967,18 +969,19 @@ var SqliteDependencyRepository = class {
|
|
|
967
969
|
|
|
968
970
|
// src/types/project.ts
|
|
969
971
|
import { z } from "zod/v4";
|
|
972
|
+
var gitRemoteField = z.string().min(1, "Git remote URL must not be empty").transform((v) => GitRemote.parse(v)).nullable().optional();
|
|
970
973
|
var CreateProjectSchema = z.object({
|
|
971
974
|
name: z.string().min(1, "Project name is required").max(255),
|
|
972
975
|
key: z.string().min(2, "Project key must be at least 2 characters").max(7, "Project key must be at most 7 characters").regex(/^[A-Za-z0-9]+$/, "Project key must contain only letters and digits").transform((v) => v.toUpperCase()).optional(),
|
|
973
976
|
description: z.string().max(5e3).optional(),
|
|
974
977
|
isDefault: z.boolean().optional(),
|
|
975
|
-
gitRemote:
|
|
978
|
+
gitRemote: gitRemoteField
|
|
976
979
|
});
|
|
977
980
|
var UpdateProjectSchema = z.object({
|
|
978
981
|
name: z.string().min(1).max(255).optional(),
|
|
979
982
|
description: z.string().max(5e3).optional(),
|
|
980
983
|
isDefault: z.boolean().optional(),
|
|
981
|
-
gitRemote:
|
|
984
|
+
gitRemote: gitRemoteField
|
|
982
985
|
});
|
|
983
986
|
|
|
984
987
|
// src/service/project.service.ts
|
|
@@ -1083,8 +1086,10 @@ var ProjectServiceImpl = class {
|
|
|
1083
1086
|
return logger.startSpan("ProjectService.linkGitRemote", () => {
|
|
1084
1087
|
const resolved = this.resolveProject(idOrName);
|
|
1085
1088
|
if (!resolved.ok) return resolved;
|
|
1086
|
-
let
|
|
1087
|
-
if (
|
|
1089
|
+
let gitRemote;
|
|
1090
|
+
if (remote) {
|
|
1091
|
+
gitRemote = GitRemote.parse(remote);
|
|
1092
|
+
} else {
|
|
1088
1093
|
const detected = this.detectRemote();
|
|
1089
1094
|
if (!detected.ok) return detected;
|
|
1090
1095
|
if (!detected.value) {
|
|
@@ -1095,9 +1100,9 @@ var ProjectServiceImpl = class {
|
|
|
1095
1100
|
)
|
|
1096
1101
|
);
|
|
1097
1102
|
}
|
|
1098
|
-
|
|
1103
|
+
gitRemote = detected.value;
|
|
1099
1104
|
}
|
|
1100
|
-
return this.repo.update(resolved.value.id, { gitRemote
|
|
1105
|
+
return this.repo.update(resolved.value.id, { gitRemote });
|
|
1101
1106
|
});
|
|
1102
1107
|
}
|
|
1103
1108
|
unlinkGitRemote(idOrName) {
|
|
@@ -2140,7 +2145,7 @@ var UpdateServiceImpl = class {
|
|
|
2140
2145
|
};
|
|
2141
2146
|
|
|
2142
2147
|
// src/cli/container.ts
|
|
2143
|
-
function createContainer(db, dbPath, detectGitRemote2, updateCachePath) {
|
|
2148
|
+
function createContainer(db, dbPath, detectGitRemote2, updateCachePath, dismissedGitRemotesPath) {
|
|
2144
2149
|
const projectRepo = new SqliteProjectRepository(db);
|
|
2145
2150
|
const taskRepo = new SqliteTaskRepository(db);
|
|
2146
2151
|
const depRepo = new SqliteDependencyRepository(db);
|
|
@@ -2153,6 +2158,7 @@ function createContainer(db, dbPath, detectGitRemote2, updateCachePath) {
|
|
|
2153
2158
|
);
|
|
2154
2159
|
return {
|
|
2155
2160
|
dbPath,
|
|
2161
|
+
dismissedGitRemotesPath: dismissedGitRemotesPath ?? join3(tmpdir(), "tayto-dismissed-git-remotes.json"),
|
|
2156
2162
|
projectService,
|
|
2157
2163
|
taskService,
|
|
2158
2164
|
dependencyService,
|
|
@@ -2165,7 +2171,7 @@ function createContainer(db, dbPath, detectGitRemote2, updateCachePath) {
|
|
|
2165
2171
|
import { Command } from "commander";
|
|
2166
2172
|
|
|
2167
2173
|
// src/version.ts
|
|
2168
|
-
var APP_VERSION = true ? "0.
|
|
2174
|
+
var APP_VERSION = true ? "0.7.0" : "0.0.0-dev";
|
|
2169
2175
|
|
|
2170
2176
|
// src/cli/output.ts
|
|
2171
2177
|
function printSuccess(data) {
|
|
@@ -2214,18 +2220,20 @@ function registerProjectList(parent, container) {
|
|
|
2214
2220
|
|
|
2215
2221
|
// src/cli/commands/project/update.ts
|
|
2216
2222
|
function registerProjectUpdate(parent, container) {
|
|
2217
|
-
parent.command("update <idOrKeyOrName>").description("Update a project (lookup by id, key, or name)").option("-n, --name <name>", "Project name").option("-d, --description <description>", "Project description").option("--default", "Set as default project").action(
|
|
2223
|
+
parent.command("update <idOrKeyOrName>").description("Update a project (lookup by id, key, or name)").option("-n, --name <name>", "Project name").option("-d, --description <description>", "Project description").option("--default", "Set as default project").option("--git-remote <url>", "Git remote URL (use --no-git-remote to unlink)").option("--no-git-remote", "Unlink git remote").action(
|
|
2218
2224
|
(idOrKeyOrName, opts) => {
|
|
2219
2225
|
const resolved = container.projectService.resolveProject(idOrKeyOrName);
|
|
2220
2226
|
if (!resolved.ok) {
|
|
2221
2227
|
handleResult(resolved);
|
|
2222
2228
|
return;
|
|
2223
2229
|
}
|
|
2224
|
-
const
|
|
2230
|
+
const updateInput = {
|
|
2225
2231
|
name: opts.name,
|
|
2226
2232
|
description: opts.description,
|
|
2227
|
-
isDefault: opts.default
|
|
2228
|
-
|
|
2233
|
+
isDefault: opts.default,
|
|
2234
|
+
...opts.gitRemote === false ? { gitRemote: null } : typeof opts.gitRemote === "string" ? { gitRemote: opts.gitRemote } : {}
|
|
2235
|
+
};
|
|
2236
|
+
const result = container.projectService.updateProject(resolved.value.id, updateInput);
|
|
2229
2237
|
handleResult(result);
|
|
2230
2238
|
}
|
|
2231
2239
|
);
|
|
@@ -2589,7 +2597,7 @@ function buildCLI(container) {
|
|
|
2589
2597
|
registerDepGraph(dep, container);
|
|
2590
2598
|
registerUpgrade(program, container);
|
|
2591
2599
|
program.command("tui").description("Launch interactive terminal UI").option("-p, --project <project>", "Start with specific project").action(async (opts) => {
|
|
2592
|
-
const { launchTUI } = await import("./tui-
|
|
2600
|
+
const { launchTUI } = await import("./tui-NCL4RFFD.js");
|
|
2593
2601
|
await launchTUI(container, opts.project);
|
|
2594
2602
|
});
|
|
2595
2603
|
return program;
|
|
@@ -2623,7 +2631,7 @@ async function main() {
|
|
|
2623
2631
|
initTelemetry(config);
|
|
2624
2632
|
const db = createDatabase(config.dbPath);
|
|
2625
2633
|
runMigrations(db);
|
|
2626
|
-
const container = createContainer(db, config.dbPath, void 0, config.updateCachePath);
|
|
2634
|
+
const container = createContainer(db, config.dbPath, void 0, config.updateCachePath, config.dismissedGitRemotesPath);
|
|
2627
2635
|
const args = process.argv.slice(2);
|
|
2628
2636
|
const isUpgradeCommand = args[0] === "upgrade";
|
|
2629
2637
|
let updateCheck = null;
|
|
@@ -2631,7 +2639,7 @@ async function main() {
|
|
|
2631
2639
|
updateCheck = await checkForUpdateQuietly(container, APP_VERSION);
|
|
2632
2640
|
}
|
|
2633
2641
|
if (args.length === 0) {
|
|
2634
|
-
const { launchTUI } = await import("./tui-
|
|
2642
|
+
const { launchTUI } = await import("./tui-NCL4RFFD.js");
|
|
2635
2643
|
await launchTUI(
|
|
2636
2644
|
container,
|
|
2637
2645
|
void 0,
|