sfora-cli 0.2.0 → 0.3.0
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 +6 -2
- package/dist/cli.js +31 -15
- package/dist/config.d.ts +5 -0
- package/dist/config.js +12 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -30,10 +30,14 @@ shell interpreter backs the interactive mode).
|
|
|
30
30
|
```bash
|
|
31
31
|
npm i -g sfora-cli # installs the `sfora` command; or: npx sfora-cli …
|
|
32
32
|
|
|
33
|
-
sfora login # authorize
|
|
34
|
-
sfora --
|
|
33
|
+
sfora login # authorize as you (browser; saves your key)
|
|
34
|
+
sfora login --bot ci # …or as a named bot (its own key)
|
|
35
|
+
sfora # interactive shell — explore /projects, cat, grep
|
|
35
36
|
```
|
|
36
37
|
|
|
38
|
+
One CLI for everyone: the default identity is **you**; add `--bot <name>` to any
|
|
39
|
+
command to run as that bot.
|
|
40
|
+
|
|
37
41
|
`sfora init` writes `~/.sfora/config.json` (chmod 600). Resolution precedence is
|
|
38
42
|
**flags > env (`SFORA_API_KEY` / `SFORA_URL` / `SFORA_ORG`) > config > default**,
|
|
39
43
|
so a saved config means no env vars on every run.
|
package/dist/cli.js
CHANGED
|
@@ -47,10 +47,12 @@ function parseArgs(argv) {
|
|
|
47
47
|
args.key = argv[++i];
|
|
48
48
|
else if (a.startsWith("--key="))
|
|
49
49
|
args.key = a.slice("--key=".length);
|
|
50
|
-
else if (a === "--agent")
|
|
51
|
-
args.
|
|
50
|
+
else if (a === "--bot" || a === "--agent")
|
|
51
|
+
args.bot = argv[++i];
|
|
52
|
+
else if (a.startsWith("--bot="))
|
|
53
|
+
args.bot = a.slice("--bot=".length);
|
|
52
54
|
else if (a.startsWith("--agent="))
|
|
53
|
-
args.
|
|
55
|
+
args.bot = a.slice("--agent=".length);
|
|
54
56
|
else if (a === "--web")
|
|
55
57
|
args.web = argv[++i];
|
|
56
58
|
else if (a.startsWith("--web="))
|
|
@@ -77,7 +79,10 @@ function parseArgs(argv) {
|
|
|
77
79
|
const HELP = `sfora — the CLI for your sfora workspace
|
|
78
80
|
|
|
79
81
|
Get started:
|
|
80
|
-
sfora login
|
|
82
|
+
sfora login Authorize as you (saves your key)
|
|
83
|
+
sfora login --bot <name> Authorize as a named bot (its own key)
|
|
84
|
+
|
|
85
|
+
Run as a bot: add --bot <name> to any command (default identity is you).
|
|
81
86
|
|
|
82
87
|
Push markdown straight to sfora:
|
|
83
88
|
sfora post <file.md> [--project <slug>] [--draft] Publish (or draft) a post
|
|
@@ -149,7 +154,7 @@ async function runLogin(args, baseUrl) {
|
|
|
149
154
|
const res = await fetch(`${base}/v1/cli/start`, {
|
|
150
155
|
method: "POST",
|
|
151
156
|
headers: { "content-type": "application/json" },
|
|
152
|
-
body: JSON.stringify({ agent: args.
|
|
157
|
+
body: JSON.stringify({ agent: args.bot }),
|
|
153
158
|
});
|
|
154
159
|
if (!res.ok)
|
|
155
160
|
throw new Error(`start failed (${res.status})`);
|
|
@@ -165,7 +170,7 @@ async function runLogin(args, baseUrl) {
|
|
|
165
170
|
? `${webOverride.replace(/\/+$/, "")}/cli?code=${encodeURIComponent(start.userCode)}`
|
|
166
171
|
: start.verifyUrl;
|
|
167
172
|
console.log(`\n${colors.bold}Authorize the sfora CLI${colors.reset}` +
|
|
168
|
-
(args.
|
|
173
|
+
(args.bot ? ` ${colors.dim}(bot: ${args.bot})${colors.reset}` : "") +
|
|
169
174
|
`\n ${colors.cyan}${verifyUrl}${colors.reset}\n ${colors.dim}code: ${start.userCode}${colors.reset}\n`);
|
|
170
175
|
openBrowser(verifyUrl);
|
|
171
176
|
process.stdout.write(`${colors.dim}Waiting for approval…${colors.reset}`);
|
|
@@ -186,12 +191,21 @@ async function runLogin(args, baseUrl) {
|
|
|
186
191
|
continue;
|
|
187
192
|
}
|
|
188
193
|
if (data.status === "approved" && data.apiKey) {
|
|
189
|
-
const
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
194
|
+
const cfg = await readConfig();
|
|
195
|
+
const next = { ...cfg, url: base };
|
|
196
|
+
if (args.bot) {
|
|
197
|
+
// Save under the bot's slot; keep the user's personal key intact.
|
|
198
|
+
next.bots = {
|
|
199
|
+
...cfg.bots,
|
|
200
|
+
[args.bot]: { apiKey: data.apiKey, org: data.orgSlug ?? undefined },
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
next.apiKey = data.apiKey;
|
|
205
|
+
next.org = data.orgSlug ?? cfg.org;
|
|
206
|
+
}
|
|
207
|
+
const path = await writeConfig(next);
|
|
208
|
+
console.log(`\n${colors.green}✓${colors.reset} Logged in as ${data.name ?? "you"}${data.orgSlug ? ` ${colors.dim}(${data.orgSlug})${colors.reset}` : ""}${args.bot ? ` ${colors.dim}[bot: ${args.bot}]${colors.reset}` : ""} — saved ${path}`);
|
|
195
209
|
return;
|
|
196
210
|
}
|
|
197
211
|
if (data.status === "expired" || data.status === "unknown") {
|
|
@@ -315,7 +329,7 @@ async function main() {
|
|
|
315
329
|
return;
|
|
316
330
|
}
|
|
317
331
|
const cfg = await readConfig();
|
|
318
|
-
const settings = resolveSettings({ url: args.url, apiKey: args.key, org: args.org }, cfg);
|
|
332
|
+
const settings = resolveSettings({ url: args.url, apiKey: args.key, org: args.org, bot: args.bot }, cfg);
|
|
319
333
|
const { url: baseUrl, apiKey } = settings;
|
|
320
334
|
if (args.command === "mcp-config") {
|
|
321
335
|
printMcpConfig(settings);
|
|
@@ -326,8 +340,10 @@ async function main() {
|
|
|
326
340
|
return;
|
|
327
341
|
}
|
|
328
342
|
if (!apiKey) {
|
|
329
|
-
process.stderr.write(
|
|
330
|
-
|
|
343
|
+
process.stderr.write(args.bot
|
|
344
|
+
? `${colors.red}error:${colors.reset} no key for bot '${args.bot}'. Run \`sfora login --bot ${args.bot}\`.\n`
|
|
345
|
+
: `${colors.red}error:${colors.reset} no API key found.\n` +
|
|
346
|
+
`Run \`sfora login\` to authorize, or \`sfora init\` to set one.\n`);
|
|
331
347
|
process.exitCode = 1;
|
|
332
348
|
return;
|
|
333
349
|
}
|
package/dist/config.d.ts
CHANGED
|
@@ -2,6 +2,10 @@ export interface SforaConfig {
|
|
|
2
2
|
url?: string;
|
|
3
3
|
apiKey?: string;
|
|
4
4
|
org?: string;
|
|
5
|
+
bots?: Record<string, {
|
|
6
|
+
apiKey?: string;
|
|
7
|
+
org?: string;
|
|
8
|
+
}>;
|
|
5
9
|
}
|
|
6
10
|
export declare const CONFIG_PATH: string;
|
|
7
11
|
export declare const DEFAULT_URL = "http://localhost:2222";
|
|
@@ -16,4 +20,5 @@ export declare function resolveSettings(flags: {
|
|
|
16
20
|
url?: string;
|
|
17
21
|
apiKey?: string;
|
|
18
22
|
org?: string;
|
|
23
|
+
bot?: string;
|
|
19
24
|
}, cfg: SforaConfig): ResolvedSettings;
|
package/dist/config.js
CHANGED
|
@@ -31,8 +31,19 @@ function pick(...vals) {
|
|
|
31
31
|
return vals.find((v) => v != null && v !== "");
|
|
32
32
|
}
|
|
33
33
|
export function resolveSettings(flags, cfg) {
|
|
34
|
+
const url = pick(flags.url, process.env.SFORA_URL, cfg.url) ?? DEFAULT_URL;
|
|
35
|
+
// `--bot <name>`: act as that saved bot. Don't fall back to the user's key /
|
|
36
|
+
// SFORA_API_KEY env, so the identity is never silently the wrong one.
|
|
37
|
+
if (flags.bot) {
|
|
38
|
+
const bot = cfg.bots?.[flags.bot];
|
|
39
|
+
return {
|
|
40
|
+
url,
|
|
41
|
+
apiKey: pick(flags.apiKey, bot?.apiKey),
|
|
42
|
+
org: pick(flags.org, bot?.org, cfg.org),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
34
45
|
return {
|
|
35
|
-
url
|
|
46
|
+
url,
|
|
36
47
|
apiKey: pick(flags.apiKey, process.env.SFORA_API_KEY, cfg.apiKey),
|
|
37
48
|
org: pick(flags.org, process.env.SFORA_ORG, cfg.org),
|
|
38
49
|
};
|
package/package.json
CHANGED