ai-builder 0.1.6 → 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 +163 -62
- 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,13 +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();
|
|
76
|
-
logger.debug(
|
|
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
|
+
);
|
|
77
164
|
} catch (error) {
|
|
78
165
|
logger.debug({ err: error, type, slug }, "Install tracking failed (non-critical)");
|
|
79
166
|
}
|
|
@@ -93,23 +180,23 @@ async function resolveStack(slug) {
|
|
|
93
180
|
}
|
|
94
181
|
|
|
95
182
|
// src/services/installer.ts
|
|
96
|
-
import * as
|
|
97
|
-
import * as
|
|
183
|
+
import * as fs3 from "fs";
|
|
184
|
+
import * as path3 from "path";
|
|
98
185
|
|
|
99
186
|
// src/services/lock-file.ts
|
|
100
|
-
import * as
|
|
101
|
-
import * as
|
|
187
|
+
import * as fs2 from "fs";
|
|
188
|
+
import * as path2 from "path";
|
|
102
189
|
var LOCK_FILE_NAME = "ai-builder.lock.json";
|
|
103
190
|
function getLockFilePath() {
|
|
104
|
-
return
|
|
191
|
+
return path2.join(process.cwd(), ".claude", LOCK_FILE_NAME);
|
|
105
192
|
}
|
|
106
193
|
function readLockFile() {
|
|
107
194
|
const lockPath = getLockFilePath();
|
|
108
|
-
if (!
|
|
195
|
+
if (!fs2.existsSync(lockPath)) {
|
|
109
196
|
return { version: 1, artifacts: {} };
|
|
110
197
|
}
|
|
111
198
|
try {
|
|
112
|
-
const content =
|
|
199
|
+
const content = fs2.readFileSync(lockPath, "utf-8");
|
|
113
200
|
return JSON.parse(content);
|
|
114
201
|
} catch {
|
|
115
202
|
return { version: 1, artifacts: {} };
|
|
@@ -117,11 +204,11 @@ function readLockFile() {
|
|
|
117
204
|
}
|
|
118
205
|
function writeLockFile(lockFile) {
|
|
119
206
|
const lockPath = getLockFilePath();
|
|
120
|
-
const lockDir =
|
|
121
|
-
if (!
|
|
122
|
-
|
|
207
|
+
const lockDir = path2.dirname(lockPath);
|
|
208
|
+
if (!fs2.existsSync(lockDir)) {
|
|
209
|
+
fs2.mkdirSync(lockDir, { recursive: true });
|
|
123
210
|
}
|
|
124
|
-
|
|
211
|
+
fs2.writeFileSync(lockPath, `${JSON.stringify(lockFile, null, 2)}
|
|
125
212
|
`);
|
|
126
213
|
}
|
|
127
214
|
function addToLockFile(entry) {
|
|
@@ -148,14 +235,14 @@ function isInstalled(type, author, slug) {
|
|
|
148
235
|
|
|
149
236
|
// src/services/installer.ts
|
|
150
237
|
function getInstallPath(type, artifactName) {
|
|
151
|
-
const basePath =
|
|
238
|
+
const basePath = path3.join(process.cwd(), ".claude");
|
|
152
239
|
switch (type) {
|
|
153
240
|
case "skill":
|
|
154
|
-
return
|
|
241
|
+
return path3.join(basePath, "skills", artifactName);
|
|
155
242
|
case "agent":
|
|
156
|
-
return
|
|
243
|
+
return path3.join(basePath, "agents", `${artifactName}.md`);
|
|
157
244
|
case "command":
|
|
158
|
-
return
|
|
245
|
+
return path3.join(basePath, "commands", `${artifactName}.md`);
|
|
159
246
|
default:
|
|
160
247
|
throw new Error(`Unknown artifact type: ${type}`);
|
|
161
248
|
}
|
|
@@ -167,24 +254,24 @@ function installArtifact(artifact, options = {}) {
|
|
|
167
254
|
if (!options.force && isInstalled(artifact.type, artifact.author, artifactName || artifact.name)) {
|
|
168
255
|
return { installed: false, files: [] };
|
|
169
256
|
}
|
|
170
|
-
const dir = artifact.type === "skill" ? installPath :
|
|
171
|
-
if (!
|
|
172
|
-
|
|
257
|
+
const dir = artifact.type === "skill" ? installPath : path3.dirname(installPath);
|
|
258
|
+
if (!fs3.existsSync(dir)) {
|
|
259
|
+
fs3.mkdirSync(dir, { recursive: true });
|
|
173
260
|
}
|
|
174
261
|
if (artifact.type === "skill") {
|
|
175
|
-
const skillMdPath =
|
|
176
|
-
|
|
262
|
+
const skillMdPath = path3.join(installPath, "SKILL.md");
|
|
263
|
+
fs3.writeFileSync(skillMdPath, artifact.content);
|
|
177
264
|
files.push(skillMdPath);
|
|
178
265
|
if (artifact.bundleFiles) {
|
|
179
266
|
for (const [filename, content] of Object.entries(artifact.bundleFiles)) {
|
|
180
|
-
const filePath =
|
|
181
|
-
|
|
182
|
-
|
|
267
|
+
const filePath = path3.join(installPath, filename);
|
|
268
|
+
fs3.mkdirSync(path3.dirname(filePath), { recursive: true });
|
|
269
|
+
fs3.writeFileSync(filePath, content);
|
|
183
270
|
files.push(filePath);
|
|
184
271
|
}
|
|
185
272
|
}
|
|
186
273
|
} else {
|
|
187
|
-
|
|
274
|
+
fs3.writeFileSync(installPath, artifact.content);
|
|
188
275
|
files.push(installPath);
|
|
189
276
|
}
|
|
190
277
|
addToLockFile({
|
|
@@ -193,19 +280,19 @@ function installArtifact(artifact, options = {}) {
|
|
|
193
280
|
author: artifact.author,
|
|
194
281
|
name: artifact.name,
|
|
195
282
|
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
196
|
-
files: files.map((f) =>
|
|
283
|
+
files: files.map((f) => path3.relative(process.cwd(), f))
|
|
197
284
|
});
|
|
198
285
|
return { installed: true, files };
|
|
199
286
|
}
|
|
200
287
|
function uninstallArtifact(type, author, slug) {
|
|
201
288
|
const installPath = getInstallPath(type, slug);
|
|
202
|
-
if (!
|
|
289
|
+
if (!fs3.existsSync(installPath)) {
|
|
203
290
|
return false;
|
|
204
291
|
}
|
|
205
292
|
if (type === "skill") {
|
|
206
|
-
|
|
293
|
+
fs3.rmSync(installPath, { recursive: true, force: true });
|
|
207
294
|
} else {
|
|
208
|
-
|
|
295
|
+
fs3.unlinkSync(installPath);
|
|
209
296
|
}
|
|
210
297
|
removeFromLockFile(type, author, slug);
|
|
211
298
|
return true;
|
|
@@ -243,22 +330,21 @@ async function addCommand(type, slug, options) {
|
|
|
243
330
|
return;
|
|
244
331
|
}
|
|
245
332
|
const [author, artifactSlug] = slug.split("/");
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
return;
|
|
250
|
-
}
|
|
251
|
-
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();
|
|
252
336
|
try {
|
|
253
337
|
const artifact = await resolveArtifact(type, slug);
|
|
254
|
-
|
|
255
|
-
|
|
338
|
+
const installVerb = isUpdate ? "Updating" : "Installing";
|
|
339
|
+
spinner.text = `${installVerb} ${artifact.name}...`;
|
|
340
|
+
const { installed } = installArtifact(artifact, { force: isUpdate || options.force });
|
|
256
341
|
if (!installed) {
|
|
257
|
-
spinner.warn(`${artifact.name}
|
|
342
|
+
spinner.warn(`${artifact.name} could not be installed.`);
|
|
258
343
|
return;
|
|
259
344
|
}
|
|
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);
|
|
@@ -315,19 +401,26 @@ async function installStack(stackSlug, options) {
|
|
|
315
401
|
}
|
|
316
402
|
console.log();
|
|
317
403
|
let installed = 0;
|
|
318
|
-
let
|
|
404
|
+
let updated = 0;
|
|
319
405
|
for (const artifact of stack.artifacts) {
|
|
320
|
-
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();
|
|
321
410
|
try {
|
|
322
411
|
const resolved = await resolveArtifact(artifact.type, artifact.slug);
|
|
323
|
-
const result = installArtifact(resolved, { force: options.force });
|
|
412
|
+
const result = installArtifact(resolved, { force: isArtifactUpdate || options.force });
|
|
324
413
|
if (result.installed) {
|
|
325
|
-
|
|
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
|
+
}
|
|
326
421
|
await trackInstall(artifact.type, artifact.slug, CLI_VERSION);
|
|
327
|
-
installed++;
|
|
328
422
|
} else {
|
|
329
|
-
artifactSpinner.
|
|
330
|
-
skipped++;
|
|
423
|
+
artifactSpinner.warn(`${artifact.name} could not be installed`);
|
|
331
424
|
}
|
|
332
425
|
} catch (error) {
|
|
333
426
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
@@ -335,8 +428,16 @@ async function installStack(stackSlug, options) {
|
|
|
335
428
|
}
|
|
336
429
|
}
|
|
337
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";
|
|
338
439
|
console.log(
|
|
339
|
-
`${chalk.green("Done!")}
|
|
440
|
+
`${chalk.green("Done!")} ${summary.charAt(0).toUpperCase() + summary.slice(1)} artifact${installed + updated === 1 ? "" : "s"}`
|
|
340
441
|
);
|
|
341
442
|
console.log();
|
|
342
443
|
console.log(chalk.dim(`Learn more: https://aibuilder.sh/stacks/${stackSlug}`));
|
|
@@ -360,9 +461,9 @@ async function confirm(question) {
|
|
|
360
461
|
}
|
|
361
462
|
|
|
362
463
|
// src/commands/completion.ts
|
|
363
|
-
import * as
|
|
364
|
-
import * as
|
|
365
|
-
import * as
|
|
464
|
+
import * as fs4 from "fs";
|
|
465
|
+
import * as os3 from "os";
|
|
466
|
+
import * as path4 from "path";
|
|
366
467
|
import chalk2 from "chalk";
|
|
367
468
|
var BASH_COMPLETION = `
|
|
368
469
|
# ai-builder bash completion
|
|
@@ -488,14 +589,14 @@ _ai_builder() {
|
|
|
488
589
|
_ai_builder "$@"
|
|
489
590
|
`.trim();
|
|
490
591
|
function getShellConfigPath(shell) {
|
|
491
|
-
const home =
|
|
592
|
+
const home = os3.homedir();
|
|
492
593
|
if (shell === "bash") {
|
|
493
|
-
const bashrc =
|
|
494
|
-
const bashProfile =
|
|
495
|
-
return
|
|
594
|
+
const bashrc = path4.join(home, ".bashrc");
|
|
595
|
+
const bashProfile = path4.join(home, ".bash_profile");
|
|
596
|
+
return fs4.existsSync(bashrc) ? bashrc : bashProfile;
|
|
496
597
|
}
|
|
497
598
|
if (shell === "zsh") {
|
|
498
|
-
return
|
|
599
|
+
return path4.join(home, ".zshrc");
|
|
499
600
|
}
|
|
500
601
|
return null;
|
|
501
602
|
}
|
|
@@ -523,14 +624,14 @@ function completionCommand(shell, options) {
|
|
|
523
624
|
}
|
|
524
625
|
const completion = detectedShell === "zsh" ? ZSH_COMPLETION : BASH_COMPLETION;
|
|
525
626
|
const marker = "# ai-builder completion";
|
|
526
|
-
const existingContent =
|
|
627
|
+
const existingContent = fs4.existsSync(configPath) ? fs4.readFileSync(configPath, "utf-8") : "";
|
|
527
628
|
if (existingContent.includes(marker)) {
|
|
528
629
|
console.log(chalk2.yellow(`
|
|
529
630
|
Completion already installed in ${configPath}`));
|
|
530
631
|
console.log(chalk2.dim(" Restart your terminal to apply changes\n"));
|
|
531
632
|
return;
|
|
532
633
|
}
|
|
533
|
-
|
|
634
|
+
fs4.appendFileSync(configPath, `
|
|
534
635
|
${completion}
|
|
535
636
|
`);
|
|
536
637
|
console.log(chalk2.green(`
|
|
@@ -766,8 +867,8 @@ function formatNumber(num) {
|
|
|
766
867
|
}
|
|
767
868
|
|
|
768
869
|
// src/commands/status.ts
|
|
769
|
-
import * as
|
|
770
|
-
import * as
|
|
870
|
+
import * as fs5 from "fs";
|
|
871
|
+
import * as path5 from "path";
|
|
771
872
|
import chalk6 from "chalk";
|
|
772
873
|
|
|
773
874
|
// src/version.ts
|
|
@@ -781,8 +882,8 @@ var API_BASE2 = process.env.AI_BUILDER_API_URL || "https://aibuilder.sh/api";
|
|
|
781
882
|
async function statusCommand() {
|
|
782
883
|
console.log(chalk6.cyan.bold("\n ai-builder status\n"));
|
|
783
884
|
console.log(` ${chalk6.dim("Version:")} ${chalk6.white(version)}`);
|
|
784
|
-
const claudeDir =
|
|
785
|
-
const claudeDirExists =
|
|
885
|
+
const claudeDir = path5.join(process.cwd(), ".claude");
|
|
886
|
+
const claudeDirExists = fs5.existsSync(claudeDir);
|
|
786
887
|
console.log(
|
|
787
888
|
` ${chalk6.dim("Claude dir:")} ${claudeDirExists ? chalk6.green(".claude/ found") : chalk6.yellow(".claude/ not found")}`
|
|
788
889
|
);
|