artifacty 0.1.0 → 0.1.1

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/src/cli.js CHANGED
@@ -18,6 +18,7 @@ import { checkMcpTools } from "./lib/check.js";
18
18
  import { installAgent } from "./lib/installer.js";
19
19
  import { serviceCommand } from "./lib/service.js";
20
20
  import { resolvePublicBaseUrl } from "./lib/server-state.js";
21
+ import { generateToken } from "./lib/token.js";
21
22
  import { startServer } from "./server.js";
22
23
 
23
24
  const PACKAGE_ROOT = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
@@ -32,17 +33,37 @@ async function main() {
32
33
  return;
33
34
  }
34
35
 
36
+ if (command === "token" || command === "generate-token") {
37
+ const token = generateToken(options);
38
+ if (options.raw) {
39
+ process.stdout.write(`${token.token}\n`);
40
+ return;
41
+ }
42
+ printJson(token);
43
+ return;
44
+ }
45
+
35
46
  if (command === "serve") {
47
+ if (options.generateToken && options.apiToken) {
48
+ throw new Error("Use either --api-token or --generate-token, not both");
49
+ }
50
+ const generatedToken = options.generateToken ? generateToken(options) : null;
36
51
  const server = await startServer({
37
52
  host: options.host,
38
53
  port: options.port,
39
54
  home: options.home,
40
- apiToken: options.apiToken,
55
+ apiToken: generatedToken?.token || options.apiToken,
41
56
  shareMode: options.shareMode,
42
57
  allowSecrets: options.allowSecrets
43
58
  });
44
59
  process.stderr.write(`Artifacty listening on ${server.url}\n`);
45
60
  process.stderr.write(`Store: ${server.store.home}\n`);
61
+ if (generatedToken) {
62
+ process.stderr.write(`API token: ${generatedToken.token}\n`);
63
+ process.stderr.write(`HTTP header: ${generatedToken.header}\n`);
64
+ process.stderr.write(`Create URL: ${server.url}/new?token=${encodeURIComponent(generatedToken.token)}\n`);
65
+ process.stderr.write(`Import URL: ${server.url}/import?token=${encodeURIComponent(generatedToken.token)}\n`);
66
+ }
46
67
  return;
47
68
  }
48
69
 
@@ -235,7 +256,7 @@ function parseArgs(args) {
235
256
  }
236
257
 
237
258
  const key = arg.slice(2);
