@ashlr/stack 0.3.0 → 0.3.6

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.
Files changed (3) hide show
  1. package/README.md +7 -7
  2. package/dist/index.js +70 -17
  3. package/package.json +3 -3
package/README.md CHANGED
@@ -1,12 +1,12 @@
1
1
  # @ashlr/stack
2
2
 
3
- > The `stack` CLI — configure supported services with Phantom-backed credentials and MCP wiring. This source checkout registers 39 providers; use `stack providers` for your installed catalog.
3
+ > The `stack` CLI — configure supported services with Phantom-backed credentials and MCP wiring. Use `stack providers` to inspect your installed catalog.
4
4
 
5
5
  Part of [Ashlr Stack](https://stack.ashlr.ai). See the monorepo at [ashlrai/ashlr-stack](https://github.com/ashlrai/ashlr-stack).
6
6
 
7
7
  ## What it is
8
8
 
9
- `stack` collapses the "spin up ten services" phase of a project into a single command. Instead of tab-hopping between Supabase, Vercel, Sentry, Neon, etc. to create projects and paste secrets into `.env`, you run:
9
+ `stack` offers template-assisted setup for selected supported services. For example:
10
10
 
11
11
  ```bash
12
12
  stack init --template nextjs-supabase-posthog
@@ -31,17 +31,17 @@ irm https://stack.ashlr.ai/install.ps1 | iex
31
31
  Or install manually from the registry:
32
32
 
33
33
  ```bash
34
- bun add -g @ashlr/stack ashlr-stack-mcp # preferred
35
- # or
36
34
  npm i -g @ashlr/stack ashlr-stack-mcp
35
+ # or use Bun as the package manager
36
+ bun add -g @ashlr/stack ashlr-stack-mcp
37
37
  ```
38
38
 
39
39
  Prerequisites:
40
40
 
41
- - [Bun](https://bun.sh) >= 1.2 for the published 0.1.1 CLI/MCP packages, including when launched with `npx`; npm does not supply Bun
41
+ - Node.js >= 20 for this npm package, including with `npx`; check your installed package manifest. Source development uses [Bun](https://bun.sh).
42
42
  - [Phantom Secrets](https://phm.dev/secrets) — installed and initialized for the intended project before writing setup flows
43
43
 
44
- This README describes source version 0.2.1. Registry metadata checked on October 14, 2026 reported CLI/MCP version 0.1.1. Verify your installed `stack --version` and command `--help`; source qualification is separate from publication and provider acceptance.
44
+ Verify your installed `stack --version` and command `--help`; available commands depend on the installed version. Package installation does not establish provider acceptance.
45
45
 
46
46
  ## Quickstart
47
47
 
@@ -72,7 +72,7 @@ stack exec -- bun dev
72
72
 
73
73
  ## Commands
74
74
 
75
- A cheat sheet for the current source interface:
75
+ A guide to common commands; check your installed version's help for availability:
76
76
 
77
77
  | Command | What it does |
78
78
  | --- | --- |
package/dist/index.js CHANGED
@@ -1490,9 +1490,9 @@ var init_stack_manifest = __esm(() => {
1490
1490
  schema_version: 1,
1491
1491
  kind: "ashlr-stack-manifest",
1492
1492
  components: Object.freeze([
1493
- Object.freeze({ id: "core", name: "@ashlr/stack-core", version: "0.3.0", distribution: "npm" }),
1494
- Object.freeze({ id: "cli", name: "@ashlr/stack", version: "0.3.0", distribution: "npm" }),
1495
- Object.freeze({ id: "mcp", name: "ashlr-stack-mcp", version: "0.3.0", distribution: "npm" }),
1493
+ Object.freeze({ id: "core", name: "@ashlr/stack-core", version: "0.3.6", distribution: "npm" }),
1494
+ Object.freeze({ id: "cli", name: "@ashlr/stack", version: "0.3.6", distribution: "npm" }),
1495
+ Object.freeze({ id: "mcp", name: "ashlr-stack-mcp", version: "0.3.6", distribution: "npm" }),
1496
1496
  Object.freeze({
1497
1497
  id: "plugin",
1498
1498
  name: "stack",
@@ -1748,7 +1748,7 @@ async function exec(args, opts = {}) {
1748
1748
  return new Promise((resolvePromise, reject) => {
1749
1749
  const hasTimeout = typeof opts.timeoutMs === "number" && Number.isFinite(opts.timeoutMs);
1750
1750
  const useGroup = hasTimeout && !opts.interactive && process.platform !== "win32";
1751
- const child = spawn(binary, args, {
1751
+ const child = spawnImpl(binary, args, {
1752
1752
  env,
1753
1753
  cwd: opts.cwd,
1754
1754
  stdio: opts.interactive ? "inherit" : ["pipe", "pipe", "pipe"],
@@ -1940,7 +1940,7 @@ function formatArgsForError(args) {
1940
1940
  });
1941
1941
  return redacted.join(" ");
1942
1942
  }
1943
- var PhantomTimeoutError, cachedInstalled;
1943
+ var PhantomTimeoutError, cachedInstalled, spawnImpl;
1944
1944
  var init_phantom = __esm(() => {
1945
1945
  init_errors();
1946
1946
  init_phantom_process();
@@ -1950,6 +1950,7 @@ var init_phantom = __esm(() => {
1950
1950
  this.name = "PhantomTimeoutError";
1951
1951
  }
1952
1952
  };
1953
+ spawnImpl = spawn;
1953
1954
  });
1954
1955
  // ../core/src/env.ts
1955
1956
  function resolveOAuthClientId(provider, envOverride) {
@@ -6663,7 +6664,7 @@ import { mkdir as mkdir3, writeFile as writeFile6 } from "node:fs/promises";
6663
6664
  import { dirname as dirname3, resolve as resolve8 } from "node:path";
6664
6665
  async function wirePhantomForRecipe(recipe, opts = {}) {
6665
6666
  const result = { present: [], missing: [], webhooks: [], skipped: [] };
6666
- if (opts.noWire)
6667
+ if (opts.noWire || recipe.providers.length === 0)
6667
6668
  return result;
6668
6669
  const cwd = opts.cwd ?? process.cwd();
6669
6670
  try {
@@ -6784,7 +6785,10 @@ function signatureHeaderFor(provider) {
6784
6785
  }
6785
6786
  }
6786
6787
  function capitalize(s) {
6787
- return s.charAt(0).toUpperCase() + s.slice(1);
6788
+ if (s === "")
6789
+ return s;
6790
+ const first = String.fromCodePoint(s.codePointAt(0));
6791
+ return first.toUpperCase() + s.slice(first.length);
6788
6792
  }
6789
6793
  var WEBHOOK_PROVIDERS;
6790
6794
  var init_phantom_wire = __esm(() => {
@@ -9139,7 +9143,7 @@ var init_dist2 = __esm(() => {
9139
9143
  const e = i(true);
9140
9144
  return Object.keys(e);
9141
9145
  } });
9142
- t = typeof process < "u" && process.env && "development" || "";
9146
+ t = typeof process < "u" && process.env && "production" || "";
9143
9147
  f2 = [["APPVEYOR"], ["AWS_AMPLIFY", "AWS_APP_ID", { ci: true }], ["AZURE_PIPELINES", "SYSTEM_TEAMFOUNDATIONCOLLECTIONURI"], ["AZURE_STATIC", "INPUT_AZURE_STATIC_WEB_APPS_API_TOKEN"], ["APPCIRCLE", "AC_APPCIRCLE"], ["BAMBOO", "bamboo_planKey"], ["BITBUCKET", "BITBUCKET_COMMIT"], ["BITRISE", "BITRISE_IO"], ["BUDDY", "BUDDY_WORKSPACE_ID"], ["BUILDKITE"], ["CIRCLE", "CIRCLECI"], ["CIRRUS", "CIRRUS_CI"], ["CLOUDFLARE_PAGES", "CF_PAGES", { ci: true }], ["CODEBUILD", "CODEBUILD_BUILD_ARN"], ["CODEFRESH", "CF_BUILD_ID"], ["DRONE"], ["DRONE", "DRONE_BUILD_EVENT"], ["DSARI"], ["GITHUB_ACTIONS"], ["GITLAB", "GITLAB_CI"], ["GITLAB", "CI_MERGE_REQUEST_ID"], ["GOCD", "GO_PIPELINE_LABEL"], ["LAYERCI"], ["HUDSON", "HUDSON_URL"], ["JENKINS", "JENKINS_URL"], ["MAGNUM"], ["NETLIFY"], ["NETLIFY", "NETLIFY_LOCAL", { ci: false }], ["NEVERCODE"], ["RENDER"], ["SAIL", "SAILCI"], ["SEMAPHORE"], ["SCREWDRIVER"], ["SHIPPABLE"], ["SOLANO", "TDDIUM"], ["STRIDER"], ["TEAMCITY", "TEAMCITY_VERSION"], ["TRAVIS"], ["VERCEL", "NOW_BUILDER"], ["VERCEL", "VERCEL", { ci: false }], ["VERCEL", "VERCEL_ENV", { ci: false }], ["APPCENTER", "APPCENTER_BUILD_ID"], ["CODESANDBOX", "CODESANDBOX_SSE", { ci: false }], ["CODESANDBOX", "CODESANDBOX_HOST", { ci: false }], ["STACKBLITZ"], ["STORMKIT"], ["CLEAVR"], ["ZEABUR"], ["CODESPHERE", "CODESPHERE_APP_ID", { ci: true }], ["RAILWAY", "RAILWAY_PROJECT_ID"], ["RAILWAY", "RAILWAY_SERVICE_ID"], ["DENO-DEPLOY", "DENO_DEPLOYMENT_ID"], ["FIREBASE_APP_HOSTING", "FIREBASE_APP_HOSTING", { ci: true }]];
9144
9148
  l = b();
9145
9149
  l.name;
@@ -11705,7 +11709,7 @@ var init_scan = __esm(() => {
11705
11709
  console.log();
11706
11710
  if (!isAuto) {
11707
11711
  const suggestion = filtered.map((h) => `stack add ${h.provider}`).join(`
11708
- `);
11712
+ `);
11709
11713
  console.log(colors2.dim(" Next steps:"));
