agentful 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.
Files changed (3) hide show
  1. package/README.md +43 -17
  2. package/dist/index.cjs +104 -29
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,28 +1,44 @@
1
1
  # Agentful CLI
2
2
 
3
- Agentful in your terminal: develop locally, then `agentful push` for a live
4
- preview URL on your Agentful workspace.
3
+ Develop locally, ship with one command. `agentful` gives you an AI coding
4
+ interface in your terminal and moves the project into your Agentful workspace
5
+ with a live preview URL — using the same models and credits as the web app.
5
6
 
7
+ ```bash
8
+ npm i -g agentful
6
9
  ```
7
- $ agentful login # device flow — confirm the code in your browser
8
- $ agentful init # create or link the cloud project (.agentful/project.json)
9
- $ agentful push # upload source → cloud build → preview URL
10
- ✔ Live preview: https://preview-<project>.agentful.dev/
11
- $ agentful push --prebuilt # upload a locally built dist/ instead
12
- $ agentful open # open the preview in the browser
10
+
11
+ ## Quickstart
12
+
13
+ ```bash
14
+ agentful login # once: confirm the code in your browser
15
+ agentful init # create or link the cloud project
16
+ agentful # start the coding interface
17
+ agentful push # upload, cloud build, preview URL
18
+ agentful open # open the preview
13
19
  ```
14
20
 
15
- ## Commands (M1)
21
+ ## Commands
16
22
 
17
23
  | Command | Purpose |
18
- |---|---|
19
- | `login` / `logout` / `whoami` | Device-flow auth against the Agentful account (14-day token in `~/.config/agentful/auth.json`, chmod 600) |
20
- | `init [--link <projectId>] [--title <t>]` | Create a cloud project (or link an existing one) and write `.agentful/project.json` |
21
- | `push [--prebuilt [--dir <path>]]` | Zip-push the tree (respects `.gitignore`; always excludes `.git`, `node_modules`, build outputs, `.env*`), cloud build, deploy, print preview URL |
22
- | `open` | Open the preview URL |
23
-
24
- Planned next: `agentful tui` (bundled OpenCode with your Agentful models/credits),
25
- `publish <subdomain>`, `pull`. See `planned/agentful-cli-tui-concept.md`.
24
+ | --- | --- |
25
+ | `login` / `logout` / `whoami` | Browser-based sign-in; the token is kept in the macOS Keychain |
26
+ | `init [--link <id>] [--title <t>]` | Create a cloud project or link an existing one (`.agentful/project.json`) |
27
+ | `push [--prebuilt [--dir <path>]]` | Upload the source and build in the cloud; `--prebuilt` uploads a local build |
28
+ | `open` | Open the live preview |
29
+ | `share [--private]` | Make the preview public, or private again |
30
+ | `publish <subdomain>` | Publish at `https://<subdomain>.agentful.dev` |
31
+ | `pull [--force]` | Fetch the cloud state into this directory |
32
+ | `models [--add-local]` | Show available models, manage your own providers |
33
+ | `upgrade` | Update the CLI to the latest published version |
34
+ | `licenses` | Licences of the software shipped with this CLI |
35
+ | `-s, --session <id>` | Resume a previous coding session |
36
+
37
+ Inside the interface, `/` lists the Agentful commands (`/setup`, `/push`,
38
+ `/preview`, `/publish`, `/share`, `/status`) alongside the built-in ones.
39
+
40
+ `push` respects your `.gitignore` and always excludes `.git`, `node_modules`,
41
+ build output and every `.env` file. Uploads are capped at 6 MB.
26
42
 
27
43
  ## Platform support
28
44
 
@@ -67,6 +83,16 @@ Local providers are governed by the organization's BYOK policy (`/api/org/me`):
67
83
  the CLI, so it is a cooperative control, not a cryptographic one — the config
68
84
  lives on the user's machine.
69
85
 
86
+ ## Known limitation: the engine's exit banner
87
+
88
+ When the TUI closes, the engine writes its own splash (its logo plus a
89
+ `opencode -s <id>` hint) straight into the scrollback. v1.18.x offers no flag,
90
+ env var or config to suppress it, and patching the binary would break its
91
+ macOS signature. We therefore let it stand and print our own last line with
92
+ the command that actually works here — `agentful -s <id>`; the engine's hint
93
+ cannot work, because that binary is not on the user's PATH and its data lives
94
+ in a different directory.
95
+
70
96
  ## Branding
