artifacty 0.10.7 → 0.10.8

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 CHANGED
@@ -92,6 +92,10 @@ artifacty serve --host 10.0.0.50 --share-mode team --api-token "$ARTIFACTY_BOOTS
92
92
  artifacty install all --mcp-url http://10.0.0.50:8787/mcp --api-token "$ARTIFACTY_PERSONAL_TOKEN"
93
93
  ```
94
94
 
95
+ When running the central server as a Linux `systemctl --user` service, enable
96
+ lingering for the service account so Artifacty stays up after logout:
97
+ `sudo loginctl enable-linger $USER`.
98
+
95
99
  Administrators can create users individually at `/admin/users` or import them
96
100
  from CSV. Use `email,name,role,password,password_reset_required` headers. If
97
101
  `password` is empty, Artifacty generates a temporary password, shows it once in
@@ -134,6 +134,19 @@ proxy, keep `ARTIFACTY_ENABLE_REACT_RENDERER` disabled unless the team trusts
134
134
  all artifact authors, and store `ARTIFACTY_HOME` on local server disk with
135
135
  regular backups.
136
136
 
137
+ For Linux user-service deployments, enable systemd lingering for the service
138
+ account:
139
+
140
+ ```bash
141
+ sudo loginctl enable-linger artifacty
142
+ loginctl show-user artifacty -p Linger
143
+ ```
144
+
145
+ Use the actual service account name. Without lingering, `systemctl --user`
146
+ services are tied to the user's login manager and can stop after logout, making
147
+ the central dashboard and `/mcp` endpoint unavailable until that user logs in
148
+ again. A system-level unit is another valid option for central servers.
149
+
137
150
  The central server exposes `/mcp` only when explicitly enabled. This keeps the
138
151
  local browser/API server behavior unchanged while making team MCP exposure an
139
152
  intentional operating mode.
@@ -393,9 +393,11 @@ The generated service runs `src/server.js` with explicit `--host` and `--home` a
393
393
  Activation is still delegated to the OS service manager:
394
394
 
395
395
  - macOS: `launchctl load ~/Library/LaunchAgents/com.artifacty.server.plist`
396
- - Linux: `systemctl --user enable --now com.artifacty.server.service`
396
+ - Linux: `sudo loginctl enable-linger $USER`, then `systemctl --user enable --now com.artifacty.server.service`
397
397
  - Windows: run the generated PowerShell script, which registers and starts the `ArtifactyServer` scheduled task.
398
398
 
399
+ On Linux central servers, user services stop when the user's systemd manager exits unless lingering is enabled. Verify durable operation with `loginctl show-user $USER -p Linger`; it should report `Linger=yes`. Without lingering, Artifacty may be unavailable after logout and appear to recover only after the user logs back in.
400
+
399
401
  For background services, prefer a stable `ARTIFACTY_API_TOKEN` or `--api-token` value. `serve --generate-token` is intended for temporary interactive sessions; the parent CLI returns the generated token in JSON.
400
402
 
401
403
  ## Backup and Audit
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "artifacty",
3
- "version": "0.10.7",
3
+ "version": "0.10.8",
4
4
  "description": "Local artifact exchange for heterogeneous LLM agents via HTTP and MCP.",
5
5
  "type": "module",
6
6
  "keywords": [
@@ -184,9 +184,11 @@ function definitionForPlatform(platform, options = {}, renderOnly) {
184
184
  path: servicePath(platform, options),
185
185
  content: createSystemdUserUnit(options),
186
186
  nextSteps: [
187
+ "sudo loginctl enable-linger $USER",
187
188
  "systemctl --user daemon-reload",
188
189
  `systemctl --user enable --now ${path.basename(servicePath(platform, options))}`,
189
190
  `systemctl --user status ${path.basename(servicePath(platform, options))}`,
191
+ "loginctl show-user $USER -p Linger",
190
192
  `journalctl --user -u ${path.basename(servicePath(platform, options))} -f`,
191
193
  `systemctl --user disable --now ${path.basename(servicePath(platform, options))}`
192
194
  ],
@@ -10,6 +10,7 @@ import { assertNoSecrets, securityConfig } from "./security.js";
10
10
  export const STORE_VERSION = 4;
11
11
  export const ARTIFACT_SCHEMA_VERSION = 1;
12
12
  export const MAX_ARTIFACT_BYTES = 16 * 1024 * 1024;
13
+ const SOURCE_AGENT_NORMALIZATION_VERSION = "1";
13
14
  export const USER_ROLES = ["admin", "user"];
14
15
  export const ARTIFACT_FORMATS = [
15
16
  "html",
@@ -123,6 +124,11 @@ export async function writeIndex(store, index) {
123
124
  insertVersionRecord(db, artifact.id, version);
124
125
  }
125
126
  }
127
+ normalizeStoredSourceAgents(db, store, {
128
+ force: true,
129
+ inTransaction: true,
130
+ rebuildSearch: false
131
+ });
126
132
  rebuildSearchIndexInDb(db, store);
127
133
  });
128
134
  } finally {
@@ -1502,9 +1508,26 @@ function migrateJsonIndex(db, store) {
1502
1508
  });
1503
1509
  }
1504
1510
 
1511
+ function metaValue(db, key) {
1512
+ return db.prepare("SELECT value FROM meta WHERE key = ?").get(key)?.value || "";
1513
+ }
1514
+
1505
1515
  const INFERABLE_SOURCE_AGENTS = new Set(["claude", "codex", "copilot", "cursor", "gemini"]);
1506
1516
 
1507
- function normalizeStoredSourceAgents(db, store) {
1517
+ function normalizeStoredSourceAgents(db, store, options = {}) {
1518
+ if (!options.force && metaValue(db, "source_agent_normalized_version") === SOURCE_AGENT_NORMALIZATION_VERSION) {
1519
+ return;
1520
+ }
1521
+
1522
+ const run = () => normalizeStoredSourceAgentsInDb(db, store, options);
1523
+ if (options.inTransaction) {
1524
+ run();
1525
+ } else {
1526
+ transaction(db, run);
1527
+ }
1528
+ }
1529
+
1530
+ function normalizeStoredSourceAgentsInDb(db, store, options = {}) {
1508
1531
  const rows = db.prepare(`