11710
11714
  console.log(colors2.dim(` ${suggestion}
11711
11715
  `));
@@ -14515,6 +14519,8 @@ async function openFleetFile(path) {
14515
14519
  async function readFleetText(path, options = {}) {
14516
14520
  const { maxBytes = 16 * 1024 * 1024, tail = false, ioStatus = "invalid" } = options;
14517
14521
  try {
14522
+ if (!Number.isSafeInteger(maxBytes) || maxBytes < 0)
14523
+ throw new Error("Invalid byte limit");
14518
14524
  const file = await openFleetFile(path);
14519
14525
  try {
14520
14526
  const stat = await file.stat();
@@ -14529,7 +14535,27 @@ async function readFleetText(path, options = {}) {
14529
14535
  `);
14530
14536
  text = newline < 0 ? "" : chunk.slice(newline + 1);
14531
14537
  } else {
14532
- text = await file.readFile("utf8");
14538
+ const initial = Math.min(64 * 1024, Math.max(1, stat.size), maxBytes);
14539
+ let buffer = Buffer.alloc(initial);
14540
+ let offset = 0;
14541
+ for (;; ) {
14542
+ if (offset === maxBytes) {
14543
+ const probe = Buffer.alloc(1);
14544
+ if ((await file.read(probe, 0, 1, offset)).bytesRead)
14545
+ throw new Error("Oversized source");
14546
+ break;
14547
+ }
14548
+ if (offset === buffer.length) {
14549
+ const grown = Buffer.alloc(Math.min(maxBytes, buffer.length * 2));
14550
+ buffer.copy(grown, 0, 0, offset);
14551
+ buffer = grown;
14552
+ }
14553
+ const { bytesRead } = await file.read(buffer, offset, buffer.length - offset, offset);
14554
+ if (!bytesRead)
14555
+ break;
14556
+ offset += bytesRead;
14557
+ }
14558
+ text = buffer.subarray(0, offset).toString("utf8");
14533
14559
  }
14534
14560
  return { status: "ok", text };
14535
14561
  } finally {
@@ -16074,7 +16100,10 @@ function serveFleet(paths, port) {
16074
16100
  hostname: "127.0.0.1",
16075
16101
  port,
16076
16102
  idleTimeout: 0,
16077
- fetch(request) {
16103
+ fetch(request, server) {
16104
+ const host = request.headers.get("host");
16105
+ if (host !== `127.0.0.1:${server.port}` && host !== `localhost:${server.port}`)
16106
+ return new Response("Forbidden", { status: 403 });
16078
16107
  if (new URL(request.url).pathname !== "/events")
16079
16108
  return new Response("Not found", { status: 404 });
16080
16109
  const abort = new AbortController;
@@ -18607,6 +18636,11 @@ var init_apply = __esm(() => {
18607
18636
  type: "boolean",
18608
18637
  default: false,
18609
18638
  description: "On partial failure, leave successfully-added services in .stack.toml instead of rolling them back."
18639
+ },
18640
+ dryRun: {
18641
+ type: "boolean",
18642
+ default: false,
18643
+ description: "Preview provisioning and wiring without making changes."
18610
18644
  }
18611
18645
  },
18612
18646
  async run({ args }) {
@@ -18614,6 +18648,23 @@ var init_apply = __esm(() => {
18614
18648
  const recipe = await pickRecipe(args.recipeId ? String(args.recipeId) : undefined);
18615
18649
  if (!recipe)
18616
18650
  return;
18651
+ if (args.dryRun) {
18652
+ console.log(` recipe ${recipe.id}`);
18653
+ await provisionProviders(recipe.providers, { rollback: false, dryRun: true });
18654
+ if (!args.noWire) {
18655
+ console.log(" Would check these secrets in Phantom and create applicable webhook stubs:");
18656
+ for (const { name } of recipe.providers) {
18657
+ const ref = findProviderRef(name);
18658
+ if (!ref)
18659
+ console.log(` ${name}: unknown provider`);
18660
+ else
18661
+ for (const secret of ref.secrets)
18662
+ console.log(` ${name}: ${secret}`);
18663
+ }
18664
+ }
18665
+ outro("Dry run complete — no changes made.");
18666
+ return;
18667
+ }
18617
18668
  if (!hasConfig()) {
18618
18669
  console.log(colors2.dim(" ○ no .stack.toml — auto-running `stack init --noInteractive`"));
18619
18670
  try {
@@ -18639,7 +18690,9 @@ var init_apply = __esm(() => {
18639
18690
  return;
18640
18691
  }
18641
18692
  }
18642
- await requirePhantom();
18693
+ if (recipe.providers.length > 0) {
18694
+ await requirePhantom();
18695
+ }
18643
18696
  console.log();
18644
18697
  console.log(` ${colors2.bold("recipe")} ${recipe.id}`);
18645
18698
  console.log(` ${colors2.dim("query")} ${recipe.query}`);
@@ -19161,7 +19214,7 @@ var init_completion = __esm(() => {
19161
19214
  ]
19162
19215
  },
19163
19216
  apply: {
19164
- flags: [{ name: "noWire" }, { name: "noRollback" }]
19217
+ flags: [{ name: "noWire" }, { name: "noRollback" }, { name: "dryRun" }]
19165
19218
  },
19166
19219
  swap: {
19167
19220
  completesProviders: true,
@@ -19262,7 +19315,7 @@ var package_default;
19262
19315
  var init_package = __esm(() => {
19263
19316
  package_default = {
19264
19317
  name: "@ashlr/stack",
19265
- version: "0.3.0",
19318
+ version: "0.3.6",
19266
19319
  description: "Ashlr Stack CLI — the control plane for your entire dev stack. One command to provision, wire, and operate every third-party service in your project. 39 providers, MCP-native.",
19267
19320
  license: "MIT",
19268
19321
  author: {
@@ -19310,13 +19363,13 @@ var init_package = __esm(() => {
19310
19363
  node: ">=20"
19311
19364
  },
19312
19365
  scripts: {
19313
- build: `bun build src/index.ts --outdir dist --target node --format esm && node -e "const fs=require('fs');const f='dist/index.js';let c=fs.readFileSync(f,'utf8');if(c.startsWith('#!'))c=c.slice(c.indexOf('\\n')+1);fs.writeFileSync(f,'#!/usr/bin/env node\\n'+c);fs.chmodSync(f,0o755)"`,
19366
+ build: `NODE_ENV=production bun build src/index.ts --outdir dist --target node --format esm && node -e "const fs=require('fs');const f='dist/index.js';let c=fs.readFileSync(f,'utf8');if(c.startsWith('#!'))c=c.slice(c.indexOf('\\n')+1);fs.writeFileSync(f,'#!/usr/bin/env node\\n'+c);fs.chmodSync(f,0o755)"`,
19314
19367
  prepack: "bun run build",
19315
19368
  dev: "bun run src/index.ts",
19316
19369
  typecheck: "bunx tsc --noEmit"
19317
19370
  },
19318
19371
  dependencies: {
19319
- "@ashlr/stack-core": "^0.3.0",
19372
+ "@ashlr/stack-core": "^0.3.6",
19320
19373
  "@clack/prompts": "^0.8.2",
19321
19374
  citty: "^0.1.6",
19322
19375
  picocolors: "^1.1.1"
@@ -19802,7 +19855,7 @@ async function checkForUpdate(currentVersion, _cacheFile) {
19802
19855
  }
19803
19856
 
19804
19857
  // src/index.ts
19805
- var VERSION = "0.3.0";
19858
+ var VERSION = "0.3.6";
19806
19859
  var main = defineCommand({
19807
19860
  meta: {
19808
19861
  name: "stack",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ashlr/stack",
3
- "version": "0.3.0",
3
+ "version": "0.3.6",
4
4
  "description": "Ashlr Stack CLI — the control plane for your entire dev stack. One command to provision, wire, and operate every third-party service in your project. 39 providers, MCP-native.",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -48,13 +48,13 @@
48
48
  "node": ">=20"
49
49
  },
50
50
  "scripts": {
51
- "build": "bun build src/index.ts --outdir dist --target node --format esm && node -e \"const fs=require('fs');const f='dist/index.js';let c=fs.readFileSync(f,'utf8');if(c.startsWith('#!'))c=c.slice(c.indexOf('\\n')+1);fs.writeFileSync(f,'#!/usr/bin/env node\\n'+c);fs.chmodSync(f,0o755)\"",
51
+ "build": "NODE_ENV=production bun build src/index.ts --outdir dist --target node --format esm && node -e \"const fs=require('fs');const f='dist/index.js';let c=fs.readFileSync(f,'utf8');if(c.startsWith('#!'))c=c.slice(c.indexOf('\\n')+1);fs.writeFileSync(f,'#!/usr/bin/env node\\n'+c);fs.chmodSync(f,0o755)\"",
52
52
  "prepack": "bun run build",
53
53
  "dev": "bun run src/index.ts",
54
54
  "typecheck": "bunx tsc --noEmit"
55
55
  },
56
56
  "dependencies": {
57
- "@ashlr/stack-core": "^0.3.0",
57
+ "@ashlr/stack-core": "^0.3.6",
58
58
  "@clack/prompts": "^0.8.2",
59
59
  "citty": "^0.1.6",
60
60
  "picocolors": "^1.1.1"