freshjots 0.2.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.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +85 -0
- data/lib/freshjots/version.rb +5 -0
- data/lib/freshjots.rb +107 -0
- metadata +49 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 878a80b88154f8d8421f1e2aea48643ecdf00dda54ebdd2d3c758ae35d66a99f
|
|
4
|
+
data.tar.gz: e4e4fe520d5007bc38d93fc29ed96ad1c36eadabfbe09aec36a91dda22cd14dc
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 4a34ff09969147fda0da083a3559ed4c4b4751f8f9a48ebd67c750f72079900dd1f0a17cf7b82236354c7b139209fb60fa8069fb7caddbf566b084b667ffe18a
|
|
7
|
+
data.tar.gz: a2619e3b7a4d32837374585fb8a1265ab98f3e28482463bca33d5dce7075a3e491d29fcc1a328b6a1ac4e0f4bbc5744f4fca29588447fd51a80ad2903d434931
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Goran Arsov
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# freshjots — Ruby
|
|
2
|
+
|
|
3
|
+
Tiny Ruby client for the [Fresh Jots](https://freshjots.com) API. One file,
|
|
4
|
+
no runtime dependencies (uses `net/http` from stdlib).
|
|
5
|
+
|
|
6
|
+
## Install
|
|
7
|
+
|
|
8
|
+
```ruby
|
|
9
|
+
# Gemfile
|
|
10
|
+
gem "freshjots"
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
bundle install
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Or globally:
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
gem install freshjots
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Use
|
|
24
|
+
|
|
25
|
+
```ruby
|
|
26
|
+
require "freshjots"
|
|
27
|
+
|
|
28
|
+
# Reads FRESHJOTS_TOKEN from the environment by default.
|
|
29
|
+
client = Freshjots::Client.new
|
|
30
|
+
|
|
31
|
+
# Append text to a note (creates it if missing).
|
|
32
|
+
client.append("cron-jobs-prod", "backup ok #{Time.now.utc.iso8601}")
|
|
33
|
+
|
|
34
|
+
# Read a note's body.
|
|
35
|
+
puts client.note("cron-jobs-prod")[:plain_body]
|
|
36
|
+
|
|
37
|
+
# List your notes.
|
|
38
|
+
client.notes.each { |note| puts "#{note[:filename]}\t#{note[:title]}" }
|
|
39
|
+
|
|
40
|
+
# Create a note. The API derives the filename from the title — for a
|
|
41
|
+
# note addressable by an exact filename, use append instead.
|
|
42
|
+
created = client.create(title: "Research 2026 Q2", body: "Initial outline.")
|
|
43
|
+
puts created[:filename] # server-derived stream name
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
The whole API is four methods: `notes`, `note(filename)`, `create(title:, body:)`, `append(filename, text)`. `note` and `create` return the note hash directly (no `{ note: … }` wrapper); `notes` returns the array.
|
|
47
|
+
|
|
48
|
+
## Errors
|
|
49
|
+
|
|
50
|
+
Any non-2xx response raises `Freshjots::ApiError` with `status`, `code`,
|
|
51
|
+
`message`, and (when present) `details` from the API's stable error
|
|
52
|
+
envelope:
|
|
53
|
+
|
|
54
|
+
```ruby
|
|
55
|
+
begin
|
|
56
|
+
client.append("huge", "x" * 5_000_000)
|
|
57
|
+
rescue Freshjots::ApiError => e
|
|
58
|
+
puts "#{e.status} #{e.code}: #{e.message}"
|
|
59
|
+
# 413 content_too_large: body exceeds the per-note 3 MB cap
|
|
60
|
+
end
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Stable error codes: `unauthenticated`, `forbidden`, `not_found`,
|
|
64
|
+
`validation_failed`, `cap_exceeded`, `storage_cap_exceeded`,
|
|
65
|
+
`content_too_large`, `content_type_mismatch`, `rate_limited`. Full list:
|
|
66
|
+
<https://freshjots.com/docs>.
|
|
67
|
+
|
|
68
|
+
## Auth
|
|
69
|
+
|
|
70
|
+
Mint a token at <https://freshjots.com/settings/api_tokens> (Dev or
|
|
71
|
+
Dev-pro tier required). Set it once:
|
|
72
|
+
|
|
73
|
+
```sh
|
|
74
|
+
export FRESHJOTS_TOKEN=<your-token>
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Or pass explicitly:
|
|
78
|
+
|
|
79
|
+
```ruby
|
|
80
|
+
Freshjots::Client.new(token: "fjk_…")
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## License
|
|
84
|
+
|
|
85
|
+
MIT.
|
data/lib/freshjots.rb
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "net/http"
|
|
5
|
+
require "uri"
|
|
6
|
+
|
|
7
|
+
require_relative "freshjots/version"
|
|
8
|
+
|
|
9
|
+
# Tiny client for the Fresh Jots API (https://freshjots.com/docs).
|
|
10
|
+
#
|
|
11
|
+
# Usage:
|
|
12
|
+
#
|
|
13
|
+
# client = Freshjots::Client.new # reads FRESHJOTS_TOKEN from ENV
|
|
14
|
+
# client.append("cron-jobs-prod", "backup ok")
|
|
15
|
+
# client.note("cron-jobs-prod")[:plain_body]
|
|
16
|
+
# client.create(title: "Deploy log")[:filename] # server-derived
|
|
17
|
+
#
|
|
18
|
+
# All methods raise Freshjots::ApiError on non-2xx, with code/status/details
|
|
19
|
+
# from the API's stable error envelope.
|
|
20
|
+
#
|
|
21
|
+
# Response shapes: GET /notes is the only endpoint that wraps its payload
|
|
22
|
+
# ({ notes: [...] }). show / show-by-filename / create return the note
|
|
23
|
+
# hash at the TOP LEVEL — there is no { note: ... } wrapper.
|
|
24
|
+
module Freshjots
|
|
25
|
+
class ApiError < StandardError
|
|
26
|
+
attr_reader :status, :code, :details
|
|
27
|
+
|
|
28
|
+
def initialize(status:, code:, message:, details: nil)
|
|
29
|
+
super(message)
|
|
30
|
+
@status, @code, @details = status, code, details
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
class Client
|
|
35
|
+
DEFAULT_BASE_URL = "https://freshjots.com/api/v1"
|
|
36
|
+
|
|
37
|
+
def initialize(token: ENV["FRESHJOTS_TOKEN"], base_url: DEFAULT_BASE_URL)
|
|
38
|
+
raise ArgumentError, "FRESHJOTS_TOKEN missing — pass token: or set the env var" if token.nil? || token.empty?
|
|
39
|
+
|
|
40
|
+
@token, @base_url = token, base_url
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def notes
|
|
44
|
+
request(:get, "/notes")[:notes]
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# show-by-filename renders the serializer at the top level (no
|
|
48
|
+
# { note: ... } wrapper), so the response *is* the note hash.
|
|
49
|
+
def note(filename)
|
|
50
|
+
request(:get, "/notes/by-filename/#{escape(filename)}")
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Create a note. The API permits note[title, plain_body, format, ...]
|
|
54
|
+
# — NOT filename: the server DERIVES the filename from the title. For
|
|
55
|
+
# a note addressable by an exact, caller-chosen filename, use append
|
|
56
|
+
# (the by-filename endpoint creates it with that exact name on first
|
|
57
|
+
# call). Returns the created note hash (top level); read [:filename]
|
|
58
|
+
# for the server-derived stream name.
|
|
59
|
+
def create(title:, body: "")
|
|
60
|
+
if title.nil? || title.to_s.empty?
|
|
61
|
+
raise ArgumentError,
|
|
62
|
+
"create requires a title — the API derives the filename from it. " \
|
|
63
|
+
"For a note addressable by an exact filename, use append."
|
|
64
|
+
end
|
|
65
|
+
payload = { note: { title: title, plain_body: body, format: "plain" } }
|
|
66
|
+
request(:post, "/notes", payload)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def append(filename, text)
|
|
70
|
+
request(:post, "/notes/by-filename/#{escape(filename)}/append", { text: text })
|
|
71
|
+
true
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
private
|
|
75
|
+
|
|
76
|
+
def request(method, path, body = nil)
|
|
77
|
+
uri = URI("#{@base_url}#{path}")
|
|
78
|
+
req = Net::HTTP.const_get(method.to_s.capitalize).new(uri)
|
|
79
|
+
req["Authorization"] = "Bearer #{@token}"
|
|
80
|
+
if body
|
|
81
|
+
req["Content-Type"] = "application/json"
|
|
82
|
+
req.body = body.to_json
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
res = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https") { |http| http.request(req) }
|
|
86
|
+
data = res.body.to_s.empty? ? {} : JSON.parse(res.body, symbolize_names: true)
|
|
87
|
+
raise_for_error!(res, data)
|
|
88
|
+
data
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def raise_for_error!(res, data)
|
|
92
|
+
return if res.is_a?(Net::HTTPSuccess)
|
|
93
|
+
|
|
94
|
+
err = data[:error] || {}
|
|
95
|
+
raise ApiError.new(
|
|
96
|
+
status: res.code.to_i,
|
|
97
|
+
code: err[:code] || "unknown",
|
|
98
|
+
message: err[:message] || "request failed",
|
|
99
|
+
details: err[:details]
|
|
100
|
+
)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def escape(value)
|
|
104
|
+
URI.encode_www_form_component(value.to_s)
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: freshjots
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.2.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Goran Arsov
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies: []
|
|
12
|
+
description: Append-only notebooks for cron jobs, deploy scripts, and bots. Wraps
|
|
13
|
+
the four-endpoint plain-text REST API at freshjots.com/api/v1.
|
|
14
|
+
email:
|
|
15
|
+
- arsphy@yahoo.com
|
|
16
|
+
executables: []
|
|
17
|
+
extensions: []
|
|
18
|
+
extra_rdoc_files: []
|
|
19
|
+
files:
|
|
20
|
+
- LICENSE
|
|
21
|
+
- README.md
|
|
22
|
+
- lib/freshjots.rb
|
|
23
|
+
- lib/freshjots/version.rb
|
|
24
|
+
homepage: https://github.com/Goran-Arsov/freshjots-ruby
|
|
25
|
+
licenses:
|
|
26
|
+
- MIT
|
|
27
|
+
metadata:
|
|
28
|
+
homepage_uri: https://github.com/Goran-Arsov/freshjots-ruby
|
|
29
|
+
source_code_uri: https://github.com/Goran-Arsov/freshjots-ruby
|
|
30
|
+
bug_tracker_uri: https://github.com/Goran-Arsov/freshjots-ruby/issues
|
|
31
|
+
documentation_uri: https://freshjots.com/docs
|
|
32
|
+
rdoc_options: []
|
|
33
|
+
require_paths:
|
|
34
|
+
- lib
|
|
35
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '3.0'
|
|
40
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
41
|
+
requirements:
|
|
42
|
+
- - ">="
|
|
43
|
+
- !ruby/object:Gem::Version
|
|
44
|
+
version: '0'
|
|
45
|
+
requirements: []
|
|
46
|
+
rubygems_version: 3.7.1
|
|
47
|
+
specification_version: 4
|
|
48
|
+
summary: Tiny Ruby client for the Fresh Jots API
|
|
49
|
+
test_files: []
|