insta 0.0.18 → 0.0.20

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.
@@ -38,4 +38,22 @@ export async function branchDelete(name) {
38
38
  return;
39
39
  info(`deleted branch ${name}`);
40
40
  }
41
+ // insta branch merge <source> [--into <target>] — structurally merge source's services into target
42
+ // (default target: current branch). No data is copied; existing services are left untouched.
43
+ export async function branchMerge(source, opts = {}) {
44
+ const api = await ApiClient.load();
45
+ const p = await requireProject();
46
+ const target = opts.into ?? p.branch;
47
+ if (!target)
48
+ throw new Error('no target branch — pass --into <branch> (or link a branch first)');
49
+ const res = await api.rawRequest('POST', `/projects/${p.projectId}/branches/${encodeURIComponent(target)}/merge`, { from: source });
50
+ if (handleApproval(res))
51
+ return;
52
+ const { created = [], skipped = [] } = (res.body ?? {});
53
+ info(`merged ${source} → ${target}: ${created.length} created, ${skipped.length} skipped`);
54
+ for (const c of created)
55
+ info(` + ${c.type}/${c.name}`);
56
+ for (const s of skipped)
57
+ info(` = ${s.type}/${s.name} (${s.reason})`);
58
+ }
41
59
  //# sourceMappingURL=branch.js.map
@@ -1,6 +1,6 @@
1
1
  import { ApiClient, requireProject } from '../api.js';
2
2
  import { info, printJson, handleApproval } from '../util.js';