1509
1532
  SELECT id, source_agent, tags_json
1510
1533
  FROM artifacts
@@ -1512,37 +1535,37 @@ function normalizeStoredSourceAgents(db, store) {
1512
1535
  const updateArtifact = db.prepare("UPDATE artifacts SET source_agent = ?, tags_json = ? WHERE id = ?");
1513
1536
  let changed = false;
1514
1537
 
1515
- transaction(db, () => {
1516
- for (const row of rows) {
1517
- const normalized = normalizeSourceAgent(row.source_agent, { defaultValue: "unknown" });
1518
- const inferred = isUnknownSourceAgent(normalized)
1519
- ? inferStoredSourceAgent(db, row)
1520
- : normalized;
1521
- const nextSourceAgent = inferred || normalized;
1522
- const nextTags = normalizeStoredSourceTags(row.tags_json, nextSourceAgent);
1523
- if (nextSourceAgent !== row.source_agent || nextTags !== row.tags_json) {
1524
- updateArtifact.run(nextSourceAgent, nextTags, row.id);
1525
- changed = true;
1526
- }
1538
+ for (const row of rows) {
1539
+ const normalized = normalizeSourceAgent(row.source_agent, { defaultValue: "unknown" });
1540
+ const inferred = isUnknownSourceAgent(normalized)
1541
+ ? inferStoredSourceAgent(db, row)
1542
+ : normalized;
1543
+ const nextSourceAgent = inferred || normalized;
1544
+ const nextTags = normalizeStoredSourceTags(row.tags_json, nextSourceAgent);
1545
+ if (nextSourceAgent !== row.source_agent || nextTags !== row.tags_json) {
1546
+ updateArtifact.run(nextSourceAgent, nextTags, row.id);
1547
+ changed = true;
1527
1548
  }
1549
+ }
1528
1550
 
1529
- const auditRows = db.prepare(`
1530
- SELECT id, source_agent
1531
- FROM audit_log
1532
- WHERE source_agent IS NOT NULL
1533
- AND TRIM(source_agent) != ''
1534
- `).all();
1535
- const updateAudit = db.prepare("UPDATE audit_log SET source_agent = ? WHERE id = ?");
1536
- for (const row of auditRows) {
1537
- const normalized = normalizeSourceAgent(row.source_agent, { defaultValue: "" });
1538
- if (normalized && normalized !== row.source_agent) {
1539
- updateAudit.run(normalized, row.id);
1540
- changed = true;
1541
- }
1551
+ const auditRows = db.prepare(`
1552
+ SELECT id, source_agent
1553
+ FROM audit_log
1554
+ WHERE source_agent IS NOT NULL
1555
+ AND TRIM(source_agent) != ''
1556
+ `).all();
1557
+ const updateAudit = db.prepare("UPDATE audit_log SET source_agent = ? WHERE id = ?");
1558
+ for (const row of auditRows) {
1559
+ const normalized = normalizeSourceAgent(row.source_agent, { defaultValue: "" });
1560
+ if (normalized && normalized !== row.source_agent) {
1561
+ updateAudit.run(normalized, row.id);
1562
+ changed = true;
1542
1563
  }
1543
- });
1564
+ }
1565
+
1566
+ db.prepare("INSERT OR REPLACE INTO meta (key, value) VALUES ('source_agent_normalized_version', ?)").run(SOURCE_AGENT_NORMALIZATION_VERSION);
1544
1567
 
1545
- if (changed) {
1568
+ if (changed && options.rebuildSearch !== false) {
1546
1569
  rebuildSearchIndexInDb(db, store);
1547
1570
  }
1548
1571
  }