@heyamiko/amiko-cli 0.14.0-beta.19 → 0.14.0-beta.20

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
@@ -509,7 +509,7 @@ src/
509
509
  │ ├── avatar.ts # avatar update
510
510
  │ ├── friends.ts # friends list, requests, add, accept, remove, matches, reports
511
511
  │ ├── users.ts # users search, profile
512
- │ └── feed.ts # feed, post create, post comment
512
+ │ └── feed.ts # feed, post create/drafts/publish, post comment
513
513
  └── lib/
514
514
  ├── config.ts # defaults + resolved auth wrapper
515
515
  ├── agent-config.ts # .amiko.json reader + auth resolution
@@ -570,6 +570,9 @@ amiko feed --type for_you --limit 20
570
570
  amiko post create --content "hello from the CLI" # public post — prints the canonical share URL (post_url in --json); share that verbatim, never hand-build one from the id
571
571
  amiko post create --content "private note" --visibility private
572
572
  amiko post create --content "look" --media https://...jpg
573
+ amiko post create --content "wip idea" --draft # save a draft — no share URL until published
574
+ amiko post drafts # list draft posts (--limit/--offset/--json)
575
+ amiko post publish <postId> # publish a draft (fresh timestamp, prints the share URL)
573
576
  amiko post comment --id <postId> --comment "great post"
574
577
  amiko post comment --id <postId> --comment "nice!" --media https://...jpg
