@jvittechs/j 1.0.57 → 1.0.59
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-3MKBSVAL.js +43 -0
- package/dist/chunk-3MKBSVAL.js.map +1 -0
- package/dist/chunk-BMDRQFY7.js +247 -0
- package/dist/chunk-BMDRQFY7.js.map +1 -0
- package/dist/{chunk-FZBVI5AX.js → chunk-SDYQQ4ZY.js} +517 -17
- package/dist/chunk-SDYQQ4ZY.js.map +1 -0
- package/dist/cli.js +881 -776
- package/dist/cli.js.map +1 -1
- package/dist/show-FGCDTIU7.js +8 -0
- package/dist/{summary-4J2OCCSA.js → summary-FIUGSB65.js} +3 -2
- package/dist/summary-FIUGSB65.js.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-FZBVI5AX.js.map +0 -1
- /package/dist/{summary-4J2OCCSA.js.map → show-FGCDTIU7.js.map} +0 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SettingsService
|
|
3
|
+
} from "./chunk-BMDRQFY7.js";
|
|
4
|
+
|
|
5
|
+
// src/commands/settings/show.ts
|
|
6
|
+
import { Command } from "commander";
|
|
7
|
+
import chalk from "chalk";
|
|
8
|
+
import YAML from "yaml";
|
|
9
|
+
function createSettingsShowCommand() {
|
|
10
|
+
return new Command("show").description("Hi\u1EC3n th\u1ECB all settings").option("-j, --json", "Output JSON").action(async (options) => {
|
|
11
|
+
const service = new SettingsService();
|
|
12
|
+
const settings = service.load();
|
|
13
|
+
if (options.json) {
|
|
14
|
+
console.log(JSON.stringify(settings, null, 2));
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
if (!service.exists()) {
|
|
18
|
+
console.log(chalk.dim("No settings file found. Run `j settings init` to create one."));
|
|
19
|
+
console.log("");
|
|
20
|
+
console.log(chalk.dim("Using defaults:"));
|
|
21
|
+
} else {
|
|
22
|
+
console.log(chalk.bold("\u{1F4CB} Project Settings"));
|
|
23
|
+
console.log(chalk.dim(` Path: ${service.getSettingsPath()}`));
|
|
24
|
+
console.log("");
|
|
25
|
+
}
|
|
26
|
+
console.log(YAML.stringify(settings, { indent: 2, lineWidth: 0 }).trim());
|
|
27
|
+
const repoUrl = service.resolveGitRepoUrl();
|
|
28
|
+
if (repoUrl) {
|
|
29
|
+
console.log("");
|
|
30
|
+
console.log(chalk.dim("Derived:"));
|
|
31
|
+
console.log(` ${chalk.dim("repo_url:")} ${repoUrl}`);
|
|
32
|
+
const projectId = service.getProjectId();
|
|
33
|
+
if (projectId) {
|
|
34
|
+
console.log(` ${chalk.dim("project_id:")} ${projectId}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export {
|
|
41
|
+
createSettingsShowCommand
|
|
42
|
+
};
|
|
43
|
+
//# sourceMappingURL=chunk-3MKBSVAL.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/settings/show.ts"],"sourcesContent":["/**\n * jai1 settings show [-j]\n */\nimport { Command } from 'commander';\nimport chalk from 'chalk';\nimport YAML from 'yaml';\nimport { SettingsService } from '../../services/settings.service.js';\nimport type { SettingsShowOptions } from '../../types/settings.types.js';\n\nexport function createSettingsShowCommand(): Command {\n return new Command('show')\n .description('Hiển thị all settings')\n .option('-j, --json', 'Output JSON')\n .action(async (options: SettingsShowOptions) => {\n const service = new SettingsService();\n const settings = service.load();\n\n if (options.json) {\n console.log(JSON.stringify(settings, null, 2));\n return;\n }\n\n if (!service.exists()) {\n console.log(chalk.dim('No settings file found. Run `j settings init` to create one.'));\n console.log('');\n console.log(chalk.dim('Using defaults:'));\n } else {\n console.log(chalk.bold('📋 Project Settings'));\n console.log(chalk.dim(` Path: ${service.getSettingsPath()}`));\n console.log('');\n }\n\n console.log(YAML.stringify(settings, { indent: 2, lineWidth: 0 }).trim());\n\n // Show derived info\n const repoUrl = service.resolveGitRepoUrl();\n if (repoUrl) {\n console.log('');\n console.log(chalk.dim('Derived:'));\n console.log(` ${chalk.dim('repo_url:')} ${repoUrl}`);\n const projectId = service.getProjectId();\n if (projectId) {\n console.log(` ${chalk.dim('project_id:')} ${projectId}`);\n }\n }\n });\n}\n"],"mappings":";;;;;AAGA,SAAS,eAAe;AACxB,OAAO,WAAW;AAClB,OAAO,UAAU;AAIV,SAAS,4BAAqC;AACjD,SAAO,IAAI,QAAQ,MAAM,EACpB,YAAY,iCAAuB,EACnC,OAAO,cAAc,aAAa,EAClC,OAAO,OAAO,YAAiC;AAC5C,UAAM,UAAU,IAAI,gBAAgB;AACpC,UAAM,WAAW,QAAQ,KAAK;AAE9B,QAAI,QAAQ,MAAM;AACd,cAAQ,IAAI,KAAK,UAAU,UAAU,MAAM,CAAC,CAAC;AAC7C;AAAA,IACJ;AAEA,QAAI,CAAC,QAAQ,OAAO,GAAG;AACnB,cAAQ,IAAI,MAAM,IAAI,8DAA8D,CAAC;AACrF,cAAQ,IAAI,EAAE;AACd,cAAQ,IAAI,MAAM,IAAI,iBAAiB,CAAC;AAAA,IAC5C,OAAO;AACH,cAAQ,IAAI,MAAM,KAAK,4BAAqB,CAAC;AAC7C,cAAQ,IAAI,MAAM,IAAI,YAAY,QAAQ,gBAAgB,CAAC,EAAE,CAAC;AAC9D,cAAQ,IAAI,EAAE;AAAA,IAClB;AAEA,YAAQ,IAAI,KAAK,UAAU,UAAU,EAAE,QAAQ,GAAG,WAAW,EAAE,CAAC,EAAE,KAAK,CAAC;AAGxE,UAAM,UAAU,QAAQ,kBAAkB;AAC1C,QAAI,SAAS;AACT,cAAQ,IAAI,EAAE;AACd,cAAQ,IAAI,MAAM,IAAI,UAAU,CAAC;AACjC,cAAQ,IAAI,KAAK,MAAM,IAAI,WAAW,CAAC,MAAM,OAAO,EAAE;AACtD,YAAM,YAAY,QAAQ,aAAa;AACvC,UAAI,WAAW;AACX,gBAAQ,IAAI,KAAK,MAAM,IAAI,aAAa,CAAC,IAAI,SAAS,EAAE;AAAA,MAC5D;AAAA,IACJ;AAAA,EACJ,CAAC;AACT;","names":[]}
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
// src/services/settings.service.ts
|
|
2
|
+
import { promises as fs, existsSync, readFileSync } from "fs";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import { execSync } from "child_process";
|
|
5
|
+
import { createHash } from "crypto";
|
|
6
|
+
import YAML from "yaml";
|
|
7
|
+
|
|
8
|
+
// src/types/settings.types.ts
|
|
9
|
+
import { z } from "zod";
|
|
10
|
+
var TaskSettingsSchema = z.object({
|
|
11
|
+
cloud: z.boolean().default(false),
|
|
12
|
+
projectId: z.string().optional()
|
|
13
|
+
// auto-derived from git remote URL
|
|
14
|
+
});
|
|
15
|
+
var ProjectSettingsSchema = z.object({
|
|
16
|
+
tasks: TaskSettingsSchema.default({})
|
|
17
|
+
// Future extensions:
|
|
18
|
+
// notifications: z.object({...}).default({}),
|
|
19
|
+
// integrations: z.object({...}).default({}),
|
|
20
|
+
});
|
|
21
|
+
var DEFAULT_SETTINGS = {
|
|
22
|
+
tasks: {
|
|
23
|
+
cloud: false
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// src/services/settings.service.ts
|
|
28
|
+
var SETTINGS_FILE = ".jai1/settings.yaml";
|
|
29
|
+
var SettingsService = class {
|
|
30
|
+
settingsPath;
|
|
31
|
+
cwd;
|
|
32
|
+
cache = null;
|
|
33
|
+
constructor(cwd) {
|
|
34
|
+
this.cwd = cwd || process.cwd();
|
|
35
|
+
this.settingsPath = join(this.cwd, SETTINGS_FILE);
|
|
36
|
+
}
|
|
37
|
+
// ============================================
|
|
38
|
+
// READ
|
|
39
|
+
// ============================================
|
|
40
|
+
/**
|
|
41
|
+
* Check if settings file exists
|
|
42
|
+
*/
|
|
43
|
+
exists() {
|
|
44
|
+
return existsSync(this.settingsPath);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Load and validate settings from YAML file
|
|
48
|
+
* Returns defaults if file doesn't exist
|
|
49
|
+
*/
|
|
50
|
+
load() {
|
|
51
|
+
if (this.cache) return this.cache;
|
|
52
|
+
if (!this.exists()) {
|
|
53
|
+
this.cache = { ...DEFAULT_SETTINGS };
|
|
54
|
+
return this.cache;
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
57
|
+
const content = readFileSync(this.settingsPath, "utf-8");
|
|
58
|
+
const raw = YAML.parse(content) || {};
|
|
59
|
+
this.cache = ProjectSettingsSchema.parse(raw);
|
|
60
|
+
return this.cache;
|
|
61
|
+
} catch (error) {
|
|
62
|
+
throw new Error(
|
|
63
|
+
`Failed to load settings: ${error instanceof Error ? error.message : String(error)}`
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Get a setting value by dot-notation key
|
|
69
|
+
* e.g., get('tasks.cloud') → true
|
|
70
|
+
*/
|
|
71
|
+
get(key) {
|
|
72
|
+
const settings = this.load();
|
|
73
|
+
const parts = key.split(".");
|
|
74
|
+
let current = settings;
|
|
75
|
+
for (const part of parts) {
|
|
76
|
+
if (current === null || current === void 0 || typeof current !== "object") {
|
|
77
|
+
return void 0;
|
|
78
|
+
}
|
|
79
|
+
current = current[part];
|
|
80
|
+
}
|
|
81
|
+
return current;
|
|
82
|
+
}
|
|
83
|
+
// ============================================
|
|
84
|
+
// WRITE
|
|
85
|
+
// ============================================
|
|
86
|
+
/**
|
|
87
|
+
* Save settings to YAML file
|
|
88
|
+
* Creates .jai1 directory if it doesn't exist
|
|
89
|
+
*/
|
|
90
|
+
async save(settings) {
|
|
91
|
+
const dir = join(this.cwd, ".jai1");
|
|
92
|
+
await fs.mkdir(dir, { recursive: true });
|
|
93
|
+
const content = YAML.stringify(settings, {
|
|
94
|
+
indent: 2,
|
|
95
|
+
lineWidth: 0
|
|
96
|
+
});
|
|
97
|
+
await fs.writeFile(this.settingsPath, content, "utf-8");
|
|
98
|
+
this.cache = settings;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Set a single setting value by dot-notation key
|
|
102
|
+
* e.g., set('tasks.cloud', true)
|
|
103
|
+
*/
|
|
104
|
+
async set(key, value) {
|
|
105
|
+
const settings = this.load();
|
|
106
|
+
const parts = key.split(".");
|
|
107
|
+
let current = settings;
|
|
108
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
109
|
+
const part = parts[i];
|
|
110
|
+
if (typeof current[part] !== "object" || current[part] === null) {
|
|
111
|
+
current[part] = {};
|
|
112
|
+
}
|
|
113
|
+
current = current[part];
|
|
114
|
+
}
|
|
115
|
+
const lastKey = parts[parts.length - 1];
|
|
116
|
+
if (value === "true") value = true;
|
|
117
|
+
else if (value === "false") value = false;
|
|
118
|
+
else if (typeof value === "string" && /^\d+$/.test(value)) value = parseInt(value, 10);
|
|
119
|
+
current[lastKey] = value;
|
|
120
|
+
const validated = ProjectSettingsSchema.parse(settings);
|
|
121
|
+
await this.save(validated);
|
|
122
|
+
return validated;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Initialize settings file with defaults
|
|
126
|
+
*/
|
|
127
|
+
async init(force = false) {
|
|
128
|
+
if (this.exists() && !force) {
|
|
129
|
+
throw new Error("Settings file already exists. Use --force to overwrite.");
|
|
130
|
+
}
|
|
131
|
+
await this.save(DEFAULT_SETTINGS);
|
|
132
|
+
return DEFAULT_SETTINGS;
|
|
133
|
+
}
|
|
134
|
+
// ============================================
|
|
135
|
+
// SHORTCUTS
|
|
136
|
+
// ============================================
|
|
137
|
+
/**
|
|
138
|
+
* Check if task cloud mode is enabled
|
|
139
|
+
*/
|
|
140
|
+
isTaskCloudEnabled() {
|
|
141
|
+
const settings = this.load();
|
|
142
|
+
return settings.tasks.cloud === true;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Get task settings
|
|
146
|
+
*/
|
|
147
|
+
getTaskSettings() {
|
|
148
|
+
return this.load().tasks;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Get or derive projectId from git remote URL.
|
|
152
|
+
* Synchronously derives if not cached; use getOrCreateProjectId() to also persist.
|
|
153
|
+
*/
|
|
154
|
+
getProjectId() {
|
|
155
|
+
const settings = this.load();
|
|
156
|
+
if (settings.tasks.projectId) {
|
|
157
|
+
return settings.tasks.projectId;
|
|
158
|
+
}
|
|
159
|
+
const repoUrl = this.resolveGitRepoUrl();
|
|
160
|
+
if (!repoUrl) return null;
|
|
161
|
+
const projectId = this.deriveProjectId(repoUrl);
|
|
162
|
+
settings.tasks.projectId = projectId;
|
|
163
|
+
this.cache = settings;
|
|
164
|
+
this.save(settings).catch(() => {
|
|
165
|
+
});
|
|
166
|
+
return projectId;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Get and cache projectId (writes to settings.yaml, awaitable)
|
|
170
|
+
*/
|
|
171
|
+
async getOrCreateProjectId() {
|
|
172
|
+
const settings = this.load();
|
|
173
|
+
if (settings.tasks.projectId) {
|
|
174
|
+
return settings.tasks.projectId;
|
|
175
|
+
}
|
|
176
|
+
const repoUrl = this.resolveGitRepoUrl();
|
|
177
|
+
if (!repoUrl) return null;
|
|
178
|
+
const projectId = this.deriveProjectId(repoUrl);
|
|
179
|
+
await this.set("tasks.projectId", projectId);
|
|
180
|
+
return projectId;
|
|
181
|
+
}
|
|
182
|
+
// ============================================
|
|
183
|
+
// GIT HELPERS
|
|
184
|
+
// ============================================
|
|
185
|
+
/**
|
|
186
|
+
* Resolve git remote URL from current repo
|
|
187
|
+
* Returns normalized URL (strip .git, credentials)
|
|
188
|
+
*/
|
|
189
|
+
resolveGitRepoUrl() {
|
|
190
|
+
try {
|
|
191
|
+
const raw = execSync("git remote get-url origin", {
|
|
192
|
+
cwd: this.cwd,
|
|
193
|
+
encoding: "utf-8",
|
|
194
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
195
|
+
}).trim();
|
|
196
|
+
return this.normalizeRepoUrl(raw);
|
|
197
|
+
} catch {
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Normalize git repo URL to consistent format
|
|
203
|
+
* git@github.com:org/repo.git → github.com/org/repo
|
|
204
|
+
* https://github.com/org/repo.git → github.com/org/repo
|
|
205
|
+
*/
|
|
206
|
+
normalizeRepoUrl(url) {
|
|
207
|
+
let normalized = url;
|
|
208
|
+
const sshMatch = normalized.match(/^(?:git@|ssh:\/\/(?:[^@]+@)?)([^:\/]+)[:/](.+?)(?:\.git)?$/);
|
|
209
|
+
if (sshMatch) {
|
|
210
|
+
return `${sshMatch[1]}/${sshMatch[2]}`;
|
|
211
|
+
}
|
|
212
|
+
const httpsMatch = normalized.match(/^https?:\/\/(?:[^@]+@)?([^/]+)\/(.+?)(?:\.git)?$/);
|
|
213
|
+
if (httpsMatch) {
|
|
214
|
+
return `${httpsMatch[1]}/${httpsMatch[2]}`;
|
|
215
|
+
}
|
|
216
|
+
return normalized;
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Derive projectId from normalized repo URL
|
|
220
|
+
* → SHA256 hash (12 chars) + slug
|
|
221
|
+
*/
|
|
222
|
+
deriveProjectId(repoUrl) {
|
|
223
|
+
const hash = createHash("sha256").update(repoUrl).digest("hex").substring(0, 12);
|
|
224
|
+
const slug = repoUrl.replace(/[^a-zA-Z0-9]+/g, "-").replace(/^-|-$/g, "").toLowerCase();
|
|
225
|
+
return `${hash}-${slug}`;
|
|
226
|
+
}
|
|
227
|
+
// ============================================
|
|
228
|
+
// UTILITY
|
|
229
|
+
// ============================================
|
|
230
|
+
/**
|
|
231
|
+
* Get settings file path
|
|
232
|
+
*/
|
|
233
|
+
getSettingsPath() {
|
|
234
|
+
return this.settingsPath;
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Clear cache (for testing or after external updates)
|
|
238
|
+
*/
|
|
239
|
+
clearCache() {
|
|
240
|
+
this.cache = null;
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
export {
|
|
245
|
+
SettingsService
|
|
246
|
+
};
|
|
247
|
+
//# sourceMappingURL=chunk-BMDRQFY7.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/services/settings.service.ts","../src/types/settings.types.ts"],"sourcesContent":["/**\n * Settings Service\n * Read/write .jai1/settings.yaml\n */\nimport { promises as fs, existsSync, readFileSync } from 'fs';\nimport { join } from 'path';\nimport { execSync } from 'child_process';\nimport { createHash } from 'crypto';\nimport YAML from 'yaml';\nimport {\n ProjectSettingsSchema,\n type ProjectSettings,\n type TaskSettings,\n DEFAULT_SETTINGS,\n} from '../types/settings.types.js';\n\nconst SETTINGS_FILE = '.jai1/settings.yaml';\n\nexport class SettingsService {\n private readonly settingsPath: string;\n private readonly cwd: string;\n private cache: ProjectSettings | null = null;\n\n constructor(cwd?: string) {\n this.cwd = cwd || process.cwd();\n this.settingsPath = join(this.cwd, SETTINGS_FILE);\n }\n\n // ============================================\n // READ\n // ============================================\n\n /**\n * Check if settings file exists\n */\n exists(): boolean {\n return existsSync(this.settingsPath);\n }\n\n /**\n * Load and validate settings from YAML file\n * Returns defaults if file doesn't exist\n */\n load(): ProjectSettings {\n if (this.cache) return this.cache;\n\n if (!this.exists()) {\n this.cache = { ...DEFAULT_SETTINGS };\n return this.cache;\n }\n\n try {\n const content = readFileSync(this.settingsPath, 'utf-8');\n const raw = YAML.parse(content) || {};\n this.cache = ProjectSettingsSchema.parse(raw);\n return this.cache;\n } catch (error) {\n throw new Error(\n `Failed to load settings: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n }\n\n /**\n * Get a setting value by dot-notation key\n * e.g., get('tasks.cloud') → true\n */\n get(key: string): unknown {\n const settings = this.load();\n const parts = key.split('.');\n let current: unknown = settings;\n\n for (const part of parts) {\n if (current === null || current === undefined || typeof current !== 'object') {\n return undefined;\n }\n current = (current as Record<string, unknown>)[part];\n }\n\n return current;\n }\n\n // ============================================\n // WRITE\n // ============================================\n\n /**\n * Save settings to YAML file\n * Creates .jai1 directory if it doesn't exist\n */\n async save(settings: ProjectSettings): Promise<void> {\n const dir = join(this.cwd, '.jai1');\n await fs.mkdir(dir, { recursive: true });\n\n const content = YAML.stringify(settings, {\n indent: 2,\n lineWidth: 0,\n });\n\n await fs.writeFile(this.settingsPath, content, 'utf-8');\n this.cache = settings;\n }\n\n /**\n * Set a single setting value by dot-notation key\n * e.g., set('tasks.cloud', true)\n */\n async set(key: string, value: unknown): Promise<ProjectSettings> {\n const settings = this.load();\n const parts = key.split('.');\n let current: Record<string, unknown> = settings as unknown as Record<string, unknown>;\n\n for (let i = 0; i < parts.length - 1; i++) {\n const part = parts[i]!;\n if (typeof current[part] !== 'object' || current[part] === null) {\n current[part] = {};\n }\n current = current[part] as Record<string, unknown>;\n }\n\n const lastKey = parts[parts.length - 1]!;\n\n // Auto-convert string values to proper types\n if (value === 'true') value = true;\n else if (value === 'false') value = false;\n else if (typeof value === 'string' && /^\\d+$/.test(value)) value = parseInt(value, 10);\n\n current[lastKey] = value;\n\n // Validate the entire settings object\n const validated = ProjectSettingsSchema.parse(settings);\n await this.save(validated);\n return validated;\n }\n\n /**\n * Initialize settings file with defaults\n */\n async init(force: boolean = false): Promise<ProjectSettings> {\n if (this.exists() && !force) {\n throw new Error('Settings file already exists. Use --force to overwrite.');\n }\n\n await this.save(DEFAULT_SETTINGS);\n return DEFAULT_SETTINGS;\n }\n\n // ============================================\n // SHORTCUTS\n // ============================================\n\n /**\n * Check if task cloud mode is enabled\n */\n isTaskCloudEnabled(): boolean {\n const settings = this.load();\n return settings.tasks.cloud === true;\n }\n\n /**\n * Get task settings\n */\n getTaskSettings(): TaskSettings {\n return this.load().tasks;\n }\n\n /**\n * Get or derive projectId from git remote URL.\n * Synchronously derives if not cached; use getOrCreateProjectId() to also persist.\n */\n getProjectId(): string | null {\n const settings = this.load();\n if (settings.tasks.projectId) {\n return settings.tasks.projectId;\n }\n\n // Try to derive from git remote\n const repoUrl = this.resolveGitRepoUrl();\n if (!repoUrl) return null;\n\n const projectId = this.deriveProjectId(repoUrl);\n\n // Cache in-memory so subsequent calls don't re-derive\n settings.tasks.projectId = projectId;\n this.cache = settings;\n\n // Fire-and-forget persist to settings.yaml (don't block sync callers)\n this.save(settings).catch(() => {});\n return projectId;\n }\n\n /**\n * Get and cache projectId (writes to settings.yaml, awaitable)\n */\n async getOrCreateProjectId(): Promise<string | null> {\n const settings = this.load();\n if (settings.tasks.projectId) {\n return settings.tasks.projectId;\n }\n\n const repoUrl = this.resolveGitRepoUrl();\n if (!repoUrl) return null;\n\n const projectId = this.deriveProjectId(repoUrl);\n await this.set('tasks.projectId', projectId);\n return projectId;\n }\n\n // ============================================\n // GIT HELPERS\n // ============================================\n\n /**\n * Resolve git remote URL from current repo\n * Returns normalized URL (strip .git, credentials)\n */\n resolveGitRepoUrl(): string | null {\n try {\n const raw = execSync('git remote get-url origin', {\n cwd: this.cwd,\n encoding: 'utf-8',\n stdio: ['pipe', 'pipe', 'pipe'],\n }).trim();\n\n return this.normalizeRepoUrl(raw);\n } catch {\n return null;\n }\n }\n\n /**\n * Normalize git repo URL to consistent format\n * git@github.com:org/repo.git → github.com/org/repo\n * https://github.com/org/repo.git → github.com/org/repo\n */\n private normalizeRepoUrl(url: string): string {\n let normalized = url;\n\n // SSH format: git@github.com:org/repo.git\n const sshMatch = normalized.match(/^(?:git@|ssh:\\/\\/(?:[^@]+@)?)([^:\\/]+)[:/](.+?)(?:\\.git)?$/);\n if (sshMatch) {\n return `${sshMatch[1]}/${sshMatch[2]}`;\n }\n\n // HTTPS format: https://github.com/org/repo.git\n const httpsMatch = normalized.match(/^https?:\\/\\/(?:[^@]+@)?([^/]+)\\/(.+?)(?:\\.git)?$/);\n if (httpsMatch) {\n return `${httpsMatch[1]}/${httpsMatch[2]}`;\n }\n\n return normalized;\n }\n\n /**\n * Derive projectId from normalized repo URL\n * → SHA256 hash (12 chars) + slug\n */\n private deriveProjectId(repoUrl: string): string {\n const hash = createHash('sha256').update(repoUrl).digest('hex').substring(0, 12);\n const slug = repoUrl\n .replace(/[^a-zA-Z0-9]+/g, '-')\n .replace(/^-|-$/g, '')\n .toLowerCase();\n return `${hash}-${slug}`;\n }\n\n // ============================================\n // UTILITY\n // ============================================\n\n /**\n * Get settings file path\n */\n getSettingsPath(): string {\n return this.settingsPath;\n }\n\n /**\n * Clear cache (for testing or after external updates)\n */\n clearCache(): void {\n this.cache = null;\n }\n}\n","/**\n * Project Settings Types\n * Schema for .jai1/settings.yaml\n */\nimport { z } from 'zod';\n\n// ============================================\n// SETTINGS SCHEMA\n// ============================================\n\nexport const TaskSettingsSchema = z.object({\n cloud: z.boolean().default(false),\n projectId: z.string().optional(), // auto-derived from git remote URL\n});\n\nexport type TaskSettings = z.infer<typeof TaskSettingsSchema>;\n\nexport const ProjectSettingsSchema = z.object({\n tasks: TaskSettingsSchema.default({}),\n // Future extensions:\n // notifications: z.object({...}).default({}),\n // integrations: z.object({...}).default({}),\n});\n\nexport type ProjectSettings = z.infer<typeof ProjectSettingsSchema>;\n\n// ============================================\n// DEFAULT VALUES\n// ============================================\n\nexport const DEFAULT_SETTINGS: ProjectSettings = {\n tasks: {\n cloud: false,\n },\n};\n\n// ============================================\n// CLI OPTION TYPES\n// ============================================\n\nexport interface SettingsSetOptions {\n json?: boolean;\n}\n\nexport interface SettingsGetOptions {\n json?: boolean;\n}\n\nexport interface SettingsShowOptions {\n json?: boolean;\n}\n\nexport interface SettingsInitOptions {\n force?: boolean;\n json?: boolean;\n}\n"],"mappings":"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","names":[]}
|