ai-builder 0.1.5 → 0.1.7
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/index.js +166 -64
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -9,6 +9,82 @@ import * as readline from "readline";
|
|
|
9
9
|
import chalk from "chalk";
|
|
10
10
|
import ora from "ora";
|
|
11
11
|
|
|
12
|
+
// src/services/metadata.ts
|
|
13
|
+
import * as os2 from "os";
|
|
14
|
+
|
|
15
|
+
// src/services/anonymous-id.ts
|
|
16
|
+
import * as crypto from "crypto";
|
|
17
|
+
import * as fs from "fs";
|
|
18
|
+
import * as os from "os";
|
|
19
|
+
import * as path from "path";
|
|
20
|
+
var CONFIG_DIR = path.join(os.homedir(), ".config", "ai-builder");
|
|
21
|
+
var ANONYMOUS_ID_FILE = path.join(CONFIG_DIR, "anonymous-id");
|
|
22
|
+
function isValidUuid(str) {
|
|
23
|
+
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
24
|
+
return uuidRegex.test(str);
|
|
25
|
+
}
|
|
26
|
+
function getAnonymousId() {
|
|
27
|
+
if (fs.existsSync(ANONYMOUS_ID_FILE)) {
|
|
28
|
+
try {
|
|
29
|
+
const id = fs.readFileSync(ANONYMOUS_ID_FILE, "utf-8").trim();
|
|
30
|
+
if (isValidUuid(id)) {
|
|
31
|
+
return id;
|
|
32
|
+
}
|
|
33
|
+
} catch {
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const newId = crypto.randomUUID();
|
|
37
|
+
try {
|
|
38
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
39
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
40
|
+
}
|
|
41
|
+
fs.writeFileSync(ANONYMOUS_ID_FILE, `${newId}
|
|
42
|
+
`, { mode: 384 });
|
|
43
|
+
} catch {
|
|
44
|
+
}
|
|
45
|
+
return newId;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// src/services/metadata.ts
|
|
49
|
+
function getShell() {
|
|
50
|
+
const shell = process.env.SHELL || process.env.COMSPEC;
|
|
51
|
+
if (!shell) return null;
|
|
52
|
+
const parts = shell.split(/[/\\]/);
|
|
53
|
+
return parts[parts.length - 1] || null;
|
|
54
|
+
}
|
|
55
|
+
function getTerminal() {
|
|
56
|
+
return process.env.TERM_PROGRAM || // iTerm.app, Apple_Terminal, vscode
|
|
57
|
+
process.env.TERMINAL_EMULATOR || // Some Linux terminals
|
|
58
|
+
(process.env.WT_SESSION ? "windows-terminal" : null);
|
|
59
|
+
}
|
|
60
|
+
function detectCi() {
|
|
61
|
+
return !!(process.env.CI || process.env.CONTINUOUS_INTEGRATION || process.env.BUILD_NUMBER || process.env.GITHUB_ACTIONS || process.env.GITLAB_CI || process.env.CIRCLECI || process.env.TRAVIS || process.env.JENKINS_URL);
|
|
62
|
+
}
|
|
63
|
+
function getLocale() {
|
|
64
|
+
return process.env.LC_ALL || process.env.LC_MESSAGES || process.env.LANG || process.env.LANGUAGE || null;
|
|
65
|
+
}
|
|
66
|
+
function getTimezone() {
|
|
67
|
+
try {
|
|
68
|
+
return Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
69
|
+
} catch {
|
|
70
|
+
return process.env.TZ || null;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function collectMetadata() {
|
|
74
|
+
return {
|
|
75
|
+
anonymousId: getAnonymousId(),
|
|
76
|
+
osType: os2.platform(),
|
|
77
|
+
osVersion: os2.release(),
|
|
78
|
+
osArch: os2.arch(),
|
|
79
|
+
nodeVersion: process.version,
|
|
80
|
+
shell: getShell(),
|
|
81
|
+
terminal: getTerminal(),
|
|
82
|
+
isCi: detectCi(),
|
|
83
|
+
locale: getLocale(),
|
|
84
|
+
timezone: getTimezone()
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
12
88
|
// src/services/api.ts
|
|
13
89
|
var API_BASE = process.env.AI_BUILDER_API_URL || "https://aibuilder.sh/api";
|
|
14
90
|
var isDebug = process.env.DEBUG?.includes("ai-builder");
|
|
@@ -67,12 +143,24 @@ async function searchArtifacts(options) {
|
|
|
67
143
|
}
|
|
68
144
|
async function trackInstall(type, slug, cliVersion) {
|
|
69
145
|
try {
|
|
146
|
+
const metadata = collectMetadata();
|
|
70
147
|
const response = await fetch(`${API_BASE}/install-events`, {
|
|
71
148
|
method: "POST",
|
|
72
149
|
headers: { "Content-Type": "application/json" },
|
|
73
|
-
body: JSON.stringify({ type, slug, cliVersion })
|
|
150
|
+
body: JSON.stringify({ type, slug, cliVersion, metadata })
|
|
74
151
|
});
|
|
75
|
-
|
|
152
|
+
const result = await response.json();
|
|
153
|
+
logger.debug(
|
|
154
|
+
{
|
|
155
|
+
type,
|
|
156
|
+
slug,
|
|
157
|
+
cliVersion,
|
|
158
|
+
anonymousId: metadata.anonymousId,
|
|
159
|
+
status: response.status,
|
|
160
|
+
result
|
|
161
|
+
},
|
|
162
|
+
"Install tracked"
|
|
163
|
+
);
|
|
76
164
|
} catch (error) {
|
|
77
165
|
logger.debug({ err: error, type, slug }, "Install tracking failed (non-critical)");
|
|
78
166
|
}
|
|
@@ -92,23 +180,23 @@ async function resolveStack(slug) {
|
|
|
92
180
|
}
|
|
93
181
|
|
|
94
182
|
// src/services/installer.ts
|
|
95
|
-
import * as
|
|
96
|
-
import * as
|
|
183
|
+
import * as fs3 from "fs";
|
|
184
|
+
import * as path3 from "path";
|
|
97
185
|
|
|
98
186
|
// src/services/lock-file.ts
|
|
99
|
-
import * as
|
|
100
|
-
import * as
|
|
187
|
+
import * as fs2 from "fs";
|
|
188
|
+
import * as path2 from "path";
|
|
101
189
|
var LOCK_FILE_NAME = "ai-builder.lock.json";
|
|
102
190
|
function getLockFilePath() {
|
|
103
|
-
return
|
|
191
|
+
return path2.join(process.cwd(), ".claude", LOCK_FILE_NAME);
|
|
104
192
|
}
|
|
105
193
|
function readLockFile() {
|
|
106
194
|
const lockPath = getLockFilePath();
|
|
107
|
-
if (!
|
|
195
|
+
if (!fs2.existsSync(lockPath)) {
|
|
108
196
|
return { version: 1, artifacts: {} };
|
|
109
197
|
}
|
|
110
198
|
try {
|
|
111
|
-
const content =
|
|
199
|
+
const content = fs2.readFileSync(lockPath, "utf-8");
|
|
112
200
|
return JSON.parse(content);
|
|
113
201
|
} catch {
|
|
114
202
|
return { version: 1, artifacts: {} };
|
|
@@ -116,11 +204,11 @@ function readLockFile() {
|
|
|
116
204
|
}
|
|
117
205
|
function writeLockFile(lockFile) {
|
|
118
206
|
const lockPath = getLockFilePath();
|
|
119
|
-
const lockDir =
|
|
120
|
-
if (!
|
|
121
|
-
|
|
207
|
+
const lockDir = path2.dirname(lockPath);
|
|
208
|
+
if (!fs2.existsSync(lockDir)) {
|
|
209
|
+
fs2.mkdirSync(lockDir, { recursive: true });
|
|
122
210
|
}
|
|
123
|
-
|
|
211
|
+
fs2.writeFileSync(lockPath, `${JSON.stringify(lockFile, null, 2)}
|
|
124
212
|
`);
|
|
125
213
|
}
|
|
126
214
|
function addToLockFile(entry) {
|
|
@@ -147,14 +235,14 @@ function isInstalled(type, author, slug) {
|
|
|
147
235
|
|
|
148
236
|
// src/services/installer.ts
|
|
149
237
|
function getInstallPath(type, artifactName) {
|
|
150
|
-
const basePath =
|
|
238
|
+
const basePath = path3.join(process.cwd(), ".claude");
|
|
151
239
|
switch (type) {
|
|
152
240
|
case "skill":
|
|
153
|
-
return
|
|
241
|
+
return path3.join(basePath, "skills", artifactName);
|
|
154
242
|
case "agent":
|
|
155
|
-
return
|
|
243
|
+
return path3.join(basePath, "agents", `${artifactName}.md`);
|
|
156
244
|
case "command":
|
|
157
|
-
return
|
|
245
|
+
return path3.join(basePath, "commands", `${artifactName}.md`);
|
|
158
246
|
default:
|
|
159
247
|
throw new Error(`Unknown artifact type: ${type}`);
|
|
160
248
|
}
|
|
@@ -166,24 +254,24 @@ function installArtifact(artifact, options = {}) {
|
|
|
166
254
|
if (!options.force && isInstalled(artifact.type, artifact.author, artifactName || artifact.name)) {
|
|
167
255
|
return { installed: false, files: [] };
|
|
168
256
|
}
|
|
169
|
-
const dir = artifact.type === "skill" ? installPath :
|
|
170
|
-
if (!
|
|
171
|
-
|
|
257
|
+
const dir = artifact.type === "skill" ? installPath : path3.dirname(installPath);
|
|
258
|
+
if (!fs3.existsSync(dir)) {
|
|
259
|
+
fs3.mkdirSync(dir, { recursive: true });
|
|
172
260
|
}
|
|
173
261
|
if (artifact.type === "skill") {
|
|
174
|
-
const skillMdPath =
|
|
175
|
-
|
|
262
|
+
const skillMdPath = path3.join(installPath, "SKILL.md");
|
|
263
|
+
fs3.writeFileSync(skillMdPath, artifact.content);
|
|
176
264
|
files.push(skillMdPath);
|
|
177
265
|
if (artifact.bundleFiles) {
|
|
178
266
|
for (const [filename, content] of Object.entries(artifact.bundleFiles)) {
|
|
179
|
-
const filePath =
|
|
180
|
-
|
|
181
|
-
|
|
267
|
+
const filePath = path3.join(installPath, filename);
|
|
268
|
+
fs3.mkdirSync(path3.dirname(filePath), { recursive: true });
|
|
269
|
+
fs3.writeFileSync(filePath, content);
|
|
182
270
|
files.push(filePath);
|
|
183
271
|
}
|
|
184
272
|
}
|
|
185
273
|
} else {
|
|
186
|
-
|
|
274
|
+
fs3.writeFileSync(installPath, artifact.content);
|
|
187
275
|
files.push(installPath);
|
|
188
276
|
}
|
|
189
277
|
addToLockFile({
|
|
@@ -192,19 +280,19 @@ function installArtifact(artifact, options = {}) {
|
|
|
192
280
|
author: artifact.author,
|
|
193
281
|
name: artifact.name,
|
|
194
282
|
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
195
|
-
files: files.map((f) =>
|
|
283
|
+
files: files.map((f) => path3.relative(process.cwd(), f))
|
|
196
284
|
});
|
|
197
285
|
return { installed: true, files };
|
|
198
286
|
}
|
|
199
287
|
function uninstallArtifact(type, author, slug) {
|
|
200
288
|
const installPath = getInstallPath(type, slug);
|
|
201
|
-
if (!
|
|
289
|
+
if (!fs3.existsSync(installPath)) {
|
|
202
290
|
return false;
|
|
203
291
|
}
|
|
204
292
|
if (type === "skill") {
|
|
205
|
-
|
|
293
|
+
fs3.rmSync(installPath, { recursive: true, force: true });
|
|
206
294
|
} else {
|
|
207
|
-
|
|
295
|
+
fs3.unlinkSync(installPath);
|
|
208
296
|
}
|
|
209
297
|
removeFromLockFile(type, author, slug);
|
|
210
298
|
return true;
|
|
@@ -242,26 +330,25 @@ async function addCommand(type, slug, options) {
|
|
|
242
330
|
return;
|
|
243
331
|
}
|
|
244
332
|
const [author, artifactSlug] = slug.split("/");
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
return;
|
|
249
|
-
}
|
|
250
|
-
const spinner = ora(`Resolving ${type} ${chalk.cyan(slug)}...`).start();
|
|
333
|
+
const isUpdate = isInstalled(type, author, artifactSlug);
|
|
334
|
+
const actionVerb = isUpdate ? "Updating" : "Resolving";
|
|
335
|
+
const spinner = ora(`${actionVerb} ${type} ${chalk.cyan(slug)}...`).start();
|
|
251
336
|
try {
|
|
252
337
|
const artifact = await resolveArtifact(type, slug);
|
|
253
|
-
|
|
254
|
-
|
|
338
|
+
const installVerb = isUpdate ? "Updating" : "Installing";
|
|
339
|
+
spinner.text = `${installVerb} ${artifact.name}...`;
|
|
340
|
+
const { installed } = installArtifact(artifact, { force: isUpdate || options.force });
|
|
255
341
|
if (!installed) {
|
|
256
|
-
spinner.warn(`${artifact.name}
|
|
342
|
+
spinner.warn(`${artifact.name} could not be installed.`);
|
|
257
343
|
return;
|
|
258
344
|
}
|
|
259
|
-
|
|
345
|
+
const successVerb = isUpdate ? "Updated" : "Installed";
|
|
260
346
|
spinner.succeed(
|
|
261
|
-
|
|
347
|
+
`${successVerb} ${chalk.green(artifact.name)} to ${chalk.dim(artifact.installPath)}`
|
|
262
348
|
);
|
|
263
349
|
console.log();
|
|
264
350
|
printUsageHint(type, artifactSlug);
|
|
351
|
+
await trackInstall(type, slug, CLI_VERSION);
|
|
265
352
|
} catch (error) {
|
|
266
353
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
267
354
|
spinner.fail(chalk.red(`Failed to install: ${message}`));
|
|
@@ -314,19 +401,26 @@ async function installStack(stackSlug, options) {
|
|
|
314
401
|
}
|
|
315
402
|
console.log();
|
|
316
403
|
let installed = 0;
|
|
317
|
-
let
|
|
404
|
+
let updated = 0;
|
|
318
405
|
for (const artifact of stack.artifacts) {
|
|
319
|
-
const
|
|
406
|
+
const [artifactAuthor, artifactSlug] = artifact.slug.split("/");
|
|
407
|
+
const isArtifactUpdate = isInstalled(artifact.type, artifactAuthor, artifactSlug);
|
|
408
|
+
const actionVerb = isArtifactUpdate ? "Updating" : "Installing";
|
|
409
|
+
const artifactSpinner = ora(`${actionVerb} ${artifact.name}...`).start();
|
|
320
410
|
try {
|
|
321
411
|
const resolved = await resolveArtifact(artifact.type, artifact.slug);
|
|
322
|
-
const result = installArtifact(resolved, { force: options.force });
|
|
412
|
+
const result = installArtifact(resolved, { force: isArtifactUpdate || options.force });
|
|
323
413
|
if (result.installed) {
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
414
|
+
if (isArtifactUpdate) {
|
|
415
|
+
artifactSpinner.succeed(`Updated ${chalk.green(artifact.name)}`);
|
|
416
|
+
updated++;
|
|
417
|
+
} else {
|
|
418
|
+
artifactSpinner.succeed(`Installed ${chalk.green(artifact.name)}`);
|
|
419
|
+
installed++;
|
|
420
|
+
}
|
|
421
|
+
await trackInstall(artifact.type, artifact.slug, CLI_VERSION);
|
|
327
422
|
} else {
|
|
328
|
-
artifactSpinner.
|
|
329
|
-
skipped++;
|
|
423
|
+
artifactSpinner.warn(`${artifact.name} could not be installed`);
|
|
330
424
|
}
|
|
331
425
|
} catch (error) {
|
|
332
426
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
@@ -334,8 +428,16 @@ async function installStack(stackSlug, options) {
|
|
|
334
428
|
}
|
|
335
429
|
}
|
|
336
430
|
console.log();
|
|
431
|
+
const parts = [];
|
|
432
|
+
if (installed > 0) {
|
|
433
|
+
parts.push(`installed ${installed}`);
|
|
434
|
+
}
|
|
435
|
+
if (updated > 0) {
|
|
436
|
+
parts.push(`updated ${updated}`);
|
|
437
|
+
}
|
|
438
|
+
const summary = parts.length > 0 ? parts.join(", ") : "no changes";
|
|
337
439
|
console.log(
|
|
338
|
-
`${chalk.green("Done!")}
|
|
440
|
+
`${chalk.green("Done!")} ${summary.charAt(0).toUpperCase() + summary.slice(1)} artifact${installed + updated === 1 ? "" : "s"}`
|
|
339
441
|
);
|
|
340
442
|
console.log();
|
|
341
443
|
console.log(chalk.dim(`Learn more: https://aibuilder.sh/stacks/${stackSlug}`));
|
|
@@ -359,9 +461,9 @@ async function confirm(question) {
|
|
|
359
461
|
}
|
|
360
462
|
|
|
361
463
|
// src/commands/completion.ts
|
|
362
|
-
import * as
|
|
363
|
-
import * as
|
|
364
|
-
import * as
|
|
464
|
+
import * as fs4 from "fs";
|
|
465
|
+
import * as os3 from "os";
|
|
466
|
+
import * as path4 from "path";
|
|
365
467
|
import chalk2 from "chalk";
|
|
366
468
|
var BASH_COMPLETION = `
|
|
367
469
|
# ai-builder bash completion
|
|
@@ -487,14 +589,14 @@ _ai_builder() {
|
|
|
487
589
|
_ai_builder "$@"
|
|
488
590
|
`.trim();
|
|
489
591
|
function getShellConfigPath(shell) {
|
|
490
|
-
const home =
|
|
592
|
+
const home = os3.homedir();
|
|
491
593
|
if (shell === "bash") {
|
|
492
|
-
const bashrc =
|
|
493
|
-
const bashProfile =
|
|
494
|
-
return
|
|
594
|
+
const bashrc = path4.join(home, ".bashrc");
|
|
595
|
+
const bashProfile = path4.join(home, ".bash_profile");
|
|
596
|
+
return fs4.existsSync(bashrc) ? bashrc : bashProfile;
|
|
495
597
|
}
|
|
496
598
|
if (shell === "zsh") {
|
|
497
|
-
return
|
|
599
|
+
return path4.join(home, ".zshrc");
|
|
498
600
|
}
|
|
499
601
|
return null;
|
|
500
602
|
}
|
|
@@ -522,14 +624,14 @@ function completionCommand(shell, options) {
|
|
|
522
624
|
}
|
|
523
625
|
const completion = detectedShell === "zsh" ? ZSH_COMPLETION : BASH_COMPLETION;
|
|
524
626
|
const marker = "# ai-builder completion";
|
|
525
|
-
const existingContent =
|
|
627
|
+
const existingContent = fs4.existsSync(configPath) ? fs4.readFileSync(configPath, "utf-8") : "";
|
|
526
628
|
if (existingContent.includes(marker)) {
|
|
527
629
|
console.log(chalk2.yellow(`
|
|
528
630
|
Completion already installed in ${configPath}`));
|
|
529
631
|
console.log(chalk2.dim(" Restart your terminal to apply changes\n"));
|
|
530
632
|
return;
|
|
531
633
|
}
|
|
532
|
-
|
|
634
|
+
fs4.appendFileSync(configPath, `
|
|
533
635
|
${completion}
|
|
534
636
|
`);
|
|
535
637
|
console.log(chalk2.green(`
|
|
@@ -765,8 +867,8 @@ function formatNumber(num) {
|
|
|
765
867
|
}
|
|
766
868
|
|
|
767
869
|
// src/commands/status.ts
|
|
768
|
-
import * as
|
|
769
|
-
import * as
|
|
870
|
+
import * as fs5 from "fs";
|
|
871
|
+
import * as path5 from "path";
|
|
770
872
|
import chalk6 from "chalk";
|
|
771
873
|
|
|
772
874
|
// src/version.ts
|
|
@@ -780,8 +882,8 @@ var API_BASE2 = process.env.AI_BUILDER_API_URL || "https://aibuilder.sh/api";
|
|
|
780
882
|
async function statusCommand() {
|
|
781
883
|
console.log(chalk6.cyan.bold("\n ai-builder status\n"));
|
|
782
884
|
console.log(` ${chalk6.dim("Version:")} ${chalk6.white(version)}`);
|
|
783
|
-
const claudeDir =
|
|
784
|
-
const claudeDirExists =
|
|
885
|
+
const claudeDir = path5.join(process.cwd(), ".claude");
|
|
886
|
+
const claudeDirExists = fs5.existsSync(claudeDir);
|
|
785
887
|
console.log(
|
|
786
888
|
` ${chalk6.dim("Claude dir:")} ${claudeDirExists ? chalk6.green(".claude/ found") : chalk6.yellow(".claude/ not found")}`
|
|
787
889
|
);
|