@heyamiko/amiko-cli 0.17.1 → 0.17.2

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
@@ -653,14 +653,16 @@ amiko feed # friends feed (default)
653
653
  amiko feed --type all --limit 20 # the "All" tab (a.k.a. for_you)
654
654
  amiko feed --type following # posts from accounts you follow
655
655
  amiko feed --type media --kind image # site-wide public media (hits /api/media/feed)
656
- 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
656
+ amiko post create --content "hello from the CLI" # saves a DRAFT by default (owner-only) — preview with `amiko post show <id>`, then `amiko post publish <id>`
657
+ amiko post create --content "ship it" --publish # create AND publish in one step — prints the canonical share URL (post_url in --json); share that verbatim, never hand-build one from the id
657
658
  amiko post create --title "Kyoto, 6am" --media ./shot.webp # image-only note — --content is optional when media/docs are attached
658
659
  amiko post create --title "Q3 report" --doc ./q3.pdf # document note — rendered as a file card
659
660
  amiko post create --content "private note" --visibility private
660
661
  amiko post create --content "look" --media https://...jpg
661
662
  amiko post create --title "the track" --media drive:<docId> # media already in the Drive (id from `amiko drive list`) — works for audio/video too
662
- amiko post create --content "wip idea" --draft # save a draft — no share URL until published
663
+ amiko post create --content "wip idea" --draft # explicit draft (same as the default) — no share URL until published
663
664
  amiko post drafts # list draft posts (--limit/--offset/--json)
665
+ amiko post show <postId> # preview any post, including your own unpublished drafts
664
666
  amiko post likers <postId> # who liked one of YOUR posts (403 on someone else's)
665
667
  amiko post publish <postId> # publish a draft (fresh timestamp, prints the share URL)
666
668
  amiko post comment --id <postId> --comment "great post"
@@ -1808,6 +1810,7 @@ Send a friend request to a user by id
1808
1810
  | Flag | Default | Description |
1809
1811
  |---|---|---|
1810
1812
  | `--id <userId>` | | Target user id |
1813
+ | `--yes` | | Skip the confirmation. Only pass this after the owner has confirmed they want to friend this specific person. |
1811
1814
  | `--json` | | Output as JSON |
1812
1815
 
1813
1816
  #### `amiko friends accept <friendshipId>`
@@ -1816,6 +1819,7 @@ Accept a pending friend request
1816
1819
 
1817
1820
  | Flag | Default | Description |
1818
1821
  |---|---|---|
1822
+ | `--yes` | | Skip the confirmation. Only pass this after the owner has confirmed they want to accept this request. |
1819
1823
  | `--json` | | Output as JSON |
1820
1824
 
1821
1825
  #### `amiko friends requests`
@@ -2039,8 +2043,9 @@ Create posts (notes) and comments on the feed — text, images, and documents
2039
2043
 
2040
2044
  | Subcommand | Description |
2041
2045
  |---|---|
2042
- | `post create` | Create a post (a.k.a. note) on your feed |
2046
+ | `post create` | Create a post (a.k.a. note) on your feed. Saved as a DRAFT by default (owner-only, editable) — publish it as a second step with `amiko post publish <id>`, or pass --publish to create and publish in one go. |
2043
2047
  | `post drafts` | List your draft posts (newest first) |
2048
+ | `post show` | Preview a single post by id. Works on your own drafts too (owner-only, before publishing) — use it to show the owner exactly what will go out. |
2044
2049
  | `post publish` | Publish a draft post (stamps a fresh timestamp and notifies mentions) |
2045
2050
  | `post delete` | Delete one of your posts. Use this to clean up a post that went out wrong (bad link, test post, wrong version) |
2046
2051
  | `post comments` | List comments on a post |
@@ -2049,7 +2054,7 @@ Create posts (notes) and comments on the feed — text, images, and documents
2049
2054
 
2050
2055
  #### `amiko post create`
2051
2056
 
2052
- Create a post (a.k.a. note) on your feed
2057
+ Create a post (a.k.a. note) on your feed. Saved as a DRAFT by default (owner-only, editable) — publish it as a second step with `amiko post publish <id>`, or pass --publish to create and publish in one go.
2053
2058
 
2054
2059
  | Flag | Default | Description |
2055
2060
  |---|---|---|
@@ -2059,8 +2064,9 @@ Create a post (a.k.a. note) on your feed
2059
2064
  | `--visibility <public|private>` | `public` | Post visibility |
