create-objectstack 17.1.0 → 17.3.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/index.js CHANGED
@@ -1,9 +1,62 @@
1
+ import {
2
+ describeEntry,
3
+ summarizeTree
4
+ } from "./chunk-ZIUW7UEA.js";
5
+
1
6
  // src/index.ts
2
7
  import { Command } from "commander";
3
- import chalk from "chalk";
4
- import fs4 from "fs";
5
- import path4 from "path";
8
+ import chalk2 from "chalk";
9
+ import fs5 from "fs";
10
+ import path5 from "path";
11
+ import { execSync as execSync2 } from "child_process";
12
+
13
+ // src/detect-package-manager.ts
14
+ import fs from "fs";
15
+ import path from "path";
6
16
  import { execSync } from "child_process";
17
+ function resolveOnPath(cmd, env = process.env) {
18
+ const raw = env.PATH ?? "";
19
+ if (!raw) return null;
20
+ const exts = process.platform === "win32" ? (env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean) : [""];
21
+ for (const dir of raw.split(path.delimiter)) {
22
+ if (!dir) continue;
23
+ for (const ext of exts) {
24
+ const candidate = path.join(dir, cmd + ext);
25
+ try {
26
+ if (fs.statSync(candidate).isFile()) return candidate;
27
+ } catch {
28
+ }
29
+ }
30
+ }
31
+ return null;
32
+ }
33
+ function probeFailureDetail(err) {
34
+ const e = err ?? {};
35
+ if (e.signal) return `killed by ${e.signal}`;
36
+ const stderr = e.stderr == null ? "" : String(e.stderr);
37
+ const firstLine = stderr.split("\n").map((l) => l.trim()).find((l) => l.length > 0);
38
+ if (firstLine) return firstLine.length > 200 ? `${firstLine.slice(0, 197)}...` : firstLine;
39
+ if (typeof e.code === "string") return e.code;
40
+ if (typeof e.status === "number") return `exited ${e.status}`;
41
+ const msg = (e.message ?? "").split("\n")[0]?.trim();
42
+ return msg || "unknown error";
43
+ }
44
+ function defaultProbe() {
45
+ execSync("pnpm --version", { stdio: ["ignore", "ignore", "pipe"] });
46
+ }
47
+ function detectPackageManager(deps = {}) {
48
+ const probe = deps.probe ?? defaultProbe;
49
+ const pnpmOnPath = deps.pnpmOnPath ?? (() => resolveOnPath("pnpm") !== null);
50
+ try {
51
+ probe();
52
+ return { pm: "pnpm", probe: "ok" };
53
+ } catch (err) {
54
+ if (!pnpmOnPath()) return { pm: "npm", probe: "absent" };
55
+ return { pm: "npm", probe: "failed", detail: probeFailureDetail(err) };
56
+ }
57
+ }
58
+
59
+ // src/index.ts
7
60
  import { fileURLToPath } from "url";
8
61
 
9
62
  // src/pkg-utils.ts
@@ -20,41 +73,41 @@ function syncObjectStackDeps(pkg, version) {
20
73
  }
21
74
 
22
75
  // src/template-copy.ts
23
- import fs from "fs";
24
- import path from "path";
76
+ import fs2 from "fs";
77
+ import path2 from "path";
25
78
  var TEMPLATE_FILE_ALIASES = /* @__PURE__ */ new Map([
26
79
  ["_gitignore", ".gitignore"]
27
80
  ]);
28
81
  function copyDir(src, dest, collected, rel = "") {
29
- fs.mkdirSync(dest, { recursive: true });
30
- for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
31
- const srcPath = path.join(src, entry.name);
82
+ fs2.mkdirSync(dest, { recursive: true });
83
+ for (const entry of fs2.readdirSync(src, { withFileTypes: true })) {
84
+ const srcPath = path2.join(src, entry.name);
32
85
  const outName = entry.isFile() ? TEMPLATE_FILE_ALIASES.get(entry.name) ?? entry.name : entry.name;
33
- const destPath = path.join(dest, outName);
86
+ const destPath = path2.join(dest, outName);
34
87
  const relPath = rel ? `${rel}/${outName}` : outName;
35
88
  if (entry.isDirectory()) {
36
89
  copyDir(srcPath, destPath, collected, relPath);
37
90
  } else if (entry.isFile()) {
38
- fs.copyFileSync(srcPath, destPath);
91
+ fs2.copyFileSync(srcPath, destPath);
39
92
  collected.push(relPath);
40
93
  }
41
94
  }
42
95
  }
