@secondlayer/cli 3.1.0 → 3.1.2-alpha.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/README.md +204 -0
- package/dist/cli.js +20 -32
- package/dist/cli.js.map +9 -9
- package/package.json +8 -8
package/README.md
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
# @secondlayer/cli
|
|
2
|
+
|
|
3
|
+
The Secondlayer CLI — one binary for dedicated Stacks indexing, real-time
|
|
4
|
+
subgraphs, and per-tenant hosting lifecycle.
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
bun add -g @secondlayer/cli
|
|
8
|
+
sl --version
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quickstart
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
sl login # magic-link auth, session cached at ~/.secondlayer/session.json
|
|
15
|
+
sl project create my-app # scaffold a project
|
|
16
|
+
sl project use my-app # bind cwd to the project (writes ./.secondlayer/project)
|
|
17
|
+
sl instance create --plan launch # provision dedicated Postgres + API + processor
|
|
18
|
+
sl subgraphs deploy ./x.ts # deploy to your instance
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Command surface
|
|
22
|
+
|
|
23
|
+
### Auth (top-level)
|
|
24
|
+
|
|
25
|
+
| Command | What it does |
|
|
26
|
+
|---|---|
|
|
27
|
+
| `sl login` | Magic-link email → 6-digit code → writes session to `~/.secondlayer/session.json` |
|
|
28
|
+
| `sl logout` | Revokes the session and clears the local file |
|
|
29
|
+
| `sl whoami` | Prints account, active project, instance URL, plan, status |
|
|
30
|
+
|
|
31
|
+
### Project
|
|
32
|
+
|
|
33
|
+
Projects are the unit that binds a working directory to a dedicated instance.
|
|
34
|
+
Binding is **per-directory** — `.secondlayer/project` in cwd takes precedence
|
|
35
|
+
over the global default at `~/.secondlayer/config.json:defaultProject`. The
|
|
36
|
+
walk-up stops at `.git` (never crosses repos).
|
|
37
|
+
|
|
38
|
+
| Command | What it does |
|
|
39
|
+
|---|---|
|
|
40
|
+
| `sl project create [name]` | Scaffold a new project on the platform |
|
|
41
|
+
| `sl project list` | List all projects for the account |
|
|
42
|
+
| `sl project use <slug>` | Write `./.secondlayer/project` — binds cwd to that project |
|
|
43
|
+
| `sl project current` | Prints the resolved slug + the file it was read from |
|
|
44
|
+
|
|
45
|
+
### Instance (dedicated hosting)
|
|
46
|
+
|
|
47
|
+
One instance per project. The platform API spawns a dedicated `sl-pg-{slug}`,
|
|
48
|
+
`sl-api-{slug}`, and `sl-proc-{slug}` container set on the hosting side.
|
|
49
|
+
|
|
50
|
+
| Command | What it does |
|
|
51
|
+
|---|---|
|
|
52
|
+
| `sl instance create --plan <launch\|grow\|scale>` | Provision containers. Boxed reveal of `serviceKey` + `anonKey` (shown once). |
|
|
53
|
+
| `sl instance info` | Plan, status, resource usage |
|
|
54
|
+
| `sl instance resize --plan <...>` | Recreate containers with new CPU/memory (~30s downtime) |
|
|
55
|
+
| `sl instance suspend` / `resume` | Stop/start containers, volume preserved |
|
|
56
|
+
| `sl instance keys rotate [--service\|--anon\|--both]` | Bump JWT gen, recreate API container, mint replacement key(s) |
|
|
57
|
+
| `sl instance delete` | Typed-slug confirm, hard teardown |
|
|
58
|
+
| `sl instance db` | Print `ssh -L` command + `DATABASE_URL` for tunneled Postgres access |
|
|
59
|
+
| `sl instance db add-key <path>` | Upload an SSH pubkey to the bastion |
|
|
60
|
+
| `sl instance db revoke-key` | Revoke your bastion access |
|
|
61
|
+
|
|
62
|
+
### Subgraphs (tenant-scoped)
|
|
63
|
+
|
|
64
|
+
All tenant-scoped commands auto-mint a 5-minute ephemeral service JWT per
|
|
65
|
+
invocation. No long-lived key on disk.
|
|
66
|
+
|
|
67
|
+
| Command | What it does |
|
|
68
|
+
|---|---|
|
|
69
|
+
| `sl subgraphs new <name>` | Scaffold a subgraph definition file |
|
|
70
|
+
| `sl subgraphs deploy <file>` | Deploy to the active instance |
|
|
71
|
+
| `sl subgraphs dev <file>` | Watch + hot-redeploy |
|
|
72
|
+
| `sl subgraphs list` | List deployed subgraphs |
|
|
73
|
+
| `sl subgraphs status <name>` | Indexing progress, row counts, gaps |
|
|
74
|
+
| `sl subgraphs query <name> <table>` | Query a subgraph table with filters, sort, pagination |
|
|
75
|
+
| `sl subgraphs reindex <name>` | Drop + re-process from the tip backwards |
|
|
76
|
+
| `sl subgraphs backfill <name>` | Fill a specific block range |
|
|
77
|
+
| `sl subgraphs stop <name>` | Pause processing |
|
|
78
|
+
| `sl subgraphs gaps <name>` | List missing block ranges |
|
|
79
|
+
| `sl subgraphs delete <name>` | Drop the subgraph + its schema |
|
|
80
|
+
| `sl subgraphs scaffold <SP...::contract>` | Generate a starter subgraph from a deployed contract |
|
|
81
|
+
| `sl subgraphs generate <name>` | Regenerate TS types for an existing subgraph |
|
|
82
|
+
|
|
83
|
+
### Local dev + OSS
|
|
84
|
+
|
|
85
|
+
| Command | What it does |
|
|
86
|
+
|---|---|
|
|
87
|
+
| `sl local start/stop/restart/status/logs` | Manage the local Docker stack |
|
|
88
|
+
| `sl local node setup/start/stop/...` | Manage the local Stacks node |
|
|
89
|
+
| `sl stack start/stop/restart` | Alias for `sl local` |
|
|
90
|
+
| `sl db blocks/txs/events/gaps/reset/resync` | Inspect the local source DB |
|
|
91
|
+
|
|
92
|
+
### Other
|
|
93
|
+
|
|
94
|
+
| Command | What it does |
|
|
95
|
+
|---|---|
|
|
96
|
+
| `sl generate [files...]` (aliases: `gen`, `codegen`) | Generate TS interfaces from Clarity contracts |
|
|
97
|
+
| `sl init` | Scaffold `secondlayer.config.ts` |
|
|
98
|
+
| `sl doctor` | Session + project + instance reachability check |
|
|
99
|
+
| `sl status` | Platform/instance health |
|
|
100
|
+
| `sl account profile` | Update display name / bio / slug |
|
|
101
|
+
| `sl config show/set/reset/clear` | Inspect or reset local config |
|
|
102
|
+
|
|
103
|
+
## Environment variables
|
|
104
|
+
|
|
105
|
+
| Var | Purpose |
|
|
106
|
+
|---|---|
|
|
107
|
+
| `SL_API_URL` | Bypass platform resolution — point at an OSS or internal API directly |
|
|
108
|
+
| `SL_SERVICE_KEY` | Service key when using env-var bypass |
|
|
109
|
+
| `SL_PLATFORM_API_URL` | Override the platform API base (default `https://api.secondlayer.tools`) |
|
|
110
|
+
| `STACKS_NETWORK` | Override via `--network <local\|testnet\|mainnet>` |
|
|
111
|
+
| `HIRO_API_KEY` | Used by `sl generate` for remote contract fetches |
|
|
112
|
+
|
|
113
|
+
## Error codes
|
|
114
|
+
|
|
115
|
+
Every tenant-scoped failure surfaces a typed code and an action hint:
|
|
116
|
+
|
|
117
|
+
| Code | CLI hint |
|
|
118
|
+
|---|---|
|
|
119
|
+
| `SESSION_EXPIRED` | `Session expired. Run: sl login` |
|
|
120
|
+
| `TENANT_SUSPENDED` | `Instance is suspended. Run: sl instance resume` |
|
|
121
|
+
| `NO_ACTIVE_PROJECT` | `No project selected. Run: sl project use <slug>` |
|
|
122
|
+
| `NO_TENANT_FOR_PROJECT` | `Project has no instance. Run: sl instance create --plan launch` |
|
|
123
|
+
| `KEY_ROTATED` | Handled transparently — `http.ts` re-mints and retries once |
|
|
124
|
+
|
|
125
|
+
## Code generation (`sl generate`)
|
|
126
|
+
|
|
127
|
+
Generate type-safe interfaces, functions, and optional React hooks from
|
|
128
|
+
Clarity contracts — works against local `.clar` files, deployed contracts
|
|
129
|
+
(network inferred from address prefix), or glob patterns.
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
# Local .clar files
|
|
133
|
+
sl generate ./contracts/token.clar -o ./src/generated.ts
|
|
134
|
+
|
|
135
|
+
# Deployed contracts (SP/SM → mainnet, ST/SN → testnet)
|
|
136
|
+
sl generate SP3K8BC0PPEVCV7NZ6QSRWPQ2JE9E5B6N3PA0KBR9.alex-vault -o ./src/generated.ts
|
|
137
|
+
|
|
138
|
+
# Glob
|
|
139
|
+
sl generate "./contracts/*.clar" -o ./src/generated.ts
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Config-driven mode:
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
sl init # creates secondlayer.config.ts
|
|
146
|
+
sl generate # regenerates from the config
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
// secondlayer.config.ts
|
|
151
|
+
import { defineConfig } from "@secondlayer/cli"
|
|
152
|
+
import { clarinet, actions, react } from "@secondlayer/cli/plugins"
|
|
153
|
+
|
|
154
|
+
export default defineConfig({
|
|
155
|
+
out: "src/generated.ts",
|
|
156
|
+
plugins: [clarinet(), actions(), react()],
|
|
157
|
+
})
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Plugins
|
|
161
|
+
|
|
162
|
+
| Plugin | What it adds |
|
|
163
|
+
|---|---|
|
|
164
|
+
| `clarinet()` | Parse local Clarinet project |
|
|
165
|
+
| `actions()` | `read.*` + `write.*` helpers on each contract |
|
|
166
|
+
| `react()` | Typed React Query hooks (`useTokenTransfer`, `useTokenBalance`, etc.) |
|
|
167
|
+
| `testing()` | Clarinet SDK test helpers |
|
|
168
|
+
|
|
169
|
+
### Usage examples
|
|
170
|
+
|
|
171
|
+
```typescript
|
|
172
|
+
import { token } from "./generated/contracts"
|
|
173
|
+
import { makeContractCall, fetchCallReadOnlyFunction } from "@stacks/transactions"
|
|
174
|
+
|
|
175
|
+
// Works with @stacks/transactions directly:
|
|
176
|
+
await makeContractCall({
|
|
177
|
+
...token.transfer({ amount: 100n, recipient: "SP..." }),
|
|
178
|
+
network: "mainnet",
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
// With actions() plugin — read/write helpers:
|
|
182
|
+
const balance = await token.read.getBalance({ account: "SP..." })
|
|
183
|
+
await token.write.transfer({ amount: 100n, recipient: "SP..." })
|
|
184
|
+
|
|
185
|
+
// Maps / vars / constants:
|
|
186
|
+
const supply = await token.vars.totalSupply.get()
|
|
187
|
+
const bal = await token.maps.balances.get("SP...")
|
|
188
|
+
const max = await token.constants.maxSupply.get()
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
// With react() plugin:
|
|
193
|
+
import { useTokenTransfer, useTokenBalance } from "./generated/hooks"
|
|
194
|
+
|
|
195
|
+
function App() {
|
|
196
|
+
const { transfer, isRequestPending } = useTokenTransfer()
|
|
197
|
+
const { data: balance } = useTokenBalance("SP...")
|
|
198
|
+
// ...
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
## License
|
|
203
|
+
|
|
204
|
+
MIT
|
package/dist/cli.js
CHANGED
|
@@ -2583,8 +2583,8 @@ function isOssMode() {
|
|
|
2583
2583
|
return !!process.env.SL_API_URL && !process.env.SL_SERVICE_KEY;
|
|
2584
2584
|
}
|
|
2585
2585
|
var init_resolve_tenant = __esm(() => {
|
|
2586
|
-
init_http();
|
|
2587
2586
|
init_config();
|
|
2587
|
+
init_http();
|
|
2588
2588
|
init_project_file();
|
|
2589
2589
|
});
|
|
2590
2590
|
|
|
@@ -32410,7 +32410,7 @@ var {
|
|
|
32410
32410
|
// package.json
|
|
32411
32411
|
var package_default = {
|
|
32412
32412
|
name: "@secondlayer/cli",
|
|
32413
|
-
version: "3.1.0",
|
|
32413
|
+
version: "3.1.2-alpha.0",
|
|
32414
32414
|
description: "CLI for subgraphs and blockchain indexing on Stacks",
|
|
32415
32415
|
type: "module",
|
|
32416
32416
|
bin: {
|
|
@@ -32431,7 +32431,8 @@ var package_default = {
|
|
|
32431
32431
|
},
|
|
32432
32432
|
files: [
|
|
32433
32433
|
"dist",
|
|
32434
|
-
"templates"
|
|
32434
|
+
"templates",
|
|
32435
|
+
"README.md"
|
|
32435
32436
|
],
|
|
32436
32437
|
scripts: {
|
|
32437
32438
|
build: "bunup",
|
|
@@ -32451,12 +32452,11 @@ var package_default = {
|
|
|
32451
32452
|
license: "MIT",
|
|
32452
32453
|
dependencies: {
|
|
32453
32454
|
"@inquirer/prompts": "^8.2.0",
|
|
32454
|
-
"@secondlayer/bundler": "^0.3.0",
|
|
32455
|
-
"@secondlayer/sdk": "^
|
|
32456
|
-
"@secondlayer/shared": "^
|
|
32457
|
-
"@secondlayer/stacks": "^0.
|
|
32458
|
-
"@secondlayer/subgraphs": "^0.
|
|
32459
|
-
"@secondlayer/workflows": "^1.1.0",
|
|
32455
|
+
"@secondlayer/bundler": "^0.3.1-alpha.0",
|
|
32456
|
+
"@secondlayer/sdk": "^3.0.0-alpha.0",
|
|
32457
|
+
"@secondlayer/shared": "^3.0.0-alpha.0",
|
|
32458
|
+
"@secondlayer/stacks": "^1.0.0-alpha.0",
|
|
32459
|
+
"@secondlayer/subgraphs": "^1.0.0-alpha.0",
|
|
32460
32460
|
"@biomejs/js-api": "^0.7.0",
|
|
32461
32461
|
"@biomejs/wasm-nodejs": "^1.9.0",
|
|
32462
32462
|
esbuild: "^0.19.0",
|
|
@@ -32487,11 +32487,6 @@ function handleApiError(err, action) {
|
|
|
32487
32487
|
console.error("Session expired. Run: sl login");
|
|
32488
32488
|
process.exit(1);
|
|
32489
32489
|
}
|
|
32490
|
-
if (err.code === "TRIAL_EXPIRED") {
|
|
32491
|
-
console.error(err.message);
|
|
32492
|
-
console.error("Run: sl instance resize --plan <...> and add payment");
|
|
32493
|
-
process.exit(1);
|
|
32494
|
-
}
|
|
32495
32490
|
if (err.code === "TENANT_SUSPENDED") {
|
|
32496
32491
|
console.error("Tenant is suspended. Run: sl instance resume");
|
|
32497
32492
|
process.exit(1);
|
|
@@ -34933,13 +34928,9 @@ function registerWhoamiCommand(program2) {
|
|
|
34933
34928
|
try {
|
|
34934
34929
|
const tenant = await httpPlatform("/api/tenants/me");
|
|
34935
34930
|
if (tenant.tenant) {
|
|
34936
|
-
const trialDays = Math.max(0, Math.ceil((new Date(tenant.tenant.trialEndsAt).getTime() - Date.now()) / (24 * 60 * 60 * 1000)));
|
|
34937
34931
|
rows.push(["Instance", tenant.tenant.apiUrl]);
|
|
34932
|
+
rows.push(["Plan", tenant.tenant.plan]);
|
|
34938
34933
|
rows.push(["Status", tenant.tenant.status]);
|
|
34939
|
-
rows.push([
|
|
34940
|
-
"Trial",
|
|
34941
|
-
`${trialDays} day${trialDays === 1 ? "" : "s"} left`
|
|
34942
|
-
]);
|
|
34943
34934
|
} else {
|
|
34944
34935
|
rows.push([
|
|
34945
34936
|
"Instance",
|
|
@@ -35037,12 +35028,12 @@ init_resolve_tenant();
|
|
|
35037
35028
|
import { confirm as confirm4, input as input3, select as select3 } from "@inquirer/prompts";
|
|
35038
35029
|
function registerInstanceCommand(program2) {
|
|
35039
35030
|
const instance = program2.command("instance").description("Manage your dedicated Secondlayer instance");
|
|
35040
|
-
instance.command("create").description("Provision a new dedicated instance for the active project").option("--plan <plan>", "Plan: launch | grow | scale", "
|
|
35031
|
+
instance.command("create").description("Provision a new dedicated instance for the active project").option("--plan <plan>", "Plan: hobby (free) | launch | grow | scale", "hobby").action(async (opts) => {
|
|
35041
35032
|
guardOssMode();
|
|
35042
35033
|
const activeSlug = await requireActiveProject();
|
|
35043
35034
|
const plan = opts.plan;
|
|
35044
|
-
if (!["launch", "grow", "scale"].includes(plan)) {
|
|
35045
|
-
error(`Invalid plan: ${plan} (expected launch, grow, or scale)`);
|
|
35035
|
+
if (!["hobby", "launch", "grow", "scale"].includes(plan)) {
|
|
35036
|
+
error(`Invalid plan: ${plan} (expected hobby, launch, grow, or scale)`);
|
|
35046
35037
|
process.exit(1);
|
|
35047
35038
|
}
|
|
35048
35039
|
try {
|
|
@@ -35060,13 +35051,17 @@ function registerInstanceCommand(program2) {
|
|
|
35060
35051
|
guardOssMode();
|
|
35061
35052
|
await renderInstanceInfo();
|
|
35062
35053
|
});
|
|
35063
|
-
instance.command("resize").description("Change your instance plan (brief downtime)").option("--plan <plan>", "Target plan: launch | grow | scale").option("--yes", "Skip confirm").action(async (opts) => {
|
|
35054
|
+
instance.command("resize").description("Change your instance plan (brief downtime)").option("--plan <plan>", "Target plan: hobby | launch | grow | scale").option("--yes", "Skip confirm").action(async (opts) => {
|
|
35064
35055
|
guardOssMode();
|
|
35065
35056
|
let target = opts.plan;
|
|
35066
35057
|
if (!target) {
|
|
35067
35058
|
const answer = await select3({
|
|
35068
35059
|
message: "Target plan",
|
|
35069
35060
|
choices: [
|
|
35061
|
+
{
|
|
35062
|
+
value: "hobby",
|
|
35063
|
+
name: "Hobby — free (0.5 vCPU · 512 MB · 5 GB, auto-pause after 7d idle)"
|
|
35064
|
+
},
|
|
35070
35065
|
{
|
|
35071
35066
|
value: "launch",
|
|
35072
35067
|
name: "Launch — $99/mo (1 vCPU · 2 GB · 10 GB)"
|
|
@@ -35268,12 +35263,10 @@ async function renderInstanceInfo() {
|
|
|
35268
35263
|
return;
|
|
35269
35264
|
}
|
|
35270
35265
|
const t = res.tenant;
|
|
35271
|
-
const trialDays = Math.max(0, Math.ceil((new Date(t.trialEndsAt).getTime() - Date.now()) / (24 * 60 * 60 * 1000)));
|
|
35272
35266
|
console.log(formatKeyValue([
|
|
35273
35267
|
["URL", t.apiUrl],
|
|
35274
35268
|
["Plan", t.plan],
|
|
35275
35269
|
["Status", t.status],
|
|
35276
|
-
["Trial", `${trialDays} day${trialDays === 1 ? "" : "s"} left`],
|
|
35277
35270
|
["Created", new Date(t.createdAt).toLocaleString()]
|
|
35278
35271
|
]));
|
|
35279
35272
|
} catch (err) {
|
|
@@ -35307,11 +35300,6 @@ function handleInstanceError(err, action) {
|
|
|
35307
35300
|
error("Session expired. Run: sl login");
|
|
35308
35301
|
process.exit(1);
|
|
35309
35302
|
}
|
|
35310
|
-
if (err.code === "TRIAL_EXPIRED") {
|
|
35311
|
-
error(err.message);
|
|
35312
|
-
error("Add a payment method at the dashboard to keep your instance running.");
|
|
35313
|
-
process.exit(1);
|
|
35314
|
-
}
|
|
35315
35303
|
if (err.code === "TENANT_SUSPENDED") {
|
|
35316
35304
|
error("Instance is suspended. Run: sl instance resume");
|
|
35317
35305
|
process.exit(1);
|
|
@@ -35344,7 +35332,7 @@ function registerProjectCommand(program2) {
|
|
|
35344
35332
|
success(`Created project ${res.project.name} (${res.project.slug})`);
|
|
35345
35333
|
const path = await writeActiveProject(res.project.slug, process.cwd());
|
|
35346
35334
|
info(dim(`Bound to this directory → ${path}`));
|
|
35347
|
-
info(dim(
|
|
35335
|
+
info(dim("Next: sl instance create --plan launch"));
|
|
35348
35336
|
} catch (err) {
|
|
35349
35337
|
handleProjectError(err);
|
|
35350
35338
|
}
|
|
@@ -35448,5 +35436,5 @@ registerLocalCommand(program);
|
|
|
35448
35436
|
registerAccountCommand(program);
|
|
35449
35437
|
program.parse();
|
|
35450
35438
|
|
|
35451
|
-
//# debugId=
|
|
35439
|
+
//# debugId=2929270B62E4733C64756E2164756E21
|
|
35452
35440
|
//# sourceMappingURL=cli.js.map
|