71
97
 
72
98
  Everything user-visible lives in `src/branding/`: `branding.ts` (palette,
package/dist/index.cjs CHANGED
@@ -3432,7 +3432,7 @@ var {
3432
3432
  } = import_index.default;
3433
3433
 
3434
3434
  // src/branding.ts
3435
- var VERSION = "0.1.0" ? "0.1.0" : null.version;
3435
+ var VERSION = "0.1.1" ? "0.1.1" : null.version;
3436
3436
  var brand = {
3437
3437
  name: "agentful",
3438
3438
  // the command users type
@@ -3792,7 +3792,11 @@ function writeProjectConfig(config, cwd = process.cwd()) {
3792
3792
  function requireProject(cwd = process.cwd()) {
3793
3793
  const config = readProjectConfig(cwd);
3794
3794
  if (!config) {
3795
- throw new ApiError(0, "not_initialized", "No Agentful project here. Run `agentful init` first.");
3795
+ throw new ApiError(
3796
+ 0,
3797
+ "not_initialized",
3798
+ "No Agentful project here. Run `agentful init` to create or link one (inside the TUI: /setup)."
3799
+ );
3796
3800
  }
3797
3801
  return config;
3798
3802
  }
@@ -4763,8 +4767,8 @@ async function openCommand() {
4763
4767
  }
4764
4768
 
4765
4769
  // src/commands/tui.ts
4766
- var import_node_child_process4 = require("child_process");
4767
- var import_node_path11 = require("path");
4770
+ var import_node_child_process5 = require("child_process");
4771
+ var import_node_path12 = require("path");
4768
4772
 
4769
4773
  // src/lib/binWrapper.ts
4770
4774
  var import_node_fs4 = require("fs");
@@ -5088,32 +5092,38 @@ export default plugin
5088
5092
  }
5089
5093
 
5090
5094
  // src/branding/commands.ts
5095
+ var IF_NOT_LINKED = 'If the command reports "No Agentful project here", this directory is not linked yet: ask me for a project title, run `agentful init --title "<title>"`, then run the original command again and report its result.';
5091
5096
  var SLASH_COMMANDS = {
5097
+ setup: {
5098
+ description: `${brand.displayName}: create or link the cloud project for this directory`,
5099
+ template: 'Check whether .agentful/project.json exists. If it does, report the linked project title and id and stop. Otherwise ask me for a project title, then run `agentful init --title "<title>"` using the bash tool and report what it created. Do not modify any other project files.'
5100
+ },
5092
5101
  push: {
5093
- description: `Deploy this project to ${brand.displayName} and get a preview URL`,
5094
- template: "Run `agentful push` from the project root using the bash tool. Stream what it prints, and when it finishes report the preview URL on its own line. If it fails, show the error and suggest the fix \u2014 do not modify project files unless I ask."
5102
+ description: `${brand.displayName}: deploy this project and get a preview URL`,
5103
+ template: "Run `agentful push` from the project root using the bash tool. Stream what it prints, and when it finishes report the preview URL on its own line. " + IF_NOT_LINKED + " If it fails for another reason, show the error and suggest the fix \u2014 do not modify project files unless I ask."
5095
5104
  },
5096
5105
  preview: {
5097
- description: "Open the live preview URL in the browser",
5098
- template: "Run `agentful open` from the project root using the bash tool and report the URL it opened."
5106
+ description: `${brand.displayName}: open the live preview in the browser`,
5107
+ template: "Run `agentful open` from the project root using the bash tool and report the URL it opened. Note that `open` neither builds nor uploads \u2014 if I have not pushed yet, say so and offer to run `agentful push` first. " + IF_NOT_LINKED
5099
5108
  },
5100
5109
  publish: {
5101
- description: "Publish the built project on a public subdomain",
5102
- template: "Ask me for the subdomain if I did not provide one in $ARGUMENTS, then run `agentful publish <subdomain>` from the project root using the bash tool and report the public URL."
5110
+ description: `${brand.displayName}: publish on a public subdomain`,
5111
+ template: "Ask me for the subdomain if I did not provide one in $ARGUMENTS, then run `agentful publish <subdomain>` from the project root using the bash tool and report the public URL. " + IF_NOT_LINKED
5103
5112
  },
5104
5113
  share: {
5105
- description: "Make the live preview public (or private again)",
5106
- template: "Run `agentful share` from the project root using the bash tool (add `--private` if $ARGUMENTS says private) and report the resulting visibility and URL."
5114
+ description: `${brand.displayName}: make the live preview public (or private again)`,
5115
+ template: "Run `agentful share` from the project root using the bash tool (add `--private` if $ARGUMENTS says private) and report the resulting visibility and URL. " + IF_NOT_LINKED
5107
5116
  },
5108
5117
  status: {
5109
- description: "Show cloud build and deployment status",
5110
- template: "Run `agentful whoami` and then check the project status by running `agentful push --help` is NOT needed \u2014 instead read .agentful/project.json and report the linked project id and title, then summarize the latest build state from the last `agentful push` output if it is in this session."
5118
+ description: `${brand.displayName}: show the linked project and account`,
5119
+ template: "Run `agentful whoami` using the bash tool. Then read .agentful/project.json and report the linked project title and id, plus the preview URL https://preview-<projectId>.agentful.dev/. If the file does not exist, say that this directory is not linked yet and that `/setup` creates the link."
5111
5120
  }
5112
5121
  };
5113
5122
  function commandMarkdown(name) {
5114
5123
  const cmd = SLASH_COMMANDS[name];
5124
+ const description = JSON.stringify(cmd.description);
5115
5125
  return `---
5116
- description: ${cmd.description}
5126
+ description: ${description}
5117
5127
  agent: build
5118
5128
  ---
5119
5129
 
@@ -5390,8 +5400,33 @@ function writeLocalProvidersExample() {
5390
5400
  return path;
5391
5401
  }
5392
5402
 
5403
+ // src/lib/sessions.ts
5404
+ var import_node_fs10 = require("fs");
5405
+ var import_node_path11 = require("path");
5406
+ var import_node_child_process4 = require("child_process");
5407
+ var SEP = "";
5408
+ function sqlite(dbPath, sql) {
5409
+ const res = (0, import_node_child_process4.spawnSync)("sqlite3", ["-readonly", dbPath, sql], { encoding: "utf8" });
5410
+ if (res.status !== 0 || !res.stdout) return null;
5411
+ return res.stdout.trim().split("\n").filter(Boolean);
5412
+ }
5413
+ function lastSessionForDirectory(cwd = process.cwd()) {
5414
+ const dbPath = (0, import_node_path11.join)(engineXdg().dataHome, "opencode", "opencode.db");
5415
+ if (!(0, import_node_fs10.existsSync)(dbPath)) return null;
5416
+ const escaped = cwd.replace(/'/g, "''");
5417
+ const rows = sqlite(
5418
+ dbPath,
5419
+ `SELECT id || char(31) || COALESCE(title, slug, '') FROM session
5420
+ WHERE directory = '${escaped}' AND time_archived IS NULL
5421
+ ORDER BY COALESCE(time_updated, time_created) DESC LIMIT 1;`
5422
+ );
5423
+ if (!rows?.length) return null;
5424
+ const [id, title] = rows[0].split(SEP);
5425
+ return id ? { id, title: title || "" } : null;
5426
+ }
5427
+
5393
5428
  // src/commands/tui.ts
5394
- async function tuiCommand() {
5429
+ async function tuiCommand(opts = {}) {
5395
5430
  console.log(banner());
5396
5431
  const auth = await ensureAuth();
5397
5432
  const project = readProjectConfig();
@@ -5434,14 +5469,15 @@ async function tuiCommand() {
5434
5469
  console.log("");
5435
5470
  if (process.stdout.isTTY) process.stdout.write(`\x1B]0;${brand.displayName}\x07`);
5436
5471
  const code = await new Promise((resolve) => {
5437
- const child = (0, import_node_child_process4.spawn)(binPath, [], {
5472
+ const engineArgs = opts.session ? ["--session", opts.session] : [];
5473
+ const child = (0, import_node_child_process5.spawn)(binPath, engineArgs, {
5438
5474
  stdio: "inherit",
5439
5475
  cwd: process.cwd(),
5440
5476
  env: {
5441
5477
  ...process.env,
5442
5478
  // Slash commands run `agentful …` through the agent's bash tool, which
5443
5479
  // has no access to the user's shell aliases.
5444
- PATH: `${binDir}${import_node_path11.delimiter}${process.env.PATH || ""}`,
5480
+ PATH: `${binDir}${import_node_path12.delimiter}${process.env.PATH || ""}`,
5445
5481
  XDG_CONFIG_HOME: xdg.configHome,
5446
5482
  XDG_DATA_HOME: xdg.dataHome,
5447
5483
  XDG_STATE_HOME: xdg.stateHome,
@@ -5476,6 +5512,14 @@ async function tuiCommand() {
5476
5512
  }
5477
5513
  } catch {
5478
5514
  }
5515
+ try {
5516
+ const last = lastSessionForDirectory();
5517
+ if (last) {
5518
+ const label = last.title ? ` ${paint.dim(`(${last.title})`)}` : "";
5519
+ ui.info(`Resume: ${paint.accent(`agentful -s ${last.id}`)}${label}`);
5520
+ }
5521
+ } catch {
5522
+ }
5479
5523
  process.exitCode = code;
5480
5524
  }
5481
5525
 
@@ -5536,26 +5580,26 @@ async function shareCommand(opts) {
5536
5580
  }
5537
5581
 
5538
5582
  // src/commands/pull.ts
5539
- var import_node_fs10 = require("fs");
5540
- var import_node_path12 = require("path");
5583
+ var import_node_fs11 = require("fs");
5584
+ var import_node_path13 = require("path");
5541
5585
  function isProbablyBase64Binary(path) {
5542
5586
  return /\.(png|jpe?g|gif|webp|ico|woff2?|ttf|otf|eot|pdf|zip|mp[34]|webm|avif)$/i.test(path);
5543
5587
  }
5544
5588
  function writeEntry(root, rel, value) {
5545
- const target = (0, import_node_path12.join)(root, rel);
5546
- (0, import_node_fs10.mkdirSync)((0, import_node_path12.dirname)(target), { recursive: true });
5589
+ const target = (0, import_node_path13.join)(root, rel);
5590
+ (0, import_node_fs11.mkdirSync)((0, import_node_path13.dirname)(target), { recursive: true });
5547
5591
  const content = typeof value === "object" && value !== null && "content" in value ? String(value.content) : String(value ?? "");
5548
5592
  if (isProbablyBase64Binary(rel)) {
5549
- (0, import_node_fs10.writeFileSync)(target, Buffer.from(content, "base64"));
5593
+ (0, import_node_fs11.writeFileSync)(target, Buffer.from(content, "base64"));
5550
5594
  } else {
5551
- (0, import_node_fs10.writeFileSync)(target, content, "utf8");
5595
+ (0, import_node_fs11.writeFileSync)(target, content, "utf8");
5552
5596
  }
5553
5597
  }
5554
5598
  async function pullCommand(opts) {
5555
5599
  console.log(banner());
5556
5600
  const auth = await ensureAuth();
5557
5601
  const project = requireProject();
5558
- const nonHidden = (0, import_node_fs10.readdirSync)(process.cwd()).filter((n) => n !== ".agentful" && !n.startsWith("."));
5602
+ const nonHidden = (0, import_node_fs11.readdirSync)(process.cwd()).filter((n) => n !== ".agentful" && !n.startsWith("."));
5559
5603
  if (nonHidden.length > 0 && !opts.force) {
5560
5604
  throw new ApiError(
5561
5605
  0,
@@ -5579,9 +5623,9 @@ async function pullCommand(opts) {
5579
5623
  ui.warn(`Skipped ${rel} (HTTP ${resp.status})`);
5580
5624
  continue;
5581
5625
  }
5582
- const target = (0, import_node_path12.join)(process.cwd(), rel);
5583
- (0, import_node_fs10.mkdirSync)((0, import_node_path12.dirname)(target), { recursive: true });
5584
- (0, import_node_fs10.writeFileSync)(target, Buffer.from(await resp.arrayBuffer()));
5626
+ const target = (0, import_node_path13.join)(process.cwd(), rel);
5627
+ (0, import_node_fs11.mkdirSync)((0, import_node_path13.dirname)(target), { recursive: true });
5628
+ (0, import_node_fs11.writeFileSync)(target, Buffer.from(await resp.arrayBuffer()));
5585
5629
  written++;
5586
5630
  }
5587
5631
  } else if (data.files) {
@@ -5663,6 +5707,36 @@ async function modelsCommand(opts) {
5663
5707
  }
5664
5708
  }
5665
5709
 
5710
+ // src/commands/upgrade.ts
5711
+ var import_node_child_process6 = require("child_process");
5712
+ async function upgradeCommand() {
5713
+ console.log(banner());
5714
+ let latest = "";
5715
+ try {
5716
+ const manifest = await request(
5717
+ `${APP_URL}/cli/engine-manifest.json`,
5718
+ { timeoutMs: 8e3 }
5719
+ );
5720
+ latest = manifest.latest_cli_version || "";
5721
+ } catch {
5722
+ }
5723
+ if (latest && compareVersions(VERSION, latest) >= 0) {
5724
+ ui.ok(`Already on the latest version (v${VERSION}).`);
5725
+ return;
5726
+ }
5727
+ if (latest) ui.step(`Updating ${brand.name} v${VERSION} \u2192 v${latest}\u2026`);
5728
+ else ui.step(`Updating ${brand.name} to the latest version\u2026`);
5729
+ const res = (0, import_node_child_process6.spawnSync)("npm", ["install", "-g", `${brand.name}@latest`], { stdio: "inherit" });
5730
+ if (res.status === 0) {
5731
+ ui.ok("Done. Run `agentful --version` to confirm.");
5732
+ return;
5733
+ }
5734
+ ui.fail("The automatic update did not succeed.");
5735
+ ui.info(`Run it yourself: ${paint.accent(`npm install -g ${brand.name}@latest`)}`);
5736
+ ui.info("If that reports a permission error, your Node installation needs sudo or a prefix change.");
5737
+ process.exitCode = 1;
5738
+ }
5739
+
5666
5740
  // src/index.ts
5667
5741
  var program2 = new Command();
5668
5742
  var run = (fn) => async (...args) => {
@@ -5684,10 +5758,11 @@ program2.command("whoami").description("Show the signed-in account").action(run(
5684
5758
  program2.command("init").description("Create (or link) the Agentful cloud project for this directory").option("--link <projectId>", "link an existing project instead of creating one").option("--title <title>", "project title (skips the prompt)").action(run((opts) => initCommand(opts)));
5685
5759
  program2.command("push").description("Upload this project and get a live preview URL").option("--prebuilt", "upload a locally built output instead of building in the cloud").option("--dir <path>", "built-output directory for --prebuilt (default: auto-detect)").action(run((opts) => pushCommand(opts)));
5686
5760
  program2.command("open").description("Open the live preview in the browser").action(run(openCommand));
5687
- program2.command("tui", { isDefault: true }).description("Start the Agentful coding TUI (OpenCode with your Agentful account)").action(run(tuiCommand));
5761
+ program2.command("tui", { isDefault: true }).description("Start the Agentful coding TUI with your Agentful account").option("-s, --session <id>", "resume a previous session").action(run((opts) => tuiCommand(opts)));
5688
5762
  program2.command("publish <subdomain>").description("Publish the built project at https://<subdomain>.agentful.dev").action(run(publishCommand));
5689
5763
  program2.command("share").description("Make the live preview public (anyone with the link)").option("--private", "make the preview owner-only again").action(run((opts) => shareCommand(opts)));
5690
5764
  program2.command("pull").description("Download the cloud workspace into this directory").option("--force", "overwrite files in a non-empty directory").action(run((opts) => pullCommand(opts)));
5691
5765
  program2.command("models").description("List available models and manage local providers with your own key").option("--add-local", "create/show the local provider file to edit").action(run((opts) => modelsCommand(opts)));
5766
+ program2.command("upgrade").description("Update the CLI to the latest version").action(run(upgradeCommand));
5692
5767
  program2.command("licenses").description("Show licenses of the software distributed with this CLI").action(run(licensesCommand));
5693
5768
  program2.parseAsync(process.argv);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentful",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Agentful in your terminal \u2014 local development with push-to-cloud previews",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://agentful.dev",