@spendgraph/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 +8 -0
- package/dist/args.js +0 -7
- package/dist/cli.js +0 -6
- package/dist/commands/config.js +0 -1
- package/dist/commands/dashboard.js +0 -5
- package/dist/commands/prompts.js +0 -1
- package/dist/commands/skills.js +0 -6
- package/dist/commands/tools.js +0 -1
- package/dist/connect.js +0 -7
- package/dist/context.js +0 -7
- package/dist/env.js +0 -14
- package/dist/errors.js +0 -1
- package/dist/help.js +0 -2
- package/dist/output.js +0 -7
- package/dist/registry.js +0 -3
- package/dist/skills.js +0 -2
- package/dist/store.js +0 -9
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
`sg` drives the spendgraph dashboard from a terminal: write a prompt, declare a
|
|
4
4
|
tool, mint a key, read what a project spent.
|
|
5
5
|
|
|
6
|
+
```sh
|
|
7
|
+
npm install -g @spendgraph/cli # or: npx @spendgraph/cli --help
|
|
8
|
+
```
|
|
9
|
+
|
|
6
10
|
It is a front-end over `@spendgraph/sdk` and nothing else. Every command is one
|
|
7
11
|
SDK call, so the base URL, auth, retries and error shapes are decided in one
|
|
8
12
|
place and this package only decides what to call and how to print it.
|
|
@@ -147,3 +151,7 @@ An existing `SKILL.md` is left alone unless `--force` says otherwise.
|
|
|
147
151
|
|
|
148
152
|
The difference between 1 and 2 is worth scripting against: a 2 never reached the
|
|
149
153
|
network, so retrying it changes nothing.
|
|
154
|
+
|
|
155
|
+
## License
|
|
156
|
+
|
|
157
|
+
MIT
|
package/dist/args.js
CHANGED
|
@@ -1,11 +1,4 @@
|
|
|
1
1
|
const isFlag = (token) => token.startsWith("--");
|
|
2
|
-
/**
|
|
3
|
-
* Splits argv into words and flags.
|
|
4
|
-
*
|
|
5
|
-
* `--name value`, `--name=value` and a bare `--name` all set `name`; `--no-name`
|
|
6
|
-
* clears it. A value starting with a single dash is still a value, so a negative
|
|
7
|
-
* number reaches the command rather than being read as the next flag.
|
|
8
|
-
*/
|
|
9
2
|
export function parse(argv) {
|
|
10
3
|
const words = [];
|
|
11
4
|
const flags = {};
|
package/dist/cli.js
CHANGED
|
@@ -9,12 +9,6 @@ import { groupHelp, overview } from "./help.js";
|
|
|
9
9
|
import { render } from "./output.js";
|
|
10
10
|
import { commandNamed, groupNamed } from "./registry.js";
|
|
11
11
|
const HINT = "A dashboard session is needed: for everything under key, project, pricing and credential, and for every write to a prompt or a tool. An sg_ key reads prompts and tools and writes usage. Set SPENDGRAPH_SESSION.";
|
|
12
|
-
/**
|
|
13
|
-
* Runs one command line and returns the exit code.
|
|
14
|
-
*
|
|
15
|
-
* 0 is a reply, 1 is the server refusing, 2 is the line being wrong — so a
|
|
16
|
-
* script can tell a bad request from a bad password without parsing text.
|
|
17
|
-
*/
|
|
18
12
|
export async function main(argv, io, opts = {}) {
|
|
19
13
|
const parsed = parse(argv);
|
|
20
14
|
const [groupName, commandName, ...positional] = parsed.words;
|
package/dist/commands/config.js
CHANGED
|
@@ -53,7 +53,6 @@ function unset(ctx) {
|
|
|
53
53
|
writeStore(path, current);
|
|
54
54
|
return { removed: [{ variable, file: path }] };
|
|
55
55
|
}
|
|
56
|
-
/** The global config, and what the CLI resolved from it, a `.env` and the environment. */
|
|
57
56
|
export const config = {
|
|
58
57
|
name: "config",
|
|
59
58
|
summary: "credentials kept globally, and what the CLI resolved",
|
|
@@ -11,7 +11,6 @@ function events(ctx) {
|
|
|
11
11
|
throw new UsageError("--file must hold { events: [...] }.");
|
|
12
12
|
return list;
|
|
13
13
|
}
|
|
14
|
-
/** Projects, their budgets and who is in them. */
|
|
15
14
|
export const projects = {
|
|
16
15
|
name: "project",
|
|
17
16
|
summary: "projects, budgets and members",
|
|
@@ -78,7 +77,6 @@ export const projects = {
|
|
|
78
77
|
},
|
|
79
78
|
],
|
|
80
79
|
};
|
|
81
|
-
/** API keys. Minting one needs a dashboard session, never a key. */
|
|
82
80
|
export const keys = {
|
|
83
81
|
name: "key",
|
|
84
82
|
summary: "api keys (session only)",
|
|
@@ -104,7 +102,6 @@ export const keys = {
|
|
|
104
102
|
},
|
|
105
103
|
],
|
|
106
104
|
};
|
|
107
|
-
/** Provider keys the server calls out with. Session only, deliberately. */
|
|
108
105
|
export const credentials = {
|
|
109
106
|
name: "credential",
|
|
110
107
|
summary: "provider keys the server holds (session only)",
|
|
@@ -126,7 +123,6 @@ export const credentials = {
|
|
|
126
123
|
},
|
|
127
124
|
],
|
|
128
125
|
};
|
|
129
|
-
/** What a model costs, and what the catalogue knows. */
|
|
130
126
|
export const pricing = {
|
|
131
127
|
name: "pricing",
|
|
132
128
|
summary: "model prices, coverage and the catalogue",
|
|
@@ -178,7 +174,6 @@ export const pricing = {
|
|
|
178
174
|
},
|
|
179
175
|
],
|
|
180
176
|
};
|
|
181
|
-
/** What was spent, by whom, on what. */
|
|
182
177
|
export const spend = {
|
|
183
178
|
name: "spend",
|
|
184
179
|
summary: "usage, stats, events and alerts",
|
package/dist/commands/prompts.js
CHANGED
|
@@ -14,7 +14,6 @@ function cases(ctx) {
|
|
|
14
14
|
throw new UsageError("--file must hold { cases: [...] }.");
|
|
15
15
|
return list;
|
|
16
16
|
}
|
|
17
|
-
/** Stored prompts: writing them, versioning them, running them and reading what they cost. */
|
|
18
17
|
export const prompts = {
|
|
19
18
|
name: "prompt",
|
|
20
19
|
summary: "stored prompts, their versions, datasets and rollouts",
|
package/dist/commands/skills.js
CHANGED
|
@@ -24,12 +24,6 @@ function write(ctx, skills) {
|
|
|
24
24
|
return { skill: skill.name, path, wrote: true };
|
|
25
25
|
});
|
|
26
26
|
}
|
|
27
|
-
/**
|
|
28
|
-
* The `skill` group, which documents itself alongside the groups it is given.
|
|
29
|
-
*
|
|
30
|
-
* Taking the groups as an argument is what keeps the reference generated rather
|
|
31
|
-
* than written down twice: a command added anywhere appears here on the next run.
|
|
32
|
-
*/
|
|
33
27
|
export function skillCommands(groups) {
|
|
34
28
|
const self = {
|
|
35
29
|
name: "skill",
|
package/dist/commands/tools.js
CHANGED
|
@@ -28,7 +28,6 @@ const saved = (ctx) => scoped(ctx, ctx.body({
|
|
|
28
28
|
effect: effect(ctx),
|
|
29
29
|
pinned: ctx.bool("pinned") || undefined,
|
|
30
30
|
}));
|
|
31
|
-
/** Tools the model can be offered: declaring them, describing them, retiring them. */
|
|
32
31
|
export const tools = {
|
|
33
32
|
name: "tool",
|
|
34
33
|
summary: "tools the model can be offered",
|
package/dist/connect.js
CHANGED
|
@@ -4,12 +4,6 @@ const pick = (parsed, name) => {
|
|
|
4
4
|
const value = parsed.flags[name];
|
|
5
5
|
return typeof value === "string" ? value : undefined;
|
|
6
6
|
};
|
|
7
|
-
/**
|
|
8
|
-
* Reads the credentials from flags first, the environment second.
|
|
9
|
-
*
|
|
10
|
-
* Both an `sg_` key and a dashboard session may be set. Which half of the API
|
|
11
|
-
* each one opens is the server's decision, not this file's.
|
|
12
|
-
*/
|
|
13
7
|
export function credentials(parsed, env = process.env) {
|
|
14
8
|
const baseUrl = pick(parsed, "base-url") ?? env.SPENDGRAPH_BASE_URL;
|
|
15
9
|
if (!baseUrl)
|
|
@@ -26,7 +20,6 @@ export function credentials(parsed, env = process.env) {
|
|
|
26
20
|
project: pick(parsed, "project") ?? env.SPENDGRAPH_PROJECT,
|
|
27
21
|
};
|
|
28
22
|
}
|
|
29
|
-
/** The client every command runs against. `fetch` is injected by tests. */
|
|
30
23
|
export function connect(creds, fetchImpl) {
|
|
31
24
|
return new Spendgraph({
|
|
32
25
|
baseUrl: creds.baseUrl,
|
package/dist/context.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import { UsageError } from "./errors.js";
|
|
3
|
-
/** Builds the `Ctx` a command runs against. `sg` is null for a command that stays local. */
|
|
4
3
|
export function context(sg, parsed, positional, loaded) {
|
|
5
4
|
const flag = (name) => {
|
|
6
5
|
const value = parsed.flags[name];
|
|
@@ -76,12 +75,6 @@ function fileBody(path) {
|
|
|
76
75
|
throw new UsageError(`--file ${path} is not a JSON object.`);
|
|
77
76
|
}
|
|
78
77
|
}
|
|
79
|
-
/**
|
|
80
|
-
* A save body with its `projectId` settled.
|
|
81
|
-
*
|
|
82
|
-
* `--file` carrying one is enough on its own, so a definition checked into a
|
|
83
|
-
* repo applies without the project being named twice.
|
|
84
|
-
*/
|
|
85
78
|
export function scoped(ctx, body) {
|
|
86
79
|
return typeof body.projectId === "string" && body.projectId
|
|
87
80
|
? body
|
package/dist/env.js
CHANGED
|
@@ -2,12 +2,6 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
2
2
|
import { dirname, join, resolve } from "node:path";
|
|
3
3
|
import { UsageError } from "./errors.js";
|
|
4
4
|
const QUOTED = /^(['"])([\s\S]*)\1$/;
|
|
5
|
-
/**
|
|
6
|
-
* Parses a `.env`: `KEY=value`, one per line.
|
|
7
|
-
*
|
|
8
|
-
* `export` prefixes, `#` comments, blank lines and surrounding quotes are all
|
|
9
|
-
* accepted, because every one of them appears in a file somebody wrote by hand.
|
|
10
|
-
*/
|
|
11
5
|
export function parseEnv(text) {
|
|
12
6
|
const out = {};
|
|
13
7
|
for (const raw of text.split(/\r?\n/)) {
|
|
@@ -24,7 +18,6 @@ export function parseEnv(text) {
|
|
|
24
18
|
}
|
|
25
19
|
return out;
|
|
26
20
|
}
|
|
27
|
-
/** The nearest `.env` at or above `from`, or undefined where there is none. */
|
|
28
21
|
export function findEnvFile(from) {
|
|
29
22
|
let dir = resolve(from);
|
|
30
23
|
for (;;) {
|
|
@@ -37,13 +30,6 @@ export function findEnvFile(from) {
|
|
|
37
30
|
dir = parent;
|
|
38
31
|
}
|
|
39
32
|
}
|
|
40
|
-
/**
|
|
41
|
-
* The environment a command runs against, layered.
|
|
42
|
-
*
|
|
43
|
-
* Highest wins: the real environment, then the nearest `.env`, then the global
|
|
44
|
-
* config. A variable already exported is never quietly overruled by a file, and
|
|
45
|
-
* a project's `.env` beats a global default the same way a local git config does.
|
|
46
|
-
*/
|
|
47
33
|
export function loadEnv(layers) {
|
|
48
34
|
const { cwd, base, explicit, store } = layers;
|
|
49
35
|
const file = explicit ? resolve(explicit) : findEnvFile(cwd);
|
package/dist/errors.js
CHANGED
package/dist/help.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { GROUPS } from "./registry.js";
|
|
2
2
|
const pad = (text, width) => text.padEnd(width);
|
|
3
|
-
/** What `sg` alone prints: the groups, and how to prove who you are. */
|
|
4
3
|
export function overview() {
|
|
5
4
|
const width = Math.max(...GROUPS.map((group) => group.name.length));
|
|
6
5
|
return [
|
|
@@ -25,7 +24,6 @@ export function overview() {
|
|
|
25
24
|
"Run `sg <group>` to list its commands.",
|
|
26
25
|
].join("\n");
|
|
27
26
|
}
|
|
28
|
-
/** What `sg <group>` prints. */
|
|
29
27
|
export function groupHelp(group) {
|
|
30
28
|
const width = Math.max(...group.commands.map((command) => command.name.length));
|
|
31
29
|
return [
|
package/dist/output.js
CHANGED
|
@@ -37,13 +37,6 @@ function table(list) {
|
|
|
37
37
|
...(hidden > 0 ? [`(${hidden} more ${hidden === 1 ? "column" : "columns"} — use --json)`] : []),
|
|
38
38
|
].join("\n");
|
|
39
39
|
}
|
|
40
|
-
/**
|
|
41
|
-
* Renders a reply for a terminal, or as JSON where `--json` asked for it.
|
|
42
|
-
*
|
|
43
|
-
* A list of flat rows becomes a table because that is what a person reads;
|
|
44
|
-
* anything else stays JSON rather than being flattened into a shape that hides
|
|
45
|
-
* a nested field.
|
|
46
|
-
*/
|
|
47
40
|
export function render(value, json) {
|
|
48
41
|
if (json)
|
|
49
42
|
return JSON.stringify(value, null, 2);
|
package/dist/registry.js
CHANGED
|
@@ -4,9 +4,6 @@ import { prompts } from "./commands/prompts.js";
|
|
|
4
4
|
import { skillCommands } from "./commands/skills.js";
|
|
5
5
|
import { tools } from "./commands/tools.js";
|
|
6
6
|
const RESOURCES = [prompts, tools, projects, keys, credentials, pricing, spend, config];
|
|
7
|
-
/** Every group the CLI offers, in the order help lists them. */
|
|
8
7
|
export const GROUPS = [...RESOURCES, skillCommands(RESOURCES)];
|
|
9
|
-
/** The group by that name, or undefined. */
|
|
10
8
|
export const groupNamed = (name) => GROUPS.find((group) => group.name === name);
|
|
11
|
-
/** The command by that name within a group, or undefined. */
|
|
12
9
|
export const commandNamed = (group, name) => group.commands.find((command) => command.name === name);
|
package/dist/skills.js
CHANGED
|
@@ -194,7 +194,5 @@ sg prompt put-cases pr_123 --file ./cases.json
|
|
|
194
194
|
first if you mean to append.
|
|
195
195
|
`,
|
|
196
196
|
});
|
|
197
|
-
/** The skills this CLI ships, with the command reference rendered from the live registry. */
|
|
198
197
|
export const catalogue = (groups) => [operating(groups), authoring()];
|
|
199
|
-
/** One skill as the SKILL.md file an agent loads. */
|
|
200
198
|
export const document = (skill) => `---\nname: ${skill.name}\ndescription: ${skill.description}\n---\n\n${skill.body}`;
|
package/dist/store.js
CHANGED
|
@@ -2,19 +2,16 @@ import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "n
|
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
4
|
import { UsageError } from "./errors.js";
|
|
5
|
-
/** The variables the global config may hold. Nothing else is written. */
|
|
6
5
|
export const SETTABLE = [
|
|
7
6
|
"SPENDGRAPH_BASE_URL",
|
|
8
7
|
"SPENDGRAPH_API_KEY",
|
|
9
8
|
"SPENDGRAPH_SESSION",
|
|
10
9
|
"SPENDGRAPH_PROJECT",
|
|
11
10
|
];
|
|
12
|
-
/** Where the global config lives: `$XDG_CONFIG_HOME/spendgraph/config.json`, or under `~/.config`. */
|
|
13
11
|
export function storePath(env = process.env) {
|
|
14
12
|
const base = env.XDG_CONFIG_HOME || join(env.HOME || homedir(), ".config");
|
|
15
13
|
return join(base, "spendgraph", "config.json");
|
|
16
14
|
}
|
|
17
|
-
/** What the global config holds, or nothing where there is no file. */
|
|
18
15
|
export function readStore(path) {
|
|
19
16
|
if (!existsSync(path))
|
|
20
17
|
return {};
|
|
@@ -32,12 +29,6 @@ export function readStore(path) {
|
|
|
32
29
|
.filter(([key]) => SETTABLE.includes(key))
|
|
33
30
|
.map(([key, value]) => [key, String(value)]));
|
|
34
31
|
}
|
|
35
|
-
/**
|
|
36
|
-
* Writes the global config, readable only by its owner.
|
|
37
|
-
*
|
|
38
|
-
* It holds an API key, so the mode is set after the write as well as during it —
|
|
39
|
-
* an existing file keeps whatever mode it already had otherwise.
|
|
40
|
-
*/
|
|
41
32
|
export function writeStore(path, values) {
|
|
42
33
|
mkdirSync(dirname(path), { recursive: true });
|
|
43
34
|
const kept = Object.fromEntries(Object.entries(values).filter(([, value]) => value));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spendgraph/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Drive the spendgraph dashboard from a terminal: prompts, tools, projects, keys and spend.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -37,11 +37,11 @@
|
|
|
37
37
|
"README.md"
|
|
38
38
|
],
|
|
39
39
|
"scripts": {
|
|
40
|
-
"build": "tsc -p tsconfig.json",
|
|
40
|
+
"build": "tsc -p tsconfig.json --emitDeclarationOnly && tsc -p tsconfig.json --declaration false --removeComments",
|
|
41
41
|
"test": "vitest run"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@spendgraph/sdk": "^0.
|
|
44
|
+
"@spendgraph/sdk": "^0.3.0"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"typescript": "^5"
|