@ai-agent-tools/picgen 0.1.0-alpha.3 → 0.1.0-alpha.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/README.md CHANGED
@@ -91,6 +91,14 @@ OPENAI_API_KEY=...
91
91
  GEMINI_API_KEY=...
92
92
  ```
93
93
 
94
+ For non-technical users, `picgen setup` can save API keys for you in:
95
+
96
+ ```text
97
+ ~/.picgen/.env
98
+ ```
99
+
100
+ PicGen loads this managed env file automatically. Shell environment variables take priority, and a project `.env` can override the managed file for local testing.
101
+
94
102
  You can start from the included example:
95
103
 
96
104
  ```bash
package/dist/cli.js CHANGED
@@ -1,7 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli.ts
4
- import "dotenv/config";
5
4
  import { Command } from "commander";
6
5
 
7
6
  // src/commands/create.ts
@@ -1055,7 +1054,7 @@ import { homedir as homedir2 } from "os";
1055
1054
 
1056
1055
  // src/version.ts
1057
1056
  var PACKAGE_NAME = "@ai-agent-tools/picgen";
1058
- var VERSION = "0.1.0-alpha.3";
1057
+ var VERSION = "0.1.0-alpha.4";
1059
1058
 
1060
1059
  // src/commands/update.ts
1061
1060
  var UPDATE_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
