insta 0.0.70 → 0.0.72

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 CHANGED
@@ -222,7 +222,7 @@ build never reaches a production installer.
222
222
  | `insta run <cmd>` | Run a command with the branch bundle injected, nothing written to disk |
223
223
  | `insta deploy [dir]` | Deploy a source directory (built remotely) or `--image <url>` |
224
224
  | `insta compute` | `start` · `stop` · `suspend` · `status` · `set-domain` · `check-domain` · `remove-domain` |
225
- | `insta domain` | Buy a domain through InstaCloud: `search` · `buy` · `attach` · `list` · `status` · `contact` |
225
+ | `insta domain` | Buy a domain through InstaCloud: `search` · `buy` · `attach` · `list` · `status` |
226
226
  | `insta db` | `url` (print the postgres DSN) · `connect` (psql session) · `limits` · `stats` · `always-on` · `volume` |
227
227
  | `insta regions` | Regions available for postgres and compute |
228
228
  | `insta manifest` | Agent-legible view of every branch and its URLs |
package/dist/agent.js CHANGED
@@ -66,7 +66,7 @@ export async function loadAgentSession(apiUrl, projectId, cwd = process.cwd()) {
66
66
  // scope.projectId. A miss fails HERE, naming the route, instead of on the platform as a
67
67
  // "for a different project" 403 whose setup hint cannot help — keep this list in step with the
68
68
  // account-level paths in src/commands/.
69
- export const ACCOUNT_ROUTES = new Set(['agent', 'auth', 'me', 'orgs', 'regions', 'templates', 'tokens', 'github']);
69
+ export const ACCOUNT_ROUTES = new Set(['agent', 'auth', 'me', 'orgs', 'regions', 'templates', 'tokens']);
70
70
  export async function agentHeaders(api, method, path, rawBody, scope = {}) {
71
71
  if (!mode)
72
72
  return {};
@@ -1,4 +1,3 @@
1
- import { readFile } from 'node:fs/promises';
2
1
  import { ApiClient } from '../api.js';
3
2
  import { info, printJson, handleApproval, die } from '../util.js';
4
3
  import { presentUrl, resolveOrgId } from './billing.js';
@@ -32,60 +31,10 @@ export async function domainSearch(keyword, opts, deps) {
32
31
  if (buyable)
33
32
  info(`buy one: insta domain buy ${buyable.domainName}`);
34
33
  }
35
- const CONTACT_KEYS = ['firstName', 'lastName', 'companyName', 'address1', 'address2', 'city', 'state', 'zip', 'country', 'email', 'phone'];
36
- export function contactFromOpts(opts, file) {
37
- if (file !== undefined)
38
- return file;
39
- const out = {};
40
- for (const k of CONTACT_KEYS)
41
- if (opts[k] !== undefined)
42
- out[k] = opts[k];
43
- return Object.keys(out).length ? out : undefined;
44
- }
45
- async function readContactFile(path) {
46
- if (!path)
47
- return undefined;
48
- try {
49
- return JSON.parse(await readFile(path, 'utf8'));
50
- }
51
- catch (e) {
52
- throw new Error(`cannot read ${path} as JSON: ${e.message}`);
53
- }
54
- }
55
- function contactLines(c) {
56
- if (!c)
57
- return ['no registrant contact set — set one with: insta domain contact set --first-name … (or --contact-file contact.json)'];
58
- return [
59
- `${c.firstName} ${c.lastName}${c.companyName ? ` (${c.companyName} — the organization is the legal registrant)` : ''}`,
60
- `${c.address1}${c.address2 ? `, ${c.address2}` : ''}, ${c.city}, ${c.state} ${c.zip}, ${c.country}`,
61
- `${c.email} ${c.phone}`,
62
- ];
63
- }
64
- export async function domainContactShow(opts, deps) {
65
- const { api, orgId } = await orgDeps(opts, deps);
66
- const r = await api.request('GET', `/orgs/${orgId}/domains/contact`);
67
- if (opts.json)
68
- return printJson(r);
69
- for (const line of contactLines(r.contact))
70
- info(line);
71
- }
72
- export async function domainContactSet(opts, deps) {
73
- const { api, orgId } = await orgDeps(opts, deps);
74
- const contact = contactFromOpts(opts, await readContactFile(opts.contactFile));
75
- if (!contact)
76
- die('pass the contact as flags (--first-name … --phone) or as --contact-file <contact.json>');
77
- const r = await api.request('PUT', `/orgs/${orgId}/domains/contact`, contact);
78
- if (opts.json)
79
- return printJson(r);
80
- info('registrant contact saved:');
81
- for (const line of contactLines(r.contact))
82
- info(` ${line}`);
83
- }
84
34
  export async function domainBuy(name, opts, deps) {
85
35
  const { api, project: p } = await domainDeps(deps);
86
36
  const branch = opts.branch ?? p.branch;
87
37
  const { target } = await domainTarget(api, p.projectId, branch, name, opts.group);
88
- const contact = await readContactFile(opts.contactFile);
89
38
  // JSON.stringify drops undefined but keeps NaN as null, which the platform rejects as a type
90
39
  // error rather than a bad term — so a malformed --years is refused here, with the reason.
91
40
  let years;
@@ -94,7 +43,7 @@ export async function domainBuy(name, opts, deps) {
94
43
  if (!Number.isInteger(years))
95
44
  die(`--years must be a whole number of years, not ${opts.years}`);
96
45
  }
97
- const res = await api.rawRequest('POST', `/projects/${p.projectId}/domains/orders`, { domainName: name, years, branch, group: target.name, contact });
46
+ const res = await api.rawRequest('POST', `/projects/${p.projectId}/domains/orders`, { domainName: name, years, branch, group: target.name });
98
47
  if (handleApproval(res, opts.json))
99
48
  return;
100
49
  if (opts.json)
@@ -81,8 +81,8 @@ const sleepSeconds = (s) => new Promise((r) => setTimeout(r, s * 1000));
81
81
  const pollDelay = (s) => Math.min(Math.max(s, 1), 60);
82
82
  // GitHub shows the person a code to type; the platform holds the device code and finishes the exchange,
83
83
  // so nothing secret passes through the CLI.
84
- export async function authorizeTerminal(api, orgId, wait = sleepSeconds) {
85
- const start = await api.request('POST', `/orgs/${encodeURIComponent(orgId)}/github/device`, {});
84
+ export async function authorizeTerminal(api, wait = sleepSeconds) {
85
+ const start = await api.request('POST', '/me/github/device', {});
86
86
  const deadline = Date.parse(start.expiresAt);
87
87
  // A NaN deadline makes every comparison false, which reads as an instant expiry — or, inverted, as a
88
88
  // loop with no way out. Fail on it rather than guess which.
@@ -100,7 +100,7 @@ export async function authorizeTerminal(api, orgId, wait = sleepSeconds) {
100
100
  await wait(interval);
101
101
  let answer;
102
102
  try {
103
- answer = await api.request('POST', `/orgs/${encodeURIComponent(orgId)}/github/device/poll`, { state: start.state });
103
+ answer = await api.request('POST', '/me/github/device/poll', { state: start.state });
104
104
  }
105
105
  catch (e) {
106
106
  // A dropped link, or the per-IP limiter this cadence already tripped on the login flow, must not
@@ -120,41 +120,18 @@ export async function authorizeTerminal(api, orgId, wait = sleepSeconds) {
120
120
  }
121
121
  throw new Error('the GitHub authorization expired before it was confirmed — run the command again');
122
122
  }
123
- // Only the platform's own "you have no usable authorization" may send the person to GitHub: any other
124
- // failure is real, and a device flow cannot fix it.
125
- const needsAuthorization = (e) => e instanceof ApiError && e.status === 400 && /not linked|no longer accepted/i.test(e.message);
126
- // The repositories THIS caller's GitHub account can reach — the same question the platform asks again
127
- // when the connect lands, so a repo missing here would be refused there anyway.
128
- // Someone has to read the code and type it at GitHub. A terminal qualifies, and so does agent mode —
129
- // an agent relays the URL to the person driving it — but --json and a bare pipe have no reader, and a
130
- // ten-minute wait there is a stall where the old code failed with something to act on.
123
+ // Device authorization needs a person to read the code; JSON and bare pipes must fail promptly.
131
124
  export function canAuthorizeHere(opts = {}) {
132
125
  if (opts.json)
133
126
  return false;
134
127
  return !!agentMode() || !!process.stderr.isTTY;
135
128
  }
136
- export async function findCallerRepo(api, orgId, ref, authorize = authorizeTerminal, canAuthorize = canAuthorizeHere()) {
137
- if (!orgId)
138
- throw new Error('this directory is linked without an org — set INSTA_ORG_ID alongside INSTA_PROJECT_ID, or link it with `insta project link`');
139
- let repos;
140
- try {
141
- repos = (await api.request('POST', `/orgs/${encodeURIComponent(orgId)}/github/repos`, {})).repos ?? [];
142
- }
143
- catch (e) {
144
- // Two 403s carry an action; a third kind would be guessed at, so it is rethrown as the platform put it.
145
- if (e instanceof ApiError && e.status === 403 && /unclassified_agent_action/.test(e.message)) {
146
- throw new Error('this backend does not let an agent authorize GitHub yet — connect the repository from the console, or pass --public for a public repository');
147
- }
148
- if (e instanceof ApiError && e.status === 403 && /requires admin/i.test(e.message)) {
149
- throw new Error('connecting a repository needs the org admin role — ask an admin to connect it, or pass --public for a public repository');
150
- }
151
- if (!needsAuthorization(e))
152
- throw e;
153
- if (!canAuthorize) {
154
- throw new Error('this GitHub account is not authorized for InstaCloud yet, and nothing here can read the code GitHub shows — run `insta compute connect-repo` from a terminal, connect the repository from the console, or pass --public for a public repository');
155
- }
156
- repos = await authorize(api, orgId);
129
+ export async function findCallerRepo(api, ref, authorize = authorizeTerminal, canAuthorize = canAuthorizeHere()) {
130
+ const mine = await api.request('GET', '/me/github/repos');
131
+ if (!mine.linked && !canAuthorize) {
132
+ throw new Error('this GitHub account is not authorized for InstaCloud yet, and nothing here can read the code GitHub shows — run `insta compute connect-repo` from a terminal, connect the repository from the console, or pass --public for a public repository');
157
133
  }
134
+ const repos = mine.linked ? mine.repos : await authorize(api);
158
135
  const whole = (n) => n !== null && n !== '' && Number.isInteger(Number(n)) && Number(n) > 0;
159
136
  const hit = repos.find((r) => r.owner.toLowerCase() === ref.owner.toLowerCase() && r.repo.toLowerCase() === ref.repo.toLowerCase());
160
137
  if (hit && whole(hit.installationId) && whole(hit.id))
@@ -187,7 +164,7 @@ export async function computeConnectRepo(rawRef, serviceName, opts) {
187
164
  const svc = await targetService(api, p.projectId, opts.branch ?? p.branch, serviceName);
188
165
  const src = opts.public
189
166
  ? { source: 'public', ...ref }
190
- : { source: 'app', ...(await findCallerRepo(api, p.orgId, ref, authorizeTerminal, canAuthorizeHere(opts))), ...ref };
167
+ : { source: 'app', ...(await findCallerRepo(api, ref, authorizeTerminal, canAuthorizeHere(opts))), ...ref };
191
168
  // Detection must scan the branch that will be built: the build refuses commands that differ from what it detects there.
192
169
  const detected = await api.request('POST', `/projects/${p.projectId}/github/detect`, { ...src, ...(opts.repoBranch ? { ref: opts.repoBranch } : {}) });
193
170
  const candidate = pickCandidate(detected.services, opts.rootDir);
package/dist/index.js CHANGED
@@ -355,7 +355,6 @@ dom.command('search <keyword>').description('Search purchasable names with price
355
355
  .action(guard((keyword, o) => domainCmd.domainSearch(keyword, o)));
356
356
  dom.command('buy <name>').description('Buy a domain and attach it to a branch compute service — pay at the printed Stripe Checkout link (gated: domain.purchase)')
357
357
  .option('--years <n>', 'registration term in years (default 1)').option('--branch <b>').option('--group <g>', "compute service (default: the branch's sole compute service)")
358
- .option('--contact-file <path>', 'registrant contact as JSON (default: the org contact from `insta domain contact set`)')
359
358
  .option('--no-open', 'print the checkout URL instead of opening a browser').option('--json')
360
359
  .action(guard((name, o) => domainCmd.domainBuy(name, o)));
361
360
  dom.command('attach <name>').description('Attach a bought domain whose service was deleted (or whose attach failed) to a compute service (gated: deploy)')
@@ -365,15 +364,6 @@ dom.command('list').description('Domains bought through InstaCloud in this proje
365
364
  .action(guard((o) => domainCmd.domainList(o)));
366
365
  dom.command('status <name>').description("A bought domain's order and attach state").option('--json')
367
366
  .action(guard((name, o) => domainCmd.domainStatus(name, o)));
368
- const domContact = dom.command('contact').description("Show the org's default registrant contact (the legal registrant of every domain bought with it)")
369
- .option('--org <id>').option('--json').action(guard((o) => domainCmd.domainContactShow(o)));
370
- domContact.command('set').description('Set the org default registrant contact (admin) from flags or --contact-file <path>; --company-name makes that organization the legal registrant')
371
- .option('--first-name <s>').option('--last-name <s>').option('--company-name <s>').option('--address1 <s>').option('--address2 <s>').option('--city <s>').option('--state <s>').option('--zip <s>')
372
- .option('--country <cc>', 'ISO 3166-1 alpha-2, e.g. US').option('--email <s>').option('--phone <e164>', 'E.164, e.g. +14155550100')
373
- .option('--contact-file <path>', 'JSON file with the contact fields').option('--org <id>').option('--json')
374
- // `contact --org X set` parks --org on the GROUP (enablePositionalOptions); without this merge the
375
- // wrong org's registrant contact is written, and that field is legal ownership.
376
- .action(guard((o) => domainCmd.domainContactSet({ ...domContact.opts(), ...o })));
377
367
  const bill = program.command('billing').description('Current billing cycle overview (tier / used / included / overage / credits / forecast + per-dimension & per-project breakdown)')
378
368
  .option('--org <id>', 'target org (default: linked project\'s org)').option('--json')
379
369
  .action(guard((o) => billing(o)));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "insta",
3
- "version": "0.0.70",
3
+ "version": "0.0.72",
4
4
  "type": "module",
5
5
  "description": "InstaCloud CLI — a thin client of the platform control-plane API.",
6
6
  "keywords": [