43
96
 
44
97
  // src/rewrite-identity.ts
45
- import fs2 from "fs";
46
- import path2 from "path";
98
+ import fs3 from "fs";
99
+ import path3 from "path";
47
100
  var CONFIG_NAMESPACE_RE = /\bnamespace:\s*(['"`])([a-z0-9_]+)\1/i;
48
101
  function readTemplateNamespace(targetDir) {
49
- const configPath = path2.join(targetDir, "objectstack.config.ts");
50
- if (fs2.existsSync(configPath)) {
51
- const m = CONFIG_NAMESPACE_RE.exec(fs2.readFileSync(configPath, "utf8"));
102
+ const configPath = path3.join(targetDir, "objectstack.config.ts");
103
+ if (fs3.existsSync(configPath)) {
104
+ const m = CONFIG_NAMESPACE_RE.exec(fs3.readFileSync(configPath, "utf8"));
52
105
  if (m) return m[2];
53
106
  }
54
- const manifestPath = path2.join(targetDir, "objectstack.manifest.json");
55
- if (fs2.existsSync(manifestPath)) {
107
+ const manifestPath = path3.join(targetDir, "objectstack.manifest.json");
108
+ if (fs3.existsSync(manifestPath)) {
56
109
  try {
57
- const m = JSON.parse(fs2.readFileSync(manifestPath, "utf8"));
110
+ const m = JSON.parse(fs3.readFileSync(manifestPath, "utf8"));
58
111
  if (typeof m.namespace === "string" && m.namespace) return m.namespace;
59
112
  } catch {
60
113
  }
@@ -62,9 +115,9 @@ function readTemplateNamespace(targetDir) {
62
115
  return void 0;
63
116
  }
64
117
  function tsFiles(dir, out = []) {
65
- if (!fs2.existsSync(dir)) return out;
66
- for (const entry of fs2.readdirSync(dir, { withFileTypes: true })) {
67
- const full = path2.join(dir, entry.name);
118
+ if (!fs3.existsSync(dir)) return out;
119
+ for (const entry of fs3.readdirSync(dir, { withFileTypes: true })) {
120
+ const full = path3.join(dir, entry.name);
68
121
  if (entry.isDirectory()) {
69
122
  if (entry.name === "node_modules") continue;
70
123
  tsFiles(full, out);
@@ -78,7 +131,7 @@ var namePrefixRe = (ns, flags) => new RegExp(`(\\bname:\\s*)(['"\`])${ns}_([a-z0
78
131
  function rewriteObjectNamePrefix(dir, from, to) {
79
132
  let rewritten = 0;
80
133
  for (const file of tsFiles(dir)) {
81
- const before = fs2.readFileSync(file, "utf8");
134
+ const before = fs3.readFileSync(file, "utf8");
82
135
  const after = before.replace(
83
136
  namePrefixRe(from, "g"),
84
137
  (_m, prefix, q, rest) => {
@@ -86,7 +139,7 @@ function rewriteObjectNamePrefix(dir, from, to) {
86
139
  return `${prefix}${q}${to}_${rest}${q}`;
87
140
  }
88
141
  );
89
- if (after !== before) fs2.writeFileSync(file, after);
142
+ if (after !== before) fs3.writeFileSync(file, after);
90
143
  }
91
144
  return rewritten;
92
145
  }
@@ -94,11 +147,11 @@ function findStaleNamespacePrefixes(dir, oldNs) {
94
147
  const re = namePrefixRe(oldNs, "");
95
148
  const stale = [];
96
149
  for (const file of tsFiles(dir)) {
97
- const lines = fs2.readFileSync(file, "utf8").split("\n");
150
+ const lines = fs3.readFileSync(file, "utf8").split("\n");
98
151
  for (let i = 0; i < lines.length; i++) {
99
152
  if (re.test(lines[i])) {
100
153
  stale.push({
101
- file: path2.relative(dir, file),
154
+ file: path3.relative(dir, file),
102
155
  line: i + 1,
103
156
  text: lines[i].trim()
104
157
  });
@@ -133,13 +186,13 @@ function templateNames() {
133
186
  }
134
187
 
135
188
  // src/runtime-image.ts
136
- import fs3 from "fs";
137
- import path3 from "path";
189
+ import fs4 from "fs";
190
+ import path4 from "path";
138
191
  var RUNTIME_FROM_RE = /^FROM ghcr\.io\/objectstack-ai\/objectstack:([A-Za-z0-9_][A-Za-z0-9_.+-]*)[ \t]*$/;
139
192
  var PINNABLE_VERSION_RE = /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/;
140
193
  var PROSE_COMMENT_RE = /^#[ \t]+\S/;
141
194
  function readResolvedCliVersion(targetDir) {
142
- const pkgPath = path3.join(
195
+ const pkgPath = path4.join(
143
196
  targetDir,
144
197
  "node_modules",
145
198
  "@objectstack",
@@ -147,7 +200,7 @@ function readResolvedCliVersion(targetDir) {
147
200
  "package.json"
148
201
  );
149
202
  try {
150
- const version = JSON.parse(fs3.readFileSync(pkgPath, "utf8")).version;
203
+ const version = JSON.parse(fs4.readFileSync(pkgPath, "utf8")).version;
151
204
  return typeof version === "string" && PINNABLE_VERSION_RE.test(version) ? version : void 0;
152
205
  } catch {
153
206
  return void 0;
@@ -165,10 +218,10 @@ function pinRuntimeImage(targetDir, version) {
165
218
  if (!PINNABLE_VERSION_RE.test(version)) {
166
219
  return { pinned: false, reason: `'${version}' is not a pinnable version` };
167
220
  }
168
- const dockerfile = path3.join(targetDir, "Dockerfile");
221
+ const dockerfile = path4.join(targetDir, "Dockerfile");
169
222
  let text;
170
223
  try {
171
- text = fs3.readFileSync(dockerfile, "utf8");
224
+ text = fs4.readFileSync(dockerfile, "utf8");
172
225
  } catch {
173
226
  return { pinned: false, reason: "no Dockerfile in the scaffolded project" };
174
227
  }
@@ -188,14 +241,34 @@ function pinRuntimeImage(targetDir, version) {
188
241
  if (!check || check[1] !== version) {
189
242
  return { pinned: false, reason: "rewrite did not produce the pinned tag" };
190
243
  }
191
- fs3.writeFileSync(dockerfile, pinnedText);
244
+ fs4.writeFileSync(dockerfile, pinnedText);
192
245
  return { pinned: true, tag: version };
193
246
  }
194
247
 
248
+ // src/banner.ts
249
+ import chalk from "chalk";
250
+ var PREFIX = " \u25C6 Create ObjectStack ";
251
+ var MIN_INNER_WIDTH = 35;
252
+ var MIN_TRAILING_PAD = 3;
253
+ function renderVersionBanner(version) {
254
+ const versionLabel = `v${version}`;
255
+ const innerWidth = Math.max(
256
+ MIN_INNER_WIDTH,
257
+ PREFIX.length + versionLabel.length + MIN_TRAILING_PAD
258
+ );
259
+ const trailingPad = innerWidth - PREFIX.length - versionLabel.length;
260
+ const border = "\u2550".repeat(innerWidth);
261
+ return [
262
+ chalk.bold.cyan(` \u2554${border}\u2557`),
263
+ chalk.bold.cyan(" \u2551") + chalk.bold(PREFIX) + chalk.dim(versionLabel) + chalk.bold.cyan(`${" ".repeat(trailingPad)}\u2551`),
264
+ chalk.bold.cyan(` \u255A${border}\u255D`)
265
+ ];
266
+ }
267
+
195
268
  // src/index.ts
196
269
  var __filename2 = fileURLToPath(import.meta.url);
197
- var __dirname2 = path4.dirname(__filename2);
198
- var BUNDLED_TEMPLATES_DIR = path4.resolve(__dirname2, "templates");
270
+ var __dirname2 = path5.dirname(__filename2);
271
+ var BUNDLED_TEMPLATES_DIR = path5.resolve(__dirname2, "templates");
199
272
  function toTitleCase(str) {
200
273
  return str.replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
201
274
  }
@@ -212,44 +285,36 @@ function sanitizeNamespace(name) {
212
285
  }
213
286
  function readCliVersion() {
214
287
  try {
215
- const pkgPath = path4.resolve(__dirname2, "..", "package.json");
216
- const pkg = JSON.parse(fs4.readFileSync(pkgPath, "utf8"));
288
+ const pkgPath = path5.resolve(__dirname2, "..", "package.json");
289
+ const pkg = JSON.parse(fs5.readFileSync(pkgPath, "utf8"));
217
290
  return String(pkg.version || "0.0.0");
218
291
  } catch {
219
292
  return "0.0.0";
220
293
  }
221
294
  }
222
295
  function printHeader(title) {
223
- console.log(chalk.bold(`
296
+ console.log(chalk2.bold(`
224
297
  \u25C6 ${title}`));
225
- console.log(chalk.dim("\u2500".repeat(40)));
298
+ console.log(chalk2.dim("\u2500".repeat(40)));
226
299
  }
227
300
  function printKV(key, value) {
228
- console.log(` ${chalk.dim(key + ":")} ${chalk.white(value)}`);
301
+ console.log(` ${chalk2.dim(key + ":")} ${chalk2.white(value)}`);
229
302
  }
230
303
  function printSuccess(msg) {
231
- console.log(chalk.green(` \u2713 ${msg}`));
304
+ console.log(chalk2.green(` \u2713 ${msg}`));
232
305
  }
233
306
  function printError(msg) {
234
- console.log(chalk.red(` \u2717 ${msg}`));
307
+ console.log(chalk2.red(` \u2717 ${msg}`));
235
308
  }
236
309
  function printStep(msg) {
237
- console.log(chalk.yellow(` \u2192 ${msg}`));
310
+ console.log(chalk2.yellow(` \u2192 ${msg}`));
238
311
  }
239
312
  function printWarning(msg) {
240
- console.log(chalk.yellow(` \u26A0 ${msg}`));
241
- }
242
- function detectPackageManager() {
243
- try {
244
- execSync("pnpm --version", { stdio: "ignore" });
245
- return "pnpm";
246
- } catch {
247
- return "npm";
248
- }
313
+ console.log(chalk2.yellow(` \u26A0 ${msg}`));
249
314
  }
250
315
  function loadBundled(templateDir, targetDir) {
251
- const src = path4.join(BUNDLED_TEMPLATES_DIR, templateDir);
252
- if (!fs4.existsSync(src)) {
316
+ const src = path5.join(BUNDLED_TEMPLATES_DIR, templateDir);
317
+ if (!fs5.existsSync(src)) {
253
318
  throw new Error(`Bundled template missing on disk: ${src}`);
254
319
  }
255
320
  const collected = [];
@@ -259,39 +324,39 @@ function loadBundled(templateDir, targetDir) {
259
324
  function rewriteProjectIdentity(targetDir, projectName, namespace) {
260
325
  const title = toTitleCase(projectName);
261
326
  const templateNamespace = readTemplateNamespace(targetDir);
262
- const pkgPath = path4.join(targetDir, "package.json");
263
- if (fs4.existsSync(pkgPath)) {
327
+ const pkgPath = path5.join(targetDir, "package.json");
328
+ if (fs5.existsSync(pkgPath)) {
264
329
  try {
265
- const pkg = JSON.parse(fs4.readFileSync(pkgPath, "utf8"));
330
+ const pkg = JSON.parse(fs5.readFileSync(pkgPath, "utf8"));
266
331
  pkg.name = projectName;
267
332
  syncObjectStackDeps(pkg, readCliVersion());
268
- fs4.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
333
+ fs5.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
269
334
  } catch {
270
335
  }
271
336
  }
272
- const manifestPath = path4.join(targetDir, "objectstack.manifest.json");
273
- if (fs4.existsSync(manifestPath)) {
337
+ const manifestPath = path5.join(targetDir, "objectstack.manifest.json");
338
+ if (fs5.existsSync(manifestPath)) {
274
339
  try {
275
- const m = JSON.parse(fs4.readFileSync(manifestPath, "utf8"));
340
+ const m = JSON.parse(fs5.readFileSync(manifestPath, "utf8"));
276
341
  m.name = projectName;
277
342
  m.displayName = title;
278
343
  if ("namespace" in m) m.namespace = namespace;
279
344
  delete m.description;
280
- fs4.writeFileSync(manifestPath, JSON.stringify(m, null, 2) + "\n");
345
+ fs5.writeFileSync(manifestPath, JSON.stringify(m, null, 2) + "\n");
281
346
  } catch {
282
347
  }
283
348
  }
284
- const configPath = path4.join(targetDir, "objectstack.config.ts");
285
- if (fs4.existsSync(configPath)) {
286
- let cfg = fs4.readFileSync(configPath, "utf8");
349
+ const configPath = path5.join(targetDir, "objectstack.config.ts");
350
+ if (fs5.existsSync(configPath)) {
351
+ let cfg = fs5.readFileSync(configPath, "utf8");
287
352
  cfg = cfg.replace(/(\bid:\s*)(['"`])[^'"`]*\2/, `$1$2${projectName}$2`);
288
353
  cfg = cfg.replace(/(\bnamespace:\s*)(['"`])[^'"`]*\2/, `$1$2${namespace}$2`);
289
354
  cfg = cfg.replace(/(\bname:\s*)(['"`])[^'"`]*\2/, `$1$2${title}$2`);
290
355
  cfg = cfg.replace(/^[ \t]*description:\s*(['"`])[^'"`]*\1,?\r?\n/m, "");
291
- fs4.writeFileSync(configPath, cfg);
356
+ fs5.writeFileSync(configPath, cfg);
292
357
  }
293
358
  if (templateNamespace && namespace !== templateNamespace) {
294
- const srcDir = path4.join(targetDir, "src");
359
+ const srcDir = path5.join(targetDir, "src");
295
360
  rewriteObjectNamePrefix(srcDir, templateNamespace, namespace);
296
361
  const stale = findStaleNamespacePrefixes(srcDir, templateNamespace);
297
362
  if (stale.length > 0) {
@@ -305,32 +370,75 @@ The generated project would fail 'objectstack build' on the \${namespace}_\${sho
305
370
  );
306
371
  }
307
372
  }
308
- const readmePath = path4.join(targetDir, "README.md");
309
- if (fs4.existsSync(readmePath)) {
310
- let md = fs4.readFileSync(readmePath, "utf8");
373
+ const readmePath = path5.join(targetDir, "README.md");
374
+ if (fs5.existsSync(readmePath)) {
375
+ let md = fs5.readFileSync(readmePath, "utf8");
311
376
  md = md.replace(/^#\s+.*$/m, `# ${title}`);
312
- fs4.writeFileSync(readmePath, md);
377
+ fs5.writeFileSync(readmePath, md);
313
378
  }
314
379
  writeAgentGuides(targetDir, title, projectName);
315
380
  }
316
381
  function writeAgentGuides(targetDir, title, projectName) {
317
- const templatePath = path4.join(BUNDLED_TEMPLATES_DIR, "AGENTS.md");
382
+ const templatePath = path5.join(BUNDLED_TEMPLATES_DIR, "AGENTS.md");
318
383
  let template;
319
384
  try {
320
- template = fs4.readFileSync(templatePath, "utf8");
385
+ template = fs5.readFileSync(templatePath, "utf8");
321
386
  } catch (err) {
322
387
  if (err?.code === "ENOENT") return;
323
388
  throw err;
324
389
  }
325
390
  const rendered = template.replace(/\{\{PROJECT_TITLE\}\}/g, title).replace(/\{\{PROJECT_NAME\}\}/g, projectName);
326
- writeIfAbsent(path4.join(targetDir, "AGENTS.md"), rendered);
327
- const copilotPath = path4.join(targetDir, ".github", "copilot-instructions.md");
328
- fs4.mkdirSync(path4.dirname(copilotPath), { recursive: true });
391
+ writeIfAbsent(path5.join(targetDir, "AGENTS.md"), rendered);
392
+ const copilotPath = path5.join(targetDir, ".github", "copilot-instructions.md");
393
+ fs5.mkdirSync(path5.dirname(copilotPath), { recursive: true });
329
394
  writeIfAbsent(copilotPath, rendered);
330
395
  }
396
+ function topLevelNames(dir) {
397
+ try {
398
+ return new Set(fs5.readdirSync(dir));
399
+ } catch {
400
+ return /* @__PURE__ */ new Set();
401
+ }
402
+ }
403
+ function printCreatedSummary(targetDir, opts) {
404
+ const entries = summarizeTree(targetDir);
405
+ if (entries.length === 0) return;
406
+ console.log(
407
+ chalk2.bold(opts.wasEmpty ? " Created files:" : " Project contents:")
408
+ );
409
+ if (!opts.wasEmpty) {
410
+ console.log(
411
+ chalk2.dim(" (the directory already had contents; this lists all of it)")
412
+ );
413
+ }
414
+ const isSkillPath = (p) => opts.skillPaths.has(p.split("/")[0]);
415
+ const width = Math.min(
416
+ 44,
417
+ Math.max(...entries.map((e) => e.path.length)) + 2
418
+ );
419
+ let flagged = false;
420
+ for (const entry of entries) {
421
+ const note = describeEntry(entry);
422
+ const flag = isSkillPath(entry.path);
423
+ if (flag) flagged = true;
424
+ const pad = note || flag ? entry.path.padEnd(width) : entry.path;
425
+ const line = ` + ${pad}${note ? chalk2.dim(note) : ""}`;
426
+ console.log(chalk2.green(line) + (flag ? chalk2.yellow(" \u26A0 skills") : ""));
427
+ }
428
+ if (flagged) {
429
+ console.log("");
430
+ console.log(
431
+ chalk2.yellow(" \u26A0 Skill files run with your coding agent's full permissions.")
432
+ );
433
+ console.log(
434
+ chalk2.dim(" Review the paths marked \u26A0 above before letting an agent use them.")
435
+ );
436
+ }
437
+ console.log("");
438
+ }
331
439
  function writeIfAbsent(filePath, contents) {
332
440
  try {
333
- fs4.writeFileSync(filePath, contents, { flag: "wx" });
441
+ fs5.writeFileSync(filePath, contents, { flag: "wx" });
334
442
  } catch (err) {
335
443
  if (err?.code !== "EEXIST") throw err;
336
444
  }
@@ -341,65 +449,65 @@ var program = new Command().name("create-objectstack").description("Create a new
341
449
  "blank"
342
450
  ).option("--skip-install", "Skip dependency installation").option("--skip-skills", "Skip installing ObjectStack AI skills").action((name, options) => {
343
451
  console.log("");
344
- console.log(chalk.bold.cyan(" \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
345
- console.log(chalk.bold.cyan(" \u2551") + chalk.bold(" \u25C6 Create ObjectStack ") + chalk.dim("v6.x") + chalk.bold.cyan(" \u2551"));
346
- console.log(chalk.bold.cyan(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
452
+ for (const line of renderVersionBanner(readCliVersion())) console.log(line);
347
453
  printHeader("New Environment");
348
454
  const lookup = lookupTemplate(options.template);
349
455
  if (lookup.kind !== "found") {
350
456
  if (lookup.kind === "retired") {
351
457
  printError(`Template "${lookup.name}" has been retired and is no longer available.`);
352
458
  console.log(
353
- chalk.dim(
459
+ chalk2.dim(
354
460
  " It was delisted from the ObjectStack template marketplace and is no longer maintained."
355
461
  )
356
462
  );
357
463
  } else {
358
464
  printError(`Unknown template: ${lookup.name}`);
359
465
  }
360
- console.log(chalk.dim(` Available: ${templateNames().join(", ")}`));
466
+ console.log(chalk2.dim(` Available: ${templateNames().join(", ")}`));
361
467
  process.exit(1);
362
468
  }
363
469
  const template = lookup.template;
364
470
  const cwd = process.cwd();
365
- const projectName = name || path4.basename(cwd);
471
+ const projectName = name || path5.basename(cwd);
366
472
  const namespace = sanitizeNamespace(projectName);
367
- const targetDir = name ? path4.resolve(cwd, name) : cwd;
473
+ const targetDir = name ? path5.resolve(cwd, name) : cwd;
368
474
  const isCurrentDir = targetDir === cwd;
475
+ const detected = detectPackageManager();
476
+ const pm = detected.pm;
477
+ if (detected.probe === "failed") {
478
+ printWarning(
479
+ `pnpm is installed but \`pnpm --version\` failed (${detected.detail}); using npm as a fallback. The commands below name npm because the probe did not answer, not because this project prefers it.`
480
+ );
481
+ console.log("");
482
+ }
369
483
  printKV("Environment", projectName);
370
484
  printKV("Namespace", namespace);
371
485
  printKV("Template", `${options.template} \u2014 ${template.description}`);
372
486
  printKV("Directory", targetDir);
373
487
  console.log("");
374
- if (!isCurrentDir && fs4.existsSync(targetDir)) {
375
- const existing = fs4.readdirSync(targetDir);
488
+ if (!isCurrentDir && fs5.existsSync(targetDir)) {
489
+ const existing = fs5.readdirSync(targetDir);
376
490
  if (existing.length > 0) {
377
491
  printError(`Directory already exists and is not empty: ${targetDir}`);
378
492
  process.exit(1);
379
493
  }
380
494
  }
495
+ const targetWasEmpty = topLevelNames(targetDir).size === 0;
381
496
  try {
382
- fs4.mkdirSync(targetDir, { recursive: true });
497
+ fs5.mkdirSync(targetDir, { recursive: true });
383
498
  const createdFiles = loadBundled(template.source.dir, targetDir);
384
499
  rewriteProjectIdentity(targetDir, projectName, namespace);
385
- console.log(chalk.bold(" Created files:"));
386
- for (const f of createdFiles.slice(0, 20)) {
387
- console.log(chalk.green(` + ${f}`));
388
- }
389
- if (createdFiles.length > 20) {
390
- console.log(chalk.dim(` \u2026 and ${createdFiles.length - 20} more`));
391
- }
500
+ printSuccess(`Template files written (${createdFiles.length})`);
392
501
  console.log("");
393
502
  if (!options.skipInstall) {
394
503
  printStep("Installing dependencies...");
395
504
  let installed = false;
396
505
  try {
397
- const pm = detectPackageManager();
398
- execSync(`${pm} install`, { stdio: "inherit", cwd: targetDir });
506
+ execSync2(`${pm} install`, { stdio: "inherit", cwd: targetDir });
399
507
  installed = true;
400
508
  console.log("");
401
509
  } catch {
402
- printWarning("Dependency installation failed. Run `npm install` manually.");
510
+ printWarning(`Dependency installation failed. Run \`${pm} install\` manually.`);
403
511
  console.log("");
404
512
  }
405
513
  if (installed) {
@@ -417,10 +525,11 @@ var program = new Command().name("create-objectstack").description("Create a new
417
525
  }
418
526
  }
419
527
  }
528
+ const beforeSkills = topLevelNames(targetDir);
420
529
  if (!options.skipInstall && !options.skipSkills) {
421
530
  printStep("Installing AI skills for your coding agent...");
422
531
  try {
423
- execSync("npx -y skills add objectstack-ai/objectstack/skills --all", {
532
+ execSync2("npx -y skills add objectstack-ai/objectstack/skills --all", {
424
533
  stdio: "inherit",
425
534
  cwd: targetDir
426
535
  });
@@ -432,22 +541,29 @@ var program = new Command().name("create-objectstack").description("Create a new
432
541
  console.log("");
433
542
  }
434
543
  }
544
+ const skillPaths = new Set(
545
+ [...topLevelNames(targetDir)].filter((p) => !beforeSkills.has(p))
546
+ );
547
+ printCreatedSummary(targetDir, { wasEmpty: targetWasEmpty, skillPaths });
435
548
  printSuccess("Environment created!");
436
549
  console.log("");
437
- console.log(chalk.bold(" Next steps:"));
550
+ console.log(chalk2.bold(" Next steps:"));
438
551
  if (!isCurrentDir) {
439
- console.log(chalk.dim(` cd ${name}`));
552
+ console.log(chalk2.dim(` cd ${name}`));
440
553
  }
441
554
  if (options.skipInstall) {
442
- console.log(chalk.dim(" npm install"));
555
+ console.log(chalk2.dim(` ${pm} install`));
443
556
  }
444
- console.log(chalk.dim(" npm run dev # Start development server"));
445
- console.log(chalk.dim(" npm run validate # Verify metadata: schema + predicates + bindings"));
446
- console.log(chalk.dim(" # (run after every metadata edit \u2014 see AGENTS.md)"));
557
+ const devLabel = `${pm} run dev`;
558
+ const validateLabel = `${pm} run validate`;
559
+ const labelWidth = Math.max(devLabel.length, validateLabel.length) + 3;
560
+ console.log(chalk2.dim(` ${devLabel.padEnd(labelWidth)}# Start development server`));
561
+ console.log(chalk2.dim(` ${validateLabel.padEnd(labelWidth)}# Verify metadata: schema + predicates + bindings`));
562
+ console.log(chalk2.dim(` ${" ".repeat(labelWidth)}# (run after every metadata edit \u2014 see AGENTS.md)`));
447
563
  if (options.skipInstall || options.skipSkills) {
448
564
  console.log("");
449
- console.log(chalk.bold(" AI Skills (recommended):"));
450
- console.log(chalk.dim(" npx skills add objectstack-ai/objectstack/skills"));
565
+ console.log(chalk2.bold(" AI Skills (recommended):"));
566
+ console.log(chalk2.dim(" npx skills add objectstack-ai/objectstack/skills"));
451
567
  }
452
568
  console.log("");
453
569
  } catch (error) {
@@ -96,6 +96,6 @@ Skills are triggered automatically based on task context:
96
96
 
97
97
  ## Learn More
98
98
 
99
- - [ObjectStack Documentation](https://objectstack.com/docs)
99
+ - [ObjectStack Documentation](https://objectstack.ai/docs)
100
100
  - [GitHub: objectstack-ai/objectstack](https://github.com/objectstack-ai/objectstack)
101
101
  - [Skills CLI](https://skills.sh/) — Manage AI skills across agents
@@ -7,7 +7,7 @@
7
7
  # my-app
8
8
  #
9
9
  # Or run the full app + Postgres stack: see docker-compose.yml.
10
- # Docs: https://docs.objectstack.ai/docs/deployment/self-hosting
10
+ # Docs: https://objectstack.ai/docs/deployment/self-hosting
11
11
 
12
12
  # ── Build stage: compile TypeScript metadata to the artifact ─────────
13
13
  FROM node:22-slim AS build
@@ -20,7 +20,7 @@ RUN npx os build # → dist/objectstack.json
20
20
  # ── Runtime: the official ObjectStack runtime image ──────────────────
21
21
  # Ships Node + @objectstack/cli with `os start`, a non-root user, the
22
22
  # /api/v1/health HEALTHCHECK, and OS_ARTIFACT_PATH/OS_PORT preset (port 8080)
23
- # — see docker/README.md in the framework repo.
23
+ # — see the self-hosting guide linked above.
24
24
  #
25
25
  # Dependencies were not installed while scaffolding, so the tag below could
26
26
  # not be resolved for you. `latest` floats to whatever release is newest,