insta 0.1.6 → 0.1.7

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
@@ -231,7 +231,7 @@ build never reaches a production installer.
231
231
  | `insta billing` | Current cycle overview; `subscribe <tier>` · `portal` · `usage` |
232
232
  | `insta agent` | `setup` (this machine's coding agents) · `manifest` · `policy …` · `approvals …` · `observe …` · `events` |
233
233
  | `insta config` | `install-mcp` · `regions` · `autoupdate` |
234
- | `insta feedback` | Report an InstaCloud-side hurdle (bug / feature-request / friction) to the team — never for the app you are building; on InstaCloud it needs `insta login`, so the team can reply |
234
+ | `insta feedback` | Report an InstaCloud-side hurdle (bug / feature-request / friction) to the team — never for the app you are building; on InstaCloud it needs `insta login`, so the team can reply, and prints the ticket it opened; `status <ticket-id>` — New / In Progress / Resolved / Closed (the replies are in the console) |
235
235
  | `insta upgrade` | Update the CLI |
236
236
 
237
237
  Every command accepts `--api-url <url>` for this invocation only (internal debugging); for `compute exec`, place it before `compute`. `insta --help` documents it.
@@ -13,7 +13,7 @@ import * as clack from '@clack/prompts';
13
13
  import { ApiClient, ApiError } from '../api.js';
14
14
  import { readGlobal, readProject } from '../config.js';
15
15
  import { envForApiUrl } from '../env.js';
16
- import { info, printJson, refuse, CliCancel } from '../util.js';
16
+ import { info, printJson, refuse, CliCancel, CliExit } from '../util.js';
17
17
  import { clean } from '../redact.js';
18
18
  import { cliVersion } from '../version.js';
19
19
  export const TYPES = ['bug', 'feature-request', 'friction', 'other'];
@@ -186,20 +186,24 @@ export async function submit(payload, fetchImpl, assertion) {
186
186
  catch { /* non-JSON body — fall through to status handling */ }
187
187
  if (!res.ok)
188
188
  return { status: 'error', error: body?.error ?? `HTTP ${res.status}` };
189
- return { status: body?.status === 'duplicate' ? 'duplicate' : 'received', id: body?.id ?? null };
189
+ return { status: body?.status === 'duplicate' ? 'duplicate' : 'received', id: body?.id ?? null, ticket: body?.ticket };
190
190
  }
191
191
  const SIGNED_OUT = 'not signed in to InstaCloud — run `insta login`, then send this again so the team can reply to you';
192
192
  const STAGING = 'not accepted from staging — send InstaCloud feedback from production';
193
+ // Bearer only: agent evidence adds a session round trip the timeout cannot bound, and 401s signing in cannot fix.
194
+ async function userAssertion(api) {
195
+ return (await api.request('GET', '/me/feedback-assertion', undefined, { evidence: false, signal: AbortSignal.timeout(ASSERTION_TIMEOUT_MS) })).token;
196
+ }
193
197
  // Exit 2, not the submit path's 0: the caller can act on this one.
194
198
  function refuseFeedback(message, json) {
195
199
  if (json)
196
200
  printJson({ status: 'refused', submitted: false, error: message });
197
201
  refuse([`insta feedback: ${message}`]);
198
202
  }
199
- function inputError(e, json) {
203
+ function inputError(e, json, fields = { submitted: false }) {
200
204
  if (!json)
201
205
  throw e;
202
- printJson({ status: 'error', submitted: false, error: e instanceof Error ? e.message : String(e) });
206
+ printJson({ status: 'error', ...fields, error: e instanceof Error ? e.message : String(e) });
203
207
  process.exitCode = 1;
204
208
  }
205
209
  export async function feedback(opts, deps = {}) {
@@ -235,8 +239,7 @@ export async function feedback(opts, deps = {}) {
235
239
  let assertion;
236
240
  if (env === 'prod') {
237
241
  try {
238
- // Bearer only: agent evidence adds a session round trip the timeout cannot bound, and 401s signing in cannot fix.
239
- assertion = (await api.request('GET', '/me/feedback-assertion', undefined, { evidence: false, signal: AbortSignal.timeout(ASSERTION_TIMEOUT_MS) })).token;
242
+ assertion = await userAssertion(api);
240
243
  }
241
244
  catch (e) {
242
245
  if (e instanceof ApiError && e.status === 401)
@@ -262,13 +265,66 @@ export async function feedback(opts, deps = {}) {
262
265
  return;
263
266
  }
264
267
  if (opts.json)
265
- return printJson({ status: result.status, id: result.id });
268
+ return printJson({ status: result.status, id: result.id, ticket: result.ticket });
266
269
  if (result.status === 'duplicate') {
267
270
  info(`already reported this week — bumped its count instead (id: ${result.id})`);
268
271
  }
269
272
  else {
270
273
  info(`feedback submitted (id: ${result.id}) — thank you!`);
271
274
  }
275
+ if (result.ticket) {
276
+ info(`ticket ${result.ticket.id} — the team's replies are in the console: ${result.ticket.url}`);
277
+ info(`check its status with \`insta feedback status ${result.ticket.id}\``);
278
+ }
272
279
  info('PII (emails, tokens, keys, home paths) was redacted before sending.');
273
280
  }
281
+ const STATUS_LABEL = { open: 'New', in_progress: 'In Progress', resolved: 'Resolved', closed: 'Closed' };
282
+ const SIGNED_OUT_STATUS = 'not signed in to InstaCloud — run `insta login` to check your ticket';
283
+ function refuseStatus(message, json) {
284
+ if (json)
285
+ printJson({ status: 'refused', error: message });
286
+ refuse([`insta feedback status: ${message}`]);
287
+ }
288
+ export async function feedbackStatus(id, opts, deps = {}) {
289
+ try {
290
+ const api = deps.api ?? await ApiClient.load();
291
+ const env = envForApiUrl(api.apiUrl);
292
+ if (env !== 'prod')
293
+ refuseStatus('ticket status is only available on production InstaCloud', opts.json);
294
+ if (!api.config.accessToken)
295
+ refuseStatus(SIGNED_OUT_STATUS, opts.json);
296
+ let assertion;
297
+ try {
298
+ assertion = await userAssertion(api);
299
+ }
300
+ catch (e) {
301
+ if (e instanceof ApiError && e.status === 401)
302
+ refuseStatus(SIGNED_OUT_STATUS, opts.json);
303
+ throw e;
304
+ }
305
+ // Relative, so an endpoint served under a path prefix keeps it.
306
+ const res = await (deps.fetchImpl ?? fetch)(new URL(`tickets/${encodeURIComponent(id)}`, FEEDBACK_ENDPOINT), {
307
+ headers: { Authorization: `Bearer ${FEEDBACK_INGEST_TOKEN}`, 'Insta-User-Assertion': assertion },
308
+ signal: AbortSignal.timeout(FEEDBACK_TIMEOUT_MS),
309
+ });
310
+ if (res.status === 404)
311
+ throw new Error(`no ticket ${id} of yours — use the ticket id \`insta feedback\` printed`);
312
+ // The platform vouched for this login a moment ago: a 401 here is the service's, not a signed-out user.
313
+ if (res.status === 401)
314
+ throw new Error('the feedback service could not verify who you are — signing in again will not fix it');
315
+ const body = (await res.json().catch(() => ({})));
316
+ if (!res.ok || !body.status)
317
+ throw new Error(`the feedback service answered ${res.status}${body.error ? `: ${body.error}` : ''}`);
318
+ if (opts.json)
319
+ return printJson({ id: body.id, status: body.status, url: body.url });
320
+ info(`ticket ${body.id}: ${STATUS_LABEL[body.status] ?? body.status}`);
321
+ info(`open it in the console: ${body.url}`);
322
+ }
323
+ catch (e) {
324
+ // A refusal has already printed and set exit 2; handled again it would print twice and exit 1.
325
+ if (e instanceof CliExit)
326
+ throw e;
327
+ return inputError(e, opts.json, {});
328
+ }
329
+ }
274
330
  //# sourceMappingURL=feedback.js.map
package/dist/index.js CHANGED
@@ -591,7 +591,7 @@ cfg.command('autoupdate [mode]').description('Show or set auto-update: on | off
591
591
  const setupCompat = program.command('setup', { hidden: true }).description('Compatibility alias: `insta setup agent` is `insta agent setup`');
592
592
  withSetupAgentOptions(setupCompat.command('agent').description('Alias of `insta agent setup`, kept for the console one-liner'));
593
593
  // ---- feedback (agent + human hurdle reports → the InstaCloud team) ----
594
- program.command('feedback')
594
+ const fb = program.command('feedback')
595
595
  .description('Report an InstaCloud-side hurdle (bug / missing feature / friction) to the InstaCloud team — about the insta toolkit itself, NEVER about the app you are building. On InstaCloud it needs `insta login`, so the team can reply; works unlinked.')
596
596
  .option('--type <type>', `what kind of hurdle: ${feedbackCmd.TYPES.join(' | ')}`)
597
597
  .option('--component <component>', `which part of the toolkit: ${feedbackCmd.COMPONENTS.join(' | ')}`)
@@ -607,6 +607,12 @@ program.command('feedback')
607
607
  .option('--severity <severity>', `${feedbackCmd.SEVERITIES.join(' | ')} (default: minor)`)
608
608
  .option('--json')
609
609
  .action(guard((o) => feedbackCmd.feedback(o)));
610
+ // As with billing: a mistyped subcommand must fail, not open the report wizard.
611
+ fb.allowExcessArguments(false);
612
+ fb.command('status <ticket-id>')
613
+ .description('Show the status of a ticket `insta feedback` opened: New, In Progress, Resolved or Closed. The replies are read and answered in the console.')
614
+ .option('--json')
615
+ .action(guard((id, o) => feedbackCmd.feedbackStatus(id, o)));
610
616
  // ---- self-update ----
611
617
  program.command('upgrade').description('Update the insta CLI to the latest release (binary or npm install)')
612
618
  .action(guard(() => selfUpdate.upgrade(cliVersion())));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "insta",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "type": "module",
5
5
  "description": "InstaCloud CLI — a thin client of the platform control-plane API.",
6
6
  "keywords": [