2060
2065
  | `--media <pathOrUrl...>` | | Attach media: local IMAGE file paths (uploaded automatically); Amiko-hosted URLs (image/music/video from `amiko create`); or `drive:<docId>` for a media file in your Drive (`amiko drive list`) |
2061
2066
  | `--doc <pathOrUrl...>` | | Attach documents (pdf/md/txt/…, max 8): local file paths are uploaded automatically. Rendered as file cards, not images |
2062
- | `--draft` | | Save as a draft instead of publishing (list: amiko post drafts · publish: amiko post publish <id>) |
2063
- | `--yes` | | Skip the publish confirmation (drafts never prompt). Only pass this after the owner has reviewed the exact copy and approved sending it. |
2067
+ | `--draft` | | Save as a draft (this is the default). Kept for clarity/back-compat — a bare `create` already drafts. List: amiko post drafts · publish: amiko post publish <id> |
2068
+ | `--publish` | | Create AND publish immediately, skipping the draft step. Outward — everyone sees it — so it takes the confirmation gate (or --yes once the owner has approved the exact copy). |
2069
+ | `--yes` | | Skip the publish confirmation (only relevant with --publish; drafts never prompt). Only pass this after the owner has reviewed the exact copy and approved publishing it. |
2064
2070
  | `--json` | | Output as JSON |
2065
2071
 
2066
2072
  #### `amiko post drafts`
@@ -2073,6 +2079,14 @@ List your draft posts (newest first)
2073
2079
  | `--offset <n>` | `0` | Pagination offset |
2074
2080
  | `--json` | | Output as JSON |
2075
2081
 
2082
+ #### `amiko post show <postId>`
2083
+
2084
+ Preview a single post by id. Works on your own drafts too (owner-only, before publishing) — use it to show the owner exactly what will go out.
2085
+
2086
+ | Flag | Default | Description |
2087
+ |---|---|---|
2088
+ | `--json` | | Output as JSON |
2089
+
2076
2090
  #### `amiko post publish <postId>`
2077
2091
 
2078
2092
  Publish a draft post (stamps a fresh timestamp and notifies mentions)
package/dist/index.js CHANGED
@@ -34041,8 +34041,14 @@ function registerFriendsCommand(program2) {
34041
34041
  process.exit(1);
34042
34042
  }
34043
34043
  });