238
- if (key === "raw" || key === "dry-run" || key === "trust" || key === "include-archived" || key === "allow-secrets") {
259
+ if (key === "raw" || key === "dry-run" || key === "trust" || key === "include-archived" || key === "allow-secrets" || key === "generate-token") {
239
260
  options[toCamelCase(key)] = true;
240
261
  continue;
241
262
  }
@@ -247,7 +268,7 @@ function parseArgs(args) {
247
268
 
248
269
  if (key === "tag") {
249
270
  options.tag = [...(options.tag || []), value];
250
- } else if (key === "port" || key === "limit" || key === "version" || key === "schema-version" || key === "timeout") {
271
+ } else if (key === "port" || key === "limit" || key === "version" || key === "schema-version" || key === "timeout" || key === "bytes") {
251
272
  options[toCamelCase(key)] = Number(value);
252
273
  } else {
253
274
  options[toCamelCase(key)] = value;
@@ -292,12 +313,13 @@ function printHelp() {
292
313
  process.stdout.write(`Artifacty
293
314
 
294
315
  Usage:
295
- artifacty serve [--host 127.0.0.1] [--port 8787] [--home ~/.artifacty]
296
- artifacty publish --title <title> (--file <path> | --content <text>) [--format html|markdown|text|json] [--source agent] [--tag tag]
297
- artifacty import --agent claude|codex|gemini|auto (--file <path> | --content <text>) [--title <title>] [--format html|markdown|text|json] [--tag tag]
298
- artifacty install claude|codex|gemini|all [--dry-run] [--config <path>] [--server-path <path>] [--url http://127.0.0.1:8787]
316
+ artifacty token [--bytes 32] [--raw]
317
+ artifacty serve [--host 127.0.0.1] [--port 8787] [--home ~/.artifacty] [--generate-token] [--bytes 32]
318
+ artifacty publish --title <title> (--file <path> | --content <text>) [--format html|markdown|text|json|code|svg|mermaid|react] [--source agent] [--tag tag]
319
+ artifacty import --agent claude|codex|gemini|auto (--file <path> | --content <text>) [--title <title>] [--format html|markdown|text|json|code|svg|mermaid|react] [--tag tag]
320
+ artifacty install claude|codex|gemini|all [--dry-run] [--config <path>] [--server-path <path>] [--url http://127.0.0.1:8787] [--timeout 30000]
299
321
  artifacty check [--server-path <path>] [--timeout 5000]
300
- artifacty update <id> (--file <path> | --content <text>) [--title <title>] [--format html|markdown|text|json]
322
+ artifacty update <id> (--file <path> | --content <text>) [--title <title>] [--format html|markdown|text|json|code|svg|mermaid|react]
301
323
  artifacty archive <id>
302
324
  artifacty restore <id>
303
325
  artifacty audit [--artifact <id>] [--limit 100]
@@ -15,6 +15,8 @@ const messages = {
15
15
  ...(globalThis.ARTIFACTY_I18N || {})
16
16
  };
17
17
 
18
+ const SUPPORTED_FORMATS = ["markdown", "html", "json", "text", "code", "svg", "mermaid", "react"];
19
+
18
20
  const editorTheme = EditorView.theme({
19
21
  "&": {
20
22
  minHeight: "52vh",
@@ -146,6 +148,16 @@ function enhanceTextarea(textarea) {
146
148
  return;
147
149
  }
148
150
 
151
+ if (format === "svg") {
152
+ const frame = document.createElement("iframe");
153
+ frame.className = "editor-preview-frame";
154
+ frame.setAttribute("sandbox", "");
155
+ frame.srcdoc = content;
156
+ preview.append(frame);
157
+ status.textContent = formatLabel(format);
158
+ return;
159
+ }
160
+
149
161
  if (format === "json") {
150
162
  const pre = document.createElement("pre");
151
163
  try {
@@ -176,7 +188,7 @@ function enhanceTextarea(textarea) {
176
188
  }
177
189
 
178
190
  function languageExtension(format) {
179
- if (format === "html") {
191
+ if (format === "html" || format === "svg") {
180
192
  return html();
181
193
  }
182
194
  if (format === "json") {
@@ -189,7 +201,7 @@ function languageExtension(format) {
189
201
  }
190
202
 
191
203
  function detectFormat({ explicit, fileName, content }) {
192
- if (["markdown", "html", "json", "text"].includes(explicit)) {
204
+ if (SUPPORTED_FORMATS.includes(explicit)) {
193
205
  return explicit;
194
206
  }
195
207
 
@@ -203,8 +215,29 @@ function detectFormat({ explicit, fileName, content }) {
203
215
  if (lowerName.endsWith(".json")) {
204
216
  return "json";
205
217
  }
218
+ if (lowerName.endsWith(".svg")) {
219
+ return "svg";
220
+ }
221
+ if (lowerName.endsWith(".mmd") || lowerName.endsWith(".mermaid")) {
222
+ return "mermaid";
223
+ }
224
+ if (lowerName.endsWith(".jsx") || lowerName.endsWith(".tsx")) {
225
+ return "react";
226
+ }
227
+ if (/\.(js|ts|py|rb|go|rs|java|c|cc|cpp|cs|php|swift|kt|sh|bash|zsh)$/.test(lowerName)) {
228
+ return "code";
229
+ }
206
230
 
207
231
  const trimmed = String(content || "").trimStart();
232
+ if (/^(?:<\?xml[\s\S]*?\?>\s*)?<svg[\s>]/i.test(trimmed)) {
233
+ return "svg";
234
+ }
235
+ if (/^(graph|flowchart|sequenceDiagram|classDiagram|stateDiagram|stateDiagram-v2|erDiagram|gantt|pie|mindmap|journey)\b/m.test(trimmed)) {
236
+ return "mermaid";
237
+ }
238
+ if (/\b(import\s+React|from\s+['"]react['"]|export\s+default\s+function|export\s+default\s+\()/m.test(trimmed) || /<[A-Z][A-Za-z0-9]*[\s/>]/.test(trimmed)) {
239
+ return "react";
240
+ }
208
241
  if (trimmed.startsWith("<!doctype") || trimmed.startsWith("<html") || trimmed.startsWith("<")) {
209
242
  return "html";
210
243
  }
@@ -0,0 +1,67 @@
1
+ import { EditorView, basicSetup } from "codemirror";
2
+ import { EditorState } from "@codemirror/state";
3
+ import { javascript } from "@codemirror/lang-javascript";
4
+ import { html } from "@codemirror/lang-html";
5
+ import { json } from "@codemirror/lang-json";
6
+ import { markdown } from "@codemirror/lang-markdown";
7
+
8
+ const viewerTheme = EditorView.theme({
9
+ "&": {
10
+ minHeight: "50vh"
11
+ },
12
+ ".cm-content": {
13
+ minHeight: "50vh"
14
+ },
15
+ ".cm-scroller": {
16
+ overflow: "auto"
17
+ }
18
+ });
19
+
20
+ for (const container of document.querySelectorAll("[data-artifacty-code-viewer]")) {
21
+ enhanceCodeViewer(container);
22
+ }
23
+
24
+ function enhanceCodeViewer(container) {
25
+ const source = container.querySelector("textarea")?.value || "";
26
+ const fallback = container.querySelector(".artifact-code-fallback");
27
+ const language = container.dataset.language || "";
28
+
29
+ const mount = document.createElement("div");
30
+ mount.className = "artifact-codemirror-mount";
31
+ container.append(mount);
32
+
33
+ new EditorView({
34
+ doc: source,
35
+ parent: mount,
36
+ extensions: [
37
+ basicSetup,
38
+ EditorState.readOnly.of(true),
39
+ EditorView.editable.of(false),
40
+ EditorView.lineWrapping,
41
+ viewerTheme,
42
+ languageExtension(language)
43
+ ]
44
+ });
45
+
46
+ fallback?.remove();
47
+ }
48
+
49
+ function languageExtension(language) {
50
+ const normalized = String(language || "").trim().toLowerCase();
51
+ if (["js", "javascript", "jsx"].includes(normalized)) {
52
+ return javascript({ jsx: true });
53
+ }
54
+ if (["ts", "typescript", "tsx"].includes(normalized)) {
55
+ return javascript({ typescript: true, jsx: normalized === "tsx" });
56
+ }
57
+ if (["html", "xml", "svg"].includes(normalized)) {
58
+ return html();
59
+ }
60
+ if (normalized === "json") {
61
+ return json();
62
+ }
63
+ if (["md", "markdown"].includes(normalized)) {
64
+ return markdown();
65
+ }
66
+ return [];
67
+ }