@@ -1525,7 +1524,7 @@ function formatQuickstart() {
1525
1524
  " npm install -g @ai-agent-tools/picgen",
1526
1525
  "",
1527
1526
  "Configure:",
1528
- " picgen setup",
1527
+ " picgen setup # can save provider API keys for you",
1529
1528
  " picgen doctor --json",
1530
1529
  "",
1531
1530
  "Preview before spending quota:",
@@ -1538,10 +1537,11 @@ function formatQuickstart() {
1538
1537
  ' picgen create --dry-run --reference ./reference.png "\u57FA\u4E8E\u53C2\u8003\u56FE\u751F\u6210\u4E00\u5F20\u54C1\u724C\u6D77\u62A5"',
1539
1538
  "",
1540
1539
  "Agent prompt:",
1541
- " \u8BF7\u5B89\u88C5\u5E76\u4F53\u9A8C @ai-agent-tools/picgen\uFF1A\u5168\u5C40\u5B89\u88C5 npm install -g @ai-agent-tools/picgen\uFF0C\u8FD0\u884C picgen setup \u914D\u7F6E\uFF0C\u7136\u540E\u5148 dry-run \u9884\u89C8\uFF0C\u518D\u786E\u8BA4\u751F\u6210\u4E00\u5F20\u6D4B\u8BD5\u56FE\u3002\u5982\u679C\u6211\u8981\u7528\u53C2\u8003\u56FE\uFF0C\u8BF7\u4F7F\u7528 --reference <\u56FE\u7247\u8DEF\u5F84>\u3002",
1540
+ " \u8BF7\u5B89\u88C5\u5E76\u4F53\u9A8C @ai-agent-tools/picgen\uFF1A\u5168\u5C40\u5B89\u88C5 npm install -g @ai-agent-tools/picgen\uFF0C\u8FD0\u884C picgen setup \u5F15\u5BFC\u6211\u914D\u7F6E provider \u548C API key\uFF0C\u7136\u540E\u5148 dry-run \u9884\u89C8\uFF0C\u518D\u786E\u8BA4\u751F\u6210\u4E00\u5F20\u6D4B\u8BD5\u56FE\u3002\u5982\u679C\u6211\u8981\u7528\u53C2\u8003\u56FE\uFF0C\u8BF7\u4F7F\u7528 --reference <\u56FE\u7247\u8DEF\u5F84>\u3002",
1542
1541
  "",
1543
1542
  "Notes:",
1544
1543
  " - Provider host URLs should not include /v1 or /v1beta.",
1544
+ " - picgen setup can store API keys in ~/.picgen/.env.",
1545
1545
  " - Agent workflows should dry-run before real generation.",
1546
1546
  " - Generated images are saved locally; do not paste base64 into chat.",
1547
1547
  " - First-user rollout checklist: docs/release-alpha.md"
@@ -1549,7 +1549,62 @@ function formatQuickstart() {
1549
1549
  }
1550
1550
 
1551
1551
  // src/commands/setup.ts
1552
- import { confirm as confirm2, input as input2, select as select2 } from "@inquirer/prompts";
1552
+ import { confirm as confirm2, input as input2, password, select as select2 } from "@inquirer/prompts";
1553
+
1554
+ // src/config/env.ts
1555
+ import { chmod, mkdir as mkdir4, readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
1556
+ import { existsSync } from "fs";
1557
+ import { dirname as dirname3, join as join5, resolve as resolve2 } from "path";
1558
+ import { homedir as homedir3 } from "os";
1559
+ import { parse } from "dotenv";
1560
+ function getManagedEnvPath() {
1561
+ return process.env.PICGEN_ENV_PATH ?? join5(homedir3(), ".picgen", ".env");
1562
+ }
1563
+ async function loadPicgenEnv() {
1564
+ const shellEnv = new Set(Object.keys(process.env));
1565
+ await loadEnvFile(getManagedEnvPath(), shellEnv, false);
1566
+ await loadEnvFile(resolve2(process.cwd(), ".env"), shellEnv, true);
1567
+ }
1568
+ async function saveManagedEnvVar(name, value) {
1569
+ const path = getManagedEnvPath();
1570
+ const current = await readManagedEnvFile(path);
1571
+ const next = {
1572
+ ...current,
1573
+ [name]: value
1574
+ };
1575
+ await mkdir4(dirname3(path), { recursive: true });
1576
+ await writeFile4(path, stringifyEnv(next), "utf8");
1577
+ await chmod(path, 384);
1578
+ process.env[name] = value;
1579
+ return path;
1580
+ }
1581
+ async function loadEnvFile(path, shellEnv, overrideManagedValues) {
1582
+ if (!existsSync(path)) return;
1583
+ const parsed = parse(await readFile4(path, "utf8"));
1584
+ for (const [name, value] of Object.entries(parsed)) {
1585
+ if (shellEnv.has(name)) continue;
1586
+ if (!overrideManagedValues && process.env[name] !== void 0) continue;
1587
+ process.env[name] = value;
1588
+ }
1589
+ }
1590
+ async function readManagedEnvFile(path) {
1591
+ try {
1592
+ return parse(await readFile4(path, "utf8"));
1593
+ } catch (error) {
1594
+ if (error.code === "ENOENT") return {};
1595
+ throw error;
1596
+ }
1597
+ }
1598
+ function stringifyEnv(values) {
1599
+ return `${Object.entries(values).map(([name, value]) => `${name}=${quoteEnvValue(value)}`).join("\n")}
1600
+ `;
1601
+ }
1602
+ function quoteEnvValue(value) {
1603
+ if (/^[A-Za-z0-9_./:@+-]+$/.test(value)) return value;
1604
+ return JSON.stringify(value);
1605
+ }
1606
+
1607
+ // src/commands/setup.ts
1553
1608
  async function runSetup() {
1554
1609
  await ensureConfig();
1555
1610
  console.log(`PicGen config: ${getConfigPath()}`);
@@ -1564,6 +1619,7 @@ async function runSetup() {
1564
1619
  { name: "Quick add a common provider/channel", value: "quick-add" },
1565
1620
  { name: "Choose default provider/channel", value: "provider" },
1566
1621
  { name: "Choose generation preference", value: "mode" },
1622
+ { name: "Configure API key", value: "key" },
1567
1623
  { name: "Test a provider", value: "test" },
1568
1624
  { name: "Advanced: add a custom provider/channel", value: "add" },
1569
1625
  { name: "Finish setup", value: "done" }
@@ -1575,6 +1631,8 @@ async function runSetup() {
1575
1631
  await chooseDefaultProvider();
1576
1632
  } else if (action === "mode") {
1577
1633
  await chooseDefaultMode();
1634
+ } else if (action === "key") {
1635
+ await chooseProviderKeyToConfigure();
1578
1636
  } else if (action === "test") {
1579
1637
  await chooseProviderToTest();
1580
1638
  } else if (action === "add") {
@@ -1593,7 +1651,7 @@ async function printSetupSummary() {
1593
1651
  for (const [providerName, provider2] of Object.entries(config.providers)) {
1594
1652
  const preference = providerName === config.routing.default_provider ? "default" : config.routing.fallback_providers.includes(providerName) ? "fallback" : "manual";
1595
1653
  console.log(
1596
- `- ${providerName}: ${provider2.enabled ? "enabled" : "disabled"}, ${preference}, ${providerLabel(provider2)}, capabilities=${provider2.capabilities.join(",")}`
1654
+ `- ${providerName}: ${provider2.enabled ? "enabled" : "disabled"}, ${preference}, ${providerLabel(provider2)}, key=${provider2.api_key_env}${process.env[provider2.api_key_env] ? " set" : " missing"}, capabilities=${provider2.capabilities.join(",")}`
1597
1655
  );
1598
1656
  }
1599
1657
  }
@@ -1641,6 +1699,18 @@ async function chooseProviderToTest() {
1641
1699
  if (result.model) console.log(`Model: ${result.model}`);
1642
1700
  if (result.http_status) console.log(`HTTP status: ${result.http_status}`);
1643
1701
  }
1702
+ async function chooseProviderKeyToConfigure() {
1703
+ const config = await loadConfig();
1704
+ const name = await select2({
1705
+ message: "Choose the provider key to configure",
1706
+ default: config.routing.default_provider,
1707
+ choices: Object.entries(config.providers).map(([providerName, provider2]) => ({
1708
+ name: `${providerName} (${provider2.api_key_env}${process.env[provider2.api_key_env] ? ", currently set" : ", missing"})`,
1709
+ value: providerName
1710
+ }))
1711
+ });
1712
+ await configureProviderApiKey(config.providers[name]);
1713
+ }
1644
1714
  async function quickAddProvider() {
1645
1715
  const config = await loadConfig();
1646
1716
  const template = await select2({
@@ -1700,7 +1770,27 @@ async function quickAddProvider() {
1700
1770
  }
1701
1771
  await saveConfig(config);
1702
1772
  console.log(`Added provider: ${name}`);
1703
- console.log(`Set ${apiKeyEnv} in your shell or .env before testing this provider.`);
1773
+ await configureProviderApiKey(provider2);
1774
+ }
1775
+ async function configureProviderApiKey(provider2) {
1776
+ if (process.env[provider2.api_key_env]) {
1777
+ const replace = await confirm2({
1778
+ message: `${provider2.api_key_env} is already available. Replace the saved PicGen key?`,
1779
+ default: false
1780
+ });
1781
+ if (!replace) return;
1782
+ }
1783
+ const value = await password({
1784
+ message: `Paste API key for ${provider2.api_key_env} (leave empty to skip)`,
1785
+ mask: "*"
1786
+ });
1787
+ if (!value.trim()) {
1788
+ console.log(`Skipped API key. You can configure it later with picgen setup.`);
1789
+ return;
1790
+ }
1791
+ const path = await saveManagedEnvVar(provider2.api_key_env, value.trim());
1792
+ console.log(`Saved ${provider2.api_key_env} to ${path}`);
1793
+ console.log(`PicGen loads this file automatically. Advanced users can override it with shell env vars or a project .env.`);
1704
1794
  }
1705
1795
  function quickProviderDefaults(template) {
1706
1796
  switch (template) {
@@ -1765,6 +1855,7 @@ function modeLabel(modeName) {
1765
1855
  }
1766
1856
 
1767
1857
  // src/cli.ts
1858
+ await loadPicgenEnv();
1768
1859
  var program = new Command();
1769
1860
  program.name("picgen").description("Lightweight image generation connector for AI agents.").version(VERSION);
1770
1861
  program.command("setup").description("Run the interactive PicGen setup wizard.").action(runSetup);
@@ -40,7 +40,17 @@ https://generativelanguage.googleapis.com
40
40
 
41
41
  Do not include `/v1` or `/v1beta`.
42
42
 
43
- 4. Set API keys in the shell or a local `.env` file:
43
+ 4. Configure API keys:
44
+
45
+ For non-technical users, prefer `picgen setup`. It can save provider API keys in PicGen's managed env file:
46
+
47
+ ```text
48
+ ~/.picgen/.env
49
+ ```
50
+
51
+ PicGen loads this file automatically.
52
+
53
+ Advanced users can still use shell environment variables or a local project `.env`:
44
54
 
45
55
  ```bash
46
56
  cp .env.example .env
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-agent-tools/picgen",
3
- "version": "0.1.0-alpha.3",
3
+ "version": "0.1.0-alpha.4",
4
4
  "description": "A lightweight image generation connector for AI agents.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -20,7 +20,7 @@ Never silently spend user quota. Do not send full conversation context to provid
20
20
  ## Workflow
21
21
 
22
22
  1. Run `picgen doctor --json` to check configuration.
23
- 2. If no usable provider is configured, guide the user to run `picgen setup`.
23
+ 2. If no usable provider is configured, guide the user to run `picgen setup`. The setup wizard can configure providers and save API keys for non-technical users.
24
24
  3. Choose a preset from the user's intent, such as `poster`, `product-shot`, or `social-cover`.
25
25
  4. Run `picgen create --dry-run --preset <preset> "<prompt>"`.
26
26
  5. Present the dry-run as a user-facing generation preview. Do not expose `dry-run` as a technical term unless useful.
@@ -75,7 +75,7 @@ PicGen redacts generated image payloads and Gemini thought signatures from metad
75
75
 
76
76
  If `doctor` reports no usable provider, ask the user to run `picgen setup`.
77
77
 
78
- If an API key is missing, name the required environment variable.
78
+ If an API key is missing, guide the user to run `picgen setup` and choose `Configure API key`. Name the required environment variable only when useful for debugging.
79
79
 
80
80
  If a provider is disabled, suggest enabling it or using a one-off provider override.
81
81