insta 0.0.26 → 0.0.28
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 +5 -4
- package/dist/api.js +11 -0
- package/dist/commands/auth.js +37 -0
- package/dist/commands/compute.js +41 -1
- package/dist/commands/db.js +58 -0
- package/dist/commands/services.js +22 -2
- package/dist/commands/setup.js +1 -1
- package/dist/ensure-skills.js +7 -7
- package/dist/index.js +11 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -79,8 +79,8 @@ plane's address, not the CLI's loopback address.
|
|
|
79
79
|
### Services are branch-scoped
|
|
80
80
|
|
|
81
81
|
A project holds services (`postgres`, `storage`, `compute`) and each branch owns its own
|
|
82
|
-
set. `insta branch create feature-x` forks the parent's services: a
|
|
83
|
-
Postgres, a copy-on-write bucket per storage, a clone of every compute service. From there
|
|
82
|
+
set. `insta branch create feature-x` forks the parent's services: a copy-on-write database
|
|
83
|
+
branch per Postgres, a copy-on-write bucket per storage, a clone of every compute service. From there
|
|
84
84
|
the two branches diverge independently. A project is capped at 10 branches.
|
|
85
85
|
|
|
86
86
|
### Credentials come from the secret seam, not a file you maintain
|
|
@@ -207,8 +207,9 @@ build never reaches a production installer.
|
|
|
207
207
|
The `insta` skill and its task guides live in
|
|
208
208
|
[InsForge/insta-skills](https://github.com/InsForge/insta-skills). `insta setup agent`
|
|
209
209
|
installs it user-globally for every coding agent on the machine. `insta project create` and
|
|
210
|
-
`insta project link` additionally install the stack skills (
|
|
211
|
-
|
|
210
|
+
`insta project link` additionally install the stack skills (Tigris, Better Auth) into the
|
|
211
|
+
project, along with the `insta observe` credential-audit hook. Postgres needs no stack
|
|
212
|
+
skill — it's plain Postgres, reached directly via `DATABASE_URL`.
|
|
212
213
|
|
|
213
214
|
## Contributing
|
|
214
215
|
|
package/dist/api.js
CHANGED
|
@@ -11,6 +11,13 @@ export class ApiError extends Error {
|
|
|
11
11
|
this.name = 'ApiError';
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
|
+
// Store a durable insta_ key as the credential: set it as the bearer and drop any refresh token (an insta_ key never rotates; a stale one would leak to /auth/refresh on a 401).
|
|
15
|
+
export function storeApiKeyCredential(cfg, token, user) {
|
|
16
|
+
cfg.accessToken = token;
|
|
17
|
+
delete cfg.refreshToken;
|
|
18
|
+
if (user)
|
|
19
|
+
cfg.user = user;
|
|
20
|
+
}
|
|
14
21
|
export class ApiClient {
|
|
15
22
|
cfg;
|
|
16
23
|
constructor(cfg) {
|
|
@@ -27,6 +34,10 @@ export class ApiClient {
|
|
|
27
34
|
if (user)
|
|
28
35
|
this.cfg.user = user;
|
|
29
36
|
}
|
|
37
|
+
// Adopt a durable insta_ key as the credential (non-interactive `login --api-key`).
|
|
38
|
+
setApiKey(token, user) {
|
|
39
|
+
storeApiKeyCredential(this.cfg, token, user);
|
|
40
|
+
}
|
|
30
41
|
clearSession() {
|
|
31
42
|
delete this.cfg.accessToken;
|
|
32
43
|
delete this.cfg.refreshToken;
|
package/dist/commands/auth.js
CHANGED
|
@@ -17,6 +17,13 @@ function targetApiUrl(opts) {
|
|
|
17
17
|
return ENVS[want].api;
|
|
18
18
|
}
|
|
19
19
|
export async function login(opts) {
|
|
20
|
+
// Login modes are exclusive — pick one. Check presence (not truthiness) so an explicit
|
|
21
|
+
// empty --api-key= is rejected by validation rather than silently falling through.
|
|
22
|
+
if (opts.apiKey !== undefined) {
|
|
23
|
+
if (opts.device || opts.oauth || opts.email)
|
|
24
|
+
die('choose one login mode: --api-key, --device, --oauth, or --email');
|
|
25
|
+
return loginApiKey(opts.apiKey, opts);
|
|
26
|
+
}
|
|
20
27
|
if (opts.device)
|
|
21
28
|
return loginDevice(opts);
|
|
22
29
|
if (opts.oauth)
|
|
@@ -65,6 +72,36 @@ export async function loginDevice(opts) {
|
|
|
65
72
|
await api.persist();
|
|
66
73
|
info(`logged in as ${me.user.email ?? me.user.id} @ ${api.apiUrl}`);
|
|
67
74
|
}
|
|
75
|
+
// Non-interactive login with a durable insta_ key (minted via POST /tokens): store it and confirm against /me. No browser, no polling.
|
|
76
|
+
export async function loginApiKey(key, opts) {
|
|
77
|
+
const api = await ApiClient.load();
|
|
78
|
+
const target = targetApiUrl(opts);
|
|
79
|
+
if (target)
|
|
80
|
+
api.setApiUrl(target);
|
|
81
|
+
const user = await applyApiKeyLogin(api, key);
|
|
82
|
+
await api.persist();
|
|
83
|
+
info(`logged in as ${user.email ?? user.id} @ ${api.apiUrl}`);
|
|
84
|
+
}
|
|
85
|
+
// Verify an insta_ key and store it: set it first so the /me probe is authed with the key itself, then re-store with the resolved user (401 → bad/revoked).
|
|
86
|
+
export async function applyApiKeyLogin(client, key) {
|
|
87
|
+
key = key.trim(); // tolerate a trailing newline / stray whitespace from `--api-key "$(cat token)"`
|
|
88
|
+
if (!key.startsWith('insta_'))
|
|
89
|
+
throw new Error('--api-key expects an insta_ token (mint one with POST /tokens)');
|
|
90
|
+
client.setApiKey(key);
|
|
91
|
+
let me;
|
|
92
|
+
try {
|
|
93
|
+
me = await client.request('GET', '/me');
|
|
94
|
+
}
|
|
95
|
+
catch (e) {
|
|
96
|
+
if (e instanceof ApiError && e.status === 401)
|
|
97
|
+
throw new Error('that insta_ API key was rejected (invalid or revoked) — check it or mint a new one');
|
|
98
|
+
throw e;
|
|
99
|
+
}
|
|
100
|
+
if (!me?.user)
|
|
101
|
+
throw new Error('unexpected response while verifying the API key');
|
|
102
|
+
client.setApiKey(key, me.user);
|
|
103
|
+
return me.user;
|
|
104
|
+
}
|
|
68
105
|
const sleepSeconds = (s) => new Promise((r) => setTimeout(r, s * 1000));
|
|
69
106
|
// Drives the device grant against the platform's Better Auth mount (/api/auth/device*) and
|
|
70
107
|
// returns the approved session token. Injectable poster + wait keep this testable without a
|
package/dist/commands/compute.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ApiClient, requireProject } from '../api.js';
|
|
2
2
|
import { info, printJson, handleApproval } from '../util.js';
|
|
3
|
-
import { resolveComputeServiceId, q } from './services.js';
|
|
3
|
+
import { resolveComputeServiceId, q, parseVolumeGib } from './services.js';
|
|
4
4
|
// Attach a developer-owned custom domain to a branch's compute service. Fly issues the cert + routes
|
|
5
5
|
// it; the platform returns the DNS records to set in your OWN zone.
|
|
6
6
|
export async function setDomain(host, opts) {
|
|
@@ -116,6 +116,46 @@ export function parseCpu(raw) {
|
|
|
116
116
|
throw new Error(`invalid cpu: ${raw} (provider sizes: ${CPU_SIZES.join(', ')})`);
|
|
117
117
|
return n;
|
|
118
118
|
}
|
|
119
|
+
// ---- volume (the persistent /data disk; attach is create-time only) ----
|
|
120
|
+
// Render the volume read. Pure, exported for tests (mirrors serviceListLine). Every plan may view;
|
|
121
|
+
// only growth is paid — that gate is the backend's to enforce, so nothing here pre-blocks.
|
|
122
|
+
export function volumeLines(name, volume, cap) {
|
|
123
|
+
if (!volume)
|
|
124
|
+
return [
|
|
125
|
+
`compute ${name}: no volume attached (attach is create-time only: \`insta services add compute <name> --volume <gi>\`)`,
|
|
126
|
+
];
|
|
127
|
+
return [
|
|
128
|
+
`compute ${name}: volume ${volume.sizeGib}Gi at ${volume.mountPath} (plan max ${cap.volumeGib}Gi)`,
|
|
129
|
+
' billing is actual data stored — the size is a cap, not a price; grow with --size (grow-only)',
|
|
130
|
+
];
|
|
131
|
+
}
|
|
132
|
+
// Show or grow a compute service's /data volume. No --size: a safe read (size + mount path + the
|
|
133
|
+
// plan cap). --size: grow via PUT .../volume — paid and grow-only, but both gates belong to the
|
|
134
|
+
// backend, whose 403/400 messages carry the upgrade hints and must reach the user verbatim (the
|
|
135
|
+
// guard prints ApiError messages as-is).
|
|
136
|
+
export async function computeVolume(serviceName, opts) {
|
|
137
|
+
const api = await ApiClient.load();
|
|
138
|
+
const p = await requireProject();
|
|
139
|
+
const branch = opts.branch ?? p.branch;
|
|
140
|
+
const { services } = await api.request('GET', `/projects/${p.projectId}/services${q(branch)}`);
|
|
141
|
+
const id = resolveComputeServiceId(services, serviceName);
|
|
142
|
+
if (!opts.size) {
|
|
143
|
+
const r = await api.request('GET', `/projects/${p.projectId}/services/${id}/volume`);
|
|
144
|
+
if (opts.json)
|
|
145
|
+
return printJson(r);
|
|
146
|
+
for (const line of volumeLines(serviceName ?? id, r.volume, r.cap))
|
|
147
|
+
info(line);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
const sizeGib = parseVolumeGib(opts.size);
|
|
151
|
+
const res = await api.rawRequest('PUT', `/projects/${p.projectId}/services/${id}/volume`, { sizeGib });
|
|
152
|
+
if (handleApproval(res))
|
|
153
|
+
return;
|
|
154
|
+
if (opts.json)
|
|
155
|
+
return printJson(res.body);
|
|
156
|
+
const v = res.body.volume;
|
|
157
|
+
info(`compute ${res.body.service?.name ?? serviceName ?? id}: volume grown to ${v.sizeGib}Gi at ${v.mountPath} (plan max ${res.body.cap.volumeGib}Gi)`);
|
|
158
|
+
}
|
|
119
159
|
// Show or set a compute service's ceiling. With no --memory it PRINTS the current limits and the
|
|
120
160
|
// plan cap (so `insta compute limits` is a safe read), which is also what a UI renders as a slider
|
|
121
161
|
// with its plan-limit marker.
|
package/dist/commands/db.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ApiClient, ApiError, requireProject } from '../api.js';
|
|
2
2
|
import { info, printJson, handleApproval } from '../util.js';
|
|
3
|
+
import { parseVolumeGib } from './services.js';
|
|
3
4
|
// Toggle a postgres service between scale-to-zero (the default: instance suspends when idle,
|
|
4
5
|
// cold-starts on the next connection) and always-on (instance stays warm; idle RAM bills at
|
|
5
6
|
// actual usage). Thin wrapper over PATCH /database/settings {scaleToZero} — insta-db-backed
|
|
@@ -116,4 +117,61 @@ export async function dbLimits(opts) {
|
|
|
116
117
|
const mem = typeof res.body?.memoryMib === 'number' ? fmtMib(res.body.memoryMib) : (opts.memory ?? 'unchanged');
|
|
117
118
|
info(`postgres ${opts.group ?? 'default'}: ceiling set to ${cpu} / ${mem}`);
|
|
118
119
|
}
|
|
120
|
+
// Render the instance's volume from a database/instance read. Pure, exported for tests. Reads the
|
|
121
|
+
// CANONICAL volume* names only — storageSize/storageGiB are deprecated aliases the platform drops
|
|
122
|
+
// next release, so depending on them here would be a scheduled breakage.
|
|
123
|
+
export function dbVolumeLines(group, body) {
|
|
124
|
+
const gib = typeof body?.volumeGib === 'number' ? `${body.volumeGib}Gi` : (typeof body?.volumeSize === 'string' ? body.volumeSize : undefined);
|
|
125
|
+
if (gib === undefined)
|
|
126
|
+
return [`postgres ${group}: provider reported no volume size`];
|
|
127
|
+
const cap = body?.cap?.volumeGib;
|
|
128
|
+
const region = typeof body?.region === 'string' ? ` ${body.region}` : '';
|
|
129
|
+
return [
|
|
130
|
+
`postgres ${group}: volume ${gib}${typeof cap === 'number' ? ` (plan max ${cap}Gi)` : ''}${region}`,
|
|
131
|
+
' billing is actual data stored — the size is a cap, not a price; grow with --size (grow-only)',
|
|
132
|
+
];
|
|
133
|
+
}
|
|
134
|
+
// Show or grow a postgres service's provisioned volume (block disk; insta-db-backed only). Viewing
|
|
135
|
+
// is available on every plan; growth is paid and grow-only — both gates are the backend's to
|
|
136
|
+
// enforce, so nothing here pre-blocks: its 403/400 messages carry the upgrade hints and are wrapped
|
|
137
|
+
// with context but kept verbatim.
|
|
138
|
+
export async function dbVolume(opts) {
|
|
139
|
+
const api = await ApiClient.load();
|
|
140
|
+
const p = await requireProject();
|
|
141
|
+
const qs = new URLSearchParams();
|
|
142
|
+
const branch = opts.branch ?? p.branch;
|
|
143
|
+
if (branch)
|
|
144
|
+
qs.set('branch', branch);
|
|
145
|
+
if (opts.group)
|
|
146
|
+
qs.set('group', opts.group);
|
|
147
|
+
const suffix = qs.toString() ? `?${qs}` : '';
|
|
148
|
+
if (!opts.size) {
|
|
149
|
+
const read = await fetchDbInstance(api, p.projectId, suffix);
|
|
150
|
+
if (read.kind === 'no-instance') {
|
|
151
|
+
info(`postgres ${opts.group ?? 'default'}: no manageable instance (Neon-backed services manage their own storage)`);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
if (opts.json)
|
|
155
|
+
return printJson(read.body);
|
|
156
|
+
for (const line of dbVolumeLines(opts.group ?? 'default', read.body))
|
|
157
|
+
info(line);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
const sizeGib = parseVolumeGib(opts.size);
|
|
161
|
+
let res;
|
|
162
|
+
try {
|
|
163
|
+
res = await api.rawRequest('PATCH', `/projects/${p.projectId}/database/settings${suffix}`, { volumeSize: `${sizeGib}Gi` });
|
|
164
|
+
}
|
|
165
|
+
catch (e) {
|
|
166
|
+
if (e instanceof ApiError)
|
|
167
|
+
throw new Error(`growing the volume failed (${e.status}): ${e.message}`);
|
|
168
|
+
throw e;
|
|
169
|
+
}
|
|
170
|
+
if (handleApproval(res))
|
|
171
|
+
return;
|
|
172
|
+
if (opts.json)
|
|
173
|
+
return printJson(res.body);
|
|
174
|
+
const vg = res.body?.volumeGib;
|
|
175
|
+
info(`postgres ${opts.group ?? 'default'}: volume ${typeof vg === 'number' ? `grown to ${vg}Gi` : `set to ${sizeGib}Gi`}`);
|
|
176
|
+
}
|
|
119
177
|
//# sourceMappingURL=db.js.map
|
|
@@ -23,6 +23,19 @@ export function parseCount(raw) {
|
|
|
23
23
|
throw new Error(`count must be a positive integer, got: ${raw}`);
|
|
24
24
|
return n;
|
|
25
25
|
}
|
|
26
|
+
// Parse a volume size in whole Gi: "10" or "10Gi" (suffix case-insensitive — unlike the db
|
|
27
|
+
// quantity strings this is not a provider pass-through; the wire value is an integer). Volumes
|
|
28
|
+
// are provisioned block disks, so fractional and Mi values are rejected locally with an example
|
|
29
|
+
// rather than travelling to the server as junk (the parseCpu lesson: NaN serializes to null).
|
|
30
|
+
export function parseVolumeGib(raw) {
|
|
31
|
+
const m = /^\s*(\d+)\s*(gi|gib|g)?\s*$/i.exec(raw);
|
|
32
|
+
if (!m)
|
|
33
|
+
throw new Error(`invalid volume size: ${raw} (whole Gi — try 1 or 10)`);
|
|
34
|
+
const n = Number(m[1]);
|
|
35
|
+
if (n < 1)
|
|
36
|
+
throw new Error(`invalid volume size: ${raw} (whole Gi — try 1 or 10)`);
|
|
37
|
+
return n;
|
|
38
|
+
}
|
|
26
39
|
// Resolve a service id from a `services list` result by (type, name).
|
|
27
40
|
export function resolveServiceId(services, type, name) {
|
|
28
41
|
const svc = services.find((s) => s.type === type && s.name === name);
|
|
@@ -54,6 +67,7 @@ export function servicesAddRequestBody(type, name, branch, opts) {
|
|
|
54
67
|
...(opts.image ? { image: opts.image } : {}), ...(opts.port ? { port: Number(opts.port) } : {}),
|
|
55
68
|
...(opts.region ? { region: opts.region } : {}),
|
|
56
69
|
...(opts.alwaysOn ? { alwaysOn: true } : {}),
|
|
70
|
+
...(opts.volume !== undefined ? { volumeGib: parseVolumeGib(opts.volume) } : {}),
|
|
57
71
|
};
|
|
58
72
|
}
|
|
59
73
|
export async function servicesAdd(type, name, opts = {}) {
|
|
@@ -68,6 +82,11 @@ export async function servicesAdd(type, name, opts = {}) {
|
|
|
68
82
|
throw new Error('--port is only valid for compute services');
|
|
69
83
|
if (opts.alwaysOn && type !== 'compute')
|
|
70
84
|
throw new Error('--always-on is only valid for compute services (for postgres, use `insta db always-on on` after creation)');
|
|
85
|
+
if (opts.volume !== undefined) {
|
|
86
|
+
if (type !== 'compute')
|
|
87
|
+
throw new Error('--volume is only valid for compute services (postgres has one by default — grow it with `insta db volume --size`)');
|
|
88
|
+
parseVolumeGib(opts.volume); // junk fails here, before any config/network access
|
|
89
|
+
}
|
|
71
90
|
const api = await ApiClient.load();
|
|
72
91
|
const p = await requireProject();
|
|
73
92
|
const branch = opts.branch ?? p.branch;
|
|
@@ -77,14 +96,15 @@ export async function servicesAdd(type, name, opts = {}) {
|
|
|
77
96
|
const svc = res.body.service;
|
|
78
97
|
const access = svc.type === 'storage' ? ` [${svc.public ? 'public' : 'private'}]` : '';
|
|
79
98
|
const img = svc.image ? ` running ${svc.image}${svc.port ? `:${svc.port}` : ''}` : '';
|
|
80
|
-
|
|
99
|
+
const vol = svc.volume_gib ? ` vol ${svc.volume_gib}Gi at /data` : '';
|
|
100
|
+
info(`added ${type} service ${name} on ${branch ?? 'default'} (${svc.id})${access}${svc.region ? ` ${svc.region}` : ''}${img}${vol}${svc.domain ? ` — ${svc.domain}` : ''}`);
|
|
81
101
|
renderNextActions(res.body.nextActions);
|
|
82
102
|
}
|
|
83
103
|
// Render one `services list` row. Pure, so it's unit-tested without a network mock (mirrors
|
|
84
104
|
// billingLines in billing.ts). Compute rows show the running image when the platform reports one.
|
|
85
105
|
export function serviceListLine(s) {
|
|
86
106
|
const extra = s.type === 'compute'
|
|
87
|
-
? ` x${s.machine_count}${s.image ? ` running ${s.image}${s.port ? `:${s.port}` : ''}` : ''}`
|
|
107
|
+
? ` x${s.machine_count}${s.volume_gib ? ` vol ${s.volume_gib}Gi` : ''}${s.image ? ` running ${s.image}${s.port ? `:${s.port}` : ''}` : ''}`
|
|
88
108
|
: s.type === 'storage' ? ` ${s.public ? 'public' : 'private'}` : '';
|
|
89
109
|
return `${s.type}/${s.name} [${s.status}]${extra}${s.domain ? ` ${s.domain}` : ''} ${s.id}`;
|
|
90
110
|
}
|
package/dist/commands/setup.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// every agent the skills tool knows: the skill is pure product knowledge with brand-gated
|
|
4
4
|
// triggers — no project state in it (the project binding is carried by ./.insta/project.json
|
|
5
5
|
// at command time), so one machine-level copy is strictly better than per-project copies.
|
|
6
|
-
// Stack skills (
|
|
6
|
+
// Stack skills (tigris/better-auth) intentionally stay per-project: their presence in a
|
|
7
7
|
// project doubles as its stack manifest — that install happens on `project create|link`.
|
|
8
8
|
import { spawn } from 'node:child_process';
|
|
9
9
|
import os from 'node:os';
|
package/dist/ensure-skills.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Install the related agent skills into a linked project so the developer's coding agent has
|
|
2
|
-
// context for what InstaCloud provisions — the `insta` CLI itself plus the
|
|
3
|
-
// directly against (
|
|
2
|
+
// context for what InstaCloud provisions — the `insta` CLI itself plus the services you build
|
|
3
|
+
// directly against (Tigris storage, Better Auth). Postgres gets no vendor skill: instadb is
|
|
4
|
+
// plain Postgres, and agents talk to it directly via DATABASE_URL. Runs `npx skills add`
|
|
4
5
|
// (vercel-labs/skills) fully non-interactively. Best-effort: a failure (offline, npx missing, a
|
|
5
6
|
// repo moved) prints a manual fallback and never blocks or fails the host command — same contract
|
|
6
7
|
// as the observe-hook install.
|
|
@@ -13,8 +14,8 @@ import { DEFAULT_ENV, ENVS } from './env.js';
|
|
|
13
14
|
// Codex → .agents/skills/ (.github/skills/ is the third well-known dir). These are regenerable
|
|
14
15
|
// agent context, not the developer's source — keep them out of git.
|
|
15
16
|
const SKILL_DIRS = ['.claude/skills/', '.agents/skills/', '.github/skills/'];
|
|
16
|
-
// `skills` prints a full-screen ASCII banner at the top of every `add`, so our
|
|
17
|
-
// would stack
|
|
17
|
+
// `skills` prints a full-screen ASCII banner at the top of every `add`, so our 3 invocations
|
|
18
|
+
// would stack 3 banners. It skips the banner when it detects an agent driving it rather than a
|
|
18
19
|
// human — AI_AGENT is its first-checked signal (any non-empty value ⇒ agent mode). Setting it is
|
|
19
20
|
// honest here (this IS programmatic, not an interactive prompt) and, because we already pin the
|
|
20
21
|
// agents/skills/-y, has no effect on the install beyond quieting the banner. Preserve a caller's
|
|
@@ -32,10 +33,9 @@ const defaultRunner = (cmd, args, inherit = false) => new Promise((resolve) => {
|
|
|
32
33
|
const AGENT_FLAGS = ['-a', 'claude-code', '-a', 'codex', '-y', '--copy'];
|
|
33
34
|
// `instaSpec` is the insta skill source for the resolved environment (`owner/repo[@ref]`), so a
|
|
34
35
|
// project created against staging gets the staging skill text. The third-party stack skills are
|
|
35
|
-
// environment-independent — they document
|
|
36
|
+
// environment-independent — they document Tigris/Better Auth, not our control plane.
|
|
36
37
|
const skillTargets = (instaSpec) => [
|
|
37
38
|
{ label: 'insta', args: ['skills', 'add', instaSpec, '-s', 'insta', ...AGENT_FLAGS] },
|
|
38
|
-
{ label: 'neon-postgres', args: ['skills', 'add', 'neondatabase/agent-skills', '-s', 'neon-postgres', ...AGENT_FLAGS] },
|
|
39
39
|
{ label: 'tigris', args: ['skills', 'add', 'tigrisdata/skills',
|
|
40
40
|
'-s', 'tigris-object-operations', '-s', 'file-storage', '-s', 'tigris-sdk-guide',
|
|
41
41
|
'-s', 'tigris-security-access-control', '-s', 'tigris-image-optimization',
|
|
@@ -63,7 +63,7 @@ export async function installSkills(deps) {
|
|
|
63
63
|
targets = skillTargets(skills);
|
|
64
64
|
}
|
|
65
65
|
catch { /* keep production defaults */ }
|
|
66
|
-
print(' installing related agent skills (insta,
|
|
66
|
+
print(' installing related agent skills (insta, tigris, better-auth) …');
|
|
67
67
|
for (const s of targets) {
|
|
68
68
|
// Don't stream: the `skills` tool's clack UI (clone spinner, banners) is noise. Run it
|
|
69
69
|
// silent (stdio 'ignore') and let the per-skill ✓/failed line below be the clean output —
|
package/dist/index.js
CHANGED
|
@@ -54,11 +54,12 @@ function resolveVersion() {
|
|
|
54
54
|
}
|
|
55
55
|
program.name('insta').description('InstaCloud CLI — manage projects, branches, secrets, deploys').version(resolveVersion());
|
|
56
56
|
// ---- auth ----
|
|
57
|
-
program.command('login').description('Log in with email + password, --oauth <github|google> (browser), or --
|
|
57
|
+
program.command('login').description('Log in with email + password, --oauth <github|google> (browser), --device (headless), or --api-key <insta_…> (headless, durable token)')
|
|
58
58
|
.option('--email <email>', 'account email')
|
|
59
59
|
.option('--password <password>', 'account password (else $INSTA_PASSWORD or prompt)')
|
|
60
60
|
.option('--oauth <provider>', 'browser OAuth login: github | google')
|
|
61
61
|
.option('--device', 'device-code login: approve from a browser on any other machine (VMs, SSH, CI)')
|
|
62
|
+
.option('--api-key <key>', 'non-interactive login with a durable insta_ API token (headless agents / CI)')
|
|
62
63
|
.option('--api-url <url>', 'control-plane API base URL')
|
|
63
64
|
.option('--env <name>', `deployment environment: ${ENV_NAMES.join(' | ')}`)
|
|
64
65
|
.action(guard((o) => auth.login(o)));
|
|
@@ -114,6 +115,7 @@ svc.command('add <type> <name>').description('Provision a service on demand (ass
|
|
|
114
115
|
.option('--image <url>', 'compute only: run this container image at creation')
|
|
115
116
|
.option('--port <n>', 'compute only: port the image listens on (default 8080)')
|
|
116
117
|
.option('--always-on', 'compute only: create as always-on — never scales to zero (all plans; billing is actual usage either way)')
|
|
118
|
+
.option('--volume <gi>', 'compute only: attach a persistent /data volume of this many whole Gi (create-time only; any plan may attach at the default 1; larger sizes are paid and plan-capped). Volume services keep 1 machine and stop (cold wake) instead of suspend when idle')
|
|
117
119
|
.action(guard((type, name, o) => services.servicesAdd(type, name, o)));
|
|
118
120
|
svc.command('list').option('--json').option('--branch <branch>', 'branch (default: current)')
|
|
119
121
|
.action(guard((o) => services.servicesList(o)));
|
|
@@ -169,8 +171,11 @@ compute.command('limits [service]').description("Show or set a compute service's
|
|
|
169
171
|
.option('--json').option('--branch <branch>', 'branch (default: current)').action(guard((service, o) => computeCmd.computeLimits(service, o)));
|
|
170
172
|
compute.command('always-on <mode> [service]').description('Set a compute service always-on (mode: on|off). on = machines never scale to zero; off = default scale-to-zero. All plans; billing is actual usage either way')
|
|
171
173
|
.option('--json').option('--branch <branch>', 'branch (default: current)').action(guard((mode, service, o) => computeCmd.computeAlwaysOn(mode, service, o)));
|
|
174
|
+
compute.command('volume [service]').description("Show or grow a compute service's persistent /data volume. No --size: print size, mount path, and the plan cap (any plan). --size grows it (paid plans; grow-only — a provisioned disk cannot shrink). Attach is create-time only: `insta services add compute <name> --volume <gi>`. Billing is actual data stored — the size is a cap, not a price")
|
|
175
|
+
.option('--size <gi>', 'new size in whole Gi, e.g. 10 (must be ≥ the current size)')
|
|
176
|
+
.option('--json').option('--branch <branch>', 'branch (default: current)').action(guard((service, o) => computeCmd.computeVolume(service, o)));
|
|
172
177
|
// ---- db (postgres service controls) ----
|
|
173
|
-
const db = program.command('db').description('Postgres service controls (limits / always-on / scale-to-zero)');
|
|
178
|
+
const db = program.command('db').description('Postgres service controls (limits / volume / always-on / scale-to-zero)');
|
|
174
179
|
db.command('limits').description("Show or set a postgres service's resource ceiling (paid plans; insta-db-backed only). Moves both directions")
|
|
175
180
|
.option('--cpu <n>', "vCPU ceiling, e.g. 2 or 2500m").option('--memory <size>', "memory ceiling, e.g. 4Gi")
|
|
176
181
|
.option('--json').option('--branch <branch>', 'branch (default: current)').option('--group <g>', 'postgres service name (default: the sole/default one)')
|
|
@@ -178,6 +183,10 @@ db.command('limits').description("Show or set a postgres service's resource ceil
|
|
|
178
183
|
db.command('always-on <mode>').description('Set a postgres service always-on (mode: on|off). on = instance stays warm, no cold starts; off = default scale-to-zero (idle instance suspends; first connection cold-starts). insta-db-backed services only')
|
|
179
184
|
.option('--json').option('--branch <branch>', 'branch (default: current)').option('--group <g>', 'postgres service name (default: the sole/default one)')
|
|
180
185
|
.action(guard((mode, o) => dbCmd.dbAlwaysOn(mode, o)));
|
|
186
|
+
db.command('volume').description("Show or grow a postgres service's provisioned volume (block disk; insta-db-backed only). No --size: print size and the plan cap (any plan). --size grows it (paid plans; grow-only — a provisioned disk cannot shrink). Billing is actual data stored — the size is a cap, not a price")
|
|
187
|
+
.option('--size <gi>', 'new size in whole Gi, e.g. 10 (must be ≥ the current size)')
|
|
188
|
+
.option('--json').option('--branch <branch>', 'branch (default: current)').option('--group <g>', 'postgres service name (default: the sole/default one)')
|
|
189
|
+
.action(guard((o) => dbCmd.dbVolume(o)));
|
|
181
190
|
// ---- manifest ----
|
|
182
191
|
program.command('manifest').description('Print an agent-legible view of the project environments').option('--json').action(guard((o) => manifest(o)));
|
|
183
192
|
// ---- regions ----
|