insta 0.0.56 → 0.0.58
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 +1 -1
- package/dist/commands/manifest.js +4 -1
- package/dist/commands/services.js +21 -5
- package/dist/telemetry.js +22 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -233,7 +233,7 @@ build never reaches a production installer.
|
|
|
233
233
|
| `INSTA_PROJECT_ID` · `INSTA_ORG_ID` · `INSTA_BRANCH` | Target a project, org or branch without linking |
|
|
234
234
|
| `INSTA_PASSWORD` | Password for non-interactive login |
|
|
235
235
|
| `INSTA_NO_AUTOUPDATE` | Disable self-update |
|
|
236
|
-
| `INSTA_NO_TELEMETRY` · `DO_NOT_TRACK` | Disable usage analytics. Each command sends one event (command, flags, outcome, version, OS) to the same PostHog project as the console. Only ids, enums and numbers among the arguments are kept — names, branches, keys, paths, secret values, free text and error messages never leave the machine; custom API hosts report nothing |
|
|
236
|
+
| `INSTA_NO_TELEMETRY` · `DO_NOT_TRACK` | Disable usage analytics. Each command sends one event (command, flags, outcome, version, OS) to the same PostHog project as the console. Only ids, enums and numbers among the arguments are kept — names, branches, keys, paths, secret values, free text and error messages never leave the machine; custom API hosts report nothing. Before you sign in, events carry a random id stored in `~/.insta/telemetry.json`; the first command after you sign in sends one extra event merging that id into your account, and the first command after the session ends (logout, `env use`) retires it |
|
|
237
237
|
|
|
238
238
|
## Agent skills
|
|
239
239
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ApiClient, requireProject } from '../api.js';
|
|
2
|
+
import { pgBadge } from './services.js';
|
|
2
3
|
import { info, printJson } from '../util.js';
|
|
3
4
|
/**
|
|
4
5
|
* The label that names WHERE a resource runs.
|
|
@@ -21,7 +22,9 @@ export function resourceLabel(r) {
|
|
|
21
22
|
// One `manifest` resource line. Pure, so the label is unit-tested without a network mock.
|
|
22
23
|
export function resourceLine(r) {
|
|
23
24
|
const where = r.ref?.url ?? r.ref?.bucket ?? r.ref?.neonProjectId ?? '';
|
|
24
|
-
|
|
25
|
+
// Database rows only: the platform stamps ref.pgVersion on insta-db (and legacy neon) resources.
|
|
26
|
+
const pg = r.kind === 'insta-db' || r.kind === 'neon' ? pgBadge(r.ref?.pgVersion) : '';
|
|
27
|
+
return ` - ${resourceLabel(r)} ${where}${pg} [${r.status}]`;
|
|
25
28
|
}
|
|
26
29
|
// Agent-legible view of each environment's databases / storage / compute.
|
|
27
30
|
export async function manifest(opts) {
|
|
@@ -118,10 +118,7 @@ export async function servicesAdd(type, name, opts = {}) {
|
|
|
118
118
|
if (opts.json)
|
|
119
119
|
return printJson(res.body.service);
|
|
120
120
|
const svc = res.body.service;
|
|
121
|
-
|
|
122
|
-
const img = svc.image ? ` running ${svc.image}${svc.port ? `:${svc.port}` : ''}` : '';
|
|
123
|
-
const vol = svc.volume_gib ? ` vol ${svc.volume_gib}Gi at /data` : '';
|
|
124
|
-
info(`added ${type} service ${name} on ${branch ?? 'default'} (${svc.id})${access}${svc.region ? ` ${svc.region}` : ''}${img}${vol}${svc.domain ? ` — ${svc.domain}` : ''}`);
|
|
121
|
+
info(serviceAddedLine(type, name, branch, svc));
|
|
125
122
|
// Discoverability: the DB is directly dialable, but its DSN is deliberately absent from the
|
|
126
123
|
// general `insta secrets` bundle — without this line nothing in the product says how to reach it.
|
|
127
124
|
if (type === 'postgres') {
|
|
@@ -132,13 +129,32 @@ export async function servicesAdd(type, name, opts = {}) {
|
|
|
132
129
|
}
|
|
133
130
|
renderNextActions(res.body.nextActions);
|
|
134
131
|
}
|
|
132
|
+
// The `services add` success line. Pure, so the badge's placement is unit-tested: a template string
|
|
133
|
+
// nothing asserts on silently loses a segment.
|
|
134
|
+
export function serviceAddedLine(type, name, branch, svc) {
|
|
135
|
+
const access = svc.type === 'storage' ? ` [${svc.public ? 'public' : 'private'}]` : '';
|
|
136
|
+
const img = svc.image ? ` running ${svc.image}${svc.port ? `:${svc.port}` : ''}` : '';
|
|
137
|
+
const vol = svc.volume_gib ? ` vol ${svc.volume_gib}Gi at /data` : '';
|
|
138
|
+
// The major belongs next to the connect hint: it decides which psql/pg_dump to reach for.
|
|
139
|
+
const pg = svc.type === 'postgres' ? pgBadge(svc.pg_version) : '';
|
|
140
|
+
return `added ${type} service ${name} on ${branch ?? 'default'} (${svc.id})${access}${svc.region ? ` ${svc.region}` : ''}${img}${vol}${pg}${svc.domain ? ` — ${svc.domain}` : ''}`;
|
|
141
|
+
}
|
|
142
|
+
// ` pg <major>` for a postgres row, or '' when the platform sent nothing usable. The field arrives
|
|
143
|
+
// as untyped API JSON: only a positive integer renders, so a malformed or nonsense value (true,
|
|
144
|
+
// '16', 16.4, 0, -1) can never print as a version an agent would pick tooling by.
|
|
145
|
+
export function pgBadge(v) {
|
|
146
|
+
return typeof v === 'number' && Number.isInteger(v) && v > 0 ? ` pg ${v}` : '';
|
|
147
|
+
}
|
|
135
148
|
// Render one `services list` row. Pure, so it's unit-tested without a network mock (mirrors
|
|
136
149
|
// billingLines in billing.ts). Compute rows show the running image when the platform reports one.
|
|
137
150
|
export function serviceListLine(s) {
|
|
138
151
|
const extra = s.type === 'compute'
|
|
139
152
|
? ` x${s.machine_count}${s.volume_gib ? ` vol ${s.volume_gib}Gi` : ''}${s.image ? ` running ${s.image}${s.port ? `:${s.port}` : ''}` : ''}`
|
|
140
153
|
: ['redis', 'mysql', 'mongodb'].includes(s.type) ? ` tcp/${s.port ?? defaultDatabasePort(s.type)}${s.volume_gib ? ` vol ${s.volume_gib}Gi` : ''}`
|
|
141
|
-
: s.type === 'storage' ? ` ${s.public ? 'public' : 'private'}`
|
|
154
|
+
: s.type === 'storage' ? ` ${s.public ? 'public' : 'private'}`
|
|
155
|
+
// Postgres major, so the reader picks matching pg_dump/psql BEFORE connecting (a newer client
|
|
156
|
+
// dumps statements an older server cannot restore). Older platforms send no pg_version.
|
|
157
|
+
: s.type === 'postgres' ? pgBadge(s.pg_version) : '';
|
|
142
158
|
return `${s.type}/${s.name} [${s.status}]${extra}${s.domain ? ` ${s.domain}` : ''} ${s.id}`;
|
|
143
159
|
}
|
|
144
160
|
export async function servicesList(opts) {
|
package/dist/telemetry.js
CHANGED
|
@@ -18,6 +18,7 @@ const PROJECT_KEYS = {
|
|
|
18
18
|
};
|
|
19
19
|
// The send is awaited before the process exits, so it gets one bounded attempt and no retry.
|
|
20
20
|
const SEND_TIMEOUT_MS = 1500;
|
|
21
|
+
const ID_FILE = join(os.homedir(), '.insta', 'telemetry.json');
|
|
21
22
|
const REDACTED = '[REDACTED]';
|
|
22
23
|
const oneOf = (values) => (v) => values.includes(v);
|
|
23
24
|
const ID = (v) => /^(?:[0-9a-f]{8}-[0-9a-f-]{27}|[a-z]+_[\w-]{1,64}|(?=.*\d)[\w-]{1,64})$/i.test(v);
|
|
@@ -144,6 +145,7 @@ export function buildCommandEvent(command, args, options, outcome, ctx) {
|
|
|
144
145
|
auth_kind: token ? (token.startsWith('insta_') ? 'api_key' : 'session') : null,
|
|
145
146
|
project_id: ctx.project?.projectId ?? null,
|
|
146
147
|
org_id: ctx.project?.orgId ?? null,
|
|
148
|
+
...(ctx.project ? { $groups: { project: ctx.project.projectId, ...(ID(ctx.project.orgId) ? { org: ctx.project.orgId } : {}) } } : {}),
|
|
147
149
|
tty: ctx.tty,
|
|
148
150
|
ci: !!ctx.env.CI,
|
|
149
151
|
agent: detectAgent(ctx.env),
|
|
@@ -154,20 +156,22 @@ export function buildCommandEvent(command, args, options, outcome, ctx) {
|
|
|
154
156
|
},
|
|
155
157
|
};
|
|
156
158
|
}
|
|
157
|
-
export async function
|
|
159
|
+
export async function readState(file = ID_FILE) {
|
|
158
160
|
try {
|
|
159
161
|
const parsed = JSON.parse(await readFile(file, 'utf8'));
|
|
160
162
|
if (typeof parsed.anonymousId === 'string' && parsed.anonymousId)
|
|
161
|
-
return parsed
|
|
163
|
+
return parsed;
|
|
162
164
|
}
|
|
163
165
|
catch { }
|
|
164
|
-
|
|
166
|
+
return writeState({ anonymousId: randomUUID() }, file);
|
|
167
|
+
}
|
|
168
|
+
async function writeState(state, file = ID_FILE) {
|
|
165
169
|
try {
|
|
166
170
|
await mkdir(dirname(file), { recursive: true });
|
|
167
|
-
await writeFile(file, JSON.stringify(
|
|
171
|
+
await writeFile(file, JSON.stringify(state, null, 2));
|
|
168
172
|
}
|
|
169
173
|
catch { }
|
|
170
|
-
return
|
|
174
|
+
return state;
|
|
171
175
|
}
|
|
172
176
|
export async function sendBatch(key, batch, fetchImpl = fetch) {
|
|
173
177
|
try {
|
|
@@ -201,16 +205,27 @@ export async function trackCommand(cmd, args, outcome, cliVersion, deps = {}) {
|
|
|
201
205
|
if (!key)
|
|
202
206
|
return;
|
|
203
207
|
const project = await (deps.loadProject ?? readProject)();
|
|
208
|
+
const userId = config.user?.id;
|
|
209
|
+
let state = await readState(deps.idFile);
|
|
210
|
+
// Session gone or switched: the id belongs to the account that left, so later commands get a fresh one.
|
|
211
|
+
if (state.identifiedAs && state.identifiedAs !== userId)
|
|
212
|
+
state = await writeState({ anonymousId: randomUUID() }, deps.idFile);
|
|
204
213
|
const event = buildCommandEvent(command, args.flat(), cmd.opts(), outcome, {
|
|
205
214
|
cliVersion,
|
|
206
215
|
channel: deps.channel ?? detectChannel(),
|
|
207
216
|
config,
|
|
208
217
|
project,
|
|
209
|
-
anonymousId:
|
|
218
|
+
anonymousId: state.anonymousId,
|
|
210
219
|
env,
|
|
211
220
|
tty: deps.tty ?? !!process.stdout.isTTY,
|
|
212
221
|
});
|
|
213
|
-
|
|
222
|
+
const batch = [event];
|
|
223
|
+
const merge = userId !== undefined && state.identifiedAs !== userId;
|
|
224
|
+
if (merge)
|
|
225
|
+
batch.push({ event: '$identify', distinct_id: userId, timestamp: event.timestamp, properties: { $anon_distinct_id: state.anonymousId } });
|
|
226
|
+
const sent = await sendBatch(key, batch, deps.fetchImpl);
|
|
227
|
+
if (merge && sent)
|
|
228
|
+
await writeState({ ...state, identifiedAs: userId }, deps.idFile);
|
|
214
229
|
}
|
|
215
230
|
catch { }
|
|
216
231
|
}
|