34044
- program2.command("add").description("Send a friend request to a user by id").requiredOption("--id <userId>", "Target user id").option("--json", "Output as JSON").action(async (opts) => {
34044
+ program2.command("add").description("Send a friend request to a user by id").requiredOption("--id <userId>", "Target user id").option("--yes", "Skip the confirmation. Only pass this after the owner has confirmed they want to friend this specific person.").option("--json", "Output as JSON").action(async (opts) => {
34045
34045
  const auth = resolveAuth();
34046
+ await requireApproval({
34047
+ free: "sends a friend request to a real person, who is notified",
34048
+ summary: `Send a friend request to user ${opts.id}`,
34049
+ yes: opts.yes,
34050
+ commandExample: `amiko friends add --id ${JSON.stringify(opts.id)}`
34051
+ });
34046
34052
  const spinner = opts.json ? null : ora("Sending friend request...").start();
34047
34053
  try {
34048
34054
  const data = await amikoWebFetch(auth, "/api/friends", {
@@ -34061,8 +34067,14 @@ function registerFriendsCommand(program2) {
34061
34067
  process.exit(1);
34062
34068
  }
34063
34069
  });
34064
- program2.command("accept <friendshipId>").description("Accept a pending friend request").option("--json", "Output as JSON").action(async (friendshipId, opts) => {
34070
+ program2.command("accept <friendshipId>").description("Accept a pending friend request").option("--yes", "Skip the confirmation. Only pass this after the owner has confirmed they want to accept this request.").option("--json", "Output as JSON").action(async (friendshipId, opts) => {
34065
34071
  const auth = resolveAuth();
34072
+ await requireApproval({
34073
+ free: "accepts a friend request, forming a mutual connection the other person sees",
34074
+ summary: `Accept friend request ${friendshipId}`,
34075
+ yes: opts.yes,
34076
+ commandExample: `amiko friends accept ${JSON.stringify(friendshipId)}`
34077
+ });
34066
34078
  const spinner = opts.json ? null : ora("Accepting...").start();
34067
34079
  try {
34068
34080
  const data = await amikoWebFetch(auth, `/api/friends/${friendshipId}`, { method: "PATCH", body: { action: "accept" } });
@@ -34990,7 +35002,7 @@ function registerFeedCommand(program2) {
34990
35002
  });
34991
35003
  }
34992
35004
  function registerPostCommand(program2) {
34993
- program2.command("create").description("Create a post (a.k.a. note) on your feed").option("--content <text>", "Post body. Optional when --media or --doc is given (an image-only note is a normal post)").option("--markdown", "Render --content as markdown (bold/lists/links/tables). @mentions and #hashtags still link. Default is plain text.").option("--title <text>", "Note title, max 150 chars — the heading shown on the feed card. Optional but strongly recommended for image notes").option("--visibility <public|private>", "Post visibility", "public").option("--media <pathOrUrl...>", "Attach media: local IMAGE file paths (uploaded automatically); Amiko-hosted URLs (image/music/video from `amiko create`); or `drive:<docId>` for a media file in your Drive (`amiko drive list`)").option("--doc <pathOrUrl...>", "Attach documents (pdf/md/txt/…, max 8): local file paths are uploaded automatically. Rendered as file cards, not images").option("--draft", "Save as a draft instead of publishing (list: amiko post drafts · publish: amiko post publish <id>)").option("--yes", "Skip the publish confirmation (drafts never prompt). Only pass this after the owner has reviewed the exact copy and approved sending it.").option("--json", "Output as JSON").action(async (opts) => {
35005
+ program2.command("create").description("Create a post (a.k.a. note) on your feed. Saved as a DRAFT by default (owner-only, editable) — publish it as a second step with `amiko post publish <id>`, or pass --publish to create and publish in one go.").option("--content <text>", "Post body. Optional when --media or --doc is given (an image-only note is a normal post)").option("--markdown", "Render --content as markdown (bold/lists/links/tables). @mentions and #hashtags still link. Default is plain text.").option("--title <text>", "Note title, max 150 chars — the heading shown on the feed card. Optional but strongly recommended for image notes").option("--visibility <public|private>", "Post visibility", "public").option("--media <pathOrUrl...>", "Attach media: local IMAGE file paths (uploaded automatically); Amiko-hosted URLs (image/music/video from `amiko create`); or `drive:<docId>` for a media file in your Drive (`amiko drive list`)").option("--doc <pathOrUrl...>", "Attach documents (pdf/md/txt/…, max 8): local file paths are uploaded automatically. Rendered as file cards, not images").option("--draft", "Save as a draft (this is the default). Kept for clarity/back-compat — a bare `create` already drafts. List: amiko post drafts · publish: amiko post publish <id>").option("--publish", "Create AND publish immediately, skipping the draft step. Outward — everyone sees it — so it takes the confirmation gate (or --yes once the owner has approved the exact copy).").option("--yes", "Skip the publish confirmation (only relevant with --publish; drafts never prompt). Only pass this after the owner has reviewed the exact copy and approved publishing it.").option("--json", "Output as JSON").action(async (opts) => {
34994
35006
  const auth = resolveAuth();
34995
35007
  const visibility = opts.visibility === "private" ? "private" : "public";
34996
35008
  const content = opts.content?.trim() ?? "";
@@ -35004,7 +35016,12 @@ function registerPostCommand(program2) {
35004
35016
  console.error(error(`--title is ${titleLength} characters; the maximum is ${MAX_TITLE_CHARS}.`));
35005
35017
  process.exit(1);
35006
35018
  }
35007
- if (!opts.draft) {
35019
+ if (opts.publish && opts.draft) {
35020
+ console.error(error("Pass either --publish or --draft, not both."));
35021
+ process.exit(1);
35022
+ }
35023
+ const isDraft = !opts.publish;
35024
+ if (!isDraft) {
35008
35025
  const previewLine = (title ? `${title} — ` : "") + content;
35009
35026
  const attachSummary = [
35010
35027
  opts.media?.length ? `${opts.media.length} media` : "",
@@ -35014,10 +35031,10 @@ function registerPostCommand(program2) {
35014
35031
  free: "publishes a post to your public feed — everyone can see it",
35015
35032
  summary: `Publish ${visibility} post${attachSummary ? ` [+${attachSummary}]` : ""}: ${previewLine.slice(0, 80)}${previewLine.length > 80 ? "…" : ""}`,
35016
35033
  yes: opts.yes,
35017
- commandExample: `amiko post create` + (opts.content ? ` --content ${JSON.stringify(opts.content)}` : "") + (opts.markdown ? " --markdown" : "") + (opts.title ? ` --title ${JSON.stringify(opts.title)}` : "") + (opts.visibility && opts.visibility !== "public" ? ` --visibility ${opts.visibility}` : "") + (opts.media?.length ? ` --media ${opts.media.map((m) => JSON.stringify(m)).join(" ")}` : "") + (opts.doc?.length ? ` --doc ${opts.doc.map((d) => JSON.stringify(d)).join(" ")}` : "")
35034
+ commandExample: `amiko post create --publish` + (opts.content ? ` --content ${JSON.stringify(opts.content)}` : "") + (opts.markdown ? " --markdown" : "") + (opts.title ? ` --title ${JSON.stringify(opts.title)}` : "") + (opts.visibility && opts.visibility !== "public" ? ` --visibility ${opts.visibility}` : "") + (opts.media?.length ? ` --media ${opts.media.map((m) => JSON.stringify(m)).join(" ")}` : "") + (opts.doc?.length ? ` --doc ${opts.doc.map((d) => JSON.stringify(d)).join(" ")}` : "")
35018
35035
  });
35019
35036
  }
35020
- const workingText = opts.draft ? "Saving draft..." : "Publishing post...";
35037
+ const workingText = isDraft ? "Saving draft..." : "Publishing post...";
35021
35038
  const spinner = opts.json ? null : ora(workingText).start();
35022
35039
  try {
35023
35040
  const body = {
@@ -35028,8 +35045,7 @@ function registerPostCommand(program2) {
35028
35045
  body.content_format = "markdown";
35029
35046
  if (title)
35030
35047
  body.title = title;
35031
- if (opts.draft)
35032
- body.status = "draft";
35048
+ body.status = isDraft ? "draft" : "published";
35033
35049
  if (opts.media?.length) {
35034
35050
  if (spinner)
35035
35051
  spinner.text = "Uploading media...";
@@ -35046,16 +35062,16 @@ function registerPostCommand(program2) {
35046
35062
  }
35047
35063
  const data = await amikoWebFetch(auth, "/api/posts", { method: "POST", body });
35048
35064
  spinner?.stop();
35049
- const postUrl = !opts.draft && data.post?.id ? `https://platform.heyamiko.com/post/${data.post.id}` : null;
35065
+ const postUrl = !isDraft && data.post?.id ? `https://platform.heyamiko.com/post/${data.post.id}` : null;
35050
35066
  renderOutput(postUrl ? { ...data, post_url: postUrl } : data, (d) => {
35051
- console.log(success(opts.draft ? "Draft saved" : "Post published"));
35067
+ console.log(success(isDraft ? "Draft saved" : "Post published"));
35052
35068
  const post = d.post;
35053
35069
  if (post) {
35054
35070
  if (post.id)
35055
35071
  console.log(label("Post ID", String(post.id)));
35056
35072
  if (postUrl)
35057
35073
  console.log(label("URL", postUrl));
35058
- if (opts.draft)
35074
+ if (isDraft)
35059
35075
  console.log(label("Status", "draft"));
35060
35076
  if (title)
35061
35077
  console.log(label("Title", title));
@@ -35064,8 +35080,8 @@ function registerPostCommand(program2) {
35064
35080
  console.log(label("Documents", (docs ?? []).map((doc2) => doc2.filename).join(", ")));
35065
35081
  }
35066
35082
  console.log(label("Visibility", visibility));
35067
- if (opts.draft && post.id) {
35068
- console.log(dim(`No public URL until published. List: amiko post drafts · Publish: amiko post publish ${post.id}`));
35083
+ if (isDraft && post.id) {
35084
+ console.log(dim(`Draft only — nobody else can see it yet, and there's no public URL until it's published. Preview: amiko post show ${post.id} · Publish (owner approves): amiko post publish ${post.id}`));
35069
35085
  }
35070
35086
  }
35071
35087
  }, { json: opts.json });
@@ -35129,6 +35145,47 @@ function registerPostCommand(program2) {
35129
35145
  process.exit(1);
35130
35146
  }
35131
35147
  });
35148
+ program2.command("show <postId>").description("Preview a single post by id. Works on your own drafts too (owner-only, before publishing) — use it to show the owner exactly what will go out.").option("--json", "Output as JSON").action(async (postId, opts) => {
35149
+ const auth = resolveAuth();
35150
+ const spinner = opts.json ? null : ora("Loading post...").start();
35151
+ try {
35152
+ const data = await amikoWebFetch(auth, `/api/posts/${encodeURIComponent(postId)}`);
35153
+ spinner?.stop();
35154
+ const post = data.post;
35155
+ if (!post) {
35156
+ console.error(error(`Post ${postId} not found.`));
35157
+ process.exit(1);
35158
+ }
35159
+ const status = post.status ?? "published";
35160
+ const postUrl = status === "published" && post.id ? `https://platform.heyamiko.com/post/${post.id}` : null;
35161
+ renderOutput(postUrl ? { ...data, post_url: postUrl } : data, () => {
35162
+ console.log(heading(status === "draft" ? "Draft (owner-only)" : "Post"));
35163
+ if (post.title)
35164
+ console.log(heading(String(post.title)));
35165
+ if (post.content)
35166
+ console.log(String(post.content));
35167
+ const mediaCount = post.media_urls?.length ?? 0;
35168
+ const docCount = post.doc_attachments?.length ?? 0;
35169
+ console.log(dim(`${post.visibility ?? "public"}${mediaCount ? ` · ${mediaCount} media` : ""}${docCount ? ` · ${docCount} docs` : ""}`));
35170
+ if (post.id)
35171
+ console.log(label("Post ID", String(post.id)));
35172
+ console.log(label("Status", status));
35173
+ if (postUrl)
35174
+ console.log(label("URL", postUrl));
35175
+ if (status === "draft" && post.id) {
35176
+ console.log(dim(`Publish (owner approves): amiko post publish ${post.id}`));
35177
+ }
35178
+ }, { json: opts.json });
35179
+ } catch (e5) {
35180
+ spinner?.stop();
35181
+ if (e5.status === 404) {
35182
+ console.error(error(`Post ${postId} not found (or a draft that isn't yours). Your drafts: amiko post drafts`));
35183
+ } else {
35184
+ console.error(error(e5 instanceof Error ? e5.message : String(e5)));
35185
+ }
35186
+ process.exit(1);
35187
+ }
35188
+ });
35132
35189
  program2.command("publish <postId>").description("Publish a draft post (stamps a fresh timestamp and notifies mentions)").option("--yes", "Skip the publish confirmation. Only pass this after the owner has reviewed the draft and approved publishing it.").option("--json", "Output as JSON").action(async (postId, opts) => {
35133
35190
  const auth = resolveAuth();
35134
35191
  await requireApproval({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heyamiko/amiko-cli",
3
- "version": "0.17.1",
3
+ "version": "0.17.2",
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
@@ -33,13 +33,13 @@ Call your shell tool (your runtime calls it `bash`, `shell`, `run`, or similar)
33
33
  | "what comments are on my post?" | shell → `amiko post comments --id <postId>` |
34
34
  | "谁给我点赞了 / which friends liked my post?" | shell → `amiko post likers <postId>` (own posts only) → cross-reference ids against `amiko friends list` |
35
35
  | "我有多少好友 / how many friends do I have?" | shell → `amiko friends list` — read the header total, don't count rows |
36
- | "发个笔记 / post this photo as a note" | draft-first → `amiko post create --title "…" --media ./photo.webp --draft` → show the owner → `amiko post publish <id> --yes` |
37
- | "post the audio/image from my Drive" | shell → `amiko drive list --json` (copy the `id`) → `amiko post create --title "…" --media drive:<docId> --draft` → owner reviews → `amiko post publish <id> --yes` |
38
- | "share this PDF on my feed" | `amiko post create --title "…" --doc ./report.pdf --draft` → owner reviews → `amiko post publish <id> --yes` |
36
+ | "发个笔记 / post this photo as a note" | draft-first (`create` drafts by default) → `amiko post create --title "…" --media ./photo.webp` → `amiko post show <id>` show the owner → `amiko post publish <id> --yes` |
37
+ | "post the audio/image from my Drive" | shell → `amiko drive list --json` (copy the `id`) → `amiko post create --title "…" --media drive:<docId>` → `amiko post show <id>` owner reviews → `amiko post publish <id> --yes` |
38
+ | "share this PDF on my feed" | `amiko post create --title "…" --doc ./report.pdf` → `amiko post show <id>` owner reviews → `amiko post publish <id> --yes` |
39
39
  | "关注 / follow @mars" | shell → `amiko users follow mars` (confirm first — it notifies them) |
40
40
  | "what did the people I follow post?" | shell → `amiko feed --type following` |
41
- | "save this as a post draft, don't publish yet" | shell → `amiko post create --content "…" --draft` |
42
- | "publish that draft" | shell → `amiko post drafts` (copy the id) → show the owner → `amiko post publish <postId> --yes` |
41
+ | "save this as a post draft, don't publish yet" | shell → `amiko post create --content "…"` (drafts by default; `--draft` is optional/back-compat) |
42
+ | "publish that draft" | shell → `amiko post drafts` (copy the id) → `amiko post show <postId>` show the owner → `amiko post publish <postId> --yes` |
43
43
  | "delete / 撤下 that post (it went out wrong)" | shell → `amiko post list` (copy the id) → confirm which one → `amiko post delete <postId> --yes` |
44
44
  | "search memory for X" | shell → `amiko memory search "X"` |
45
45
  | "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>"` |
@@ -73,7 +73,7 @@ Discover everything via `amiko --help` (or `amiko <group> --help` / `amiko <grou
73
73
 
74
74
  ## Critical Rules
75
75
 
76
- 1. **Every paid / value-moving command is hard-gated on `--yes` in a non-interactive shell.** The CLI refuses to run `search *`, `markets *`, `create *`, `card mint`, `credits topup`, `wallets swap send`, `wallets bridge send`, `wallets transfer` unless `--yes` is passed. The refusal prints the cost and the re-run command. **Quote the cost, get explicit approval, THEN append `--yes`.** For `create *` there is a step before that — propose the model and settings and get the owner's sign-off on the *plan* first; see **Proposing, then quoting, before you generate**. The same `--yes` gate also covers **free outward social actions** — `chat send`, `chat pin` (the whole chat sees a pinned-message announcement), `chat group create`, `chat group add` (real people see them, so get the owner's explicit approval for the action itself) — and **destructive ops** like `chat unpin` (removes the pin for everyone), `chat mark-read --all` (irreversibly marks every conversation read — senders see read ticks), `chat group remove/leave/rename/promote/mention-all`, `chat lists create/rename/add/remove/delete` (private to the owner, but they reshape the owner's chat UI), `friends nickname set/remove` (private to the owner, but it changes how that friend is displayed everywhere in the owner's apps), `twin update --public`, `drive delete`, `drive share` / `drive folder share` (exposes the file — or the folder's ENTIRE subtree — to anyone with the link), `friends remove`, `friends reports request`, `avatar update`, `voice reset`, `review reject`. **For these free actions never mention cost, never say "the cost is 0", and never call it a paid operation** — ask for plain confirmation of the action itself (e.g. "Send "hi" to Mars — go ahead?").
76
+ 1. **Every paid / value-moving command is hard-gated on `--yes` in a non-interactive shell.** The CLI refuses to run `search *`, `markets *`, `create *`, `card mint`, `credits topup`, `wallets swap send`, `wallets bridge send`, `wallets transfer` unless `--yes` is passed. The refusal prints the cost and the re-run command. **Quote the cost, get explicit approval, THEN append `--yes`.** For `create *` there is a step before that — propose the model and settings and get the owner's sign-off on the *plan* first; see **Proposing, then quoting, before you generate**. The same `--yes` gate also covers **free outward social actions** — `chat send`, `chat pin` (the whole chat sees a pinned-message announcement), `chat group create`, `chat group add`, `friends add` / `friends accept` (real people see them — a friended user is notified — so get the owner's explicit approval for the action itself) — and **destructive ops** like `chat unpin` (removes the pin for everyone), `chat mark-read --all` (irreversibly marks every conversation read — senders see read ticks), `chat group remove/leave/rename/promote/mention-all`, `chat lists create/rename/add/remove/delete` (private to the owner, but they reshape the owner's chat UI), `friends nickname set/remove` (private to the owner, but it changes how that friend is displayed everywhere in the owner's apps), `twin update --public`, `drive delete`, `drive share` / `drive folder share` (exposes the file — or the folder's ENTIRE subtree — to anyone with the link), `friends remove`, `friends reports request`, `avatar update`, `voice reset`, `review reject`. **For these free actions never mention cost, never say "the cost is 0", and never call it a paid operation** — ask for plain confirmation of the action itself (e.g. "Send "hi" to Mars — go ahead?").
77
77
  2. **After every paid command, report the remaining balance.** The CLI prints a `Balance: N credits` line — include that figure in your reply.
78
78
  3. **Never retry a failed command.** Report and stop. Every paid call costs tokens even on failure.
79
79
  4. **Auth is automatic.** Never suggest `amiko login` / `amiko connect`. Run from the agent's workspace folder; if anything looks off, `amiko accounts` shows the resolved `userId` / `twinId`.
@@ -352,7 +352,9 @@ Two different relationships — pick the one the owner actually asked for:
352
352
 
353
353
  **"Notes" and "posts" are the same thing.** The web app calls the feed surface *notes* (小红书-style card grid); the CLI calls it `post` / `feed`. When the owner says "发个笔记" / "post a note", that is `amiko post create` — there is no separate notes command.
354
354
 
355
- > **⚠️ Draft-first, publish on the owner's word.** Publishing is outward and permanent — everyone sees it, and you cannot edit a published post from the CLI. Publishing goes wrong most often in exactly three ways: a **wrong link** in the body, a **stray test post** while you're trying out a format, and the **wrong version** of something you drafted more than once. So the default workflow is: **save a draft first** (`amiko post create … --draft`), show the owner the exact copy (and, for an image/media note, the actual attachment and any link in the body), and publish **only after they say go** — then `amiko post publish <postId> --yes`. Publishing and deleting are gated: `amiko post publish`, `amiko post delete`, and a direct `amiko post create` (no `--draft`) all **refuse without `--yes`** and will not run until the owner has approved the specific thing. `--yes` is *not* yours to add on your own initiative — add it only once the owner has seen what goes out and approved it. When in doubt, draft it and ask; do not publish to "check how it looks." (Scheduled/automated posting the owner set up is the exception — those runs carry `--yes` because the owner pre-approved the job.)
355
+ > **⚠️ Draft-first, publish on the owner's word.** Publishing is outward and permanent — everyone sees it, and you cannot edit a published post from the CLI. Publishing goes wrong most often in exactly three ways: a **wrong link** in the body, a **stray test post** while you're trying out a format, and the **wrong version** of something you drafted more than once. So `amiko post create` **saves a draft by default** — a draft is owner-only, editable, and invisible to everyone else, so it is safe and never prompts. Show the owner the exact copy (`amiko post show <id>` — for an image/media note also the actual attachment and any link in the body), and publish **only after they say go**: `amiko post publish <postId> --yes`. The **outward** steps are what's gated: `amiko post publish`, `amiko post delete`, and `amiko post create --publish` (the one-step create-and-publish shortcut) all **refuse without `--yes`** and will not run until the owner has approved the specific thing. A bare `amiko post create` does NOT need `--yes` — it only makes a draft. `--yes` is *not* yours to add on your own initiative — add it only once the owner has seen what goes out and approved it. When in doubt, draft it and ask; do not publish to "check how it looks."
356
+
357
+ > **⏰ Schedule mode — publishing needs `--publish` (the job is the owner's pre-approval).** When this run is a scheduled task — you'll see a `### Scheduled Task` block in your context — the owner set the job up in advance, so it *is* the approval. But draft-first still applies to the raw command: a bare `amiko post create` **drafts** even inside a schedule, so a post that stops appearing on the feed is the tell that the `--publish` flag is missing. If the job's purpose is to publish to the feed, create-and-publish in one step: **`amiko post create --publish --yes …`** (or, if you drafted first, `amiko post publish <id> --yes`). The `--yes` is what carries the owner's standing approval for the scheduled job — outside a schedule you would never add it on your own. If the job only says "draft" (e.g. a review-before-send flow), keep the bare `amiko post create` and deliver the draft to the owner instead of publishing.
356
358
 
357
359
  **A note is a card, so give it a `--title`.** `--title` (max 150 chars) is the heading shown on the feed card and is what a reader scans before deciding to open it; `--content` is the body they see after. For an image note, the title is doing nearly all the work — always pass one. Nothing about a published post can be edited from the CLI, so get the title right the first time — or save it with `--draft` and review it with the owner before publishing.
358
360