novence 0.1.2 → 0.1.4
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/dist/commands/data.d.ts +2 -0
- package/dist/commands/data.js +62 -0
- package/dist/commands/domains.js +33 -4
- package/dist/commands/rollback.js +1 -1
- package/dist/commands/site-mcp.d.ts +2 -0
- package/dist/commands/site-mcp.js +57 -0
- package/dist/index.js +17 -0
- package/dist/lib/auth.js +19 -3
- package/package.json +2 -2
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { Novence } from "@novence/sdk";
|
|
3
|
+
import { loadApiKey, resolveApiBase } from "../lib/config.js";
|
|
4
|
+
import { flagString } from "../lib/args.js";
|
|
5
|
+
import { explain } from "../lib/errors.js";
|
|
6
|
+
import { fail, isJsonMode, json, result, step } from "../lib/ui.js";
|
|
7
|
+
async function resolveProjectId(client, explicit) {
|
|
8
|
+
if (explicit)
|
|
9
|
+
return explicit;
|
|
10
|
+
const { projects } = await client.projects.list();
|
|
11
|
+
if (projects.length === 0)
|
|
12
|
+
fail("No projects yet. Run `novence deploy ./dist` first.");
|
|
13
|
+
if (projects.length > 1) {
|
|
14
|
+
fail("You have more than one project — say which:\n" +
|
|
15
|
+
" novence data put <name> [file|-] --project <id>");
|
|
16
|
+
}
|
|
17
|
+
return projects[0].id;
|
|
18
|
+
}
|
|
19
|
+
function readJsonSource(src) {
|
|
20
|
+
let text;
|
|
21
|
+
if (!src || src === "-") {
|
|
22
|
+
text = readFileSync(0, "utf8");
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
text = readFileSync(src, "utf8");
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
return JSON.parse(text);
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
fail("File must be valid JSON (object or array).");
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export async function dataCommand(args) {
|
|
35
|
+
const apiBase = resolveApiBase(flagString(args.flags, "api"));
|
|
36
|
+
const apiKey = loadApiKey();
|
|
37
|
+
if (!apiKey)
|
|
38
|
+
fail("Not signed in. Run `novence login`.");
|
|
39
|
+
const client = new Novence({ apiKey, baseUrl: apiBase });
|
|
40
|
+
const sub = args.positionals[0] ?? "put";
|
|
41
|
+
const projectFlag = flagString(args.flags, "project");
|
|
42
|
+
try {
|
|
43
|
+
if (sub !== "put") {
|
|
44
|
+
fail("Usage: novence data put <name> [file|-] [--project <id>]");
|
|
45
|
+
}
|
|
46
|
+
const name = args.positionals[1];
|
|
47
|
+
if (!name)
|
|
48
|
+
fail("Usage: novence data put <name> [file|-] [--project <id>]");
|
|
49
|
+
const projectId = await resolveProjectId(client, projectFlag);
|
|
50
|
+
const value = readJsonSource(args.positionals[2]);
|
|
51
|
+
const res = await client.data.put(projectId, name, value);
|
|
52
|
+
if (isJsonMode()) {
|
|
53
|
+
json({ ok: true, projectId, ...res });
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
step(`Wrote ${res.path} (${res.bytes} bytes) — live in ~60s`);
|
|
57
|
+
result(String(res.url ?? res.path));
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
fail(explain(err, { apiBase, projectId: projectFlag }));
|
|
61
|
+
}
|
|
62
|
+
}
|
package/dist/commands/domains.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Novence } from "@novence/sdk";
|
|
2
2
|
import { loadApiKey, resolveApiBase } from "../lib/config.js";
|
|
3
|
-
import { flagString } from "../lib/args.js";
|
|
3
|
+
import { flagBool, flagString } from "../lib/args.js";
|
|
4
4
|
import { explain } from "../lib/errors.js";
|
|
5
5
|
import { fail, isJsonMode, json, result, step } from "../lib/ui.js";
|
|
6
6
|
async function resolveProjectId(client, explicit) {
|
|
@@ -31,19 +31,31 @@ export async function domainsCommand(args) {
|
|
|
31
31
|
fail("Usage: novence domains add <hostname> [--project <id>] [--primary www|apex]");
|
|
32
32
|
}
|
|
33
33
|
const projectId = await resolveProjectId(client, projectFlag);
|
|
34
|
-
const
|
|
34
|
+
const primaryRaw = flagString(args.flags, "primary");
|
|
35
|
+
if (primaryRaw && primaryRaw !== "www" && primaryRaw !== "apex") {
|
|
36
|
+
fail("--primary must be www or apex");
|
|
37
|
+
}
|
|
38
|
+
const redirect = args.flags.redirect === undefined ? undefined : flagBool(args.flags, "redirect");
|
|
39
|
+
const res = await client.domains.configure(projectId, hostname, {
|
|
40
|
+
primary: primaryRaw === "www" || primaryRaw === "apex" ? primaryRaw : undefined,
|
|
41
|
+
redirect,
|
|
42
|
+
});
|
|
35
43
|
if (isJsonMode()) {
|
|
36
44
|
json({ ok: true, projectId, hostname, ...res });
|
|
37
45
|
return;
|
|
38
46
|
}
|
|
39
47
|
step(`Attached ${hostname}`);
|
|
40
48
|
const records = res.instructions?.records ?? [];
|
|
49
|
+
const apexOptions = res.instructions?.apexOptions ?? [];
|
|
41
50
|
if (records.length === 0) {
|
|
42
51
|
result("No DNS records returned — check `novence domains status`.");
|
|
43
52
|
return;
|
|
44
53
|
}
|
|
45
54
|
result("");
|
|
46
|
-
|
|
55
|
+
if (res.instructions?.summary)
|
|
56
|
+
result(String(res.instructions.summary));
|
|
57
|
+
result("");
|
|
58
|
+
result("Required DNS records:");
|
|
47
59
|
result("");
|
|
48
60
|
for (const rec of records) {
|
|
49
61
|
const type = String(rec.type ?? "?");
|
|
@@ -51,8 +63,19 @@ export async function domainsCommand(args) {
|
|
|
51
63
|
const target = String(rec.target ?? rec.value ?? "");
|
|
52
64
|
result(` ${type.padEnd(14)} ${name.padEnd(20)} ${target}`);
|
|
53
65
|
}
|
|
66
|
+
if (apexOptions.length > 0) {
|
|
67
|
+
result("");
|
|
68
|
+
result("Then do exactly one apex option:");
|
|
69
|
+
result("");
|
|
70
|
+
for (const rec of apexOptions) {
|
|
71
|
+
const type = String(rec.type ?? "?");
|
|
72
|
+
const name = String(rec.name ?? rec.host ?? "@");
|
|
73
|
+
const target = String(rec.target ?? rec.value ?? "");
|
|
74
|
+
result(` ${type.padEnd(14)} ${name.padEnd(20)} ${target}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
54
77
|
result("");
|
|
55
|
-
result("Then check
|
|
78
|
+
result("Then check: novence domains status (follow dns.next until empty)");
|
|
56
79
|
return;
|
|
57
80
|
}
|
|
58
81
|
if (sub === "status") {
|
|
@@ -70,6 +93,12 @@ export async function domainsCommand(args) {
|
|
|
70
93
|
}
|
|
71
94
|
result(`domain ${host}`);
|
|
72
95
|
result(`status ${status.customDomainStatus ?? status.status ?? "unknown"}`);
|
|
96
|
+
const dns = status.dns;
|
|
97
|
+
if (dns) {
|
|
98
|
+
result(`www ${dns.www ?? "skipped"}`);
|
|
99
|
+
result(`apex ${dns.apex ?? "skipped"}`);
|
|
100
|
+
result(`next ${dns.next ?? "(none — wait for SSL)"}`);
|
|
101
|
+
}
|
|
73
102
|
const verifications = status.verifications;
|
|
74
103
|
if (Array.isArray(verifications) && verifications.length > 0) {
|
|
75
104
|
result(`checks ${verifications.length} verification record(s) pending or done`);
|
|
@@ -35,7 +35,7 @@ export async function rollbackCommand(args) {
|
|
|
35
35
|
});
|
|
36
36
|
return;
|
|
37
37
|
}
|
|
38
|
-
step("Restored previous snapshot (HTML cache ~60s; hard-refresh if the old page lingers).");
|
|
38
|
+
step("Restored previous snapshot (HTML and unhashed CSS/JS cache ~60s; hard-refresh if the old page lingers).");
|
|
39
39
|
if (previewUrl && previewUrl !== liveUrl)
|
|
40
40
|
step(`Preview ${previewUrl}`);
|
|
41
41
|
result(liveUrl);
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { Novence } from "@novence/sdk";
|
|
2
|
+
import { loadApiKey, resolveApiBase } from "../lib/config.js";
|
|
3
|
+
import { flagString } from "../lib/args.js";
|
|
4
|
+
import { explain } from "../lib/errors.js";
|
|
5
|
+
import { fail, isJsonMode, json, result, step } from "../lib/ui.js";
|
|
6
|
+
async function resolveProjectId(client, explicit) {
|
|
7
|
+
if (explicit)
|
|
8
|
+
return explicit;
|
|
9
|
+
const { projects } = await client.projects.list();
|
|
10
|
+
if (projects.length === 0)
|
|
11
|
+
fail("No projects yet. Run `novence deploy ./dist` first.");
|
|
12
|
+
if (projects.length > 1) {
|
|
13
|
+
fail("You have more than one project — say which:\n" +
|
|
14
|
+
" novence site-mcp on --project <id>");
|
|
15
|
+
}
|
|
16
|
+
return projects[0].id;
|
|
17
|
+
}
|
|
18
|
+
export async function siteMcpCommand(args) {
|
|
19
|
+
const apiBase = resolveApiBase(flagString(args.flags, "api"));
|
|
20
|
+
const apiKey = loadApiKey();
|
|
21
|
+
if (!apiKey)
|
|
22
|
+
fail("Not signed in. Run `novence login`.");
|
|
23
|
+
const client = new Novence({ apiKey, baseUrl: apiBase });
|
|
24
|
+
const sub = (args.positionals[0] ?? "status").toLowerCase();
|
|
25
|
+
const projectFlag = flagString(args.flags, "project");
|
|
26
|
+
try {
|
|
27
|
+
const projectId = await resolveProjectId(client, projectFlag);
|
|
28
|
+
if (sub === "status") {
|
|
29
|
+
const { project } = await client.projects.get(projectId);
|
|
30
|
+
const mode = project.siteMcp ?? "off";
|
|
31
|
+
const mcpUrl = `${apiBase.replace(/\/$/, "")}/sites/${project.suffix}/mcp`;
|
|
32
|
+
if (isJsonMode()) {
|
|
33
|
+
json({ ok: true, projectId, siteMcp: mode, mcpUrl, wellKnown: `${project.url}/.well-known/mcp.json` });
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
step(`siteMcp=${mode}`);
|
|
37
|
+
result(mcpUrl);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (sub !== "on" && sub !== "off" && sub !== "forms" && sub !== "read") {
|
|
41
|
+
fail("Usage: novence site-mcp on|off|forms|status [--project <id>]");
|
|
42
|
+
}
|
|
43
|
+
const siteMcp = sub === "on" ? "read" : sub;
|
|
44
|
+
const { project } = await client.projects.update(projectId, { siteMcp });
|
|
45
|
+
const mode = project.siteMcp ?? siteMcp;
|
|
46
|
+
const mcpUrl = `${apiBase.replace(/\/$/, "")}/sites/${project.suffix}/mcp`;
|
|
47
|
+
if (isJsonMode()) {
|
|
48
|
+
json({ ok: true, projectId, siteMcp: mode, mcpUrl });
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
step(`siteMcp=${mode}`);
|
|
52
|
+
result(mcpUrl);
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
fail(explain(err, { apiBase, projectId: projectFlag }));
|
|
56
|
+
}
|
|
57
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -16,12 +16,16 @@ import { domainsCommand } from "./commands/domains.js";
|
|
|
16
16
|
import { keysCommand } from "./commands/keys.js";
|
|
17
17
|
import { checksCommand } from "./commands/checks.js";
|
|
18
18
|
import { rollbackCommand } from "./commands/rollback.js";
|
|
19
|
+
import { dataCommand } from "./commands/data.js";
|
|
20
|
+
import { siteMcpCommand } from "./commands/site-mcp.js";
|
|
19
21
|
const HELP = `
|
|
20
22
|
novence — deploy a static site to the edge
|
|
21
23
|
|
|
22
24
|
Usage
|
|
23
25
|
novence deploy [dir|file] Upload a directory or one .html file and publish it live
|
|
24
26
|
novence rollback Restore the previous live snapshot (Pro/Scale)
|
|
27
|
+
novence data put <name> Write /data/{name}.json live without a deploy (Pro/Scale)
|
|
28
|
+
novence site-mcp on|off|forms Visitor MCP on the live site (Pro/Scale; off by default)
|
|
25
29
|
novence open [project] Open a project's live URL
|
|
26
30
|
novence checks [project] Quality-gate results for the latest deploy
|
|
27
31
|
novence domains add <host> Attach a custom domain (prints DNS records)
|
|
@@ -46,6 +50,12 @@ const HELP = `
|
|
|
46
50
|
--project <id> Project (default: first in the list)
|
|
47
51
|
--deployment <id> Specific retained deploy (last 5)
|
|
48
52
|
|
|
53
|
+
Live JSON (Pro/Scale)
|
|
54
|
+
novence data put menu ./menu.json
|
|
55
|
+
novence data put hours - Read JSON from stdin
|
|
56
|
+
--project <id> Project (default: first in the list)
|
|
57
|
+
Free: upload /data/*.json and deploy instead.
|
|
58
|
+
|
|
49
59
|
Global
|
|
50
60
|
--json Machine-readable output (implies --no-input)
|
|
51
61
|
--no-input Never prompt; fail instead
|
|
@@ -58,6 +68,7 @@ const HELP = `
|
|
|
58
68
|
|
|
59
69
|
Auth
|
|
60
70
|
Reads NOVENCE_API_KEY, then ./.novence-key, then ~/.novence/credentials.
|
|
71
|
+
NOVENCE_ATTRIBUTION, when set to JSON, is sent with the first signup.
|
|
61
72
|
|
|
62
73
|
Docs https://novence.ai/docs
|
|
63
74
|
`;
|
|
@@ -92,6 +103,8 @@ async function main() {
|
|
|
92
103
|
login: ["email", "recover"],
|
|
93
104
|
whoami: [],
|
|
94
105
|
rollback: ["project", "deployment"],
|
|
106
|
+
data: ["project"],
|
|
107
|
+
"site-mcp": ["project"],
|
|
95
108
|
};
|
|
96
109
|
const allowed = ALLOWED[args.command];
|
|
97
110
|
if (allowed) {
|
|
@@ -121,6 +134,10 @@ async function main() {
|
|
|
121
134
|
return whoamiCommand(args);
|
|
122
135
|
case "rollback":
|
|
123
136
|
return rollbackCommand(args);
|
|
137
|
+
case "data":
|
|
138
|
+
return dataCommand(args);
|
|
139
|
+
case "site-mcp":
|
|
140
|
+
return siteMcpCommand(args);
|
|
124
141
|
default:
|
|
125
142
|
fail(`Unknown command: ${args.command}\n Run \`novence --help\` for usage.`);
|
|
126
143
|
}
|
package/dist/lib/auth.js
CHANGED
|
@@ -1,6 +1,22 @@
|
|
|
1
|
-
import { bootstrapAccount, claimAccount, createDemoAccount, verifyEmail } from "@novence/sdk";
|
|
1
|
+
import { bootstrapAccount, claimAccount, createDemoAccount, verifyEmail, } from "@novence/sdk";
|
|
2
2
|
import { loadApiKey, saveApiKey } from "./config.js";
|
|
3
3
|
import { fail, prompt, step, warn, withSpin } from "./ui.js";
|
|
4
|
+
function attributionFromEnv() {
|
|
5
|
+
const raw = process.env.NOVENCE_ATTRIBUTION?.trim();
|
|
6
|
+
let parsed = {};
|
|
7
|
+
if (raw) {
|
|
8
|
+
try {
|
|
9
|
+
const value = JSON.parse(raw);
|
|
10
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
11
|
+
parsed = value;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
parsed = {};
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return { ...parsed, method: "cli" };
|
|
19
|
+
}
|
|
4
20
|
/**
|
|
5
21
|
* Returns a usable API key. With no stored key and no `--email`, creates an
|
|
6
22
|
* anonymous demo account so deploy can print a live URL first.
|
|
@@ -11,7 +27,7 @@ export async function ensureApiKey(opts) {
|
|
|
11
27
|
return { apiKey: existing, fresh: false, anonymous: false };
|
|
12
28
|
if (opts.email)
|
|
13
29
|
return bootstrapWithEmail(opts.email, opts.apiBase, opts.interactive);
|
|
14
|
-
const demo = await withSpin("Creating a temporary account…", () => createDemoAccount(opts.apiBase));
|
|
30
|
+
const demo = await withSpin("Creating a temporary account…", () => createDemoAccount(opts.apiBase, attributionFromEnv()));
|
|
15
31
|
const path = saveApiKey(demo.apiKey);
|
|
16
32
|
step(`Anonymous account ${demo.accountId} · key saved to ${path}`);
|
|
17
33
|
warn("Unclaimed site — run `novence claim --email you@example.com` to keep it.");
|
|
@@ -22,7 +38,7 @@ async function bootstrapWithEmail(email, apiBase, interactive) {
|
|
|
22
38
|
fail(`Not a valid email: ${email}`);
|
|
23
39
|
let boot;
|
|
24
40
|
try {
|
|
25
|
-
boot = await withSpin(`Creating your account (${email})…`, () => bootstrapAccount(email, apiBase));
|
|
41
|
+
boot = await withSpin(`Creating your account (${email})…`, () => bootstrapAccount(email, apiBase, attributionFromEnv()));
|
|
26
42
|
}
|
|
27
43
|
catch (err) {
|
|
28
44
|
const message = err instanceof Error ? err.message : String(err);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "novence",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "CLI for Novence — deploy a static site to the edge in one command.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"access": "public"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@novence/sdk": "^0.2.
|
|
43
|
+
"@novence/sdk": "^0.2.6"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@types/node": "^22.10.2",
|