@instafy/cli 0.1.8-staging.357 → 0.1.8-staging.365
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 -0
- package/dist/auth.js +60 -18
- package/dist/config.js +133 -5
- package/dist/git.js +2 -5
- package/dist/index.js +73 -8
- package/dist/project-manifest.js +24 -0
- package/dist/project.js +57 -3
- package/dist/runtime.js +2 -22
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -9,6 +9,7 @@ Run Instafy projects locally and connect them back to Instafy Studio — from an
|
|
|
9
9
|
0. Log in once: `instafy login`
|
|
10
10
|
- Opens a Studio URL; the CLI continues automatically after you sign in.
|
|
11
11
|
- Also enables Git auth (credential helper) for Instafy Git Service. Disable with `instafy login --no-git-setup`.
|
|
12
|
+
- Multiple accounts: `instafy login --profile work` (then bind folders with `instafy project:init --profile work` or `instafy project:profile work`).
|
|
12
13
|
- Optional: set defaults with `instafy config set controller-url <url>` / `instafy config set studio-url <url>`
|
|
13
14
|
1. Link a folder to a project:
|
|
14
15
|
- VS Code: install the Instafy extension and run `Instafy: Link Workspace to Project`, or
|
package/dist/auth.js
CHANGED
|
@@ -4,7 +4,7 @@ import * as http from "node:http";
|
|
|
4
4
|
import { createRequire } from "node:module";
|
|
5
5
|
import { createInterface } from "node:readline/promises";
|
|
6
6
|
import { stdin as input, stdout as output } from "node:process";
|
|
7
|
-
import { clearInstafyCliConfig, getInstafyConfigPath, resolveConfiguredControllerUrl, resolveConfiguredStudioUrl, resolveUserAccessToken, writeInstafyCliConfig, } from "./config.js";
|
|
7
|
+
import { clearInstafyCliConfig, clearInstafyProfileConfig, getInstafyConfigPath, getInstafyProfileConfigPath, resolveConfiguredControllerUrl, resolveConfiguredStudioUrl, resolveUserAccessToken, writeInstafyCliConfig, writeInstafyProfileConfig, } from "./config.js";
|
|
8
8
|
import { installGitCredentialHelper, uninstallGitCredentialHelper } from "./git-setup.js";
|
|
9
9
|
const require = createRequire(import.meta.url);
|
|
10
10
|
const cliVersion = (() => {
|
|
@@ -45,11 +45,33 @@ function looksLikeLocalControllerUrl(controllerUrl) {
|
|
|
45
45
|
return controllerUrl.includes("127.0.0.1") || controllerUrl.includes("localhost");
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
|
-
function
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
async function isStudioHealthy(studioUrl, timeoutMs) {
|
|
49
|
+
const target = new URL("/cli/login", studioUrl).toString();
|
|
50
|
+
const abort = new AbortController();
|
|
51
|
+
const timeout = setTimeout(() => abort.abort(), timeoutMs);
|
|
52
|
+
timeout.unref?.();
|
|
53
|
+
try {
|
|
54
|
+
const response = await fetch(target, { signal: abort.signal });
|
|
55
|
+
return response.ok;
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
finally {
|
|
61
|
+
clearTimeout(timeout);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
async function resolveDefaultStudioUrl(controllerUrl) {
|
|
65
|
+
const hosted = "https://staging.instafy.dev";
|
|
66
|
+
if (isStagingCli) {
|
|
67
|
+
return hosted;
|
|
51
68
|
}
|
|
52
|
-
|
|
69
|
+
if (!looksLikeLocalControllerUrl(controllerUrl)) {
|
|
70
|
+
return hosted;
|
|
71
|
+
}
|
|
72
|
+
const local = "http://localhost:5173";
|
|
73
|
+
const healthy = await isStudioHealthy(local, 250);
|
|
74
|
+
return healthy ? local : hosted;
|
|
53
75
|
}
|
|
54
76
|
async function isControllerHealthy(controllerUrl, timeoutMs) {
|
|
55
77
|
const target = `${controllerUrl.replace(/\/$/, "")}/healthz`;
|
|
@@ -212,10 +234,11 @@ async function startCliLoginCallbackServer() {
|
|
|
212
234
|
};
|
|
213
235
|
}
|
|
214
236
|
export async function login(options) {
|
|
237
|
+
const profile = typeof options.profile === "string" && options.profile.trim() ? options.profile.trim() : null;
|
|
215
238
|
const explicitControllerUrl = normalizeUrl(options.controllerUrl ?? null) ??
|
|
216
239
|
normalizeUrl(process.env["INSTAFY_SERVER_URL"] ?? null) ??
|
|
217
240
|
normalizeUrl(process.env["CONTROLLER_BASE_URL"] ?? null) ??
|
|
218
|
-
(isStagingCli ? null : resolveConfiguredControllerUrl());
|
|
241
|
+
(isStagingCli ? null : resolveConfiguredControllerUrl({ profile }));
|
|
219
242
|
const defaultLocalControllerUrl = "http://127.0.0.1:8788";
|
|
220
243
|
const defaultHostedControllerUrl = "https://controller.instafy.dev";
|
|
221
244
|
const controllerUrl = explicitControllerUrl ??
|
|
@@ -226,12 +249,16 @@ export async function login(options) {
|
|
|
226
249
|
: defaultHostedControllerUrl);
|
|
227
250
|
const studioUrl = normalizeUrl(options.studioUrl ?? null) ??
|
|
228
251
|
normalizeUrl(process.env["INSTAFY_STUDIO_URL"] ?? null) ??
|
|
229
|
-
(isStagingCli ? null : resolveConfiguredStudioUrl()) ??
|
|
230
|
-
|
|
252
|
+
(isStagingCli ? null : resolveConfiguredStudioUrl({ profile })) ??
|
|
253
|
+
(await resolveDefaultStudioUrl(controllerUrl));
|
|
231
254
|
const url = new URL("/cli/login", studioUrl);
|
|
232
255
|
url.searchParams.set("serverUrl", controllerUrl);
|
|
233
256
|
if (options.json) {
|
|
234
|
-
console.log(JSON.stringify({
|
|
257
|
+
console.log(JSON.stringify({
|
|
258
|
+
url: url.toString(),
|
|
259
|
+
profile,
|
|
260
|
+
configPath: profile ? getInstafyProfileConfigPath(profile) : getInstafyConfigPath(),
|
|
261
|
+
}));
|
|
235
262
|
return;
|
|
236
263
|
}
|
|
237
264
|
let callbackServer = null;
|
|
@@ -259,7 +286,7 @@ export async function login(options) {
|
|
|
259
286
|
console.log("2) After you sign in, copy the token shown on that page.");
|
|
260
287
|
}
|
|
261
288
|
console.log("");
|
|
262
|
-
const existing = resolveUserAccessToken();
|
|
289
|
+
const existing = resolveUserAccessToken({ profile });
|
|
263
290
|
let token = provided;
|
|
264
291
|
if (!token && callbackServer) {
|
|
265
292
|
if (input.isTTY) {
|
|
@@ -337,9 +364,14 @@ export async function login(options) {
|
|
|
337
364
|
throw new Error("No token provided.");
|
|
338
365
|
}
|
|
339
366
|
if (!options.noStore) {
|
|
340
|
-
|
|
367
|
+
if (profile) {
|
|
368
|
+
writeInstafyProfileConfig(profile, { controllerUrl, studioUrl, accessToken: token });
|
|
369
|
+
}
|
|
370
|
+
else {
|
|
371
|
+
writeInstafyCliConfig({ controllerUrl, studioUrl, accessToken: token });
|
|
372
|
+
}
|
|
341
373
|
console.log("");
|
|
342
|
-
console.log(kleur.green(`Saved token to ${getInstafyConfigPath()}`));
|
|
374
|
+
console.log(kleur.green(`Saved token to ${profile ? getInstafyProfileConfigPath(profile) : getInstafyConfigPath()}`));
|
|
343
375
|
if (options.gitSetup !== false) {
|
|
344
376
|
try {
|
|
345
377
|
const result = installGitCredentialHelper();
|
|
@@ -362,16 +394,26 @@ export async function login(options) {
|
|
|
362
394
|
console.log(`- ${kleur.cyan("instafy runtime:start")}`);
|
|
363
395
|
}
|
|
364
396
|
export async function logout(options) {
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
uninstallGitCredentialHelper();
|
|
397
|
+
if (options?.profile) {
|
|
398
|
+
clearInstafyProfileConfig(options.profile, ["accessToken"]);
|
|
368
399
|
}
|
|
369
|
-
|
|
370
|
-
|
|
400
|
+
else {
|
|
401
|
+
clearInstafyCliConfig(["accessToken"]);
|
|
402
|
+
try {
|
|
403
|
+
uninstallGitCredentialHelper();
|
|
404
|
+
}
|
|
405
|
+
catch {
|
|
406
|
+
// ignore git helper cleanup failures
|
|
407
|
+
}
|
|
371
408
|
}
|
|
372
409
|
if (options?.json) {
|
|
373
410
|
console.log(JSON.stringify({ ok: true }));
|
|
374
411
|
return;
|
|
375
412
|
}
|
|
376
|
-
|
|
413
|
+
if (options?.profile) {
|
|
414
|
+
console.log(kleur.green(`Logged out (cleared saved access token for profile "${options.profile}").`));
|
|
415
|
+
}
|
|
416
|
+
else {
|
|
417
|
+
console.log(kleur.green("Logged out (cleared saved access token)."));
|
|
418
|
+
}
|
|
377
419
|
}
|
package/dist/config.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import { findProjectManifest } from "./project-manifest.js";
|
|
4
5
|
const INSTAFY_DIR = path.join(os.homedir(), ".instafy");
|
|
5
6
|
const CONFIG_PATH = path.join(INSTAFY_DIR, "config.json");
|
|
7
|
+
const PROFILES_DIR = path.join(INSTAFY_DIR, "profiles");
|
|
6
8
|
function normalizeToken(value) {
|
|
7
9
|
if (typeof value !== "string") {
|
|
8
10
|
return null;
|
|
@@ -24,9 +26,45 @@ function normalizeUrl(value) {
|
|
|
24
26
|
}
|
|
25
27
|
return trimmed.replace(/\/$/, "");
|
|
26
28
|
}
|
|
29
|
+
function normalizeProfileName(value) {
|
|
30
|
+
const trimmed = typeof value === "string" ? value.trim() : "";
|
|
31
|
+
if (!trimmed) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.test(trimmed)) {
|
|
35
|
+
throw new Error(`Invalid profile name "${value}". Use letters/numbers plus . _ - (max 64 chars).`);
|
|
36
|
+
}
|
|
37
|
+
if (trimmed.includes("..")) {
|
|
38
|
+
throw new Error(`Invalid profile name "${value}".`);
|
|
39
|
+
}
|
|
40
|
+
return trimmed;
|
|
41
|
+
}
|
|
27
42
|
export function getInstafyConfigPath() {
|
|
28
43
|
return CONFIG_PATH;
|
|
29
44
|
}
|
|
45
|
+
export function getInstafyProfilesDirPath() {
|
|
46
|
+
return PROFILES_DIR;
|
|
47
|
+
}
|
|
48
|
+
export function getInstafyProfileConfigPath(profile) {
|
|
49
|
+
const normalized = normalizeProfileName(profile);
|
|
50
|
+
if (!normalized) {
|
|
51
|
+
throw new Error("Profile name is required.");
|
|
52
|
+
}
|
|
53
|
+
return path.join(PROFILES_DIR, `${normalized}.json`);
|
|
54
|
+
}
|
|
55
|
+
export function listInstafyProfileNames() {
|
|
56
|
+
try {
|
|
57
|
+
const entries = fs.readdirSync(PROFILES_DIR, { withFileTypes: true });
|
|
58
|
+
return entries
|
|
59
|
+
.filter((entry) => entry.isFile() && entry.name.endsWith(".json"))
|
|
60
|
+
.map((entry) => entry.name.slice(0, -".json".length))
|
|
61
|
+
.filter(Boolean)
|
|
62
|
+
.sort((a, b) => a.localeCompare(b));
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return [];
|
|
66
|
+
}
|
|
67
|
+
}
|
|
30
68
|
export function readInstafyCliConfig() {
|
|
31
69
|
try {
|
|
32
70
|
const raw = fs.readFileSync(CONFIG_PATH, "utf8");
|
|
@@ -46,6 +84,26 @@ export function readInstafyCliConfig() {
|
|
|
46
84
|
return {};
|
|
47
85
|
}
|
|
48
86
|
}
|
|
87
|
+
export function readInstafyProfileConfig(profile) {
|
|
88
|
+
const filePath = getInstafyProfileConfigPath(profile);
|
|
89
|
+
try {
|
|
90
|
+
const raw = fs.readFileSync(filePath, "utf8");
|
|
91
|
+
const parsed = JSON.parse(raw);
|
|
92
|
+
if (!parsed || typeof parsed !== "object") {
|
|
93
|
+
return {};
|
|
94
|
+
}
|
|
95
|
+
const record = parsed;
|
|
96
|
+
return {
|
|
97
|
+
controllerUrl: normalizeUrl(typeof record.controllerUrl === "string" ? record.controllerUrl : null),
|
|
98
|
+
studioUrl: normalizeUrl(typeof record.studioUrl === "string" ? record.studioUrl : null),
|
|
99
|
+
accessToken: normalizeToken(typeof record.accessToken === "string" ? record.accessToken : null),
|
|
100
|
+
updatedAt: typeof record.updatedAt === "string" ? record.updatedAt : null,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
return {};
|
|
105
|
+
}
|
|
106
|
+
}
|
|
49
107
|
export function writeInstafyCliConfig(update) {
|
|
50
108
|
const existing = readInstafyCliConfig();
|
|
51
109
|
const next = {
|
|
@@ -64,6 +122,25 @@ export function writeInstafyCliConfig(update) {
|
|
|
64
122
|
}
|
|
65
123
|
return next;
|
|
66
124
|
}
|
|
125
|
+
export function writeInstafyProfileConfig(profile, update) {
|
|
126
|
+
const filePath = getInstafyProfileConfigPath(profile);
|
|
127
|
+
const existing = readInstafyProfileConfig(profile);
|
|
128
|
+
const next = {
|
|
129
|
+
controllerUrl: normalizeUrl(update.controllerUrl ?? existing.controllerUrl ?? null),
|
|
130
|
+
studioUrl: normalizeUrl(update.studioUrl ?? existing.studioUrl ?? null),
|
|
131
|
+
accessToken: normalizeToken(update.accessToken ?? existing.accessToken ?? null),
|
|
132
|
+
updatedAt: new Date().toISOString(),
|
|
133
|
+
};
|
|
134
|
+
fs.mkdirSync(PROFILES_DIR, { recursive: true });
|
|
135
|
+
fs.writeFileSync(filePath, JSON.stringify(next, null, 2), { encoding: "utf8" });
|
|
136
|
+
try {
|
|
137
|
+
fs.chmodSync(filePath, 0o600);
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
// ignore chmod failures (windows / unusual fs)
|
|
141
|
+
}
|
|
142
|
+
return next;
|
|
143
|
+
}
|
|
67
144
|
export function clearInstafyCliConfig(keys) {
|
|
68
145
|
if (!keys || keys.length === 0) {
|
|
69
146
|
try {
|
|
@@ -81,20 +158,70 @@ export function clearInstafyCliConfig(keys) {
|
|
|
81
158
|
}
|
|
82
159
|
writeInstafyCliConfig(next);
|
|
83
160
|
}
|
|
84
|
-
export function
|
|
161
|
+
export function clearInstafyProfileConfig(profile, keys) {
|
|
162
|
+
const filePath = getInstafyProfileConfigPath(profile);
|
|
163
|
+
if (!keys || keys.length === 0) {
|
|
164
|
+
try {
|
|
165
|
+
fs.rmSync(filePath, { force: true });
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
// ignore
|
|
169
|
+
}
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
const existing = readInstafyProfileConfig(profile);
|
|
173
|
+
const next = { ...existing };
|
|
174
|
+
for (const key of keys) {
|
|
175
|
+
next[key] = null;
|
|
176
|
+
}
|
|
177
|
+
writeInstafyProfileConfig(profile, next);
|
|
178
|
+
}
|
|
179
|
+
export function resolveActiveProfileName(params) {
|
|
180
|
+
const explicit = normalizeProfileName(params?.profile ?? null);
|
|
181
|
+
if (explicit) {
|
|
182
|
+
return explicit;
|
|
183
|
+
}
|
|
184
|
+
const fromEnv = normalizeProfileName(process.env["INSTAFY_PROFILE"] ?? null);
|
|
185
|
+
if (fromEnv) {
|
|
186
|
+
return fromEnv;
|
|
187
|
+
}
|
|
188
|
+
const cwd = (params?.cwd ?? process.cwd()).trim();
|
|
189
|
+
if (!cwd) {
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
const manifest = findProjectManifest(cwd).manifest;
|
|
193
|
+
return normalizeProfileName(manifest?.profile ?? null);
|
|
194
|
+
}
|
|
195
|
+
export function resolveConfiguredControllerUrl(params) {
|
|
196
|
+
const profile = resolveActiveProfileName(params);
|
|
197
|
+
if (profile) {
|
|
198
|
+
const configured = readInstafyProfileConfig(profile);
|
|
199
|
+
return normalizeUrl(configured.controllerUrl ?? null);
|
|
200
|
+
}
|
|
85
201
|
const config = readInstafyCliConfig();
|
|
86
202
|
return normalizeUrl(config.controllerUrl ?? null);
|
|
87
203
|
}
|
|
88
|
-
export function resolveConfiguredStudioUrl() {
|
|
204
|
+
export function resolveConfiguredStudioUrl(params) {
|
|
205
|
+
const profile = resolveActiveProfileName(params);
|
|
206
|
+
if (profile) {
|
|
207
|
+
const configured = readInstafyProfileConfig(profile);
|
|
208
|
+
return normalizeUrl(configured.studioUrl ?? null);
|
|
209
|
+
}
|
|
89
210
|
const config = readInstafyCliConfig();
|
|
90
211
|
return normalizeUrl(config.studioUrl ?? null);
|
|
91
212
|
}
|
|
92
|
-
export function resolveConfiguredAccessToken() {
|
|
213
|
+
export function resolveConfiguredAccessToken(params) {
|
|
214
|
+
const profile = resolveActiveProfileName(params);
|
|
215
|
+
if (profile) {
|
|
216
|
+
const configured = readInstafyProfileConfig(profile);
|
|
217
|
+
return normalizeToken(configured.accessToken ?? null);
|
|
218
|
+
}
|
|
93
219
|
const config = readInstafyCliConfig();
|
|
94
220
|
return normalizeToken(config.accessToken ?? null);
|
|
95
221
|
}
|
|
96
222
|
export function resolveControllerUrl(params) {
|
|
97
|
-
const
|
|
223
|
+
const profile = resolveActiveProfileName({ profile: params?.profile ?? null, cwd: params?.cwd ?? null });
|
|
224
|
+
const config = profile ? readInstafyProfileConfig(profile) : readInstafyCliConfig();
|
|
98
225
|
return (normalizeUrl(params?.controllerUrl ?? null) ??
|
|
99
226
|
normalizeUrl(process.env["INSTAFY_SERVER_URL"] ?? null) ??
|
|
100
227
|
normalizeUrl(process.env["CONTROLLER_BASE_URL"] ?? null) ??
|
|
@@ -102,7 +229,8 @@ export function resolveControllerUrl(params) {
|
|
|
102
229
|
"http://127.0.0.1:8788");
|
|
103
230
|
}
|
|
104
231
|
export function resolveUserAccessToken(params) {
|
|
105
|
-
const
|
|
232
|
+
const profile = resolveActiveProfileName({ profile: params?.profile ?? null, cwd: params?.cwd ?? null });
|
|
233
|
+
const config = profile ? readInstafyProfileConfig(profile) : readInstafyCliConfig();
|
|
106
234
|
return (normalizeToken(params?.accessToken ?? null) ??
|
|
107
235
|
normalizeToken(process.env["INSTAFY_ACCESS_TOKEN"] ?? null) ??
|
|
108
236
|
normalizeToken(process.env["CONTROLLER_ACCESS_TOKEN"] ?? null) ??
|
package/dist/git.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import kleur from "kleur";
|
|
4
|
-
import { resolveConfiguredAccessToken } from "./config.js";
|
|
4
|
+
import { resolveConfiguredAccessToken, resolveControllerUrl } from "./config.js";
|
|
5
5
|
function normalizeToken(value) {
|
|
6
6
|
if (typeof value !== "string") {
|
|
7
7
|
return null;
|
|
@@ -68,10 +68,7 @@ export async function mintGitAccessToken(params) {
|
|
|
68
68
|
};
|
|
69
69
|
}
|
|
70
70
|
export async function gitToken(options) {
|
|
71
|
-
const controllerUrl = options.controllerUrl ??
|
|
72
|
-
process.env["INSTAFY_SERVER_URL"] ??
|
|
73
|
-
process.env["CONTROLLER_BASE_URL"] ??
|
|
74
|
-
"http://127.0.0.1:8788";
|
|
71
|
+
const controllerUrl = resolveControllerUrl({ controllerUrl: options.controllerUrl ?? null });
|
|
75
72
|
const token = resolveControllerAccessToken({
|
|
76
73
|
controllerAccessToken: options.controllerAccessToken,
|
|
77
74
|
supabaseAccessToken: options.supabaseAccessToken,
|
package/dist/index.js
CHANGED
|
@@ -6,10 +6,11 @@ import { runtimeStart, runtimeStatus, runtimeStop, runtimeToken, findProjectMani
|
|
|
6
6
|
import { login, logout } from "./auth.js";
|
|
7
7
|
import { gitToken } from "./git.js";
|
|
8
8
|
import { runGitCredentialHelper } from "./git-credential.js";
|
|
9
|
-
import { projectInit } from "./project.js";
|
|
9
|
+
import { projectInit, projectProfile } from "./project.js";
|
|
10
10
|
import { listTunnelSessions, startTunnelDetached, stopTunnelSession, tailTunnelLogs, runTunnelCommand } from "./tunnel.js";
|
|
11
11
|
import { requestControllerApi } from "./api.js";
|
|
12
12
|
import { configGet, configList, configPath, configSet, configUnset } from "./config-command.js";
|
|
13
|
+
import { getInstafyProfileConfigPath, listInstafyProfileNames, readInstafyProfileConfig } from "./config.js";
|
|
13
14
|
export const program = new Command();
|
|
14
15
|
const require = createRequire(import.meta.url);
|
|
15
16
|
const pkg = require("../package.json");
|
|
@@ -40,6 +41,7 @@ program
|
|
|
40
41
|
.description("Log in and save an access token for future CLI commands")
|
|
41
42
|
.option("--studio-url <url>", "Studio web URL (default: staging or localhost)")
|
|
42
43
|
.option("--server-url <url>", "Instafy server/controller URL")
|
|
44
|
+
.option("--profile <name>", "Save token under a named profile (multi-account support)")
|
|
43
45
|
.option("--token <token>", "Provide token directly (skips prompt)")
|
|
44
46
|
.option("--no-git-setup", "Do not configure git credential helper")
|
|
45
47
|
.option("--no-store", "Do not save token to ~/.instafy/config.json")
|
|
@@ -52,6 +54,7 @@ program
|
|
|
52
54
|
token: opts.token,
|
|
53
55
|
gitSetup: opts.gitSetup,
|
|
54
56
|
noStore: opts.store === false,
|
|
57
|
+
profile: opts.profile,
|
|
55
58
|
json: opts.json,
|
|
56
59
|
});
|
|
57
60
|
}
|
|
@@ -63,10 +66,11 @@ program
|
|
|
63
66
|
program
|
|
64
67
|
.command("logout")
|
|
65
68
|
.description("Clear the saved CLI access token")
|
|
69
|
+
.option("--profile <name>", "Clear the token for a named profile")
|
|
66
70
|
.option("--json", "Output JSON")
|
|
67
71
|
.action(async (opts) => {
|
|
68
72
|
try {
|
|
69
|
-
await logout({ json: opts.json });
|
|
73
|
+
await logout({ json: opts.json, profile: opts.profile });
|
|
70
74
|
}
|
|
71
75
|
catch (error) {
|
|
72
76
|
console.error(kleur.red(String(error)));
|
|
@@ -80,6 +84,7 @@ const projectInitCommand = program
|
|
|
80
84
|
addServerUrlOptions(projectInitCommand);
|
|
81
85
|
projectInitCommand
|
|
82
86
|
.option("--access-token <token>", "Instafy or Supabase access token")
|
|
87
|
+
.option("--profile <name>", "CLI profile to use when running commands in this folder")
|
|
83
88
|
.option("--project-type <type>", "Project type (customer|sandbox)")
|
|
84
89
|
.option("--org-id <uuid>", "Optional organization id")
|
|
85
90
|
.option("--org-name <name>", "Optional organization name")
|
|
@@ -92,6 +97,7 @@ projectInitCommand
|
|
|
92
97
|
path: opts.path,
|
|
93
98
|
controllerUrl: opts.serverUrl ?? opts.controllerUrl,
|
|
94
99
|
accessToken: opts.accessToken,
|
|
100
|
+
profile: opts.profile,
|
|
95
101
|
projectType: opts.projectType,
|
|
96
102
|
orgId: opts.orgId,
|
|
97
103
|
orgName: opts.orgName,
|
|
@@ -105,6 +111,65 @@ projectInitCommand
|
|
|
105
111
|
process.exit(1);
|
|
106
112
|
}
|
|
107
113
|
});
|
|
114
|
+
program
|
|
115
|
+
.command("project:profile")
|
|
116
|
+
.description("Get/set the CLI profile for this folder (.instafy/project.json)")
|
|
117
|
+
.argument("[profile]", "Profile name to set (omit to print current)")
|
|
118
|
+
.option("--unset", "Clear the configured profile for this folder")
|
|
119
|
+
.option("--path <dir>", "Directory to search for the manifest (default: cwd)")
|
|
120
|
+
.option("--json", "Output JSON")
|
|
121
|
+
.action(async (profile, opts) => {
|
|
122
|
+
try {
|
|
123
|
+
projectProfile({
|
|
124
|
+
profile,
|
|
125
|
+
unset: opts.unset,
|
|
126
|
+
path: opts.path,
|
|
127
|
+
json: opts.json,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
console.error(kleur.red(String(error)));
|
|
132
|
+
process.exit(1);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
program
|
|
136
|
+
.command("profile:list")
|
|
137
|
+
.description("List saved CLI profiles (~/.instafy/profiles)")
|
|
138
|
+
.option("--json", "Output JSON")
|
|
139
|
+
.action(async (opts) => {
|
|
140
|
+
try {
|
|
141
|
+
const names = listInstafyProfileNames();
|
|
142
|
+
const profiles = names.map((name) => {
|
|
143
|
+
const config = readInstafyProfileConfig(name);
|
|
144
|
+
return {
|
|
145
|
+
name,
|
|
146
|
+
path: getInstafyProfileConfigPath(name),
|
|
147
|
+
controllerUrl: config.controllerUrl ?? null,
|
|
148
|
+
studioUrl: config.studioUrl ?? null,
|
|
149
|
+
accessTokenSet: Boolean(config.accessToken),
|
|
150
|
+
updatedAt: config.updatedAt ?? null,
|
|
151
|
+
};
|
|
152
|
+
});
|
|
153
|
+
if (opts.json) {
|
|
154
|
+
console.log(JSON.stringify({ profiles }, null, 2));
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
if (profiles.length === 0) {
|
|
158
|
+
console.log(kleur.yellow("No profiles found."));
|
|
159
|
+
console.log(`Create one with: ${kleur.cyan("instafy login --profile <name>")}`);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
console.log(kleur.green("Instafy CLI profiles"));
|
|
163
|
+
for (const profile of profiles) {
|
|
164
|
+
const token = profile.accessTokenSet ? kleur.green("token") : kleur.yellow("no-token");
|
|
165
|
+
console.log(`- ${kleur.cyan(profile.name)} (${token})`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
console.error(kleur.red(String(error)));
|
|
170
|
+
process.exit(1);
|
|
171
|
+
}
|
|
172
|
+
});
|
|
108
173
|
const configCommand = program.command("config").description("Get/set saved CLI configuration");
|
|
109
174
|
configCommand
|
|
110
175
|
.command("path")
|
|
@@ -274,8 +339,8 @@ runtimeTokenCommand
|
|
|
274
339
|
}
|
|
275
340
|
});
|
|
276
341
|
const gitTokenCommand = program
|
|
277
|
-
.command("git:token")
|
|
278
|
-
.description("
|
|
342
|
+
.command("git:token", { hidden: true })
|
|
343
|
+
.description("Advanced: mint a git access token for the project repo")
|
|
279
344
|
.option("--project <id>", "Project UUID (defaults to .instafy/project.json)");
|
|
280
345
|
addServerUrlOptions(gitTokenCommand);
|
|
281
346
|
addAccessTokenOptions(gitTokenCommand, "Instafy access token (required)");
|
|
@@ -536,22 +601,22 @@ function configureApiCommand(command, method) {
|
|
|
536
601
|
});
|
|
537
602
|
}
|
|
538
603
|
const apiGetCommand = program
|
|
539
|
-
.command("api:get")
|
|
604
|
+
.command("api:get", { hidden: true })
|
|
540
605
|
.description("Advanced: authenticated GET request to the controller API")
|
|
541
606
|
.argument("<path>", "API path (or full URL), e.g. /conversations/<id>/messages?limit=50");
|
|
542
607
|
configureApiCommand(apiGetCommand, "GET");
|
|
543
608
|
const apiPostCommand = program
|
|
544
|
-
.command("api:post")
|
|
609
|
+
.command("api:post", { hidden: true })
|
|
545
610
|
.description("Advanced: authenticated POST request to the controller API")
|
|
546
611
|
.argument("<path>", "API path (or full URL)");
|
|
547
612
|
configureApiCommand(apiPostCommand, "POST");
|
|
548
613
|
const apiPatchCommand = program
|
|
549
|
-
.command("api:patch")
|
|
614
|
+
.command("api:patch", { hidden: true })
|
|
550
615
|
.description("Advanced: authenticated PATCH request to the controller API")
|
|
551
616
|
.argument("<path>", "API path (or full URL)");
|
|
552
617
|
configureApiCommand(apiPatchCommand, "PATCH");
|
|
553
618
|
const apiDeleteCommand = program
|
|
554
|
-
.command("api:delete")
|
|
619
|
+
.command("api:delete", { hidden: true })
|
|
555
620
|
.description("Advanced: authenticated DELETE request to the controller API")
|
|
556
621
|
.argument("<path>", "API path (or full URL)");
|
|
557
622
|
configureApiCommand(apiDeleteCommand, "DELETE");
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
export function findProjectManifest(startDir) {
|
|
4
|
+
let current = path.resolve(startDir);
|
|
5
|
+
const root = path.parse(current).root;
|
|
6
|
+
while (true) {
|
|
7
|
+
const candidate = path.join(current, ".instafy", "project.json");
|
|
8
|
+
if (existsSync(candidate)) {
|
|
9
|
+
try {
|
|
10
|
+
const parsed = JSON.parse(readFileSync(candidate, "utf8"));
|
|
11
|
+
if (parsed?.projectId) {
|
|
12
|
+
return { manifest: parsed, path: candidate };
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
// ignore malformed manifest
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
if (current === root)
|
|
20
|
+
break;
|
|
21
|
+
current = path.dirname(current);
|
|
22
|
+
}
|
|
23
|
+
return { manifest: null, path: null };
|
|
24
|
+
}
|
package/dist/project.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import kleur from "kleur";
|
|
4
|
-
import { resolveControllerUrl, resolveUserAccessToken } from "./config.js";
|
|
4
|
+
import { getInstafyProfileConfigPath, resolveControllerUrl, resolveUserAccessToken } from "./config.js";
|
|
5
5
|
import { formatAuthRequiredError } from "./errors.js";
|
|
6
|
+
import { findProjectManifest } from "./project-manifest.js";
|
|
6
7
|
async function fetchOrganizations(controllerUrl, token) {
|
|
7
8
|
const response = await fetch(`${controllerUrl}/orgs`, {
|
|
8
9
|
headers: {
|
|
@@ -111,8 +112,16 @@ export async function listProjects(options) {
|
|
|
111
112
|
}
|
|
112
113
|
export async function projectInit(options) {
|
|
113
114
|
const rootDir = path.resolve(options.path ?? process.cwd());
|
|
114
|
-
const controllerUrl = resolveControllerUrl({
|
|
115
|
-
|
|
115
|
+
const controllerUrl = resolveControllerUrl({
|
|
116
|
+
controllerUrl: options.controllerUrl ?? null,
|
|
117
|
+
profile: options.profile ?? null,
|
|
118
|
+
cwd: rootDir,
|
|
119
|
+
});
|
|
120
|
+
const token = resolveUserAccessToken({
|
|
121
|
+
accessToken: options.accessToken ?? null,
|
|
122
|
+
profile: options.profile ?? null,
|
|
123
|
+
cwd: rootDir,
|
|
124
|
+
});
|
|
116
125
|
if (!token) {
|
|
117
126
|
throw formatAuthRequiredError({
|
|
118
127
|
retryCommand: "instafy project:init",
|
|
@@ -146,6 +155,7 @@ export async function projectInit(options) {
|
|
|
146
155
|
orgId: json.org_id ?? org.orgId ?? null,
|
|
147
156
|
orgName: json.org_name ?? org.orgName ?? null,
|
|
148
157
|
controllerUrl,
|
|
158
|
+
profile: options.profile ?? null,
|
|
149
159
|
createdAt: new Date().toISOString(),
|
|
150
160
|
}, null, 2), "utf8");
|
|
151
161
|
}
|
|
@@ -171,3 +181,47 @@ export async function projectInit(options) {
|
|
|
171
181
|
}
|
|
172
182
|
return json;
|
|
173
183
|
}
|
|
184
|
+
export function projectProfile(options) {
|
|
185
|
+
const rootDir = path.resolve(options.path ?? process.cwd());
|
|
186
|
+
const manifestInfo = findProjectManifest(rootDir);
|
|
187
|
+
if (!manifestInfo.path || !manifestInfo.manifest) {
|
|
188
|
+
throw new Error("No project configured. Run `instafy project:init` in this folder first.");
|
|
189
|
+
}
|
|
190
|
+
const shouldUpdate = options.unset === true || typeof options.profile === "string";
|
|
191
|
+
const currentProfile = typeof manifestInfo.manifest.profile === "string" && manifestInfo.manifest.profile.trim()
|
|
192
|
+
? manifestInfo.manifest.profile.trim()
|
|
193
|
+
: null;
|
|
194
|
+
if (!shouldUpdate) {
|
|
195
|
+
if (options.json) {
|
|
196
|
+
console.log(JSON.stringify({ ok: true, path: manifestInfo.path, profile: currentProfile }, null, 2));
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
console.log(kleur.green("Project profile"));
|
|
200
|
+
console.log(`Manifest: ${manifestInfo.path}`);
|
|
201
|
+
console.log(`Profile: ${currentProfile ?? kleur.yellow("(none)")}`);
|
|
202
|
+
}
|
|
203
|
+
return { path: manifestInfo.path, profile: currentProfile };
|
|
204
|
+
}
|
|
205
|
+
const nextProfileRaw = options.unset === true ? null : typeof options.profile === "string" ? options.profile.trim() : null;
|
|
206
|
+
const nextProfile = nextProfileRaw && nextProfileRaw.length > 0 ? nextProfileRaw : null;
|
|
207
|
+
if (nextProfile) {
|
|
208
|
+
getInstafyProfileConfigPath(nextProfile);
|
|
209
|
+
}
|
|
210
|
+
const updated = { ...manifestInfo.manifest };
|
|
211
|
+
if (nextProfile) {
|
|
212
|
+
updated.profile = nextProfile;
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
delete updated.profile;
|
|
216
|
+
}
|
|
217
|
+
fs.writeFileSync(manifestInfo.path, JSON.stringify(updated, null, 2), "utf8");
|
|
218
|
+
if (options.json) {
|
|
219
|
+
console.log(JSON.stringify({ ok: true, path: manifestInfo.path, profile: nextProfile }, null, 2));
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
console.log(kleur.green("Updated project profile."));
|
|
223
|
+
console.log(`Manifest: ${manifestInfo.path}`);
|
|
224
|
+
console.log(`Profile: ${nextProfile ?? kleur.yellow("(none)")}`);
|
|
225
|
+
}
|
|
226
|
+
return { path: manifestInfo.path, profile: nextProfile };
|
|
227
|
+
}
|
package/dist/runtime.js
CHANGED
|
@@ -7,6 +7,7 @@ import { randomUUID } from "node:crypto";
|
|
|
7
7
|
import os from "node:os";
|
|
8
8
|
import { ensureRatholeBinary } from "./rathole.js";
|
|
9
9
|
import { resolveConfiguredAccessToken } from "./config.js";
|
|
10
|
+
import { findProjectManifest } from "./project-manifest.js";
|
|
10
11
|
const INSTAFY_DIR = path.join(os.homedir(), ".instafy");
|
|
11
12
|
const STATE_FILE = path.join(INSTAFY_DIR, "cli-runtime-state.json");
|
|
12
13
|
const LOG_DIR = path.join(INSTAFY_DIR, "cli-runtime-logs");
|
|
@@ -142,28 +143,7 @@ function normalizeToken(value) {
|
|
|
142
143
|
const trimmed = value.trim();
|
|
143
144
|
return trimmed.length > 0 ? trimmed : null;
|
|
144
145
|
}
|
|
145
|
-
export
|
|
146
|
-
let current = path.resolve(startDir);
|
|
147
|
-
const root = path.parse(current).root;
|
|
148
|
-
while (true) {
|
|
149
|
-
const candidate = path.join(current, ".instafy", "project.json");
|
|
150
|
-
if (existsSync(candidate)) {
|
|
151
|
-
try {
|
|
152
|
-
const parsed = JSON.parse(readFileSync(candidate, "utf8"));
|
|
153
|
-
if (parsed?.projectId) {
|
|
154
|
-
return { manifest: parsed, path: candidate };
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
catch {
|
|
158
|
-
// ignore malformed manifest
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
if (current === root)
|
|
162
|
-
break;
|
|
163
|
-
current = path.dirname(current);
|
|
164
|
-
}
|
|
165
|
-
return { manifest: null, path: null };
|
|
166
|
-
}
|
|
146
|
+
export { findProjectManifest };
|
|
167
147
|
function readTokenFromFile(filePath) {
|
|
168
148
|
const normalized = normalizeToken(filePath);
|
|
169
149
|
if (!normalized) {
|
package/package.json
CHANGED