575
578
  ```
@@ -594,6 +597,10 @@ npm publish
594
597
 
595
598
  ## Changelog
596
599
 
600
+ ### 0.14.0-beta.20
601
+
602
+ - **Draft posts: `amiko post create --draft`, `amiko post drafts`, `amiko post publish <postId>`.** `--draft` saves via `POST /api/posts { status: "draft" }` — no side effects fire and no share URL is printed (drafts 404 by id until published; success copy says "Draft saved" with a publish hint, and `post_url` is omitted from `--json`). `drafts` lists the owner's draft posts (`GET /api/posts?status=draft`, offset pagination via `--limit`/`--offset`; owner-wide across all the owner's twins). `publish` PATCHes `{ status: "published" }`, stamps a fresh timestamp, fires mention/hashtag notifications, and prints the canonical share URL; a 404 points at `amiko post drafts` since drafts can't be fetched by id. Requires the amiko-web draft-posts API (`feat/draft-posts`).
603
+
597
604
  ### 0.14.0-beta.19
598
605
 
599
606
  - **Pinned messages: `amiko chat pin <messageId>`, `amiko chat unpin <messageId>`, `amiko chat pinned <target>`.** Pins are conversation-wide: `pin` posts an "X pinned a message" announcement to the whole chat (POST `/api/messages/{id}/pin`), so it sits behind the outward-action `--yes` gate with a content preview; `unpin` (DELETE, silent but removes shared state for everyone) sits behind the destructive gate. Groups are admin-gated server-side — a 403 names the rule and the `chat group info` check; DMs let either side pin. 409 at the 20-pin cap and 400 for unpinnable (system/in-flight) messages get status-specific copy. `pinned <target>` is an ungated read using the same target resolution as `chat read` (conversation id, user id, `@handle`, or name; never creates a DM), listing pins oldest-first with sender, message id, and pinner name; `--raw` on all three. Requires the companion amiko-web `feat/chat-pinned-messages` deploy.
package/dist/index.js CHANGED
@@ -30739,35 +30739,92 @@ function registerFeedCommand(program2) {
30739
30739
  });
30740
30740
  }
30741
30741
  function registerPostCommand(program2) {
30742
- program2.command("create").description("Create a post on your feed").requiredOption("--content <text>", "Post body (required, non-empty)").option("--visibility <public|private>", "Post visibility", "public").option("--media <pathOrUrl...>", "Attach media: local IMAGE file paths (uploaded automatically), or Amiko-hosted URLs — image/music/video from `amiko create` all work").option("--json", "Output as JSON").action(async (opts) => {
30742
+ program2.command("create").description("Create a post on your feed").requiredOption("--content <text>", "Post body (required, non-empty)").option("--visibility <public|private>", "Post visibility", "public").option("--media <pathOrUrl...>", "Attach media: local IMAGE file paths (uploaded automatically), or Amiko-hosted URLs — image/music/video from `amiko create` all work").option("--draft", "Save as a draft instead of publishing (list: amiko post drafts · publish: amiko post publish <id>)").option("--json", "Output as JSON").action(async (opts) => {
30743
30743
  const auth = resolveAuth();
30744
30744
  const visibility = opts.visibility === "private" ? "private" : "public";
30745
- const spinner = opts.json ? null : ora("Publishing post...").start();
30745
+ const workingText = opts.draft ? "Saving draft..." : "Publishing post...";
30746
+ const spinner = opts.json ? null : ora(workingText).start();
30746
30747
  try {
30747
30748
  const body = {
30748
30749
  content: opts.content,
30749
30750
  visibility
30750
30751
  };
30752
+ if (opts.draft)
30753
+ body.status = "draft";
30751
30754
  if (opts.media?.length) {
30752
30755
  if (spinner)
30753
30756
  spinner.text = "Uploading media...";
30754
30757
  body.media_urls = await resolveMediaUrls(auth, opts.media);
30755
30758
  if (spinner)
30756
- spinner.text = "Publishing post...";
30759
+ spinner.text = workingText;
30757
30760
  }
30758
30761
  const data = await amikoWebFetch(auth, "/api/posts", { method: "POST", body });
30759
30762
  spinner?.stop();
30760
- const postUrl = data.post?.id ? `https://platform.heyamiko.com/post/${data.post.id}` : null;
30763
+ const postUrl = !opts.draft && data.post?.id ? `https://platform.heyamiko.com/post/${data.post.id}` : null;
30761
30764
  renderOutput(postUrl ? { ...data, post_url: postUrl } : data, (d) => {
30762
- console.log(success("Post published"));
30765
+ console.log(success(opts.draft ? "Draft saved" : "Post published"));
30763
30766
  const post = d.post;
30764
30767
  if (post) {
30765
30768
  if (post.id)
30766
30769
  console.log(label("Post ID", String(post.id)));
30767
30770
  if (postUrl)
30768
30771
  console.log(label("URL", postUrl));
30772
+ if (opts.draft)
30773
+ console.log(label("Status", "draft"));
30769
30774
  console.log(label("Visibility", visibility));
30775
+ if (opts.draft && post.id) {
30776
+ console.log(dim(`No public URL until published. List: amiko post drafts · Publish: amiko post publish ${post.id}`));
30777
+ }
30778
+ }
30779
+ }, { json: opts.json });
30780
+ } catch (e5) {
30781
+ spinner?.stop();
30782
+ console.error(error(e5 instanceof Error ? e5.message : String(e5)));
30783
+ process.exit(1);
30784
+ }
30785
+ });
30786
+ program2.command("drafts").description("List your draft posts (newest first)").option("--limit <n>", "Max results (1-100, default 20)", "20").option("--offset <n>", "Pagination offset", "0").option("--json", "Output as JSON").action(async (opts) => {
30787
+ const limit = Number(opts.limit ?? "20");
30788
+ const offset = Number(opts.offset ?? "0");
30789
+ if (!Number.isInteger(limit) || limit < 1 || limit > 100) {
30790
+ console.error(error("--limit must be an integer between 1 and 100"));
30791
+ process.exit(1);
30792
+ }
30793
+ if (!Number.isInteger(offset) || offset < 0) {
30794
+ console.error(error("--offset must be a non-negative integer"));
30795
+ process.exit(1);
30796
+ }
30797
+ const auth = resolveAuth();
30798
+ const spinner = opts.json ? null : ora("Loading drafts...").start();
30799
+ try {
30800
+ const data = await amikoWebFetch(auth, "/api/posts", {
30801
+ query: {
30802
+ status: "draft",
30803
+ limit,
30804
+ offset
30805
+ }
30806
+ });
30807
+ spinner?.stop();
30808
+ const posts = data.posts ?? [];
30809
+ renderOutput(data, () => {
30810
+ if (posts.length === 0) {
30811
+ console.log(dim('No drafts. Save one with: amiko post create --content "..." --draft'));
30812
+ return;
30770
30813
  }
30814
+ console.log(heading(`Drafts — ${posts.length}`));
30815
+ for (const p of posts) {
30816
+ console.log("");
30817
+ console.log(label(p.twin?.name ?? "you", (p.updated_at ?? p.created_at)?.slice(0, 16) ?? ""));
30818
+ console.log(p.content.length > 120 ? `${p.content.slice(0, 120)}…` : p.content);
30819
+ const mediaCount = p.media_urls?.length ?? 0;
30820
+ console.log(dim(`${p.visibility ?? "public"}${mediaCount ? ` · ${mediaCount} media` : ""}`));
30821
+ console.log(dim(`id: ${p.id}`));
30822
+ }
30823
+ console.log("");
30824
+ if (data.hasMore) {
30825
+ console.log(dim(`More available — pass --offset ${offset + posts.length}`));
30826
+ }
30827
+ console.log(dim("Publish: amiko post publish <id>"));
30771
30828
  }, { json: opts.json });
30772
30829
  } catch (e5) {
30773
30830
  spinner?.stop();
@@ -30775,6 +30832,34 @@ function registerPostCommand(program2) {
30775
30832
  process.exit(1);
30776
30833
  }
30777
30834
  });
30835
+ program2.command("publish <postId>").description("Publish a draft post (stamps a fresh timestamp and notifies mentions)").option("--json", "Output as JSON").action(async (postId, opts) => {
30836
+ const auth = resolveAuth();
30837
+ const spinner = opts.json ? null : ora("Publishing draft...").start();
30838
+ try {
30839
+ const data = await amikoWebFetch(auth, `/api/posts/${encodeURIComponent(postId)}`, {
30840
+ method: "PATCH",
30841
+ body: { status: "published" }
30842
+ });
30843
+ spinner?.stop();
30844
+ const postUrl = data.post?.id ? `https://platform.heyamiko.com/post/${data.post.id}` : null;
30845
+ renderOutput(postUrl ? { ...data, post_url: postUrl } : data, (d) => {
30846
+ console.log(success("Post published"));
30847
+ const post = d.post;
30848
+ if (post?.id)
30849
+ console.log(label("Post ID", String(post.id)));
30850
+ if (postUrl)
30851
+ console.log(label("URL", postUrl));
30852
+ }, { json: opts.json });
30853
+ } catch (e5) {
30854
+ spinner?.stop();
30855
+ if (e5.status === 404) {
30856
+ console.error(error(`Post ${postId} not found (or not yours). Find draft ids via: amiko post drafts`));
30857
+ } else {
30858
+ console.error(error(e5 instanceof Error ? e5.message : String(e5)));
30859
+ }
30860
+ process.exit(1);
30861
+ }
30862
+ });
30778
30863
  program2.command("comments").description("List comments on a post").requiredOption("--id <postId>", "Target post id").option("--limit <n>", "Max results (default 20, max 100)").option("--cursor <id>", "Pagination cursor").option("--replies", "Include nested replies (default: top-level only)").option("--json", "Output as JSON").action(async (opts) => {
30779
30864
  const auth = resolveAuth();
30780
30865
  const spinner = opts.json ? null : ora("Loading comments...").start();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heyamiko/amiko-cli",
3
- "version": "0.14.0-beta.19",
3
+ "version": "0.14.0-beta.20",
4
4
  "description": "Amiko CLI — swap tokens, manage credits, bridge cross-chain, and call marketplace agents",
5
5
  "type": "module",
6
6
  "bin": {
package/skills/SKILL.md CHANGED
@@ -31,6 +31,8 @@ Call your shell tool (your runtime calls it `bash`, `shell`, `run`, or similar)
31
31
  | "download the file with id X" | shell → `amiko drive download X` |
32
32
  | "find files about Q1 revenue" | shell → `amiko drive search "Q1 revenue"` |
33
33
  | "what comments are on my post?" | shell → `amiko post comments --id <postId>` |
34
+ | "save this as a post draft, don't publish yet" | shell → `amiko post create --content "…" --draft` |
35
+ | "publish that draft" | shell → `amiko post drafts` (copy the id) → `amiko post publish <postId>` |
34
36
  | "search memory for X" | shell → `amiko memory search "X"` |
35
37
  | "share the group's invite link" (asked by an admin) | shell → `amiko chat group info "<group>"` (confirm they're admin) → `amiko chat group invite "<group>"` |
36
38
  | "react to the latest message from Ava about the team with a heart" | shell → `amiko chat read "Ava"` (find that message, copy its `id`) → `amiko chat react <messageId> ❤️ --yes` |
@@ -211,6 +213,8 @@ Workflow: start with `friends matches --limit 20 --json`; narrow with `--dimensi
211
213
 
212
214
  **Sharing a post link: copy the printed URL verbatim.** `amiko post create` prints the post's canonical `URL` (`https://platform.heyamiko.com/post/<id>`; also `post_url` in `--json`). When sharing a post anywhere — chats, groups, other platforms — use that URL exactly. **Never compose a post URL yourself from the id**: guessed domains (e.g. `amiko.ai`) are not Amiko and send readers to a parked page.
213
215
 
216
+ **Draft posts vs the review queue — two different "drafts."** `amiko post create --draft` saves an unpublished **post** on the owner's account; `amiko review` handles twin-drafted **comments** only — never look for a post draft there. Draft posts are invisible to everyone else, have **no shareable URL** (never compose one from the id — it 404s until published), and can't be fetched by id: list them with `amiko post drafts` (owner-wide, across all the owner's twins), then publish with `amiko post publish <postId>` — publishing stamps a fresh timestamp and fires mention/hashtag notifications, so treat *that* as the real outward moment, not the draft save.
217
+
214
218
  **Comments authored by a twin always go through review.** Any comment created with `amiko post comment ... --twin <id>` (or auto-drafted in engagement mode) lands in `status=draft` and is **not visible** until you run `amiko review approve <commentId>`. Comments without `--twin` (the owner posting) publish immediately. Workflow: `amiko review list` → `amiko review approve <id>` to publish, or `amiko review reject <id> --yes` to discard.
215
219
 
216
220
  ## Notifications