@instafy/cli 0.1.8-staging.365 → 0.1.8-staging.368
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 +14 -13
- package/dist/auth.js +2 -2
- package/dist/git-setup.js +2 -2
- package/dist/git-wrapper.js +497 -0
- package/dist/index.js +129 -133
- package/dist/org.js +1 -1
- package/dist/project.js +3 -3
- package/dist/runtime.js +2 -2
- package/dist/tunnel.js +7 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -9,26 +9,27 @@ 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
|
|
12
|
+
- Multiple accounts: `instafy login --profile work` (then bind folders with `instafy project init --profile work` or `instafy project profile work`).
|
|
13
13
|
- Optional: set defaults with `instafy config set controller-url <url>` / `instafy config set studio-url <url>`
|
|
14
14
|
1. Link a folder to a project:
|
|
15
15
|
- VS Code: install the Instafy extension and run `Instafy: Link Workspace to Project`, or
|
|
16
|
-
- Terminal: `instafy project
|
|
17
|
-
2. Start the runtime: `instafy runtime
|
|
16
|
+
- Terminal: `instafy project init`
|
|
17
|
+
2. Start the runtime: `instafy runtime start`
|
|
18
18
|
3. Check status / stop:
|
|
19
|
-
- `instafy runtime
|
|
20
|
-
- `instafy runtime
|
|
19
|
+
- `instafy runtime status`
|
|
20
|
+
- `instafy runtime stop`
|
|
21
21
|
|
|
22
22
|
## Common commands
|
|
23
23
|
|
|
24
|
-
- `instafy runtime
|
|
25
|
-
- `instafy runtime
|
|
26
|
-
- `instafy runtime
|
|
27
|
-
- `instafy
|
|
28
|
-
|
|
29
|
-
- `instafy tunnel
|
|
30
|
-
- `instafy tunnel
|
|
31
|
-
- `instafy
|
|
24
|
+
- `instafy runtime start` — start a local runtime (agent + origin).
|
|
25
|
+
- `instafy runtime status` — show health of the last started runtime.
|
|
26
|
+
- `instafy runtime stop` — stop the last started runtime.
|
|
27
|
+
- `instafy git <args...>` — run git commands against an Instafy canonical checkout (`.instafy/.git`) when present.
|
|
28
|
+
- `instafy tunnel start` — start a detached tunnel for a local port.
|
|
29
|
+
- `instafy tunnel list` — list local tunnels started by the CLI.
|
|
30
|
+
- `instafy tunnel logs <tunnelId> --follow` — tail tunnel logs.
|
|
31
|
+
- `instafy tunnel stop <tunnelId>` — stop + revoke a tunnel.
|
|
32
|
+
- `instafy api get` — query controller endpoints (conversations, messages, runs, etc).
|
|
32
33
|
|
|
33
34
|
Run `instafy --help` for the full command list and options.
|
|
34
35
|
|
package/dist/auth.js
CHANGED
|
@@ -390,8 +390,8 @@ export async function login(options) {
|
|
|
390
390
|
}
|
|
391
391
|
console.log("");
|
|
392
392
|
console.log("Next:");
|
|
393
|
-
console.log(`- ${kleur.cyan("instafy project
|
|
394
|
-
console.log(`- ${kleur.cyan("instafy runtime
|
|
393
|
+
console.log(`- ${kleur.cyan("instafy project init")}`);
|
|
394
|
+
console.log(`- ${kleur.cyan("instafy runtime start")}`);
|
|
395
395
|
}
|
|
396
396
|
export async function logout(options) {
|
|
397
397
|
if (options?.profile) {
|
package/dist/git-setup.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
|
-
const INSTAFY_GIT_HELPER_VALUE = "!instafy git
|
|
2
|
+
const INSTAFY_GIT_HELPER_VALUE = "!instafy git credential";
|
|
3
3
|
function isLikelySameHelper(value) {
|
|
4
|
-
return value.includes("instafy git
|
|
4
|
+
return value.includes("instafy git credential");
|
|
5
5
|
}
|
|
6
6
|
function runGit(args) {
|
|
7
7
|
const result = spawnSync("git", args, { encoding: "utf8" });
|
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import kleur from "kleur";
|
|
6
|
+
function pathExists(candidate) {
|
|
7
|
+
try {
|
|
8
|
+
fs.statSync(candidate);
|
|
9
|
+
return true;
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export function findInstafyGitContext(startDir) {
|
|
16
|
+
let current = path.resolve(startDir);
|
|
17
|
+
const root = path.parse(current).root;
|
|
18
|
+
while (true) {
|
|
19
|
+
const gitDir = path.join(current, ".instafy", ".git");
|
|
20
|
+
if (pathExists(gitDir)) {
|
|
21
|
+
return { workTree: current, gitDir };
|
|
22
|
+
}
|
|
23
|
+
if (current === root)
|
|
24
|
+
break;
|
|
25
|
+
current = path.dirname(current);
|
|
26
|
+
}
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
function splitGitCommand(userArgs) {
|
|
30
|
+
const globalArgs = [];
|
|
31
|
+
let index = 0;
|
|
32
|
+
while (index < userArgs.length) {
|
|
33
|
+
const arg = userArgs[index] ?? "";
|
|
34
|
+
if (arg === "--") {
|
|
35
|
+
index += 1;
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
if (!arg.startsWith("-") || arg === "-") {
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
globalArgs.push(arg);
|
|
42
|
+
// Skip values for global options that consume the next token.
|
|
43
|
+
if (arg === "-c" || arg === "--config-env" || arg === "--exec-path" || arg === "--namespace") {
|
|
44
|
+
if (index + 1 < userArgs.length) {
|
|
45
|
+
globalArgs.push(userArgs[index + 1] ?? "");
|
|
46
|
+
index += 2;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
index += 1;
|
|
51
|
+
}
|
|
52
|
+
const command = index < userArgs.length ? (userArgs[index] ?? null) : null;
|
|
53
|
+
const commandArgs = command ? userArgs.slice(index + 1) : [];
|
|
54
|
+
return { globalArgs, command, commandArgs };
|
|
55
|
+
}
|
|
56
|
+
function validateGitArgs(userArgs) {
|
|
57
|
+
let parsingGlobalOptions = true;
|
|
58
|
+
for (let index = 0; index < userArgs.length; index += 1) {
|
|
59
|
+
const arg = userArgs[index] ?? "";
|
|
60
|
+
if (!parsingGlobalOptions) {
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (arg === "--") {
|
|
64
|
+
parsingGlobalOptions = false;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (!arg.startsWith("-") || arg === "-") {
|
|
68
|
+
parsingGlobalOptions = false;
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
if (arg === "--git-dir" || arg.startsWith("--git-dir=") || arg === "--work-tree" || arg.startsWith("--work-tree=") || arg === "-C") {
|
|
72
|
+
throw new Error(`Unsupported argument "${arg}". Use ${kleur.cyan("cd")} instead; ${kleur.cyan("instafy git")} manages --git-dir/--work-tree automatically.`);
|
|
73
|
+
}
|
|
74
|
+
// Skip the value for global options that consume the next token.
|
|
75
|
+
if (arg === "-c" || arg === "--config-env" || arg === "--exec-path" || arg === "--namespace") {
|
|
76
|
+
index += 1;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
export function buildInstafyGitArgs(context, userArgs) {
|
|
81
|
+
validateGitArgs(userArgs);
|
|
82
|
+
return ["--git-dir", context.gitDir, "--work-tree", context.workTree, ...userArgs];
|
|
83
|
+
}
|
|
84
|
+
function normalizePathSpec(raw) {
|
|
85
|
+
return (raw ?? "").replace(/\\/g, "/").trim().replace(/^\/+/, "");
|
|
86
|
+
}
|
|
87
|
+
function isReservedSyncPath(pathSpec) {
|
|
88
|
+
const normalized = normalizePathSpec(pathSpec);
|
|
89
|
+
if (!normalized) {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
if (normalized === ".instafy" || normalized.startsWith(".instafy/")) {
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
if (normalized === ".instafy/origin-staging" || normalized.startsWith(".instafy/origin-staging/")) {
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
return normalized.split("/").some((segment) => segment === ".git" || segment.startsWith(".git.instafy-hidden-"));
|
|
99
|
+
}
|
|
100
|
+
function gitCapture(context, args, options) {
|
|
101
|
+
const result = spawnSync("git", ["--git-dir", context.gitDir, "--work-tree", context.workTree, ...args], {
|
|
102
|
+
cwd: options.cwd,
|
|
103
|
+
encoding: "utf8",
|
|
104
|
+
env: { ...process.env, GIT_TERMINAL_PROMPT: "0" },
|
|
105
|
+
});
|
|
106
|
+
return {
|
|
107
|
+
status: typeof result.status === "number" ? result.status : 1,
|
|
108
|
+
stdout: typeof result.stdout === "string" ? result.stdout : "",
|
|
109
|
+
stderr: typeof result.stderr === "string" ? result.stderr : "",
|
|
110
|
+
error: result.error,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
function collectDirtyPaths(context, options) {
|
|
114
|
+
const result = gitCapture(context, ["status", "--porcelain=v1"], options);
|
|
115
|
+
if (result.error) {
|
|
116
|
+
throw new Error(`Failed to run git status: ${result.error.message}`);
|
|
117
|
+
}
|
|
118
|
+
if (result.status !== 0) {
|
|
119
|
+
throw new Error(`git status failed (${result.status}): ${result.stderr.trim()}`);
|
|
120
|
+
}
|
|
121
|
+
const paths = [];
|
|
122
|
+
for (const line of result.stdout.split(/\r?\n/)) {
|
|
123
|
+
if (!line.trim())
|
|
124
|
+
continue;
|
|
125
|
+
if (line.length < 4)
|
|
126
|
+
continue;
|
|
127
|
+
const raw = line.slice(3).trim();
|
|
128
|
+
if (!raw)
|
|
129
|
+
continue;
|
|
130
|
+
const rename = raw.split(" -> ");
|
|
131
|
+
if (rename.length === 2) {
|
|
132
|
+
for (const entry of rename) {
|
|
133
|
+
const normalized = normalizePathSpec(entry).replace(/\/+$/, "");
|
|
134
|
+
if (normalized)
|
|
135
|
+
paths.push(normalized);
|
|
136
|
+
}
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
const normalized = normalizePathSpec(raw).replace(/\/+$/, "");
|
|
140
|
+
if (normalized)
|
|
141
|
+
paths.push(normalized);
|
|
142
|
+
}
|
|
143
|
+
paths.sort((a, b) => a.localeCompare(b));
|
|
144
|
+
const unique = [];
|
|
145
|
+
for (const value of paths) {
|
|
146
|
+
if (unique.length === 0 || unique[unique.length - 1] !== value) {
|
|
147
|
+
unique.push(value);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return unique;
|
|
151
|
+
}
|
|
152
|
+
function findEmbeddedGitDirs(context, pathSpecs) {
|
|
153
|
+
const instafyGitDir = path.resolve(context.gitDir);
|
|
154
|
+
const seen = new Set();
|
|
155
|
+
for (const rawPath of pathSpecs) {
|
|
156
|
+
const normalized = normalizePathSpec(rawPath).replace(/\/+$/, "");
|
|
157
|
+
if (!normalized || normalized === "." || normalized === "/") {
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
let cursor = normalized;
|
|
161
|
+
while (cursor && cursor !== ".") {
|
|
162
|
+
const candidate = path.join(context.workTree, ...cursor.split("/"), ".git");
|
|
163
|
+
if (pathExists(candidate) && path.resolve(candidate) !== instafyGitDir) {
|
|
164
|
+
seen.add(path.resolve(candidate));
|
|
165
|
+
}
|
|
166
|
+
const next = path.posix.dirname(cursor);
|
|
167
|
+
if (next === "." || next === cursor) {
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
cursor = next;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
const candidates = Array.from(seen);
|
|
174
|
+
candidates.sort((a, b) => {
|
|
175
|
+
const depthA = a.split(path.sep).length;
|
|
176
|
+
const depthB = b.split(path.sep).length;
|
|
177
|
+
return depthB - depthA;
|
|
178
|
+
});
|
|
179
|
+
return candidates;
|
|
180
|
+
}
|
|
181
|
+
function hideEmbeddedGitDirs(context, pathSpecs) {
|
|
182
|
+
const candidates = findEmbeddedGitDirs(context, pathSpecs);
|
|
183
|
+
if (candidates.length === 0) {
|
|
184
|
+
return [];
|
|
185
|
+
}
|
|
186
|
+
const stagingBase = path.join(context.workTree, ".instafy", "origin-staging", "embedded-git");
|
|
187
|
+
fs.mkdirSync(stagingBase, { recursive: true });
|
|
188
|
+
const renames = [];
|
|
189
|
+
for (const candidate of candidates) {
|
|
190
|
+
let hidden = path.join(stagingBase, randomUUID());
|
|
191
|
+
for (let attempt = 0; attempt < 8; attempt += 1) {
|
|
192
|
+
if (!pathExists(hidden))
|
|
193
|
+
break;
|
|
194
|
+
hidden = path.join(stagingBase, randomUUID());
|
|
195
|
+
}
|
|
196
|
+
fs.renameSync(candidate, hidden);
|
|
197
|
+
renames.push({ original: candidate, hidden });
|
|
198
|
+
}
|
|
199
|
+
return renames;
|
|
200
|
+
}
|
|
201
|
+
function restoreEmbeddedGitDirs(renames) {
|
|
202
|
+
for (let index = renames.length - 1; index >= 0; index -= 1) {
|
|
203
|
+
const rename = renames[index];
|
|
204
|
+
if (!rename)
|
|
205
|
+
continue;
|
|
206
|
+
try {
|
|
207
|
+
fs.renameSync(rename.hidden, rename.original);
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
// ignore restore failures; leave evidence in .instafy/origin-staging for debugging
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
function parseAddArgs(commandArgs) {
|
|
215
|
+
const addOptions = [];
|
|
216
|
+
const pathSpecs = [];
|
|
217
|
+
let inPaths = false;
|
|
218
|
+
for (const arg of commandArgs) {
|
|
219
|
+
if (inPaths) {
|
|
220
|
+
pathSpecs.push(arg);
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
if (arg === "--") {
|
|
224
|
+
inPaths = true;
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
if (arg.startsWith("-") && arg !== "-") {
|
|
228
|
+
addOptions.push(arg);
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
inPaths = true;
|
|
232
|
+
pathSpecs.push(arg);
|
|
233
|
+
}
|
|
234
|
+
return { addOptions, pathSpecs };
|
|
235
|
+
}
|
|
236
|
+
function runInstafyGitAdd(context, globalArgs, commandArgs, options) {
|
|
237
|
+
const parsed = parseAddArgs(commandArgs);
|
|
238
|
+
const normalizedPathSpecs = parsed.pathSpecs
|
|
239
|
+
.map((spec) => normalizePathSpec(spec))
|
|
240
|
+
.filter((spec) => spec && spec !== "." && spec !== "/");
|
|
241
|
+
const stageAll = normalizedPathSpecs.length === 0;
|
|
242
|
+
const stagePaths = stageAll
|
|
243
|
+
? collectDirtyPaths(context, options).filter((entry) => !isReservedSyncPath(entry))
|
|
244
|
+
: normalizedPathSpecs.filter((entry) => !isReservedSyncPath(entry));
|
|
245
|
+
if (stagePaths.length === 0) {
|
|
246
|
+
return 0;
|
|
247
|
+
}
|
|
248
|
+
const addOptions = [...parsed.addOptions];
|
|
249
|
+
if (stageAll && !addOptions.some((opt) => opt === "-A" || opt === "--all")) {
|
|
250
|
+
addOptions.unshift("-A");
|
|
251
|
+
}
|
|
252
|
+
const renames = hideEmbeddedGitDirs(context, stagePaths);
|
|
253
|
+
try {
|
|
254
|
+
const result = spawnSync("git", [
|
|
255
|
+
"--git-dir",
|
|
256
|
+
context.gitDir,
|
|
257
|
+
"--work-tree",
|
|
258
|
+
context.workTree,
|
|
259
|
+
...globalArgs,
|
|
260
|
+
"add",
|
|
261
|
+
...addOptions,
|
|
262
|
+
"--",
|
|
263
|
+
...stagePaths,
|
|
264
|
+
], { stdio: "inherit", cwd: options.cwd, env: { ...process.env, GIT_TERMINAL_PROMPT: "0" } });
|
|
265
|
+
if (result.error) {
|
|
266
|
+
throw new Error(`Failed to run git add: ${result.error.message}`);
|
|
267
|
+
}
|
|
268
|
+
return typeof result.status === "number" ? result.status : 1;
|
|
269
|
+
}
|
|
270
|
+
finally {
|
|
271
|
+
restoreEmbeddedGitDirs(renames);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
export function runInstafyGit(userArgs, options) {
|
|
275
|
+
const cwd = options?.cwd ?? process.cwd();
|
|
276
|
+
if (userArgs.length === 0 || userArgs[0] === "--help" || userArgs[0] === "-h") {
|
|
277
|
+
// Keep this minimal so it doesn't diverge from Git help output.
|
|
278
|
+
console.log("instafy git <git-args...>");
|
|
279
|
+
console.log("");
|
|
280
|
+
console.log(`Runs ${kleur.cyan("git")} against the Instafy canonical repo at ${kleur.cyan(".instafy/.git")} (auto-detected by walking up from cwd).`);
|
|
281
|
+
console.log("");
|
|
282
|
+
console.log("Example:");
|
|
283
|
+
console.log(` ${kleur.cyan('instafy git status')}`);
|
|
284
|
+
console.log(` ${kleur.cyan('instafy git add -A')}`);
|
|
285
|
+
console.log(` ${kleur.cyan('instafy git commit -am "instafy: checkpoint"')}`);
|
|
286
|
+
return 0;
|
|
287
|
+
}
|
|
288
|
+
const context = findInstafyGitContext(cwd);
|
|
289
|
+
if (!context) {
|
|
290
|
+
throw new Error([
|
|
291
|
+
"No Instafy canonical git checkout found (expected .instafy/.git).",
|
|
292
|
+
"",
|
|
293
|
+
"Tips:",
|
|
294
|
+
`- Run this inside a git-canonical workspace (where ${kleur.cyan(".instafy/.git")} exists).`,
|
|
295
|
+
`- If you're trying to operate on a user repo, use normal ${kleur.cyan("git")} (it uses .git).`,
|
|
296
|
+
].join("\n"));
|
|
297
|
+
}
|
|
298
|
+
validateGitArgs(userArgs);
|
|
299
|
+
const split = splitGitCommand(userArgs);
|
|
300
|
+
if (split.command === "add") {
|
|
301
|
+
return runInstafyGitAdd(context, split.globalArgs, split.commandArgs, { cwd });
|
|
302
|
+
}
|
|
303
|
+
const args = buildInstafyGitArgs(context, userArgs);
|
|
304
|
+
const result = spawnSync("git", args, { stdio: "inherit", cwd });
|
|
305
|
+
if (result.error) {
|
|
306
|
+
throw new Error(`Failed to run git: ${result.error.message}`);
|
|
307
|
+
}
|
|
308
|
+
return typeof result.status === "number" ? result.status : 1;
|
|
309
|
+
}
|
|
310
|
+
function printGitSyncHelp() {
|
|
311
|
+
console.log("instafy git sync [options]");
|
|
312
|
+
console.log("");
|
|
313
|
+
console.log("Calls the Origin git sync endpoint (POST /git/sync) for the current runtime/workspace.");
|
|
314
|
+
console.log("");
|
|
315
|
+
console.log("Options:");
|
|
316
|
+
console.log(" -m, --message <msg> Commit message (recommended)");
|
|
317
|
+
console.log(" --path <relativePath> Only sync selected path(s) (repeatable)");
|
|
318
|
+
console.log(" --origin-endpoint <url> Override Origin base URL (default: $ORIGIN_ENDPOINT or localhost)");
|
|
319
|
+
console.log(" --origin-token <token> Override Origin bearer token (default: $ORIGIN_INTERNAL_TOKEN)");
|
|
320
|
+
console.log(" --json Output JSON");
|
|
321
|
+
console.log("");
|
|
322
|
+
console.log("Env fallback:");
|
|
323
|
+
console.log(" ORIGIN_ENDPOINT, ORIGIN_BIND_PORT, ORIGIN_INTERNAL_TOKEN, RUNTIME_ACCESS_TOKEN");
|
|
324
|
+
}
|
|
325
|
+
function normalizeToken(raw) {
|
|
326
|
+
if (typeof raw !== "string")
|
|
327
|
+
return null;
|
|
328
|
+
const trimmed = raw.trim();
|
|
329
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
330
|
+
}
|
|
331
|
+
function normalizeUrl(raw) {
|
|
332
|
+
return raw.trim().replace(/\/+$/, "");
|
|
333
|
+
}
|
|
334
|
+
function resolveOriginEndpoint(override) {
|
|
335
|
+
const direct = normalizeToken(override) ?? normalizeToken(process.env["ORIGIN_ENDPOINT"]);
|
|
336
|
+
if (direct) {
|
|
337
|
+
return normalizeUrl(direct);
|
|
338
|
+
}
|
|
339
|
+
const portRaw = normalizeToken(process.env["ORIGIN_BIND_PORT"]);
|
|
340
|
+
const port = portRaw ? Number.parseInt(portRaw, 10) : 54332;
|
|
341
|
+
const resolvedPort = Number.isFinite(port) && port > 0 ? port : 54332;
|
|
342
|
+
return `http://127.0.0.1:${resolvedPort}`;
|
|
343
|
+
}
|
|
344
|
+
function resolveOriginToken(override) {
|
|
345
|
+
const token = normalizeToken(override) ??
|
|
346
|
+
normalizeToken(process.env["ORIGIN_INTERNAL_TOKEN"]) ??
|
|
347
|
+
normalizeToken(process.env["RUNTIME_ACCESS_TOKEN"]);
|
|
348
|
+
if (!token) {
|
|
349
|
+
throw new Error([
|
|
350
|
+
"Origin bearer token missing.",
|
|
351
|
+
"",
|
|
352
|
+
"Provide one of:",
|
|
353
|
+
"- --origin-token <token>",
|
|
354
|
+
"- ORIGIN_INTERNAL_TOKEN=<token> (recommended inside runtimes)",
|
|
355
|
+
"- RUNTIME_ACCESS_TOKEN=<token>",
|
|
356
|
+
].join("\n"));
|
|
357
|
+
}
|
|
358
|
+
return token;
|
|
359
|
+
}
|
|
360
|
+
function parseGitSyncArgs(args) {
|
|
361
|
+
let message = null;
|
|
362
|
+
let json = false;
|
|
363
|
+
let originEndpointOverride = null;
|
|
364
|
+
let originTokenOverride = null;
|
|
365
|
+
const paths = [];
|
|
366
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
367
|
+
const arg = args[index] ?? "";
|
|
368
|
+
if (arg === "--help" || arg === "-h") {
|
|
369
|
+
return "help";
|
|
370
|
+
}
|
|
371
|
+
if (arg === "--json") {
|
|
372
|
+
json = true;
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
375
|
+
if (arg === "-m" || arg === "--message") {
|
|
376
|
+
const value = args[index + 1];
|
|
377
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
378
|
+
throw new Error(`${arg} requires a non-empty value`);
|
|
379
|
+
}
|
|
380
|
+
message = value.trim();
|
|
381
|
+
index += 1;
|
|
382
|
+
continue;
|
|
383
|
+
}
|
|
384
|
+
if (arg.startsWith("--message=")) {
|
|
385
|
+
const value = arg.slice("--message=".length).trim();
|
|
386
|
+
if (!value) {
|
|
387
|
+
throw new Error("--message requires a non-empty value");
|
|
388
|
+
}
|
|
389
|
+
message = value;
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
if (arg === "--origin-endpoint") {
|
|
393
|
+
const value = args[index + 1];
|
|
394
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
395
|
+
throw new Error("--origin-endpoint requires a URL");
|
|
396
|
+
}
|
|
397
|
+
originEndpointOverride = value.trim();
|
|
398
|
+
index += 1;
|
|
399
|
+
continue;
|
|
400
|
+
}
|
|
401
|
+
if (arg.startsWith("--origin-endpoint=")) {
|
|
402
|
+
const value = arg.slice("--origin-endpoint=".length).trim();
|
|
403
|
+
if (!value) {
|
|
404
|
+
throw new Error("--origin-endpoint requires a URL");
|
|
405
|
+
}
|
|
406
|
+
originEndpointOverride = value;
|
|
407
|
+
continue;
|
|
408
|
+
}
|
|
409
|
+
if (arg === "--origin-token") {
|
|
410
|
+
const value = args[index + 1];
|
|
411
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
412
|
+
throw new Error("--origin-token requires a token");
|
|
413
|
+
}
|
|
414
|
+
originTokenOverride = value.trim();
|
|
415
|
+
index += 1;
|
|
416
|
+
continue;
|
|
417
|
+
}
|
|
418
|
+
if (arg.startsWith("--origin-token=")) {
|
|
419
|
+
const value = arg.slice("--origin-token=".length).trim();
|
|
420
|
+
if (!value) {
|
|
421
|
+
throw new Error("--origin-token requires a token");
|
|
422
|
+
}
|
|
423
|
+
originTokenOverride = value;
|
|
424
|
+
continue;
|
|
425
|
+
}
|
|
426
|
+
if (arg === "--path") {
|
|
427
|
+
const value = args[index + 1];
|
|
428
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
429
|
+
throw new Error("--path requires a relative path");
|
|
430
|
+
}
|
|
431
|
+
paths.push(value.trim());
|
|
432
|
+
index += 1;
|
|
433
|
+
continue;
|
|
434
|
+
}
|
|
435
|
+
if (arg.startsWith("--path=")) {
|
|
436
|
+
const value = arg.slice("--path=".length).trim();
|
|
437
|
+
if (!value) {
|
|
438
|
+
throw new Error("--path requires a relative path");
|
|
439
|
+
}
|
|
440
|
+
paths.push(value);
|
|
441
|
+
continue;
|
|
442
|
+
}
|
|
443
|
+
throw new Error(`Unknown argument: ${arg}`);
|
|
444
|
+
}
|
|
445
|
+
const originEndpoint = resolveOriginEndpoint(originEndpointOverride);
|
|
446
|
+
const originToken = resolveOriginToken(originTokenOverride);
|
|
447
|
+
const resolvedMessage = message ?? `instafy: sync ${new Date().toISOString().replace(/\..*$/, "")}Z`;
|
|
448
|
+
return {
|
|
449
|
+
message: resolvedMessage,
|
|
450
|
+
json,
|
|
451
|
+
originEndpoint,
|
|
452
|
+
originToken,
|
|
453
|
+
paths: paths.length > 0 ? paths : null,
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
export async function runInstafyGitSync(args, options) {
|
|
457
|
+
void options;
|
|
458
|
+
const parsed = parseGitSyncArgs(args);
|
|
459
|
+
if (parsed === "help") {
|
|
460
|
+
printGitSyncHelp();
|
|
461
|
+
return 0;
|
|
462
|
+
}
|
|
463
|
+
const url = `${parsed.originEndpoint.replace(/\/+$/, "")}/git/sync`;
|
|
464
|
+
const response = await fetch(url, {
|
|
465
|
+
method: "POST",
|
|
466
|
+
headers: {
|
|
467
|
+
accept: "application/json",
|
|
468
|
+
authorization: `Bearer ${parsed.originToken}`,
|
|
469
|
+
"content-type": "application/json",
|
|
470
|
+
},
|
|
471
|
+
body: JSON.stringify({
|
|
472
|
+
message: parsed.message,
|
|
473
|
+
paths: parsed.paths ?? undefined,
|
|
474
|
+
}),
|
|
475
|
+
}).catch((error) => {
|
|
476
|
+
throw new Error(`Origin git sync request failed: ${String(error)}`);
|
|
477
|
+
});
|
|
478
|
+
const text = await response.text().catch(() => "");
|
|
479
|
+
if (!response.ok) {
|
|
480
|
+
const suffix = text.trim() ? `: ${text.trim()}` : "";
|
|
481
|
+
console.error(kleur.red(`Origin git sync failed (${response.status} ${response.statusText})${suffix}`));
|
|
482
|
+
return 1;
|
|
483
|
+
}
|
|
484
|
+
const payload = text.trim() ? JSON.parse(text) : null;
|
|
485
|
+
const rev = typeof payload?.rev === "string" ? payload.rev.trim() : "";
|
|
486
|
+
if (!rev) {
|
|
487
|
+
console.error(kleur.red("Origin git sync response missing rev field."));
|
|
488
|
+
return 1;
|
|
489
|
+
}
|
|
490
|
+
if (parsed.json) {
|
|
491
|
+
console.log(JSON.stringify({ rev }, null, 2));
|
|
492
|
+
}
|
|
493
|
+
else {
|
|
494
|
+
console.log(rev);
|
|
495
|
+
}
|
|
496
|
+
return 0;
|
|
497
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -4,13 +4,13 @@ import { createRequire } from "node:module";
|
|
|
4
4
|
import kleur from "kleur";
|
|
5
5
|
import { runtimeStart, runtimeStatus, runtimeStop, runtimeToken, findProjectManifest, } from "./runtime.js";
|
|
6
6
|
import { login, logout } from "./auth.js";
|
|
7
|
-
import { gitToken } from "./git.js";
|
|
8
7
|
import { runGitCredentialHelper } from "./git-credential.js";
|
|
9
8
|
import { projectInit, projectProfile } from "./project.js";
|
|
10
9
|
import { listTunnelSessions, startTunnelDetached, stopTunnelSession, tailTunnelLogs, runTunnelCommand } from "./tunnel.js";
|
|
11
10
|
import { requestControllerApi } from "./api.js";
|
|
12
11
|
import { configGet, configList, configPath, configSet, configUnset } from "./config-command.js";
|
|
13
12
|
import { getInstafyProfileConfigPath, listInstafyProfileNames, readInstafyProfileConfig } from "./config.js";
|
|
13
|
+
import { runInstafyGit, runInstafyGitSync } from "./git-wrapper.js";
|
|
14
14
|
export const program = new Command();
|
|
15
15
|
const require = createRequire(import.meta.url);
|
|
16
16
|
const pkg = require("../package.json");
|
|
@@ -77,8 +77,12 @@ program
|
|
|
77
77
|
process.exit(1);
|
|
78
78
|
}
|
|
79
79
|
});
|
|
80
|
-
const
|
|
81
|
-
.command("project
|
|
80
|
+
const projectCommand = program
|
|
81
|
+
.command("project")
|
|
82
|
+
.description("Create/link and manage Instafy projects");
|
|
83
|
+
projectCommand.action(() => projectCommand.outputHelp());
|
|
84
|
+
const projectInitCommand = projectCommand
|
|
85
|
+
.command("init")
|
|
82
86
|
.description("Create an Instafy project and link this folder (.instafy/project.json)")
|
|
83
87
|
.option("--path <dir>", "Directory where the manifest should be written (default: cwd)");
|
|
84
88
|
addServerUrlOptions(projectInitCommand);
|
|
@@ -111,8 +115,8 @@ projectInitCommand
|
|
|
111
115
|
process.exit(1);
|
|
112
116
|
}
|
|
113
117
|
});
|
|
114
|
-
|
|
115
|
-
.command("
|
|
118
|
+
projectCommand
|
|
119
|
+
.command("profile")
|
|
116
120
|
.description("Get/set the CLI profile for this folder (.instafy/project.json)")
|
|
117
121
|
.argument("[profile]", "Profile name to set (omit to print current)")
|
|
118
122
|
.option("--unset", "Clear the configured profile for this folder")
|
|
@@ -132,38 +136,24 @@ program
|
|
|
132
136
|
process.exit(1);
|
|
133
137
|
}
|
|
134
138
|
});
|
|
135
|
-
|
|
136
|
-
.command("
|
|
137
|
-
.description("List
|
|
139
|
+
const projectListCommand = projectCommand
|
|
140
|
+
.command("list")
|
|
141
|
+
.description("List projects for your account")
|
|
142
|
+
.option("--access-token <token>", "Instafy or Supabase access token");
|
|
143
|
+
addServerUrlOptions(projectListCommand);
|
|
144
|
+
projectListCommand
|
|
145
|
+
.option("--org-id <uuid>", "Filter by organization id")
|
|
146
|
+
.option("--org-slug <slug>", "Filter by organization slug")
|
|
138
147
|
.option("--json", "Output JSON")
|
|
139
148
|
.action(async (opts) => {
|
|
140
149
|
try {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
controllerUrl: config.controllerUrl ?? null,
|
|
148
|
-
studioUrl: config.studioUrl ?? null,
|
|
149
|
-
accessTokenSet: Boolean(config.accessToken),
|
|
150
|
-
updatedAt: config.updatedAt ?? null,
|
|
151
|
-
};
|
|
150
|
+
await (await import("./project.js")).listProjects({
|
|
151
|
+
controllerUrl: opts.serverUrl ?? opts.controllerUrl,
|
|
152
|
+
accessToken: opts.accessToken,
|
|
153
|
+
orgId: opts.orgId,
|
|
154
|
+
orgSlug: opts.orgSlug,
|
|
155
|
+
json: opts.json,
|
|
152
156
|
});
|
|
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
157
|
}
|
|
168
158
|
catch (error) {
|
|
169
159
|
console.error(kleur.red(String(error)));
|
|
@@ -171,6 +161,7 @@ program
|
|
|
171
161
|
}
|
|
172
162
|
});
|
|
173
163
|
const configCommand = program.command("config").description("Get/set saved CLI configuration");
|
|
164
|
+
configCommand.action(() => configCommand.outputHelp());
|
|
174
165
|
configCommand
|
|
175
166
|
.command("path")
|
|
176
167
|
.description("Print the config file path")
|
|
@@ -240,8 +231,12 @@ configCommand
|
|
|
240
231
|
process.exit(1);
|
|
241
232
|
}
|
|
242
233
|
});
|
|
243
|
-
const
|
|
244
|
-
.command("runtime
|
|
234
|
+
const runtimeCommand = program
|
|
235
|
+
.command("runtime")
|
|
236
|
+
.description("Start/stop the local Instafy runtime");
|
|
237
|
+
runtimeCommand.action(() => runtimeCommand.outputHelp());
|
|
238
|
+
const runtimeStartCommand = runtimeCommand
|
|
239
|
+
.command("start")
|
|
245
240
|
.description("Start a local runtime for this project")
|
|
246
241
|
.option("--project <id>", "Project UUID");
|
|
247
242
|
addServerUrlOptions(runtimeStartCommand);
|
|
@@ -280,8 +275,8 @@ runtimeStartCommand
|
|
|
280
275
|
process.exit(1);
|
|
281
276
|
}
|
|
282
277
|
});
|
|
283
|
-
|
|
284
|
-
.command("
|
|
278
|
+
runtimeCommand
|
|
279
|
+
.command("status")
|
|
285
280
|
.description("Show runtime health")
|
|
286
281
|
.option("--json", "Output status as JSON")
|
|
287
282
|
.action(async (opts) => {
|
|
@@ -293,8 +288,8 @@ program
|
|
|
293
288
|
process.exit(1);
|
|
294
289
|
}
|
|
295
290
|
});
|
|
296
|
-
|
|
297
|
-
.command("
|
|
291
|
+
runtimeCommand
|
|
292
|
+
.command("stop")
|
|
298
293
|
.description("Stop the local Instafy runtime")
|
|
299
294
|
.option("--json", "Output result as JSON")
|
|
300
295
|
.action(async (opts) => {
|
|
@@ -306,8 +301,8 @@ program
|
|
|
306
301
|
process.exit(1);
|
|
307
302
|
}
|
|
308
303
|
});
|
|
309
|
-
const runtimeTokenCommand =
|
|
310
|
-
.command("
|
|
304
|
+
const runtimeTokenCommand = runtimeCommand
|
|
305
|
+
.command("token")
|
|
311
306
|
.description("Mint a runtime access token")
|
|
312
307
|
.option("--project <id>", "Project UUID (defaults to .instafy/project.json)");
|
|
313
308
|
addServerUrlOptions(runtimeTokenCommand);
|
|
@@ -322,7 +317,7 @@ runtimeTokenCommand
|
|
|
322
317
|
? opts.project.trim()
|
|
323
318
|
: findProjectManifest(process.cwd()).manifest?.projectId ?? null;
|
|
324
319
|
if (!project) {
|
|
325
|
-
throw new Error("No project configured. Run `instafy project
|
|
320
|
+
throw new Error("No project configured. Run `instafy project init` or pass --project.");
|
|
326
321
|
}
|
|
327
322
|
await runtimeToken({
|
|
328
323
|
project,
|
|
@@ -338,69 +333,23 @@ runtimeTokenCommand
|
|
|
338
333
|
process.exit(1);
|
|
339
334
|
}
|
|
340
335
|
});
|
|
341
|
-
const gitTokenCommand = program
|
|
342
|
-
.command("git:token", { hidden: true })
|
|
343
|
-
.description("Advanced: mint a git access token for the project repo")
|
|
344
|
-
.option("--project <id>", "Project UUID (defaults to .instafy/project.json)");
|
|
345
|
-
addServerUrlOptions(gitTokenCommand);
|
|
346
|
-
addAccessTokenOptions(gitTokenCommand, "Instafy access token (required)");
|
|
347
|
-
gitTokenCommand
|
|
348
|
-
.option("--supabase-access-token <token>", "Supabase session token (alternative to Studio token)")
|
|
349
|
-
.option("--supabase-access-token-file <path>", "File containing the Supabase session token")
|
|
350
|
-
.option("--scope <scope...>", "Requested scopes (git.read, git.write)")
|
|
351
|
-
.option("--ttl-seconds <seconds>", "Token TTL in seconds (default server policy)")
|
|
352
|
-
.option("--json", "Output token response as JSON")
|
|
353
|
-
.action(async (opts) => {
|
|
354
|
-
try {
|
|
355
|
-
const project = typeof opts.project === "string" && opts.project.trim()
|
|
356
|
-
? opts.project.trim()
|
|
357
|
-
: findProjectManifest(process.cwd()).manifest?.projectId ?? null;
|
|
358
|
-
if (!project) {
|
|
359
|
-
throw new Error("No project configured. Run `instafy project:init` or pass --project.");
|
|
360
|
-
}
|
|
361
|
-
const ttlSecondsRaw = typeof opts.ttlSeconds === "string" ? opts.ttlSeconds.trim() : "";
|
|
362
|
-
const ttlSeconds = ttlSecondsRaw.length > 0 ? Number.parseInt(ttlSecondsRaw, 10) : undefined;
|
|
363
|
-
if (ttlSecondsRaw.length > 0 && (!Number.isFinite(ttlSeconds) || ttlSeconds <= 0)) {
|
|
364
|
-
throw new Error("--ttl-seconds must be a positive integer");
|
|
365
|
-
}
|
|
366
|
-
await gitToken({
|
|
367
|
-
project,
|
|
368
|
-
controllerUrl: opts.serverUrl ?? opts.controllerUrl,
|
|
369
|
-
controllerAccessToken: opts.accessToken ?? opts.controllerAccessToken,
|
|
370
|
-
supabaseAccessToken: opts.supabaseAccessToken,
|
|
371
|
-
supabaseAccessTokenFile: opts.supabaseAccessTokenFile,
|
|
372
|
-
scopes: opts.scope,
|
|
373
|
-
ttlSeconds,
|
|
374
|
-
json: opts.json,
|
|
375
|
-
});
|
|
376
|
-
}
|
|
377
|
-
catch (error) {
|
|
378
|
-
console.error(kleur.red(String(error)));
|
|
379
|
-
process.exit(1);
|
|
380
|
-
}
|
|
381
|
-
});
|
|
382
336
|
program
|
|
383
|
-
.command("git
|
|
384
|
-
.description("
|
|
385
|
-
.
|
|
386
|
-
.action(async (operation) => {
|
|
387
|
-
try {
|
|
388
|
-
await runGitCredentialHelper(operation);
|
|
389
|
-
}
|
|
390
|
-
catch (error) {
|
|
391
|
-
console.error(String(error));
|
|
392
|
-
process.exit(1);
|
|
393
|
-
}
|
|
394
|
-
});
|
|
337
|
+
.command("git")
|
|
338
|
+
.description("Run git against the Instafy canonical checkout (.instafy/.git)")
|
|
339
|
+
.allowUnknownOption(true);
|
|
395
340
|
const tunnelCommand = program
|
|
396
341
|
.command("tunnel")
|
|
397
|
-
.description("
|
|
342
|
+
.description("Create and manage shareable tunnels");
|
|
343
|
+
tunnelCommand.action(() => tunnelCommand.outputHelp());
|
|
344
|
+
const tunnelStartCommand = tunnelCommand
|
|
345
|
+
.command("start")
|
|
346
|
+
.description("Create a shareable tunnel URL for a local port (defaults to detached)")
|
|
398
347
|
.option("--port <port>", "Local port to expose (default 3000)")
|
|
399
348
|
.option("--project <id>", "Project UUID (defaults to .instafy/project.json or PROJECT_ID)");
|
|
400
|
-
addServerUrlOptions(
|
|
401
|
-
addAccessTokenOptions(
|
|
402
|
-
addServiceTokenOptions(
|
|
403
|
-
|
|
349
|
+
addServerUrlOptions(tunnelStartCommand);
|
|
350
|
+
addAccessTokenOptions(tunnelStartCommand, "Instafy access token (defaults to saved `instafy login` token)");
|
|
351
|
+
addServiceTokenOptions(tunnelStartCommand, "Instafy service token (advanced)");
|
|
352
|
+
tunnelStartCommand
|
|
404
353
|
.option("--no-detach", "Run in foreground until interrupted")
|
|
405
354
|
.option("--rathole-bin <path>", "Path to rathole binary (or set RATHOLE_BIN)")
|
|
406
355
|
.option("--log-file <path>", "Write tunnel logs to a file (default: ~/.instafy/cli-tunnel-logs/*)")
|
|
@@ -439,9 +388,9 @@ tunnelCommand
|
|
|
439
388
|
console.log(kleur.gray(`log: ${started.logFile}`));
|
|
440
389
|
console.log("");
|
|
441
390
|
console.log("Next:");
|
|
442
|
-
console.log(`- ${kleur.cyan(`instafy tunnel
|
|
443
|
-
console.log(`- ${kleur.cyan(`instafy tunnel
|
|
444
|
-
console.log(`- ${kleur.cyan(`instafy tunnel
|
|
391
|
+
console.log(`- ${kleur.cyan(`instafy tunnel list`)}`);
|
|
392
|
+
console.log(`- ${kleur.cyan(`instafy tunnel logs ${started.tunnelId} --follow`)}`);
|
|
393
|
+
console.log(`- ${kleur.cyan(`instafy tunnel stop ${started.tunnelId}`)}`);
|
|
445
394
|
if (process.platform !== "win32") {
|
|
446
395
|
console.log(kleur.gray(`(or) tail -n 200 -f ${started.logFile}`));
|
|
447
396
|
}
|
|
@@ -451,8 +400,8 @@ tunnelCommand
|
|
|
451
400
|
process.exit(1);
|
|
452
401
|
}
|
|
453
402
|
});
|
|
454
|
-
|
|
455
|
-
.command("
|
|
403
|
+
tunnelCommand
|
|
404
|
+
.command("list")
|
|
456
405
|
.description("List local tunnel sessions started by this CLI")
|
|
457
406
|
.option("--all", "Include stopped/stale tunnels")
|
|
458
407
|
.option("--json", "Output JSON")
|
|
@@ -476,8 +425,8 @@ program
|
|
|
476
425
|
process.exit(1);
|
|
477
426
|
}
|
|
478
427
|
});
|
|
479
|
-
|
|
480
|
-
.command("
|
|
428
|
+
tunnelCommand
|
|
429
|
+
.command("stop")
|
|
481
430
|
.description("Stop a local tunnel session and revoke it")
|
|
482
431
|
.argument("[tunnelId]", "Tunnel ID (defaults to the only active tunnel)")
|
|
483
432
|
.option("--server-url <url>", "Instafy server URL")
|
|
@@ -503,8 +452,8 @@ program
|
|
|
503
452
|
process.exit(1);
|
|
504
453
|
}
|
|
505
454
|
});
|
|
506
|
-
|
|
507
|
-
.command("
|
|
455
|
+
tunnelCommand
|
|
456
|
+
.command("logs")
|
|
508
457
|
.description("Show logs for a local tunnel session")
|
|
509
458
|
.argument("[tunnelId]", "Tunnel ID (defaults to the only active tunnel)")
|
|
510
459
|
.option("--lines <n>", "Number of lines to show", "200")
|
|
@@ -525,8 +474,12 @@ program
|
|
|
525
474
|
process.exit(1);
|
|
526
475
|
}
|
|
527
476
|
});
|
|
528
|
-
const
|
|
529
|
-
.command("org
|
|
477
|
+
const orgCommand = program
|
|
478
|
+
.command("org")
|
|
479
|
+
.description("Organization utilities");
|
|
480
|
+
orgCommand.action(() => orgCommand.outputHelp());
|
|
481
|
+
const orgListCommand = orgCommand
|
|
482
|
+
.command("list")
|
|
530
483
|
.description("List organizations for your account");
|
|
531
484
|
addServerUrlOptions(orgListCommand);
|
|
532
485
|
orgListCommand
|
|
@@ -545,24 +498,42 @@ orgListCommand
|
|
|
545
498
|
process.exit(1);
|
|
546
499
|
}
|
|
547
500
|
});
|
|
548
|
-
const
|
|
549
|
-
.command("
|
|
550
|
-
.description("
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
.
|
|
555
|
-
.option("--org-slug <slug>", "Filter by organization slug")
|
|
501
|
+
const profileCommand = program
|
|
502
|
+
.command("profile")
|
|
503
|
+
.description("Manage saved CLI profiles (~/.instafy/profiles)");
|
|
504
|
+
profileCommand.action(() => profileCommand.outputHelp());
|
|
505
|
+
profileCommand
|
|
506
|
+
.command("list")
|
|
507
|
+
.description("List saved CLI profiles")
|
|
556
508
|
.option("--json", "Output JSON")
|
|
557
509
|
.action(async (opts) => {
|
|
558
510
|
try {
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
511
|
+
const names = listInstafyProfileNames();
|
|
512
|
+
const profiles = names.map((name) => {
|
|
513
|
+
const config = readInstafyProfileConfig(name);
|
|
514
|
+
return {
|
|
515
|
+
name,
|
|
516
|
+
path: getInstafyProfileConfigPath(name),
|
|
517
|
+
controllerUrl: config.controllerUrl ?? null,
|
|
518
|
+
studioUrl: config.studioUrl ?? null,
|
|
519
|
+
accessTokenSet: Boolean(config.accessToken),
|
|
520
|
+
updatedAt: config.updatedAt ?? null,
|
|
521
|
+
};
|
|
565
522
|
});
|
|
523
|
+
if (opts.json) {
|
|
524
|
+
console.log(JSON.stringify({ profiles }, null, 2));
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
if (profiles.length === 0) {
|
|
528
|
+
console.log(kleur.yellow("No profiles found."));
|
|
529
|
+
console.log(`Create one with: ${kleur.cyan("instafy login --profile <name>")}`);
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
console.log(kleur.green("Instafy CLI profiles"));
|
|
533
|
+
for (const profile of profiles) {
|
|
534
|
+
const token = profile.accessTokenSet ? kleur.green("token") : kleur.yellow("no-token");
|
|
535
|
+
console.log(`- ${kleur.cyan(profile.name)} (${token})`);
|
|
536
|
+
}
|
|
566
537
|
}
|
|
567
538
|
catch (error) {
|
|
568
539
|
console.error(kleur.red(String(error)));
|
|
@@ -600,23 +571,27 @@ function configureApiCommand(command, method) {
|
|
|
600
571
|
}
|
|
601
572
|
});
|
|
602
573
|
}
|
|
603
|
-
const
|
|
604
|
-
.command("api
|
|
574
|
+
const apiCommand = program
|
|
575
|
+
.command("api", { hidden: true })
|
|
576
|
+
.description("Advanced: authenticated requests to the controller API");
|
|
577
|
+
apiCommand.action(() => apiCommand.outputHelp());
|
|
578
|
+
const apiGetCommand = apiCommand
|
|
579
|
+
.command("get")
|
|
605
580
|
.description("Advanced: authenticated GET request to the controller API")
|
|
606
581
|
.argument("<path>", "API path (or full URL), e.g. /conversations/<id>/messages?limit=50");
|
|
607
582
|
configureApiCommand(apiGetCommand, "GET");
|
|
608
|
-
const apiPostCommand =
|
|
609
|
-
.command("
|
|
583
|
+
const apiPostCommand = apiCommand
|
|
584
|
+
.command("post")
|
|
610
585
|
.description("Advanced: authenticated POST request to the controller API")
|
|
611
586
|
.argument("<path>", "API path (or full URL)");
|
|
612
587
|
configureApiCommand(apiPostCommand, "POST");
|
|
613
|
-
const apiPatchCommand =
|
|
614
|
-
.command("
|
|
588
|
+
const apiPatchCommand = apiCommand
|
|
589
|
+
.command("patch")
|
|
615
590
|
.description("Advanced: authenticated PATCH request to the controller API")
|
|
616
591
|
.argument("<path>", "API path (or full URL)");
|
|
617
592
|
configureApiCommand(apiPatchCommand, "PATCH");
|
|
618
|
-
const apiDeleteCommand =
|
|
619
|
-
.command("
|
|
593
|
+
const apiDeleteCommand = apiCommand
|
|
594
|
+
.command("delete")
|
|
620
595
|
.description("Advanced: authenticated DELETE request to the controller API")
|
|
621
596
|
.argument("<path>", "API path (or full URL)");
|
|
622
597
|
configureApiCommand(apiDeleteCommand, "DELETE");
|
|
@@ -625,6 +600,27 @@ export async function runCli(argv = process.argv) {
|
|
|
625
600
|
program.outputHelp();
|
|
626
601
|
return;
|
|
627
602
|
}
|
|
603
|
+
const args = argv.slice(2);
|
|
604
|
+
if (args[0] === "git") {
|
|
605
|
+
if (args[1] === "credential") {
|
|
606
|
+
try {
|
|
607
|
+
await runGitCredentialHelper(args[2] ?? "");
|
|
608
|
+
}
|
|
609
|
+
catch (error) {
|
|
610
|
+
console.error(String(error));
|
|
611
|
+
process.exitCode = 1;
|
|
612
|
+
}
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
if (args[1] === "sync") {
|
|
616
|
+
const code = await runInstafyGitSync(args.slice(2));
|
|
617
|
+
process.exitCode = code;
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
const code = runInstafyGit(args.slice(1));
|
|
621
|
+
process.exitCode = code;
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
628
624
|
await program.parseAsync(argv);
|
|
629
625
|
}
|
|
630
626
|
if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) {
|
package/dist/org.js
CHANGED
|
@@ -5,7 +5,7 @@ export async function listOrganizations(params) {
|
|
|
5
5
|
const controllerUrl = resolveControllerUrl({ controllerUrl: params.controllerUrl ?? null });
|
|
6
6
|
const token = resolveUserAccessToken({ accessToken: params.accessToken ?? null });
|
|
7
7
|
if (!token) {
|
|
8
|
-
throw formatAuthRequiredError({ retryCommand: "instafy org
|
|
8
|
+
throw formatAuthRequiredError({ retryCommand: "instafy org list" });
|
|
9
9
|
}
|
|
10
10
|
const response = await fetch(`${controllerUrl}/orgs`, {
|
|
11
11
|
headers: { authorization: `Bearer ${token}` },
|
package/dist/project.js
CHANGED
|
@@ -67,7 +67,7 @@ export async function listProjects(options) {
|
|
|
67
67
|
const controllerUrl = resolveControllerUrl({ controllerUrl: options.controllerUrl ?? null });
|
|
68
68
|
const token = resolveUserAccessToken({ accessToken: options.accessToken ?? null });
|
|
69
69
|
if (!token) {
|
|
70
|
-
throw formatAuthRequiredError({ retryCommand: "instafy project
|
|
70
|
+
throw formatAuthRequiredError({ retryCommand: "instafy project list" });
|
|
71
71
|
}
|
|
72
72
|
const orgs = await fetchOrganizations(controllerUrl, token);
|
|
73
73
|
let targetOrgs = orgs;
|
|
@@ -124,7 +124,7 @@ export async function projectInit(options) {
|
|
|
124
124
|
});
|
|
125
125
|
if (!token) {
|
|
126
126
|
throw formatAuthRequiredError({
|
|
127
|
-
retryCommand: "instafy project
|
|
127
|
+
retryCommand: "instafy project init",
|
|
128
128
|
advancedHint: "pass --access-token or set INSTAFY_ACCESS_TOKEN / SUPABASE_ACCESS_TOKEN",
|
|
129
129
|
});
|
|
130
130
|
}
|
|
@@ -185,7 +185,7 @@ export function projectProfile(options) {
|
|
|
185
185
|
const rootDir = path.resolve(options.path ?? process.cwd());
|
|
186
186
|
const manifestInfo = findProjectManifest(rootDir);
|
|
187
187
|
if (!manifestInfo.path || !manifestInfo.manifest) {
|
|
188
|
-
throw new Error("No project configured. Run `instafy project
|
|
188
|
+
throw new Error("No project configured. Run `instafy project init` in this folder first.");
|
|
189
189
|
}
|
|
190
190
|
const shouldUpdate = options.unset === true || typeof options.profile === "string";
|
|
191
191
|
const currentProfile = typeof manifestInfo.manifest.profile === "string" && manifestInfo.manifest.profile.trim()
|
package/dist/runtime.js
CHANGED
|
@@ -29,7 +29,7 @@ function resolveRuntimeBinary() {
|
|
|
29
29
|
return candidate;
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
|
-
throw new Error("runtime-agent binary not found. If you're in the instafy repo, run `cargo build -p runtime-agent`. Otherwise install Docker and rerun `instafy runtime
|
|
32
|
+
throw new Error("runtime-agent binary not found. If you're in the instafy repo, run `cargo build -p runtime-agent`. Otherwise install Docker and rerun `instafy runtime start`.");
|
|
33
33
|
}
|
|
34
34
|
function tryResolveRuntimeBinary() {
|
|
35
35
|
try {
|
|
@@ -289,7 +289,7 @@ export async function runtimeStart(options) {
|
|
|
289
289
|
const manifestInfo = findProjectManifest(process.cwd());
|
|
290
290
|
const projectId = options.project ?? env["PROJECT_ID"] ?? manifestInfo.manifest?.projectId ?? null;
|
|
291
291
|
if (!projectId) {
|
|
292
|
-
throw new Error("No project configured. Run `instafy project
|
|
292
|
+
throw new Error("No project configured. Run `instafy project init` in this folder (recommended) or pass --project.");
|
|
293
293
|
}
|
|
294
294
|
env["PROJECT_ID"] = projectId;
|
|
295
295
|
const supabaseAccessToken = resolveSupabaseAccessToken(options, env);
|
package/dist/tunnel.js
CHANGED
|
@@ -32,7 +32,7 @@ function resolveProject(opts) {
|
|
|
32
32
|
if (manifest?.projectId) {
|
|
33
33
|
return manifest.projectId;
|
|
34
34
|
}
|
|
35
|
-
throw new Error("No project configured for this folder.\n\nNext:\n- instafy project
|
|
35
|
+
throw new Error("No project configured for this folder.\n\nNext:\n- instafy project init\n\nOr pass --project <uuid> / set PROJECT_ID.");
|
|
36
36
|
}
|
|
37
37
|
function resolveControllerUrl(opts) {
|
|
38
38
|
const explicit = opts.controllerUrl?.trim();
|
|
@@ -53,7 +53,7 @@ function resolveControllerUrl(opts) {
|
|
|
53
53
|
}
|
|
54
54
|
return resolveConfiguredControllerUrl() ?? "http://127.0.0.1:8788";
|
|
55
55
|
}
|
|
56
|
-
function resolveControllerToken(opts) {
|
|
56
|
+
function resolveControllerToken(opts, retryCommand = "instafy tunnel start") {
|
|
57
57
|
const explicit = opts.controllerToken?.trim();
|
|
58
58
|
if (explicit) {
|
|
59
59
|
return explicit;
|
|
@@ -74,7 +74,7 @@ function resolveControllerToken(opts) {
|
|
|
74
74
|
return serviceToken;
|
|
75
75
|
}
|
|
76
76
|
throw formatAuthRequiredError({
|
|
77
|
-
retryCommand
|
|
77
|
+
retryCommand,
|
|
78
78
|
advancedHint: "pass --access-token / --service-token, or set INSTAFY_ACCESS_TOKEN / SUPABASE_ACCESS_TOKEN / INSTAFY_SERVICE_TOKEN",
|
|
79
79
|
});
|
|
80
80
|
}
|
|
@@ -336,7 +336,7 @@ export async function startTunnelDetached(opts) {
|
|
|
336
336
|
exitedEarly = true;
|
|
337
337
|
exitMessage = code !== null ? `code ${code}` : `signal ${signal}`;
|
|
338
338
|
});
|
|
339
|
-
// Give rathole a moment to fail fast, so `instafy tunnel` can report errors.
|
|
339
|
+
// Give rathole a moment to fail fast, so `instafy tunnel start` can report errors.
|
|
340
340
|
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
341
341
|
if (exitedEarly || !isProcessAlive(child.pid ?? -1)) {
|
|
342
342
|
const logTail = (() => {
|
|
@@ -382,7 +382,7 @@ export async function stopTunnelSession(opts) {
|
|
|
382
382
|
if (active.length === 1) {
|
|
383
383
|
return stopTunnelSession({ ...opts, tunnelId: active[0]?.tunnelId });
|
|
384
384
|
}
|
|
385
|
-
throw new Error("Tunnel id is required. Use `instafy tunnel
|
|
385
|
+
throw new Error("Tunnel id is required. Use `instafy tunnel list` to find it.");
|
|
386
386
|
}
|
|
387
387
|
const entry = removeTunnelState(tunnelId);
|
|
388
388
|
if (!entry) {
|
|
@@ -405,7 +405,7 @@ export async function stopTunnelSession(opts) {
|
|
|
405
405
|
}
|
|
406
406
|
}
|
|
407
407
|
}
|
|
408
|
-
const controllerToken = resolveControllerToken(opts);
|
|
408
|
+
const controllerToken = resolveControllerToken(opts, `instafy tunnel stop ${tunnelId}`);
|
|
409
409
|
await revokeTunnel(entry.controllerUrl, controllerToken, entry.projectId, entry.tunnelId);
|
|
410
410
|
try {
|
|
411
411
|
fs.rmSync(entry.workdir, { recursive: true, force: true });
|
|
@@ -429,7 +429,7 @@ export function resolveTunnelLogFile(tunnelId) {
|
|
|
429
429
|
if (active.length === 1) {
|
|
430
430
|
return active[0];
|
|
431
431
|
}
|
|
432
|
-
throw new Error("Tunnel id is required. Use `instafy tunnel
|
|
432
|
+
throw new Error("Tunnel id is required. Use `instafy tunnel list` to find it.");
|
|
433
433
|
}
|
|
434
434
|
export async function tailTunnelLogs(options) {
|
|
435
435
|
const entry = resolveTunnelLogFile(options.tunnelId);
|
package/package.json
CHANGED