3
- import { resolveComputeServiceId } from './services.js';
3
+ import { resolveComputeServiceId, q } 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) {
@@ -46,7 +46,8 @@ function printDomain(r, json) {
46
46
  async function lifecycle(verb, serviceName, opts) {
47
47
  const api = await ApiClient.load();
48
48
  const p = await requireProject();
49
- const { services } = await api.request('GET', `/projects/${p.projectId}/services`);
49
+ const branch = opts.branch ?? p.branch;
50
+ const { services } = await api.request('GET', `/projects/${p.projectId}/services${q(branch)}`);
50
51
  const id = resolveComputeServiceId(services, serviceName);
51
52
  const res = await api.rawRequest('POST', `/projects/${p.projectId}/services/${id}/${verb}`);
52
53
  if (handleApproval(res))
@@ -61,7 +62,8 @@ export const computeSuspend = (service, opts) => lifecycle('suspend', service, o
61
62
  export async function computeStatus(serviceName, opts) {
62
63
  const api = await ApiClient.load();
63
64
  const p = await requireProject();
64
- const { services } = await api.request('GET', `/projects/${p.projectId}/services`);
65
+ const branch = opts.branch ?? p.branch;
66
+ const { services } = await api.request('GET', `/projects/${p.projectId}/services${q(branch)}`);
65
67
  const id = resolveComputeServiceId(services, serviceName);
66
68
  const r = await api.request('GET', `/projects/${p.projectId}/services/${id}/state`);
67
69
  if (opts.json)
@@ -2,6 +2,9 @@
2
2
  import { ApiClient, requireProject } from '../api.js';
3
3
  import { info, printJson, handleApproval, renderNextActions } from '../util.js';
4
4
  export const SERVICE_TYPES = ['postgres', 'storage', 'compute'];
5
+ export function q(branch) {
6
+ return branch ? `?branch=${encodeURIComponent(branch)}` : '';
7
+ }
5
8
  // ---- pure, unit-tested helpers (throw plain Errors; the CLI guard turns them into clean output) ----
6
9
  // Validate a service-type argument against the allowed set for a command.
7
10
  export function assertType(type, allowed = SERVICE_TYPES) {
@@ -38,40 +41,69 @@ export function resolveComputeServiceId(services, name) {
38
41
  return compute[0].id;
39
42
  }
40
43
  // ---- commands ----
41
- export async function servicesAdd(type, name) {
44
+ export async function servicesAdd(type, name, opts = {}) {
42
45
  assertType(type);
46
+ if (opts.public && type !== 'storage')
47
+ throw new Error('--public is only valid for storage services');
43
48
  const api = await ApiClient.load();
44
49
  const p = await requireProject();
45
- const res = await api.rawRequest('POST', `/projects/${p.projectId}/services`, { type, name });
50
+ const branch = opts.branch ?? p.branch;
51
+ const res = await api.rawRequest('POST', `/projects/${p.projectId}/services`, { type, name, ...(branch ? { branch } : {}), public: !!opts.public });
46
52
  if (handleApproval(res))
47
53
  return;
48
54
  const svc = res.body.service;
49
- info(`added ${type} service ${name} (${svc.id})${svc.domain ? ` — ${svc.domain}` : ''}`);
55
+ const access = svc.type === 'storage' ? ` [${svc.public ? 'public' : 'private'}]` : '';
56
+ info(`added ${type} service ${name} on ${branch ?? 'default'} (${svc.id})${access}${svc.domain ? ` — ${svc.domain}` : ''}`);
50
57
  renderNextActions(res.body.nextActions);
51
58
  }
52
59
  export async function servicesList(opts) {
53
60
  const api = await ApiClient.load();
54
61
  const p = await requireProject();
55
- const { services } = await api.request('GET', `/projects/${p.projectId}/services`);
62
+ const branch = opts.branch ?? p.branch;
63
+ const { services } = await api.request('GET', `/projects/${p.projectId}/services${q(branch)}`);
56
64
  if (opts.json)
57
65
  return printJson(services);
58
66
  if (!services.length)
59
- return info('(no services — add one with `insta services add <postgres|storage|compute> <name>`)');
67
+ return info(`(no services on ${branch ?? 'default'} — add one with \`insta services add <postgres|storage|compute> <name>\`)`);
60
68
  for (const s of services) {
61
- const extra = s.type === 'compute' ? ` x${s.machine_count}` : '';
69
+ const extra = s.type === 'compute' ? ` x${s.machine_count}` : s.type === 'storage' ? ` ${s.public ? 'public' : 'private'}` : '';
62
70
  info(`${s.type}/${s.name} [${s.status}]${extra}${s.domain ? ` ${s.domain}` : ''} ${s.id}`);
63
71
  }
64
72
  }
65
- export async function servicesRemove(type, name) {
73
+ export async function servicesRemove(type, name, opts = {}) {
66
74
  assertType(type);
67
75
  const api = await ApiClient.load();
68
76
  const p = await requireProject();
69
- const { services } = await api.request('GET', `/projects/${p.projectId}/services`);
77
+ const branch = opts.branch ?? p.branch;
78
+ const { services } = await api.request('GET', `/projects/${p.projectId}/services${q(branch)}`);
70
79
  const id = resolveServiceId(services, type, name);
71
80
  const res = await api.rawRequest('DELETE', `/projects/${p.projectId}/services/${id}`);
72
81
  if (handleApproval(res))
73
82
  return;
74
- info(`removed ${type} service ${name}`);
83
+ info(`removed ${type} service ${name} from ${branch ?? 'default'}`);
84
+ }
85
+ // Validate a bucket access-mode argument.
86
+ export function parseAccess(raw) {
87
+ if (raw === 'public')
88
+ return true;
89
+ if (raw === 'private')
90
+ return false;
91
+ throw new Error(`access must be public|private, got: ${raw}`);
92
+ }
93
+ // insta services set-access storage <name> <public|private>
94
+ export async function servicesSetAccess(type, name, access, _opts) {
95
+ assertType(type, ['storage']);
96
+ const isPublic = parseAccess(access);
97
+ const api = await ApiClient.load();
98
+ const p = await requireProject();
99
+ const { services } = await api.request('GET', `/projects/${p.projectId}/services${q(p.branch)}`);
100
+ const id = resolveServiceId(services, type, name);
101
+ const res = await api.rawRequest('PUT', `/projects/${p.projectId}/services/${id}/access`, { public: isPublic });
102
+ if (handleApproval(res))
103
+ return;
104
+ if (_opts.json)
105
+ return printJson(res.body.service);
106
+ info(`set storage ${name} access to ${access}`);
75
107
  }
76
108
  // insta services scale compute <name> <number> [region]
77
109
  export async function servicesScale(type, name, number, region, _opts) {
@@ -79,7 +111,7 @@ export async function servicesScale(type, name, number, region, _opts) {
79
111
  const machineCount = parseCount(number);
80
112
  const api = await ApiClient.load();
81
113
  const p = await requireProject();
82
- const { services } = await api.request('GET', `/projects/${p.projectId}/services`);
114
+ const { services } = await api.request('GET', `/projects/${p.projectId}/services${q(_opts.branch ?? p.branch)}`);
83
115
  const id = resolveServiceId(services, type, name);
84
116
  const res = await api.rawRequest('POST', `/projects/${p.projectId}/services/${id}/scale`, { machineCount, region });
85
117
  if (handleApproval(res))
@@ -93,7 +125,7 @@ export async function servicesUpgrade(type, name, spec, _opts) {
93
125
  assertType(type, ['compute', 'postgres']);
94
126
  const api = await ApiClient.load();
95
127
  const p = await requireProject();
96
- const { services } = await api.request('GET', `/projects/${p.projectId}/services`);
128
+ const { services } = await api.request('GET', `/projects/${p.projectId}/services${q(_opts.branch ?? p.branch)}`);
97
129
  const id = resolveServiceId(services, type, name);
98
130
  const res = await api.rawRequest('POST', `/projects/${p.projectId}/services/${id}/upgrade`, { spec });
99
131
  if (handleApproval(res))
@@ -47,9 +47,13 @@ export async function installSkills(deps) {
47
47
  const run = deps.run ?? defaultRunner;
48
48
  const print = deps.print ?? ((s) => process.stdout.write(s + '\n'));
49
49
  try {
50
- print(' installing related agent skills (insta, neon-postgres, tigris, better-auth) via `npx skills add` …');
50
+ print(' installing related agent skills (insta, neon-postgres, tigris, better-auth) …');
51
51
  for (const s of SKILLS) {
52
- const r = await run('npx', s.args, true); // streamed so the user sees download progress
52
+ // Don't stream: the `skills` tool's clack UI (clone spinner, banners) is noise. Run it
53
+ // silent (stdio 'ignore') and let the per-skill ✓/failed line below be the clean output —
54
+ // it appears as each skill finishes, so there's still live progress. (Also avoids the
55
+ // child inheriting a piped stdin.)
56
+ const r = await run('npx', s.args);
53
57
  print(r.ok ? ` ${s.label} ✓` : ` ${s.label} failed — add manually: npx ${s.args.join(' ')}`);
54
58
  }
55
59
  const added = ensureGitignore(deps.cwd, SKILL_DIRS);
package/dist/index.js CHANGED
@@ -83,17 +83,25 @@ br.command('create <name>').option('--from <branch>', 'parent branch (default: c
83
83
  br.command('list').option('--json').action(guard((o) => branch.branchList(o)));
84
84
  br.command('switch <name>').action(guard((name) => branch.branchSwitch(name)));
85
85
  br.command('delete <name>').action(guard((name) => branch.branchDelete(name)));
86
+ br.command('merge <source>').description('Merge a branch service set into another (structural, no data)')
87
+ .option('--into <branch>', 'target branch (default: current)').action(guard((source, o) => branch.branchMerge(source, o)));
86
88
  // ---- services (opt-in postgres/storage/compute) ----
87
89
  const svc = program.command('services').alias('svc').description('Manage project services (postgres|storage|compute)');
88
90
  svc.command('add <type> <name>').description('Provision a service on demand (assigns a default domain for postgres/compute)')
89
- .action(guard((type, name) => services.servicesAdd(type, name)));
90
- svc.command('list').option('--json').action(guard((o) => services.servicesList(o)));
91
+ .option('--branch <branch>', 'target branch (default: current)')
92
+ .option('--public', 'storage only: serve the bucket with anonymous public-read (default private)')
93
+ .action(guard((type, name, o) => services.servicesAdd(type, name, o)));
94
+ svc.command('list').option('--json').option('--branch <branch>', 'branch (default: current)')
95
+ .action(guard((o) => services.servicesList(o)));
91
96
  svc.command('remove <type> <name>').description('Remove a service and destroy its resources')
92
- .action(guard((type, name) => services.servicesRemove(type, name)));
97
+ .option('--branch <branch>', 'branch (default: current)')
98
+ .action(guard((type, name, o) => services.servicesRemove(type, name, o)));
99
+ svc.command('set-access <type> <name> <access>').description('Set a storage service bucket access mode (access: public|private)')
100
+ .option('--json').action(guard((type, name, access, o) => services.servicesSetAccess(type, name, access, o)));
93
101
  svc.command('scale <type> <name> <number> [region]').description('Set a compute service machine count (paid plans only)')
94
- .option('--json').action(guard((type, name, number, region, o) => services.servicesScale(type, name, number, region, o)));
102
+ .option('--json').option('--branch <branch>', 'branch (default: current)').action(guard((type, name, number, region, o) => services.servicesScale(type, name, number, region, o)));
95
103
  svc.command('upgrade <type> <name> <spec>').description('Change a compute/postgres service spec (paid plans only)')
96
- .option('--json').action(guard((type, name, spec, o) => services.servicesUpgrade(type, name, spec, o)));
104
+ .option('--json').option('--branch <branch>', 'branch (default: current)').action(guard((type, name, spec, o) => services.servicesUpgrade(type, name, spec, o)));
97
105
  // ---- secrets (seam) ----
98
106
  const sec = program.command('secrets').description('Fetch the credential bundle (secret seam) into .env')
99
107
  .option('--branch <branch>').option('-o, --output <file>', 'output file (default .env)').option('--print', 'print instead of writing').option('--json')
@@ -117,13 +125,13 @@ compute.command('check-domain <host>').description("Show a custom domain's cert
117
125
  compute.command('remove-domain <host>').description('Detach a custom domain (gated: deploy)')
118
126
  .option('--branch <b>').option('--group <g>').action(guard((host, o) => computeCmd.removeDomain(host, o)));
119
127
  compute.command('start [service]').description('Bring a compute service online (persistent — re-enables auto-wake)')
120
- .option('--json').action(guard((service, o) => computeCmd.computeStart(service, o)));
128
+ .option('--json').option('--branch <branch>', 'branch (default: current)').action(guard((service, o) => computeCmd.computeStart(service, o)));
121
129
  compute.command('stop [service]').description('Take a compute service offline; traffic will NOT wake it until `start`')
122
- .option('--json').action(guard((service, o) => computeCmd.computeStop(service, o)));
130
+ .option('--json').option('--branch <branch>', 'branch (default: current)').action(guard((service, o) => computeCmd.computeStop(service, o)));
123
131
  compute.command('suspend [service]').description('Suspend a compute service (RAM snapshot); stays down until `start`')
124
- .option('--json').action(guard((service, o) => computeCmd.computeSuspend(service, o)));
132
+ .option('--json').option('--branch <branch>', 'branch (default: current)').action(guard((service, o) => computeCmd.computeSuspend(service, o)));
125
133
  compute.command('status [service]').description("Show a compute service's desired vs. live state")
126
- .option('--json').action(guard((service, o) => computeCmd.computeStatus(service, o)));
134
+ .option('--json').option('--branch <branch>', 'branch (default: current)').action(guard((service, o) => computeCmd.computeStatus(service, o)));
127
135
  // ---- manifest ----
128
136
  program.command('manifest').description('Print an agent-legible view of the project environments').option('--json').action(guard((o) => manifest(o)));
129
137
  // ---- observability ----
@@ -161,7 +169,7 @@ ob.command('sync').description('Upload findings into the project timeline').acti
161
169
  // ---- policy ----
162
170
  const pol = program.command('policy').description('Governance policy');
163
171
  pol.command('get').option('--json').action(guard((o) => govern.policyGet(o)));
164
- pol.command('set <action> <decision>').description('action: secrets.read|secrets.write|deploy|project.delete|branch.delete|service.add|service.remove|service.scale|service.upgrade; decision: allow|deny|approve').action(guard((a, d) => govern.policySet(a, d)));
172
+ pol.command('set <action> <decision>').description('action: secrets.read|secrets.write|deploy|project.delete|branch.delete|service.add|service.remove|service.scale|service.upgrade|service.setAccess; decision: allow|deny|approve').action(guard((a, d) => govern.policySet(a, d)));
165
173
  // ---- self-update ----
166
174
  program.command('upgrade').description('Update the insta CLI to the latest release (binary or npm install)')
167
175
  .action(guard(() => selfUpdate.upgrade()));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "insta",
3
- "version": "0.0.18",
3
+ "version": "0.0.20",
4
4
  "type": "module",
5
5
  "description": "InstaCloud CLI — a thin client of the platform control-plane API.",
6
6
  "keywords": [